def _IsHelpShortcut(component_trace, remaining_args): """Determines if the user is trying to access help without '--' separator. For example, mycmd.py --help instead of mycmd.py -- --help. Args: component_trace: (FireTrace) The trace for the Fire command. remaining_args: List of remaining args that haven't been consumed yet. Returns: True if help is requested, False otherwise. """ show_help = False if remaining_args: target = remaining_args[0] if target == '-h': show_help = True elif target == '--help': # Check if --help would be consumed as a keyword argument, or is a member. component = component_trace.GetResult() if inspect.isclass(component) or inspect.isroutine(component): fn_spec = inspectutils.GetFullArgSpec(component) _, remaining_kwargs, _ = _ParseKeywordArgs( remaining_args, fn_spec) show_help = target in remaining_kwargs else: members = dict(inspect.getmembers(component)) show_help = target not in members if show_help: component_trace.show_help = True command = '{cmd} -- --help'.format(cmd=component_trace.GetCommand()) print('INFO: Showing help with the command {cmd}.\n'.format( cmd=pipes.quote(command)), file=sys.stderr) return show_help
def Completions(component, verbose=False): """Gives possible Fire command completions for the component. A completion is a string that can be appended to a command to continue that command. These are used for TAB-completions in Bash for Fire CLIs. Args: component: The component whose completions to list. verbose: Whether to include all completions, even private members. Returns: A list of completions for a command that would so far return the component. """ if inspect.isroutine(component) or inspect.isclass(component): spec = inspectutils.GetFullArgSpec(component) return _CompletionsFromArgs(spec.args + spec.kwonlyargs) if isinstance(component, (tuple, list)): return [str(index) for index in range(len(component))] if inspect.isgenerator(component): # TODO(dbieber): There are currently no commands available for generators. return [] return [ _FormatForCommand(member_name) for member_name, _ in VisibleMembers(component, verbose=verbose) ]
def testGetFullArgSpecFromBuiltin(self): spec = inspectutils.GetFullArgSpec('test'.upper) self.assertEqual(spec.args, []) self.assertEqual(spec.defaults, ()) self.assertEqual(spec.kwonlyargs, []) self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {})
def UsageTextForFunction(component, trace=None): """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. Returns: String suitable for display in an error screen. """ 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 = spec.args if spec.defaults is None: num_defaults = 0 else: num_defaults = len(spec.defaults) args_with_no_defaults = args[:len(args) - num_defaults] args_with_defaults = args[len(args) - num_defaults:] flags = args_with_defaults + spec.kwonlyargs # 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 flags: items.append('<flags>') availability_lines = ('\nAvailable flags: ' + ' | '.join('--' + flag for flag in flags) + '\n') else: availability_lines = '' 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=availability_lines, hyphen_hyphen=hyphen_hyphen)
def UsageString(component, trace=None, verbose=False): """Returns a string showing how to use the component as a Fire command.""" if trace: command = trace.GetCommand() else: command = None if command: command += ' ' else: command = '' if inspect.isroutine(component) or inspect.isclass(component): spec = inspectutils.GetFullArgSpec(component) return _UsageStringFromFullArgSpec(command, spec) if isinstance(component, (list, tuple)): length = len(component) if length == 0: return command if length == 1: return command + '[0]' return command + '[0..{cap}]'.format(cap=length - 1) completions = completion.Completions(component, verbose) if command: completions = [''] + completions return '\n'.join(command + end for end in completions)
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) all_args = fn_spec.args + fn_spec.kwonlyargs 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, all_args, fn_spec.varkw) # 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 testGetFullArgSpec(self): spec = inspectutils.GetFullArgSpec(tc.identity) self.assertEqual(spec.args, ["arg1", "arg2", "arg3", "arg4"]) self.assertEqual(spec.defaults, (10, 20)) self.assertEqual(spec.varargs, "arg5") self.assertEqual(spec.varkw, "arg6") self.assertEqual(spec.kwonlyargs, []) self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {"arg2": int, "arg4": int})
def testGetFullArgSpecFromMethod(self): spec = inspectutils.GetFullArgSpec(tc.NoDefaults().double) self.assertEqual(spec.args, ['count']) self.assertEqual(spec.defaults, ()) self.assertEqual(spec.varargs, None) self.assertEqual(spec.varkw, None) self.assertEqual(spec.kwonlyargs, []) self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {})
def testGetFullArgSpecFromClassNoInit(self): spec = inspectutils.GetFullArgSpec(tc.OldStyleEmpty) self.assertEqual(spec.args, []) self.assertEqual(spec.defaults, ()) self.assertEqual(spec.varargs, None) self.assertEqual(spec.varkw, None) self.assertEqual(spec.kwonlyargs, []) self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {})
def testGetFullArgSpecFromSlotWrapper(self): spec = inspectutils.GetFullArgSpec(tc.NoDefaults) self.assertEqual(spec.args, []) self.assertEqual(spec.defaults, ()) self.assertEqual(spec.varargs, None) self.assertEqual(spec.varkw, None) self.assertEqual(spec.kwonlyargs, []) self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {})
def testGetFullArgSpec(self): spec = inspectutils.GetFullArgSpec(tc.identity) self.assertEqual(spec.args, ['arg1', 'arg2', 'arg3', 'arg4']) self.assertEqual(spec.defaults, (10, 20)) self.assertEqual(spec.varargs, 'arg5') self.assertEqual(spec.varkw, 'arg6') self.assertEqual(spec.kwonlyargs, []) self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {'arg2': int, 'arg4': int})
def testGetFullArgSpecFromNamedTupleSubclass(self): spec = inspectutils.GetFullArgSpec(tc.SubPoint) self.assertEqual(spec.args, ['x', 'y']) self.assertEqual(spec.defaults, ()) self.assertEqual(spec.varargs, None) self.assertEqual(spec.varkw, None) self.assertEqual(spec.kwonlyargs, []) self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {})
def testGetFullArgSpecFromNamedTuple(self): spec = inspectutils.GetFullArgSpec(tc.NamedTuplePoint) self.assertEqual(spec.args, ["x", "y"]) self.assertEqual(spec.defaults, ()) self.assertEqual(spec.varargs, None) self.assertEqual(spec.varkw, None) self.assertEqual(spec.kwonlyargs, []) self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {})
def testGetFullArgSpecPy3(self): spec = inspectutils.GetFullArgSpec(tc.py3.identity) self.assertEqual(spec.args, ['arg1', 'arg2', 'arg3', 'arg4']) self.assertEqual(spec.defaults, (10, 20)) self.assertEqual(spec.varargs, 'arg5') self.assertEqual(spec.varkw, 'arg10') self.assertEqual(spec.kwonlyargs, ['arg6', 'arg7', 'arg8', 'arg9']) self.assertEqual(spec.kwonlydefaults, {'arg8': 30, 'arg9': 40}) self.assertEqual(spec.annotations, {'arg2': int, 'arg4': int, 'arg7': int, 'arg9': int})
def UsageTextForFunction(component, trace=None): """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. Returns: String suitable for display in error screen. """ 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() is_help_an_arg = trace.NeedsSeparatingHyphenHyphen() else: command = None is_help_an_arg = False if not command: command = '' spec = inspectutils.GetFullArgSpec(component) args = spec.args if spec.defaults is None: num_defaults = 0 else: num_defaults = len(spec.defaults) args_with_no_defaults = args[:len(args) - num_defaults] args_with_defaults = args[len(args) - num_defaults:] flags = args_with_defaults + spec.kwonlyargs items = [arg.upper() for arg in args_with_no_defaults] if flags: items.append('<flags>') availability_lines = ( '\nAvailable flags: ' + ' | '.join('--' + flag for flag in flags) + '\n') else: availability_lines = '' args_and_flags = ' '.join(items) hyphen_hyphen = ' --' if is_help_an_arg else '' return output_template.format( current_command=command, args_and_flags=args_and_flags, availability_lines=availability_lines, hyphen_hyphen=hyphen_hyphen)
def testGetFullArgSpecPy3(self): spec = inspectutils.GetFullArgSpec(tc.py3.identity) self.assertEqual(spec.args, ["arg1", "arg2", "arg3", "arg4"]) self.assertEqual(spec.defaults, (10, 20)) self.assertEqual(spec.varargs, "arg5") self.assertEqual(spec.varkw, "arg10") self.assertEqual(spec.kwonlyargs, ["arg6", "arg7", "arg8", "arg9"]) self.assertEqual(spec.kwonlydefaults, {"arg8": 30, "arg9": 40}) self.assertEqual( spec.annotations, {"arg2": int, "arg4": int, "arg7": int, "arg9": int} )
def GetArgsAngFlags(component): """Returns all types of arguments and flags of a component.""" spec = inspectutils.GetFullArgSpec(component) args = spec.args if spec.defaults is None: num_defaults = 0 else: num_defaults = len(spec.defaults) args_with_no_defaults = args[:len(args) - num_defaults] args_with_defaults = args[len(args) - num_defaults:] flags = args_with_defaults + spec.kwonlyargs return args_with_no_defaults, args_with_defaults, flags
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 NeedsSeparatingHyphenHyphen(self, flag="help"): """Returns whether a the trace need '--' before '--help'. '--' is needed when the component takes keyword arguments, when the value of flag matches one of the argument of the component, or the component takes in keyword-only arguments(e.g. argument with default value). Args: flag: the flag available for the trace Returns: True for needed '--', False otherwise. """ element = self.GetLastHealthyElement() component = element.component spec = inspectutils.GetFullArgSpec(component) return spec.varkw is not None or flag in spec.args or flag in spec.kwonlyargs
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) summary, description = GetSummaryAndDescription(info['docstring_info']) spec = inspectutils.GetFullArgSpec(component) args = spec.args 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, command_summary=command_summary_str) args_and_flags = '' if args_with_no_defaults: items = [arg.upper() for arg in args_with_no_defaults] args_and_flags = ' '.join(items) synopsis_flag_template = '[--{flag_name}={flag_name_upper}]' if flags: items = [ synopsis_flag_template.format( flag_name=flag, flag_name_upper=flag.upper()) for flag in flags ] args_and_flags = args_and_flags + ' '.join(items) # Synopsis section synopsis_section_template = '{current_command} {args_and_flags}' positional_arguments = '|'.join(args) if positional_arguments: positional_arguments = ' ' + positional_arguments 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 = [] pos_arg_items = [] pos_arg_items = [ _CreatePositionalArgItem(arg, docstring_info) for arg in args_with_no_defaults ] if pos_arg_items: positional_arguments_section = ('POSITIONAL ARGUMENTS', '\n'.join(pos_arg_items).rstrip('\n')) args_and_flags_sections.append(positional_arguments_section) notes_sections.append( ('NOTES', 'You could 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 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) summary, description = GetSummaryAndDescription(info['docstring_info']) spec = inspectutils.GetFullArgSpec(component) args = spec.args if spec.defaults is None: num_defaults = 0 else: num_defaults = len(spec.defaults) args_with_no_defaults = args[:len(args) - num_defaults] # TODO(joejoevictor): Generate flag section using these # args_with_defaults = args[len(args) - num_defaults:] # flags = args_with_defaults + spec.kwonlyargs output_template = """NAME {name_section} SYNOPSIS {synopsis_section} DESCRIPTION {description_section} {args_and_flags_section} NOTES You could also use flags syntax for POSITIONAL ARGUMENTS """ # 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, command_summary=command_summary_str) items = [arg.upper() for arg in args_with_no_defaults] args_and_flags = ' '.join(items) # Synopsis section synopsis_section_template = '{current_command} {args_and_flags}' positional_arguments = '|'.join(args) if positional_arguments: positional_arguments = ' ' + positional_arguments synopsis_section = synopsis_section_template.format( current_command=current_command, args_and_flags=args_and_flags) # Description section description_section = description if description else summary args_and_flags_section = '' # Positional arguments and flags section pos_arg_template = """ POSITIONAL ARGUMENTS {items} """ pos_arg_items = [] for arg in args_with_no_defaults: item_template = ' {arg_name}\n {arg_description}\n' arg_description = None for arg_in_docstring in info['docstring_info'].args: if arg_in_docstring.name == arg: arg_description = arg_in_docstring.description item = item_template.format(arg_name=arg.upper(), arg_description=arg_description) pos_arg_items.append(item) if pos_arg_items: args_and_flags_section += pos_arg_template.format( items='\n'.join(pos_arg_items).rstrip('\n')) return output_template.format( name_section=name_section, synopsis_section=synopsis_section, description_section=description_section, args_and_flags_section=args_and_flags_section)
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 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)