def _MakeParseFn(fn): """Creates a parse function for fn. Args: fn: The function or class to create the parse function for. Returns: A parse function for fn. The parse function accepts a list of arguments and returns (varargs, kwargs), remaining_args. The original function fn can then be called with fn(*varargs, **kwargs). The remaining_args are the leftover args from the arguments to the parse function. """ fn_spec = inspectutils.GetFullArgSpec(fn) metadata = decorators.GetMetadata(fn) # Note: num_required_args is the number of positional arguments without # default values. All of these arguments are required. num_required_args = len(fn_spec.args) - len(fn_spec.defaults) required_kwonly = set(fn_spec.kwonlyargs) - set(fn_spec.kwonlydefaults) def _ParseFn(args): """Parses the list of `args` into (varargs, kwargs), remaining_args.""" kwargs, remaining_kwargs, remaining_args = _ParseKeywordArgs( args, fn_spec) # Note: _ParseArgs modifies kwargs. parsed_args, kwargs, remaining_args, capacity = _ParseArgs( fn_spec.args, fn_spec.defaults, num_required_args, kwargs, remaining_args, metadata) if fn_spec.varargs or fn_spec.varkw: # If we're allowed *varargs or **kwargs, there's always capacity. capacity = True extra_kw = set(kwargs) - set(fn_spec.kwonlyargs) if fn_spec.varkw is None and extra_kw: raise FireError('Unexpected kwargs present:', extra_kw) missing_kwonly = set(required_kwonly) - set(kwargs) if missing_kwonly: raise FireError('Missing required flags:', missing_kwonly) # If we accept *varargs, then use all remaining arguments for *varargs. if fn_spec.varargs is not None: varargs, remaining_args = remaining_args, [] else: varargs = [] for index, value in enumerate(varargs): varargs[index] = _ParseValue(value, None, None, metadata) varargs = parsed_args + varargs remaining_args += remaining_kwargs consumed_args = args[:len(args) - len(remaining_args)] return (varargs, kwargs), consumed_args, remaining_args, capacity return _ParseFn
def _CallAndUpdateTrace(component, args, component_trace, treatment='class', target=None): """Call the component by consuming args from args, and update the FireTrace. The component could be a class, a routine, or a callable object. This function calls the component and adds the appropriate action to component_trace. Args: component: The component to call args: Args for calling the component component_trace: FireTrace object that contains action trace treatment: Type of treatment used. Indicating whether we treat the component as a class, a routine, or a callable. target: Target in FireTrace element, default is None. If the value is None, the component itself will be used as target. Returns: component: The object that is the result of the callable call. remaining_args: The remaining args that haven't been consumed yet. """ if not target: target = component filename, lineno = inspectutils.GetFileAndLine(component) metadata = decorators.GetMetadata(component) fn = component.__call__ if treatment == 'callable' else component parse = _MakeParseFn(fn, metadata) (varargs, kwargs), consumed_args, remaining_args, capacity = parse(args) # Call the function. if inspectutils.IsCoroutineFunction(fn): loop = asyncio.get_event_loop() component = loop.run_until_complete(fn(*varargs, **kwargs)) else: component = fn(*varargs, **kwargs) if treatment == 'class': action = trace.INSTANTIATED_CLASS elif treatment == 'routine': action = trace.CALLED_ROUTINE else: action = trace.CALLED_CALLABLE component_trace.AddCalledComponent(component, target, consumed_args, filename, lineno, capacity, action=action) return component, remaining_args
def HelpText(component, trace=None, verbose=False): """Gets the help string for the current component, suitalbe for a help screen. Args: component: The component to construct the help string for. trace: The Fire trace of the command so far. The command executed so far can be extracted from this trace. verbose: Whether to include private members in the help screen. Returns: The full help screen as a string. """ # Preprocessing needed to create the sections: info = inspectutils.Info(component) actions_grouped_by_kind = _GetActionsGroupedByKind(component, verbose=verbose) spec = inspectutils.GetFullArgSpec(component) metadata = decorators.GetMetadata(component) # Sections: name_section = _NameSection(component, info, trace=trace, verbose=verbose) synopsis_section = _SynopsisSection( component, actions_grouped_by_kind, spec, metadata, trace=trace) description_section = _DescriptionSection(component, info) # TODO(dbieber): Add returns and raises sections for functions. if inspect.isroutine(component) or inspect.isclass(component): # For functions (ARGUMENTS / POSITIONAL ARGUMENTS, FLAGS) args_and_flags_sections, notes_sections = _ArgsAndFlagsSections( info, spec, metadata) usage_details_sections = [] else: # For objects (GROUPS, COMMANDS, VALUES, INDEXES) # TODO(dbieber): Show callable function usage in help text. args_and_flags_sections = [] notes_sections = [] usage_details_sections = _UsageDetailsSections(component, actions_grouped_by_kind) sections = ( [name_section, synopsis_section, description_section] + args_and_flags_sections + usage_details_sections + notes_sections ) return '\n\n'.join( _CreateOutputSection(*section) for section in sections if section is not None )
def UsageTextForFunction(component, trace=None, verbose=False): """Returns usage text for function objects. Args: component: The component to determine the usage text for. trace: The Fire trace object containing all metadata of current execution. verbose: Whether to display the usage text in verbose mode. Returns: String suitable for display in an error screen. """ del verbose # Unused. output_template = """Usage: {current_command} {args_and_flags} {availability_lines} For detailed information on this command, run: {current_command}{hyphen_hyphen} --help""" if trace: command = trace.GetCommand() needs_separating_hyphen_hyphen = trace.NeedsSeparatingHyphenHyphen() else: command = None needs_separating_hyphen_hyphen = False if not command: command = '' spec = inspectutils.GetFullArgSpec(component) args_with_no_defaults = spec.args[:len(spec.args) - len(spec.defaults)] args_with_defaults = spec.args[len(spec.args) - len(spec.defaults):] # Check if positional args are allowed. If not, show flag syntax for args. metadata = decorators.GetMetadata(component) accepts_positional_args = metadata.get(decorators.ACCEPTS_POSITIONAL_ARGS) if not accepts_positional_args: items = ['--{arg}={upper}'.format(arg=arg, upper=arg.upper()) for arg in args_with_no_defaults] else: items = [arg.upper() for arg in args_with_no_defaults] # If there are any arguments that are treated as flags: if args_with_defaults or spec.kwonlyargs or spec.varkw: items.append('<flags>') optional_flags = [('--' + flag) for flag in args_with_defaults] required_flags = [('--' + flag) for flag in spec.kwonlyargs] # Flags section: availability_lines = [] if optional_flags: availability_lines.append( _CreateAvailabilityLine(header='Optional flags:', items=optional_flags, header_indent=0)) if required_flags: availability_lines.append( _CreateAvailabilityLine(header='Required flags:', items=required_flags, header_indent=0)) if spec.varkw: additional_flags = ('Additional flags are accepted.' if optional_flags or required_flags else 'Flags are accepted.') availability_lines.append(additional_flags + '\n') if availability_lines: # Start the section with blank lines. availability_lines.insert(0, '\n') if spec.varargs: items.append('[{varargs}]...'.format(varargs=spec.varargs.upper())) args_and_flags = ' '.join(items) hyphen_hyphen = ' --' if needs_separating_hyphen_hyphen else '' return output_template.format( current_command=command, args_and_flags=args_and_flags, availability_lines=''.join(availability_lines), hyphen_hyphen=hyphen_hyphen)
def HelpTextForFunction(component, info, trace=None, verbose=False): """Returns detail help text for a function component. Args: component: Current component to generate help text for. info: Info containing metadata of component. trace: FireTrace object that leads to current component. verbose: Whether to display help text in verbose mode. Returns: Formatted help text for display. """ # TODO(joejoevictor): Implement verbose related output del verbose current_command = GetCurrentCommand(trace) current_command_without_separator = GetCurrentCommand( trace, include_separators=False) summary, description = GetSummaryAndDescription(info['docstring_info']) args_with_no_defaults, args_with_defaults, flags = GetArgsAngFlags( component) del args_with_defaults # Name section name_section_template = '{current_command}{command_summary}' command_summary_str = ' - ' + summary if summary else '' name_section = name_section_template.format( current_command=current_command_without_separator, command_summary=command_summary_str) # Check if positional args are allowed. If not, require flag syntax for args. metadata = decorators.GetMetadata(component) accepts_positional_args = metadata.get(decorators.ACCEPTS_POSITIONAL_ARGS) arg_and_flag_strings = [] if args_with_no_defaults: if accepts_positional_args: arg_strings = [ formatting.Underline(arg.upper()) for arg in args_with_no_defaults ] else: arg_strings = [ '--{arg}={arg_upper}'.format(arg=arg, arg_upper=formatting.Underline( arg.upper())) for arg in args_with_no_defaults ] arg_and_flag_strings.extend(arg_strings) flag_string_template = '[--{flag_name}={flag_name_upper}]' if flags: flag_strings = [ flag_string_template.format(flag_name=formatting.Underline(flag), flag_name_upper=flag.upper()) for flag in flags ] arg_and_flag_strings.extend(flag_strings) args_and_flags = ' '.join(arg_and_flag_strings) # Synopsis section synopsis_section_template = '{current_command} {args_and_flags}' synopsis_section = synopsis_section_template.format( current_command=current_command, args_and_flags=args_and_flags) # Description section command_description = GetDescriptionSectionText(summary, description) description_sections = [] if command_description: description_sections.append(('DESCRIPTION', command_description)) # Positional arguments and flags section docstring_info = info['docstring_info'] args_and_flags_sections = [] notes_sections = [] arg_items = [ _CreateArgItem(arg, docstring_info) for arg in args_with_no_defaults ] if arg_items: title = 'POSITIONAL ARGUMENTS' if accepts_positional_args else 'ARGUMENTS' arguments_section = (title, '\n'.join(arg_items).rstrip('\n')) args_and_flags_sections.append(arguments_section) if accepts_positional_args: notes_sections.append( ('NOTES', 'You can also use flags syntax for POSITIONAL ARGUMENTS')) flag_items = [_CreateFlagItem(flag, docstring_info) for flag in flags] if flag_items: flags_section = ('FLAGS', '\n'.join(flag_items)) args_and_flags_sections.append(flags_section) output_sections = [ ('NAME', name_section), ('SYNOPSIS', synopsis_section), ] + description_sections + args_and_flags_sections + notes_sections return '\n\n'.join( _CreateOutputSection(name, content) for name, content in output_sections)
def UsageText(component, trace=None, verbose=False): """Returns usage text for the given component. Args: component: The component to determine the usage text for. trace: The Fire trace object containing all metadata of current execution. verbose: Whether to display the usage text in verbose mode. Returns: String suitable for display in an error screen. """ output_template = """Usage: {continued_command} {availability_lines} For detailed information on this command, run: {help_command}""" # Get the command so far: if trace: command = trace.GetCommand() needs_separating_hyphen_hyphen = trace.NeedsSeparatingHyphenHyphen() else: command = None needs_separating_hyphen_hyphen = False if not command: command = '' # Build the continuations for the command: continued_command = command spec = inspectutils.GetFullArgSpec(component) metadata = decorators.GetMetadata(component) # Usage for objects. actions_grouped_by_kind = _GetActionsGroupedByKind(component, verbose=verbose) possible_actions = _GetPossibleActions(actions_grouped_by_kind) continuations = [] if possible_actions: continuations.append(_GetPossibleActionsUsageString(possible_actions)) availability_lines = _UsageAvailabilityLines(actions_grouped_by_kind) if callable(component): callable_items = _GetCallableUsageItems(spec, metadata) if callable_items: continuations.append(' '.join(callable_items)) elif trace: continuations.append(trace.separator) availability_lines.extend(_GetCallableAvailabilityLines(spec)) if continuations: continued_command += ' ' + ' | '.join(continuations) help_command = ( command + (' -- ' if needs_separating_hyphen_hyphen else ' ') + '--help' ) return output_template.format( continued_command=continued_command, availability_lines=''.join(availability_lines), help_command=help_command)