Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature for Displaying Commands in User Provided Order #298 #310

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
25 changes: 15 additions & 10 deletions fire/core.py
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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()

Expand All @@ -287,15 +292,15 @@ 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:
print(formatting.Error('ERROR: ')
+ component_trace.elements[-1].ErrorAsStr(),
file=sys.stderr)
error_text = helptext.UsageText(result, trace=component_trace,
verbose=component_trace.verbose)
verbose=component_trace.verbose, help_sequence=help_sequence)
varunjohn786 marked this conversation as resolved.
Show resolved Hide resolved
print(error_text, file=sys.stderr)


Expand Down
23 changes: 18 additions & 5 deletions fire/helptext.py
Expand Up @@ -49,14 +49,18 @@
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:
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.
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.
Expand All @@ -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]
Expand Down Expand Up @@ -254,15 +258,15 @@ 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

sections = []
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:
Expand Down Expand Up @@ -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():
Expand All @@ -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))

Expand Down
33 changes: 33 additions & 0 deletions fire/helptext_test.py
Expand Up @@ -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):

Expand Down
33 changes: 33 additions & 0 deletions fire/test_components.py
Expand Up @@ -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.

Expand Down