diff --git a/fire/core.py b/fire/core.py index 8ca142c7..55a5ae86 100644 --- a/fire/core.py +++ b/fire/core.py @@ -78,7 +78,7 @@ def main(argv): import asyncio # pylint: disable=import-error,g-import-not-at-top # pytype: disable=import-error -def Fire(component=None, command=None, name=None): +def Fire(component=None, command=None, name=None, help_sequence=None): """This function, Fire, is the main entrypoint for Python Fire. Executes a command either from the `command` argument or from sys.argv by @@ -94,6 +94,11 @@ def Fire(component=None, command=None, name=None): 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. + help_sequence: Optional. If supplied, the sequence of commands + will be reordered based on provided list in argument. They will + be displayed before all the other commands. This should be + a list of strings. + Returns: The result of executing the Fire command. Execution begins with the initial target component. The component is updated by using the command arguments @@ -141,13 +146,13 @@ def Fire(component=None, command=None, name=None): component_trace = _Fire(component, args, parsed_flag_args, context, name) if component_trace.HasError(): - _DisplayError(component_trace) + _DisplayError(component_trace, help_sequence=help_sequence) 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_text = helptext.HelpText( - result, trace=component_trace, verbose=component_trace.verbose) + result, trace=component_trace, verbose=component_trace.verbose, help_sequence=help_sequence) output.append(help_text) Display(output, out=sys.stderr) raise FireExit(0, component_trace) @@ -158,13 +163,13 @@ def Fire(component=None, command=None, name=None): if component_trace.show_help: result = component_trace.GetResult() help_text = helptext.HelpText( - result, trace=component_trace, verbose=component_trace.verbose) + result, trace=component_trace, verbose=component_trace.verbose, help_sequence=help_sequence) output = [help_text] Display(output, out=sys.stderr) raise FireExit(0, component_trace) # The command succeeded normally; print the result. - _PrintResult(component_trace, verbose=component_trace.verbose) + _PrintResult(component_trace, verbose=component_trace.verbose, help_sequence=help_sequence) result = component_trace.GetResult() return result @@ -241,7 +246,7 @@ def _IsHelpShortcut(component_trace, remaining_args): return show_help -def _PrintResult(component_trace, verbose=False): +def _PrintResult(component_trace, verbose=False, help_sequence=None): """Prints the result of the Fire call to stdout in a human readable way.""" # TODO(dbieber): Design human readable deserializable serialization method # and move serialization to its own module. @@ -267,12 +272,12 @@ def _PrintResult(component_trace, verbose=False): print(result) else: help_text = helptext.HelpText( - result, trace=component_trace, verbose=verbose) + result, trace=component_trace, verbose=verbose, help_sequence=help_sequence) output = [help_text] Display(output, out=sys.stdout) -def _DisplayError(component_trace): +def _DisplayError(component_trace, help_sequence=None): """Prints the Fire trace and the error to stdout.""" result = component_trace.GetResult() @@ -287,7 +292,7 @@ def _DisplayError(component_trace): print('INFO: Showing help with the command {cmd}.\n'.format( cmd=pipes.quote(command)), file=sys.stderr) help_text = helptext.HelpText(result, trace=component_trace, - verbose=component_trace.verbose) + verbose=component_trace.verbose, help_sequence=help_sequence) output.append(help_text) Display(output, out=sys.stderr) else: diff --git a/fire/helptext.py b/fire/helptext.py index b1d10b44..def4c869 100644 --- a/fire/helptext.py +++ b/fire/helptext.py @@ -49,7 +49,7 @@ SUBSECTION_INDENTATION = 4 -def HelpText(component, trace=None, verbose=False): +def HelpText(component, trace=None, verbose=False, help_sequence=None): """Gets the help string for the current component, suitable for a help screen. Args: @@ -57,6 +57,10 @@ def HelpText(component, trace=None, verbose=False): 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. + help_sequence: Optional. If supplied, the sequence of commands + will be reordered based on provided list in argument. They will + be displayed before all the other commands. This should be + a list of strings. Returns: The full help screen as a string. @@ -81,7 +85,7 @@ def HelpText(component, trace=None, verbose=False): args_and_flags_sections = [] notes_sections = [] usage_details_sections = _UsageDetailsSections(component, - actions_grouped_by_kind) + actions_grouped_by_kind, help_sequence=help_sequence) sections = ( [name_section, synopsis_section, description_section] @@ -254,7 +258,7 @@ def _ArgsAndFlagsSections(info, spec, metadata): return args_and_flags_sections, notes_sections -def _UsageDetailsSections(component, actions_grouped_by_kind): +def _UsageDetailsSections(component, actions_grouped_by_kind, help_sequence=None): """The usage details sections of the help string.""" groups, commands, values, indexes = actions_grouped_by_kind @@ -262,7 +266,7 @@ def _UsageDetailsSections(component, actions_grouped_by_kind): if groups.members: sections.append(_MakeUsageDetailsSection(groups)) if commands.members: - sections.append(_MakeUsageDetailsSection(commands)) + sections.append(_MakeUsageDetailsSection(commands,help_sequence=help_sequence)) if values.members: sections.append(_ValuesUsageDetailsSection(component, values)) if indexes.members: @@ -543,7 +547,7 @@ def _GetArgDescription(name, docstring_info): return None -def _MakeUsageDetailsSection(action_group): +def _MakeUsageDetailsSection(action_group, help_sequence=None): """Creates a usage details section for the provided action group.""" item_strings = [] for name, member in action_group.GetItems(): @@ -560,6 +564,15 @@ def _MakeUsageDetailsSection(action_group): summary = None item = _CreateItem(name, summary) item_strings.append(item) + + + if help_sequence: + com_names = [name for name, memeber in action_group.GetItems()] + help_sequence = [x for x in help_sequence if x in com_names] + com_names_reorder = [x for x in com_names if x not in help_sequence] + help_sequence.extend(com_names_reorder) + com_names_reorder = help_sequence + item_strings = [item_strings[i] for x in com_names_reorder for i in range(len(com_names)) if x == com_names[i]] return (action_group.plural.upper(), _NewChoicesSection(action_group.name.upper(), item_strings)) diff --git a/fire/helptext_test.py b/fire/helptext_test.py index 3cb40fbe..7513c876 100644 --- a/fire/helptext_test.py +++ b/fire/helptext_test.py @@ -423,6 +423,39 @@ def testHelpTextNameSectionCommandWithSeparatorVerbose(self): self.assertIn('double -', help_screen) self.assertIn('double - -', help_screen) + def testHelpTextWithHelpSequence(self): + component = tc.ClassWithMultipleCommands() + help_output = helptext.HelpText(component=component, help_sequence=['print_msg']) + expected_output = """ + NAME + - Test class for testing help text output with help_sequence=True argument. + +SYNOPSIS + COMMAND | VALUE + +DESCRIPTION + This is some detail description of this test class. + +COMMANDS + COMMAND is one of the following: + + print_msg + Prints a message. + + append_msg + Appends string to current message. + + update_msg + Adds a message. + +VALUES + VALUE is one of the following: + + message + The default message to print.""" + self.assertEqual(textwrap.dedent(expected_output).strip(), + help_output.strip()) + class UsageTest(testutils.BaseTestCase): diff --git a/fire/test_components.py b/fire/test_components.py index eee9a07c..9bf868b2 100644 --- a/fire/test_components.py +++ b/fire/test_components.py @@ -370,6 +370,39 @@ def print_msg(self, msg=None): print(msg) +class ClassWithMultipleCommands(object): + """Test class for testing help text output with help_sequence=True argument. + + This is some detail description of this test class. + """ + + def __init__(self, message='Hello!'): + """Constructor of the test class. + + Constructs a new ClassWithDocstring object. + + Args: + message: The default message to print. + """ + self.message = message + + def print_msg(self, msg=None): + """Prints a message.""" + if msg is None: + msg = self.message + print(msg) + + def update_msg(self, msg=None): + """Adds a message.""" + self.message = msg + print(msg) + + def append_msg(self, msg=None): + """Appends string to current message.""" + self.message = self.message + ' ' + msg + print(msg) + + class ClassWithMultilineDocstring(object): """Test class for testing help text output with multiline docstring.