1#!/usr/bin/env python 2 3r""" 4This module provides command execution functions such as cmd_fnc and cmd_fnc_u. 5""" 6 7import os 8import sys 9import subprocess 10import collections 11import signal 12import time 13import re 14import inspect 15 16import gen_print as gp 17import gen_valid as gv 18import gen_misc as gm 19import func_args as fa 20 21robot_env = gp.robot_env 22 23if robot_env: 24 from robot.libraries.BuiltIn import BuiltIn 25 26 27# cmd_fnc and cmd_fnc_u should now be considered deprecated. shell_cmd and t_shell_cmd should be used 28# instead. 29def cmd_fnc(cmd_buf, 30 quiet=None, 31 test_mode=None, 32 debug=0, 33 print_output=1, 34 show_err=1, 35 return_stderr=0, 36 ignore_err=1): 37 r""" 38 Run the given command in a shell and return the shell return code and the output. 39 40 Description of arguments: 41 cmd_buf The command string to be run in a shell. 42 quiet Indicates whether this function should run the print_issuing() function 43 which prints "Issuing: <cmd string>" to stdout. 44 test_mode If test_mode is set, this function will not actually run the command. If 45 print_output is set, it will print "(test_mode) Issuing: <cmd string>" to 46 stdout. 47 debug If debug is set, this function will print extra debug info. 48 print_output If this is set, this function will print the stdout/stderr generated by 49 the shell command. 50 show_err If show_err is set, this function will print a standardized error report 51 if the shell command returns non-zero. 52 return_stderr If return_stderr is set, this function will process the stdout and stderr 53 streams from the shell command separately. It will also return stderr in 54 addition to the return code and the stdout. 55 """ 56 57 # Determine default values. 58 quiet = int(gm.global_default(quiet, 0)) 59 test_mode = int(gm.global_default(test_mode, 0)) 60 61 if debug: 62 gp.print_vars(cmd_buf, quiet, test_mode, debug) 63 64 err_msg = gv.valid_value(cmd_buf) 65 if err_msg != "": 66 raise ValueError(err_msg) 67 68 if not quiet: 69 gp.pissuing(cmd_buf, test_mode) 70 71 if test_mode: 72 if return_stderr: 73 return 0, "", "" 74 else: 75 return 0, "" 76 77 if return_stderr: 78 err_buf = "" 79 stderr = subprocess.PIPE 80 else: 81 stderr = subprocess.STDOUT 82 83 sub_proc = subprocess.Popen(cmd_buf, 84 bufsize=1, 85 shell=True, 86 executable='/bin/bash', 87 stdout=subprocess.PIPE, 88 stderr=stderr) 89 out_buf = "" 90 if return_stderr: 91 for line in sub_proc.stderr: 92 try: 93 err_buf += line 94 except TypeError: 95 line = line.decode("utf-8") 96 err_buf += line 97 if not print_output: 98 continue 99 gp.gp_print(line) 100 for line in sub_proc.stdout: 101 try: 102 out_buf += line 103 except TypeError: 104 line = line.decode("utf-8") 105 out_buf += line 106 if not print_output: 107 continue 108 gp.gp_print(line) 109 if print_output and not robot_env: 110 sys.stdout.flush() 111 sub_proc.communicate() 112 shell_rc = sub_proc.returncode 113 if shell_rc != 0: 114 err_msg = "The prior shell command failed.\n" 115 err_msg += gp.sprint_var(shell_rc, gp.hexa()) 116 if not print_output: 117 err_msg += "out_buf:\n" + out_buf 118 119 if show_err: 120 gp.print_error_report(err_msg) 121 if not ignore_err: 122 if robot_env: 123 BuiltIn().fail(err_msg) 124 else: 125 raise ValueError(err_msg) 126 127 if return_stderr: 128 return shell_rc, out_buf, err_buf 129 else: 130 return shell_rc, out_buf 131 132 133def cmd_fnc_u(cmd_buf, 134 quiet=None, 135 debug=None, 136 print_output=1, 137 show_err=1, 138 return_stderr=0, 139 ignore_err=1): 140 r""" 141 Call cmd_fnc with test_mode=0. See cmd_fnc (above) for details. 142 143 Note the "u" in "cmd_fnc_u" stands for "unconditional". 144 """ 145 146 return cmd_fnc(cmd_buf, test_mode=0, quiet=quiet, debug=debug, 147 print_output=print_output, show_err=show_err, 148 return_stderr=return_stderr, ignore_err=ignore_err) 149 150 151def parse_command_string(command_string): 152 r""" 153 Parse a bash command-line command string and return the result as a dictionary of parms. 154 155 This can be useful for answering questions like "What did the user specify as the value for parm x in the 156 command string?". 157 158 This function expects the command string to follow the following posix conventions: 159 - Short parameters: 160 -<parm name><space><arg value> 161 - Long parameters: 162 --<parm name>=<arg value> 163 164 The first item in the string will be considered to be the command. All values not conforming to the 165 specifications above will be considered positional parms. If there are multiple parms with the same 166 name, they will be put into a list (see illustration below where "-v" is specified multiple times). 167 168 Description of argument(s): 169 command_string The complete command string including all parameters and arguments. 170 171 Sample input: 172 173 robot_cmd_buf: robot -v OPENBMC_HOST:dummy1 -v keyword_string:'Set 174 Auto Reboot no' -v lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot -v quiet:0 -v 175 test_mode:0 -v debug:0 --outputdir='/home/user1/status/children/' 176 --output=dummy1.Auto_reboot.170802.124544.output.xml --log=dummy1.Auto_reboot.170802.124544.log.html 177 --report=dummy1.Auto_reboot.170802.124544.report.html 178 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot 179 180 Sample output: 181 182 robot_cmd_buf_dict: 183 robot_cmd_buf_dict[command]: robot 184 robot_cmd_buf_dict[v]: 185 robot_cmd_buf_dict[v][0]: OPENBMC_HOST:dummy1 186 robot_cmd_buf_dict[v][1]: keyword_string:Set Auto Reboot no 187 robot_cmd_buf_dict[v][2]: 188 lib_file_path:/home/user1/git/openbmc-test-automation/lib/utils.robot 189 robot_cmd_buf_dict[v][3]: quiet:0 190 robot_cmd_buf_dict[v][4]: test_mode:0 191 robot_cmd_buf_dict[v][5]: debug:0 192 robot_cmd_buf_dict[outputdir]: /home/user1/status/children/ 193 robot_cmd_buf_dict[output]: dummy1.Auto_reboot.170802.124544.output.xml 194 robot_cmd_buf_dict[log]: dummy1.Auto_reboot.170802.124544.log.html 195 robot_cmd_buf_dict[report]: dummy1.Auto_reboot.170802.124544.report.html 196 robot_cmd_buf_dict[positional]: 197 /home/user1/git/openbmc-test-automation/extended/run_keyword.robot 198 """ 199 200 # We want the parms in the string broken down the way bash would do it, so we'll call upon bash to do 201 # that by creating a simple inline bash function. 202 bash_func_def = "function parse { for parm in \"${@}\" ; do" +\ 203 " echo $parm ; done ; }" 204 205 rc, outbuf = cmd_fnc_u(bash_func_def + " ; parse " + command_string, 206 quiet=1, print_output=0) 207 command_string_list = outbuf.rstrip("\n").split("\n") 208 209 command_string_dict = collections.OrderedDict() 210 ix = 1 211 command_string_dict['command'] = command_string_list[0] 212 while ix < len(command_string_list): 213 if command_string_list[ix].startswith("--"): 214 key, value = command_string_list[ix].split("=") 215 key = key.lstrip("-") 216 elif command_string_list[ix].startswith("-"): 217 key = command_string_list[ix].lstrip("-") 218 ix += 1 219 try: 220 value = command_string_list[ix] 221 except IndexError: 222 value = "" 223 else: 224 key = 'positional' 225 value = command_string_list[ix] 226 if key in command_string_dict: 227 if isinstance(command_string_dict[key], str): 228 command_string_dict[key] = [command_string_dict[key]] 229 command_string_dict[key].append(value) 230 else: 231 command_string_dict[key] = value 232 ix += 1 233 234 return command_string_dict 235 236 237# Save the original SIGALRM handler for later restoration by shell_cmd. 238original_sigalrm_handler = signal.getsignal(signal.SIGALRM) 239 240 241def shell_cmd_timed_out(signal_number, 242 frame): 243 r""" 244 Handle an alarm signal generated during the shell_cmd function. 245 """ 246 247 gp.dprint_executing() 248 global command_timed_out 249 command_timed_out = True 250 # Get subprocess pid from shell_cmd's call stack. 251 sub_proc = gp.get_stack_var('sub_proc', 0) 252 pid = sub_proc.pid 253 gp.dprint_var(pid) 254 # Terminate the child process group. 255 os.killpg(pid, signal.SIGKILL) 256 # Restore the original SIGALRM handler. 257 signal.signal(signal.SIGALRM, original_sigalrm_handler) 258 259 return 260 261 262def shell_cmd(command_string, 263 quiet=None, 264 print_output=None, 265 show_err=1, 266 test_mode=0, 267 time_out=None, 268 max_attempts=1, 269 retry_sleep_time=5, 270 valid_rcs=[0], 271 ignore_err=None, 272 return_stderr=0, 273 fork=0, 274 error_regexes=None): 275 r""" 276 Run the given command string in a shell and return a tuple consisting of the shell return code and the 277 output. 278 279 Description of argument(s): 280 command_string The command string to be run in a shell (e.g. "ls /tmp"). 281 quiet If set to 0, this function will print "Issuing: <cmd string>" to stdout. 282 When the quiet argument is set to None, this function will assign a 283 default value by searching upward in the stack for the quiet variable 284 value. If no such value is found, quiet is set to 0. 285 print_output If this is set, this function will print the stdout/stderr generated by 286 the shell command to stdout. 287 show_err If show_err is set, this function will print a standardized error report 288 if the shell command fails (i.e. if the shell command returns a shell_rc 289 that is not in valid_rcs). Note: Error text is only printed if ALL 290 attempts to run the command_string fail. In other words, if the command 291 execution is ultimately successful, initial failures are hidden. 292 test_mode If test_mode is set, this function will not actually run the command. If 293 print_output is also set, this function will print "(test_mode) Issuing: 294 <cmd string>" to stdout. A caller should call shell_cmd directly if they 295 wish to have the command string run unconditionally. They should call 296 the t_shell_cmd wrapper (defined below) if they wish to run the command 297 string only if the prevailing test_mode variable is set to 0. 298 time_out A time-out value expressed in seconds. If the command string has not 299 finished executing within <time_out> seconds, it will be halted and 300 counted as an error. 301 max_attempts The max number of attempts that should be made to run the command string. 302 retry_sleep_time The number of seconds to sleep between attempts. 303 valid_rcs A list of integers indicating which shell_rc values are not to be 304 considered errors. 305 ignore_err Ignore error means that a failure encountered by running the command 306 string will not be raised as a python exception. When the ignore_err 307 argument is set to None, this function will assign a default value by 308 searching upward in the stack for the ignore_err variable value. If no 309 such value is found, ignore_err is set to 1. 310 return_stderr If return_stderr is set, this function will process the stdout and stderr 311 streams from the shell command separately. In such a case, the tuple 312 returned by this function will consist of three values rather than just 313 two: rc, stdout, stderr. 314 fork Run the command string asynchronously (i.e. don't wait for status of the 315 child process and don't try to get stdout/stderr) and return the Popen 316 object created by the subprocess.popen() function. See the kill_cmd 317 function for details on how to process the popen object. 318 error_regexes A list of regular expressions to be used to identify errors in the 319 command output. If there is a match for any of these regular 320 expressions, the command will be considered a failure and the shell_rc 321 will be set to -1. For example, if error_regexes = ['ERROR:'] and the 322 command output contains 'ERROR: Unrecognized option', it will be counted 323 as an error even if the command returned 0. This is useful when running 324 commands that do not always return non-zero on error. 325 """ 326 327 err_msg = gv.valid_value(command_string) 328 if err_msg: 329 raise ValueError(err_msg) 330 331 # Assign default values to some of the arguments to this function. 332 quiet = int(gm.dft(quiet, gp.get_stack_var('quiet', 0))) 333 print_output = int(gm.dft(print_output, not quiet)) 334 show_err = int(show_err) 335 ignore_err = int(gm.dft(ignore_err, gp.get_stack_var('ignore_err', 1))) 336 337 gp.qprint_issuing(command_string, test_mode) 338 if test_mode: 339 return (0, "", "") if return_stderr else (0, "") 340 341 # Convert a string python dictionary definition to a dictionary. 342 valid_rcs = fa.source_to_object(valid_rcs) 343 # Convert each list entry to a signed value. 344 valid_rcs = [gm.to_signed(x) for x in valid_rcs] 345 346 stderr = subprocess.PIPE if return_stderr else subprocess.STDOUT 347 348 # Write all output to func_out_history_buf rather than directly to stdout. This allows us to decide 349 # what to print after all attempts to run the command string have been made. func_out_history_buf will 350 # contain the complete history from the current invocation of this function. 351 global command_timed_out 352 command_timed_out = False 353 func_out_history_buf = "" 354 for attempt_num in range(1, max_attempts + 1): 355 sub_proc = subprocess.Popen(command_string, 356 preexec_fn=os.setsid, 357 bufsize=1, 358 shell=True, 359 universal_newlines=True, 360 executable='/bin/bash', 361 stdout=subprocess.PIPE, 362 stderr=stderr) 363 if fork: 364 return sub_proc 365 366 if time_out: 367 command_timed_out = False 368 # Designate a SIGALRM handling function and set alarm. 369 signal.signal(signal.SIGALRM, shell_cmd_timed_out) 370 signal.alarm(time_out) 371 try: 372 stdout_buf, stderr_buf = sub_proc.communicate() 373 except IOError: 374 command_timed_out = True 375 # Restore the original SIGALRM handler and clear the alarm. 376 signal.signal(signal.SIGALRM, original_sigalrm_handler) 377 signal.alarm(0) 378 379 # Output from this loop iteration is written to func_out_buf for later processing. This can include 380 # stdout, stderr and our own error messages. 381 func_out_buf = "" 382 if print_output: 383 if return_stderr: 384 func_out_buf += stderr_buf 385 func_out_buf += stdout_buf 386 shell_rc = sub_proc.returncode 387 if shell_rc in valid_rcs: 388 # Check output for text indicating there is an error. 389 if error_regexes and re.match('|'.join(error_regexes), stdout_buf): 390 shell_rc = -1 391 else: 392 break 393 err_msg = "The prior shell command failed.\n" 394 err_msg += gp.sprint_var(attempt_num) 395 err_msg += gp.sprint_vars(command_string, command_timed_out, time_out) 396 err_msg += gp.sprint_varx("child_pid", sub_proc.pid) 397 err_msg += gp.sprint_vars(shell_rc, valid_rcs, fmt=gp.hexa()) 398 if error_regexes: 399 err_msg += gp.sprint_vars(error_regexes) 400 if not print_output: 401 if return_stderr: 402 err_msg += "stderr_buf:\n" + stderr_buf 403 err_msg += "stdout_buf:\n" + stdout_buf 404 if show_err: 405 func_out_buf += gp.sprint_error_report(err_msg) 406 if attempt_num < max_attempts: 407 cmd_buf = "time.sleep(" + str(retry_sleep_time) + ")" 408 if show_err: 409 func_out_buf += gp.sprint_issuing(cmd_buf) 410 exec(cmd_buf) 411 func_out_history_buf += func_out_buf 412 413 if shell_rc in valid_rcs: 414 gp.gp_print(func_out_buf) 415 else: 416 if show_err: 417 gp.gp_print(func_out_history_buf, stream='stderr') 418 else: 419 # There is no error information to show so just print output from last loop iteration. 420 gp.gp_print(func_out_buf) 421 if not ignore_err: 422 # If the caller has already asked to show error info, avoid repeating that in the failure message. 423 err_msg = "The prior shell command failed.\n" if show_err \ 424 else err_msg 425 if robot_env: 426 BuiltIn().fail(err_msg) 427 else: 428 raise ValueError(err_msg) 429 430 return (shell_rc, stdout_buf, stderr_buf) if return_stderr \ 431 else (shell_rc, stdout_buf) 432 433 434def t_shell_cmd(command_string, **kwargs): 435 r""" 436 Search upward in the the call stack to obtain the test_mode argument, add it to kwargs and then call 437 shell_cmd and return the result. 438 439 See shell_cmd prolog for details on all arguments. 440 """ 441 442 if 'test_mode' in kwargs: 443 error_message = "Programmer error - test_mode is not a valid" +\ 444 " argument to this function." 445 gp.print_error_report(error_message) 446 exit(1) 447 448 test_mode = int(gp.get_stack_var('test_mode', 0)) 449 kwargs['test_mode'] = test_mode 450 451 return shell_cmd(command_string, **kwargs) 452 453 454def kill_cmd(popen, sig=signal.SIGTERM): 455 r""" 456 Kill the subprocess represented by the Popen object and return a tuple consisting of the shell return 457 code and the output. 458 459 This function is meant to be used as the follow-up for a call to shell_cmd(..., fork=1). 460 461 Example: 462 popen = shell_cmd("some_pgm.py", fork=1) 463 ... 464 shell_rc, output = kill_cmd(popen) 465 466 Description of argument(s): 467 popen A Popen object returned by the subprocess.Popen() command. 468 sig The signal to be sent to the child process. 469 """ 470 471 gp.dprint_var(popen.pid) 472 os.killpg(popen.pid, sig) 473 stdout, stderr = popen.communicate() 474 shell_rc = popen.returncode 475 return (shell_rc, stdout, stderr) if stderr else (shell_rc, stdout) 476 477 478def re_order_kwargs(stack_frame_ix, **kwargs): 479 r""" 480 Re-order the kwargs to match the order in which they were specified on a function invocation and return 481 as an ordered dictionary. 482 483 Note that this re_order_kwargs function should not be necessary in python versions 3.6 and beyond. 484 485 Example: 486 487 The caller calls func1 like this: 488 489 func1('mike', arg1='one', arg2='two', arg3='three') 490 491 And func1 is defined as follows: 492 493 def func1(first_arg, **kwargs): 494 495 kwargs = re_order_kwargs(first_arg_num=2, stack_frame_ix=3, **kwargs) 496 497 The kwargs dictionary before calling re_order_kwargs (where order is not guaranteed): 498 499 kwargs: 500 kwargs[arg3]: three 501 kwargs[arg2]: two 502 kwargs[arg1]: one 503 504 The kwargs dictionary after calling re_order_kwargs: 505 506 kwargs: 507 kwargs[arg1]: one 508 kwargs[arg2]: two 509 kwargs[arg3]: three 510 511 Note that the re-ordered kwargs match the order specified on the call to func1. 512 513 Description of argument(s): 514 stack_frame_ix The stack frame of the function whose kwargs values must be re-ordered. 515 0 is the stack frame of re_order_kwargs, 1 is the stack from of its 516 caller and so on. 517 kwargs The keyword argument dictionary which is to be re-ordered. 518 """ 519 520 new_kwargs = collections.OrderedDict() 521 522 # Get position number of first keyword on the calling line of code. 523 (args, varargs, keywords, locals) =\ 524 inspect.getargvalues(inspect.stack()[stack_frame_ix][0]) 525 first_kwarg_pos = 1 + len(args) 526 if varargs is not None: 527 first_kwarg_pos += len(locals[varargs]) 528 for arg_num in range(first_kwarg_pos, first_kwarg_pos + len(kwargs)): 529 # This will result in an arg_name value such as "arg1='one'". 530 arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix + 2) 531 # Continuing with the prior example, the following line will result 532 # in key being set to 'arg1'. 533 key = arg_name.split('=')[0] 534 new_kwargs[key] = kwargs[key] 535 536 return new_kwargs 537 538 539def default_arg_delim(arg_dashes): 540 r""" 541 Return the default argument delimiter value for the given arg_dashes value. 542 543 Note: this function is useful for functions that manipulate bash command line arguments (e.g. --parm=1 or 544 -parm 1). 545 546 Description of argument(s): 547 arg_dashes The argument dashes specifier (usually, "-" or "--"). 548 """ 549 550 if arg_dashes == "--": 551 return "=" 552 553 return " " 554 555 556def create_command_string(command, *pos_parms, **options): 557 r""" 558 Create and return a bash command string consisting of the given arguments formatted as text. 559 560 The default formatting of options is as follows: 561 562 <single dash><option name><space delim><option value> 563 564 Example: 565 566 -parm value 567 568 The caller can change the kind of dashes/delimiters used by specifying "arg_dashes" and/or "arg_delims" 569 as options. These options are processed specially by the create_command_string function and do NOT get 570 inserted into the resulting command string. All options following the arg_dashes/arg_delims options will 571 then use the specified values for dashes/delims. In the special case of arg_dashes equal to "--", the 572 arg_delim will automatically be changed to "=". See examples below. 573 574 Quoting rules: 575 576 The create_command_string function will single quote option values as needed to prevent bash expansion. 577 If the caller wishes to defeat this action, they may single or double quote the option value themselves. 578 See examples below. 579 580 pos_parms are NOT automatically quoted. The caller is advised to either explicitly add quotes or to use 581 the quote_bash_parm functions to quote any pos_parms. 582 583 Examples: 584 585 command_string = create_command_string('cd', '~') 586 587 Result: 588 cd ~ 589 590 Note that the pos_parm ("~") does NOT get quoted, as per the aforementioned rules. If quotes are 591 desired, they may be added explicitly by the caller: 592 593 command_string = create_command_string('cd', '\'~\'') 594 595 Result: 596 cd '~' 597 598 command_string = create_command_string('grep', '\'^[^ ]*=\'', 599 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always') 600 601 Result: 602 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile 603 604 In the preceding example, note the use of None to cause the "i" parm to be treated as a flag (i.e. no 605 argument value is generated). Also, note the use of arg_dashes to change the type of dashes used on all 606 subsequent options. The following example is equivalent to the prior. Note that quote_bash_parm is used 607 instead of including the quotes explicitly. 608 609 command_string = create_command_string('grep', quote_bash_parm('^[^ ]*='), 610 '/tmp/myfile', i=None, m='1', arg_dashes='--', color='always') 611 612 Result: 613 grep -i -m 1 --color=always '^[^ ]*=' /tmp/myfile 614 615 In the following example, note the automatic quoting of the password option, as per the aforementioned 616 rules. 617 618 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None, 619 m='1', arg_dashes='--', password='${my_pw}') 620 621 However, let's say that the caller wishes to have bash expand the password value. To achieve this, the 622 caller can use double quotes: 623 624 command_string = create_command_string('my_pgm', '/tmp/myfile', i=None, 625 m='1', arg_dashes='--', password='"${my_pw}"') 626 627 Result: 628 my_pgm -i -m 1 --password="${my_pw}" /tmp/myfile 629 630 command_string = create_command_string('ipmitool', 'power status', 631 I='lanplus', C='3', 'p=623', U='root', P='0penBmc', H='xx.xx.xx.xx') 632 633 Result: 634 ipmitool -I lanplus -C 3 -p 623 -U root -P 0penBmc -H xx.xx.xx.xx power status 635 636 By default create_command_string will take measures to preserve the order of the callers options. In 637 some cases, this effort may fail (as when calling directly from a robot program). In this case, the 638 caller can accept the responsibility of keeping an ordered list of options by calling this function with 639 the last positional parm as some kind of dictionary (preferably an OrderedDict) and avoiding the use of 640 any actual option args. 641 642 Example: 643 kwargs = collections.OrderedDict([('pass', 0), ('fail', 0)]) 644 command_string = create_command_string('my program', 'pos_parm1', kwargs) 645 646 Result: 647 648 my program -pass 0 -fail 0 pos_parm1 649 650 Note to programmers who wish to write a wrapper to this function: If the python version is less than 651 3.6, to get the options to be processed correctly, the wrapper function must include a _stack_frame_ix_ 652 keyword argument to allow this function to properly re-order options: 653 654 def create_ipmi_ext_command_string(command, **kwargs): 655 656 return create_command_string('ipmitool', command, _stack_frame_ix_=2, 657 **kwargs) 658 659 Example call of wrapper function: 660 661 command_string = create_ipmi_ext_command_string('power status', I='lanplus') 662 663 Description of argument(s): 664 command The command (e.g. "cat", "sort", "ipmitool", etc.). 665 pos_parms The positional parms for the command (e.g. PATTERN, FILENAME, etc.). 666 These will be placed at the end of the resulting command string. 667 options The command options (e.g. "-m 1", "--max-count=NUM", etc.). Note that if 668 the value of any option is None, then it will be understood to be a flag 669 (for which no value is required). 670 """ 671 672 arg_dashes = "-" 673 delim = default_arg_delim(arg_dashes) 674 675 command_string = command 676 677 if len(pos_parms) > 0 and gp.is_dict(pos_parms[-1]): 678 # Convert pos_parms from tuple to list. 679 pos_parms = list(pos_parms) 680 # Re-assign options to be the last pos_parm value (which is a dictionary). 681 options = pos_parms[-1] 682 # Now delete the last pos_parm. 683 del pos_parms[-1] 684 else: 685 # Either get stack_frame_ix from the caller via options or set it to the default value. 686 stack_frame_ix = options.pop('_stack_frame_ix_', 1) 687 if gm.python_version < gm.ordered_dict_version: 688 # Re-establish the original options order as specified on the original line of code. This 689 # function depends on correct order. 690 options = re_order_kwargs(stack_frame_ix, **options) 691 for key, value in options.items(): 692 # Check for special values in options and process them. 693 if key == "arg_dashes": 694 arg_dashes = str(value) 695 delim = default_arg_delim(arg_dashes) 696 continue 697 if key == "arg_delim": 698 delim = str(value) 699 continue 700 # Format the options elements into the command string. 701 command_string += " " + arg_dashes + key 702 if value is not None: 703 command_string += delim 704 if re.match(r'^(["].*["]|[\'].*[\'])$', str(value)): 705 # Already quoted. 706 command_string += str(value) 707 else: 708 command_string += gm.quote_bash_parm(str(value)) 709 # Finally, append the pos_parms to the end of the command_string. Use filter to eliminate blank pos 710 # parms. 711 command_string = ' '.join([command_string] + list(filter(None, pos_parms))) 712 713 return command_string 714