def testSeparateFlagArgs(self): self.assertEqual(parser.SeparateFlagArgs([]), ([], [])) self.assertEqual(parser.SeparateFlagArgs(['a', 'b']), (['a', 'b'], [])) self.assertEqual(parser.SeparateFlagArgs(['a', 'b', '--']), (['a', 'b'], [])) self.assertEqual(parser.SeparateFlagArgs(['a', 'b', '--', 'c']), (['a', 'b'], ['c'])) self.assertEqual(parser.SeparateFlagArgs(['--']), ([], [])) self.assertEqual(parser.SeparateFlagArgs(['--', 'c', 'd']), ([], ['c', 'd'])) self.assertEqual(parser.SeparateFlagArgs(['a', 'b', '--', 'c', 'd']), (['a', 'b'], ['c', 'd'])) self.assertEqual(parser.SeparateFlagArgs(['a', 'b', '--', 'c', 'd', '--']), (['a', 'b', '--', 'c', 'd'], [])) self.assertEqual(parser.SeparateFlagArgs(['a', 'b', '--', 'c', '--', 'd']), (['a', 'b', '--', 'c'], ['d']))
def testSeparateFlagArgs(self): self.assertEqual(parser.SeparateFlagArgs([]), ([], [])) self.assertEqual(parser.SeparateFlagArgs(["a", "b"]), (["a", "b"], [])) self.assertEqual(parser.SeparateFlagArgs(["a", "b", "--"]), (["a", "b"], [])) self.assertEqual(parser.SeparateFlagArgs(["a", "b", "--", "c"]), (["a", "b"], ["c"])) self.assertEqual(parser.SeparateFlagArgs(["--"]), ([], [])) self.assertEqual(parser.SeparateFlagArgs(["--", "c", "d"]), ([], ["c", "d"])) self.assertEqual( parser.SeparateFlagArgs(["a", "b", "--", "c", "d"]), (["a", "b"], ["c", "d"]), ) self.assertEqual( parser.SeparateFlagArgs(["a", "b", "--", "c", "d", "--"]), (["a", "b", "--", "c", "d"], []), ) self.assertEqual( parser.SeparateFlagArgs(["a", "b", "--", "c", "--", "d"]), (["a", "b", "--", "c"], ["d"]), )
def _Fire(component, args, context, name=None): """Execute a Fire command on a target component using the args supplied. Arguments that come after a final isolated '--' are treated as Flags, eg for interactive mode or completion script generation. Other arguments are consumed by the execution of the Fire command, eg in the traversal of the members of the component, or in calling a function or instantiating a class found during the traversal. The steps performed by this method are: 1. Parse any Flag args (the args after the final --) 2. Start with component as the current component. 2a. If the current component is a class, instantiate it using args from args. 2b. If the current component is a routine, call it using args from args. 2c. Otherwise access a member from component using an arg from args. 2d. Repeat 2a-2c until no args remain. 3a. Embed into ipython REPL if interactive mode is selected. 3b. Generate a completion script if that flag is provided. In step 2, arguments will only ever be consumed up to a separator; a single step will never consume arguments from both sides of a separator. The separator defaults to a hyphen (-), and can be overwritten with the --separator Fire argument. Args: component: The target component for Fire. args: A list of args to consume in Firing on the component, usually from the command line. context: A dict with the local and global variables available at the call to Fire. name: Optional. The name of the command. Used in interactive mode and in the tab completion script. Returns: FireTrace of components starting with component, tracing Fire's execution path as it consumes args. Raises: ValueError: If there are arguments that cannot be consumed. ValueError: If --completion is specified but no name available. """ args, flag_args = parser.SeparateFlagArgs(args) argparser = parser.CreateParser() parsed_flag_args, unused_args = argparser.parse_known_args(flag_args) verbose = parsed_flag_args.verbose interactive = parsed_flag_args.interactive separator = parsed_flag_args.separator show_completion = parsed_flag_args.completion show_help = parsed_flag_args.help show_trace = parsed_flag_args.trace # component can be a module, class, routine, object, etc. if component is None: component = context initial_component = component component_trace = trace.FireTrace(initial_component=initial_component, name=name, separator=separator, verbose=verbose, show_help=show_help, show_trace=show_trace) instance = None remaining_args = args while True: last_component = component initial_args = remaining_args if not remaining_args and (show_help or interactive or show_trace or show_completion): # Don't initialize the final class or call the final function unless # there's a separator after it, and instead process the current component. break saved_args = [] used_separator = False if separator in remaining_args: # For the current component, only use arguments up to the separator. separator_index = remaining_args.index(separator) saved_args = remaining_args[separator_index + 1:] remaining_args = remaining_args[:separator_index] used_separator = True assert separator not in remaining_args if inspect.isclass(component) or inspect.isroutine(component): # The component is a class or a routine; we'll try to initialize it or # call it. isclass = inspect.isclass(component) try: target = component.__name__ filename, lineno = _GetFileAndLine(component) component, consumed_args, remaining_args, capacity = _CallCallable( component, remaining_args) # Update the trace. if isclass: component_trace.AddInstantiatedClass( component, target, consumed_args, filename, lineno, capacity) else: component_trace.AddCalledRoutine(component, target, consumed_args, filename, lineno, capacity) except FireError as error: component_trace.AddError(error, initial_args) return component_trace if last_component is initial_component: # If the initial component is a class, keep an instance for use with -i. instance = component elif isinstance(component, (list, tuple)) and remaining_args: # The component is a tuple or list; we'll try to access a member. arg = remaining_args[0] try: index = int(arg) component = component[index] except (ValueError, IndexError): error = FireError( 'Unable to index into component with argument:', arg) component_trace.AddError(error, initial_args) return component_trace remaining_args = remaining_args[1:] filename = None lineno = None component_trace.AddAccessedProperty(component, index, [arg], filename, lineno) elif isinstance(component, dict) and remaining_args: # The component is a dict; we'll try to access a member. target = remaining_args[0] if target in component: component = component[target] elif target.replace('-', '_') in component: component = component[target.replace('-', '_')] else: # The target isn't present in the dict as a string, but maybe it is as # another type. # TODO: Consider alternatives for accessing non-string keys. found_target = False for key, value in component.items(): if target == str(key): component = value found_target = True break if not found_target: error = FireError('Cannot find target in dict:', target, component) component_trace.AddError(error, initial_args) return component_trace remaining_args = remaining_args[1:] filename = None lineno = None component_trace.AddAccessedProperty(component, target, [target], filename, lineno) elif remaining_args: # We'll try to access a member of the component. try: target = remaining_args[0] component, consumed_args, remaining_args = _GetMember( component, remaining_args) filename, lineno = _GetFileAndLine(component) component_trace.AddAccessedProperty(component, target, consumed_args, filename, lineno) except FireError as error: component_trace.AddError(error, initial_args) return component_trace if used_separator: # Add back in the arguments from after the separator. if remaining_args: remaining_args = remaining_args + [separator] + saved_args elif (inspect.isclass(last_component) or inspect.isroutine(last_component)): remaining_args = saved_args component_trace.AddSeparator() elif component is not last_component: remaining_args = [separator] + saved_args else: # It was an unnecessary separator. remaining_args = saved_args if component is last_component and remaining_args == initial_args: # We're making no progress. break if remaining_args: component_trace.AddError( FireError('Could not consume arguments:', remaining_args), initial_args) return component_trace if show_completion: if name is None: raise ValueError( 'Cannot make completion script without command name') script = CompletionScript(name, initial_component) component_trace.AddCompletionScript(script) if interactive: variables = context.copy() if name is not None: variables[name] = initial_component variables['component'] = initial_component variables['result'] = component variables['trace'] = component_trace if instance is not None: variables['self'] = instance interact.Embed(variables, verbose) component_trace.AddInteractiveMode() return component_trace
def Fire(component=None, command=None, name=None): """This function, Fire, is the main entrypoint for Python Fire. Executes a command either from the `command` argument or from sys.argv by recursively traversing the target object `component`'s members consuming arguments, evaluating functions, and instantiating classes as it goes. When building a CLI with Fire, your main method should call this function. Args: component: The initial target component. command: Optional. If supplied, this is the command executed. If not supplied, then the command is taken from sys.argv instead. This can be a string or a list of strings; a list of strings is preferred. name: Optional. The name of the command as entered at the command line. Used in interactive mode and for generating the completion script. Returns: The result of executing the Fire command. Execution begins with the initial target component. The component is updated by using the command arguments to either access a member of the current component, call the current component (if it's a function), or instantiate the current component (if it's a class). When all arguments are consumed and there's no function left to call or class left to instantiate, the resulting current component is the final result. Raises: ValueError: If the command argument is supplied, but not a string or a sequence of arguments. FireExit: When Fire encounters a FireError, Fire will raise a FireExit with code 2. When used with the help or trace flags, Fire will raise a FireExit with code 0 if successful. """ name = name or os.path.basename(sys.argv[0]) # Get args as a list. if isinstance(command, six.string_types): args = shlex.split(command) elif isinstance(command, (list, tuple)): args = command elif command is None: # Use the command line args by default if no command is specified. args = sys.argv[1:] else: raise ValueError( 'The command argument must be a string or a sequence of ' 'arguments.') args, flag_args = parser.SeparateFlagArgs(args) argparser = parser.CreateParser() parsed_flag_args, unused_args = argparser.parse_known_args(flag_args) context = {} if parsed_flag_args.interactive or component is None: # Determine the calling context. caller = inspect.stack()[1] caller_frame = caller[0] caller_globals = caller_frame.f_globals caller_locals = caller_frame.f_locals context.update(caller_globals) context.update(caller_locals) component_trace = _Fire(component, args, parsed_flag_args, context, name) if component_trace.HasError(): _DisplayError(component_trace) raise FireExit(2, component_trace) if component_trace.show_trace and component_trace.show_help: output = ['Fire trace:\n{trace}\n'.format(trace=component_trace)] result = component_trace.GetResult() help_string = helptext.HelpString(result, component_trace, component_trace.verbose) output.append(help_string) Display(output) raise FireExit(0, component_trace) if component_trace.show_trace: output = ['Fire trace:\n{trace}'.format(trace=component_trace)] Display(output) raise FireExit(0, component_trace) if component_trace.show_help: result = component_trace.GetResult() help_string = helptext.HelpString(result, component_trace, component_trace.verbose) output = [help_string] Display(output) raise FireExit(0, component_trace) # The command succeeded normally; print the result. _PrintResult(component_trace, verbose=component_trace.verbose) result = component_trace.GetResult() return result