9 Commits
Author SHA1 Message Date
David Bieber 669cf281a5 Split helputils into helputils (old, still used) and helptext (new module, scheduled to replace helputils)
PiperOrigin-RevId: 240429364
Change-Id: Ifddbb6a4aff20407df9a84931898239d0b26649c
2019-03-26 14:36:22 -07:00
Rebecca Chen c507c093fa Fix or ignore type errors generated by the next release of pytype.
PiperOrigin-RevId: 239662678
Change-Id: Iab3c34c8c5b59e0de2b0ff887699428fe28c68d5
2019-03-21 13:40:51 -07:00
Joe Chen 2f80522f6d Fixed naming of test cases broke travis build
PiperOrigin-RevId: 239256432
Change-Id: Idb349ed14613fbb3d65082eac6e0d7dc1f30555c
2019-03-19 13:27:31 -07:00
Joe Chen 665c497693 Generating flags information for function objects
PiperOrigin-RevId: 239247774
Change-Id: I2dc83a9006f94239cfc5b267c434304e4317f39a
2019-03-19 12:41:36 -07:00
David Bieber 36fcb1995b Resolve no-else-raise linter errors.
PiperOrigin-RevId: 236714504
Change-Id: Ifaeee426ee05a5e5deb3039639da6bd6ab8de7d8
2019-03-04 13:33:17 -08:00
Rebecca Chen 1c1f6dd110 Add type-checking to Python Fire via pytype.
PiperOrigin-RevId: 236693773
Change-Id: I8eb393b69283445b952780c288704e4b8e262628
2019-03-04 11:39:00 -08:00
David Bieber 2363b5f2b7 Mark Python 3.7 support in setup.py
PiperOrigin-RevId: 235782693
Change-Id: I55f0bfd76f6d1750223ca7378581985d48fae14d
2019-02-26 13:35:17 -08:00
David Bieber 3578817f16 Enable pylint for all Python versions
PiperOrigin-RevId: 235739402
Change-Id: Ia95401b3983546c6b392789b12ed5e7754cf5c8f
2019-02-26 09:52:44 -08:00
David Bieber 2bfb82ef23 Support Python 3.7 in travis tests for Python Fire
PiperOrigin-RevId: 235738586
Change-Id: I85b79f073b2db8b95e98d38bb3b3e7bcce4b8ab6
2019-02-26 09:47:38 -08:00
13 changed files with 755 additions and 571 deletions
+3
View File
@@ -98,3 +98,6 @@ ENV/
# PyCharm IDE
.idea/
# Type-checking
.pytype/
+18 -2
View File
@@ -4,14 +4,30 @@ python:
- "3.4"
- "3.5"
- "3.6"
# Workaround for testing Python 3.7:
# https://github.com/travis-ci/travis-ci/issues/9815
matrix:
include:
- python: 3.7
dist: xenial
sudo: yes
before_install:
- pip install --upgrade setuptools pip
- pip install --upgrade pylint pytest pytest-pylint pytest-runner
install:
- pip install hypothesis
- pip install hypothesis python-Levenshtein
- python setup.py develop
script:
- python -m pytest # Run the tests without IPython.
- pip install ipython
- python -m pytest # Now run the tests with IPython.
- if [[ $TRAVIS_PYTHON_VERSION != 3.6 ]]; then pylint fire --ignore=test_components_py3.py,parser_fuzz_test.py,console; fi
- pylint fire --ignore=test_components_py3.py,parser_fuzz_test.py,console
- pip install pytype
# Run type-checking, excluding files that define or use py3 features in py2.
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then
pytype -x
fire/fire_test.py
fire/inspectutils_test.py
fire/test_components_py3.py;
else
pytype; fi
+9 -9
View File
@@ -133,7 +133,7 @@ def Fire(component=None, command=None, name=None):
if component_trace.HasError():
_DisplayError(component_trace)
raise FireExit(2, component_trace)
elif component_trace.show_trace and component_trace.show_help:
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 = helputils.HelpString(
@@ -141,22 +141,22 @@ def Fire(component=None, command=None, name=None):
output.append(help_string)
Display(output)
raise FireExit(0, component_trace)
elif component_trace.show_trace:
if component_trace.show_trace:
output = ['Fire trace:\n{trace}'.format(trace=component_trace)]
Display(output)
raise FireExit(0, component_trace)
elif component_trace.show_help:
if component_trace.show_help:
result = component_trace.GetResult()
help_string = helputils.HelpString(
result, component_trace, component_trace.verbose)
output = [help_string]
Display(output)
raise FireExit(0, component_trace)
else:
# The command succeeded normally; print the result.
_PrintResult(component_trace, verbose=component_trace.verbose)
result = component_trace.GetResult()
return result
# The command succeeded normally; print the result.
_PrintResult(component_trace, verbose=component_trace.verbose)
result = component_trace.GetResult()
return result
def Display(lines):
@@ -487,7 +487,7 @@ def _Fire(component, args, context, name=None):
# If the component is a namedtuple, we need to convert it to dict to
# be able to use the .items() method.
if inspectutils.IsNamedTuple(component):
component = component._asdict()
component = component._asdict() # pytype: disable=attribute-error
for key, value in component.items():
if target == str(key):
component = value
+2 -2
View File
@@ -82,7 +82,7 @@ class Namespace(dict):
def __getattr__(self, key):
if key not in self:
self[key] = Namespace()
return self.get(key)
return self[key]
def __setattr__(self, key, value):
self[key] = value
@@ -398,7 +398,7 @@ def _consume_line(line_info, state):
if state.section.new and state.section.format == Formats.RST:
# The current line starts with an RST directive, e.g. ":param arg:".
directive = _get_directive(line_info)
directive_tokens = directive.split()
directive_tokens = directive.split() # pytype: disable=attribute-error
if state.section.title == Sections.ARGS:
name = directive_tokens[-1]
arg = _get_or_create_arg_by_name(state, name)
+473
View File
@@ -0,0 +1,473 @@
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""helptext is the new, work in progress, help text module for Fire.
This is a fork of, and is intended to replace, helputils.
Utility for producing help strings for use in Fire CLIs.
Can produce help strings suitable for display in Fire CLIs for any type of
Python object, module, class, or function.
There are two types of informative strings: Usage and Help screens.
Usage screens are shown when the user accesses a group or accesses a command
without calling it. A Usage screen shows information about how to use that group
or command. Usage screens are typically short and show the minimal information
necessary for the user to determine how to proceed.
Help screens are shown when the user requests help with the help flag (--help).
Help screens are shown in a less-style console view, and contain detailed help
information.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import inspect
from fire import completion
from fire import docstrings
from fire import inspectutils
from fire import value_types
def Text(component, trace=None, verbose=False):
"""Returns the text to show for a supplied component.
The component can be any Python class, object, function, module, etc.
Args:
component: The component to determine the help string for.
trace: The Fire trace leading to this component.
verbose: Whether to include private members in the help string.
Returns:
String suitable for display giving information about the component.
"""
info = inspectutils.Info(component)
info['docstring_info'] = docstrings.parse(info['docstring'])
is_error_screen = False
if trace:
is_error_screen = trace.HasError()
if is_error_screen:
return UsageText(info, trace, verbose=verbose)
else:
return HelpText(info, trace, verbose=verbose)
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 GetSummaryAndDescription(docstring_info):
"""Retrieves summary and description for help text generation."""
# To handle both empty string and None
summary = docstring_info.summary if docstring_info.summary else None
description = (
docstring_info.description if docstring_info.description else None)
return summary, description
def GetCurrentCommand(trace=None):
"""Returns current command for the purpose of generating help text."""
if trace:
current_command = trace.GetCommand()
else:
current_command = ''
return current_command
def HelpText(component, info, trace=None, verbose=False):
if inspect.isroutine(component) or inspect.isclass(component):
return HelpTextForFunction(component, info, trace)
else:
return HelpTextForObject(component, info, trace, verbose)
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
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)
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
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'))
flags_template = """
FLAGS
{items}
"""
flag_items = []
for flag in flags:
item_template = ' --{flag_name}\n {flag_description}\n'
flag_description = None
for arg_in_docstring in info['docstring_info'].args:
if arg_in_docstring.name == flag:
flag_description = arg_in_docstring.description
item = item_template.format(
flag_name=flag, flag_description=flag_description)
flag_items.append(item)
if flag_items:
args_and_flags_section += flags_template.format(
items='\n'.join(flag_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 HelpTextForObject(component, info, trace=None, verbose=False):
"""Generates help text for python objects.
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.
"""
output_template = """NAME
{current_command} - {command_summary}
SYNOPSIS
{synopsis}
DESCRIPTION
{command_description}
{detail_section}
"""
current_command = GetCurrentCommand(trace)
docstring_info = info['docstring_info']
command_summary = docstring_info.summary if docstring_info.summary else ''
if docstring_info.description:
command_description = docstring_info.description
else:
command_description = ''
groups = []
commands = []
values = []
members = completion._Members(component, verbose) # pylint: disable=protected-access
for member_name, member in members:
if value_types.IsGroup(member):
groups.append((member_name, member))
if value_types.IsCommand(member):
commands.append((member_name, member))
if value_types.IsValue(member):
values.append((member_name, member))
possible_actions = []
# TODO(joejoevictor): Add global flags to here. Also, if it's a callable,
# there will be additional flags.
possible_flags = ''
detail_section_string = ''
item_template = """
{name}
{command_summary}
"""
if groups:
# TODO(joejoevictor): Add missing GROUPS section handling
possible_actions.append('GROUP')
if commands:
possible_actions.append('COMMAND')
commands_str_template = """
COMMANDS
COMMAND is one of the followings:
{items}
"""
command_item_strings = []
for command_name, command in commands:
command_docstring_info = docstrings.parse(
inspectutils.Info(command)['docstring'])
command_item_strings.append(
item_template.format(
name=command_name,
command_summary=command_docstring_info.summary))
detail_section_string += commands_str_template.format(
items=('\n'.join(command_item_strings)).rstrip('\n'))
if values:
possible_actions.append('VALUES')
values_str_template = """
VALUES
VALUE is one of the followings:
{items}
"""
value_item_strings = []
for value_name, value in values:
del value
init_docstring_info = docstrings.parse(
inspectutils.Info(component.__class__.__init__)['docstring'])
for arg_info in init_docstring_info.args:
if arg_info.name == value_name:
value_item_strings.append(
item_template.format(
name=value_name, command_summary=arg_info.description))
detail_section_string += values_str_template.format(
items=('\n'.join(value_item_strings)).rstrip('\n'))
possible_actions_string = ' ' + (' | '.join(possible_actions))
synopsis_template = '{current_command}{possible_actions}{possible_flags}'
synopsis_string = synopsis_template.format(
current_command=current_command,
possible_actions=possible_actions_string,
possible_flags=possible_flags)
return output_template.format(
current_command=current_command,
command_summary=command_summary,
synopsis=synopsis_string,
command_description=command_description,
detail_section=detail_section_string)
def UsageText(component, trace=None, verbose=False):
if inspect.isroutine(component) or inspect.isclass(component):
return UsageTextForFunction(component, trace)
else:
return UsageTextForObject(component, trace, verbose)
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 UsageTextForObject(component, trace=None, verbose=False):
"""Returns help text for usage screen for objects.
Construct help text for usage screen to inform the user about error occurred
and correct syntax for invoking the object.
Args:
component: The component to determine the usage text for.
trace: The Fire trace object containing all metadata of current execution.
verbose: Whether to include private members in the usage text.
Returns:
String suitable for display in error screen.
"""
output_template = """Usage: {current_command} <{possible_actions}>
{availability_lines}
For detailed information on this command, run:
{current_command} --help
"""
if trace:
command = trace.GetCommand()
else:
command = None
if not command:
command = ''
groups = []
commands = []
values = []
members = completion._Members(component, verbose) # pylint: disable=protected-access
for member_name, member in members:
if value_types.IsGroup(member):
groups.append(member_name)
if value_types.IsCommand(member):
commands.append(member_name)
if value_types.IsValue(member):
values.append(member_name)
possible_actions = []
availability_lines = []
availability_lint_format = '{header:20s}{choices}'
if groups:
possible_actions.append('groups')
groups_string = ' | '.join(groups)
groups_text = availability_lint_format.format(
header='available groups:',
choices=groups_string)
availability_lines.append(groups_text)
if commands:
possible_actions.append('commands')
commands_string = ' | '.join(commands)
commands_text = availability_lint_format.format(
header='available commands:',
choices=commands_string)
availability_lines.append(commands_text)
if values:
possible_actions.append('values')
values_string = ' | '.join(values)
values_text = availability_lint_format.format(
header='available values:',
choices=values_string)
availability_lines.append(values_text)
possible_actions_string = '|'.join(possible_actions)
availability_lines_string = '\n'.join(availability_lines)
return output_template.format(
current_command=command,
possible_actions=possible_actions_string,
availability_lines=availability_lines_string)
+231
View File
@@ -0,0 +1,231 @@
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for the helptext module."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import textwrap
from fire import docstrings
from fire import helptext
from fire import inspectutils
from fire import test_components as tc
from fire import testutils
from fire import trace
class HelpScreenTest(testutils.BaseTestCase):
def testHelpScreen(self):
component = tc.ClassWithDocstring()
t = trace.FireTrace(component, name='ClassWithDocstring')
info = inspectutils.Info(component)
info['docstring_info'] = docstrings.parse(info['docstring'])
help_output = helptext.HelpText(component, info, t)
expected_output = """
NAME
ClassWithDocstring - Test class for testing help text output.
SYNOPSIS
ClassWithDocstring COMMAND | VALUES
DESCRIPTION
This is some detail description of this test class.
COMMANDS
COMMAND is one of the followings:
print_msg
Prints a message.
VALUES
VALUE is one of the followings:
message
The default message to print.
"""
self.assertEqual(textwrap.dedent(expected_output).lstrip('\n'), help_output)
def testHelpScreenForFunctionDocstringWithLineBreak(self):
component = tc.ClassWithMultilineDocstring.example_generator
t = trace.FireTrace(component, name='example_generator')
info = inspectutils.Info(component)
info['docstring_info'] = docstrings.parse(info['docstring'])
help_output = helptext.HelpText(component, info, t)
expected_output = """
NAME
example_generator - Generators have a ``Yields`` section instead of a ``Returns`` section.
SYNOPSIS
example_generator N
DESCRIPTION
Generators have a ``Yields`` section instead of a ``Returns`` section.
POSITIONAL ARGUMENTS
N
The upper limit of the range to generate, from 0 to `n` - 1.
NOTES
You could also use flags syntax for POSITIONAL ARGUMENTS
"""
self.assertEqual(textwrap.dedent(expected_output).lstrip('\n'), help_output)
def testHelpScreenForFunctionFunctionWithDefaultArgs(self):
component = tc.WithDefaults().double
t = trace.FireTrace(component, name='double')
info = inspectutils.Info(component)
info['docstring_info'] = docstrings.parse(info['docstring'])
help_output = helptext.HelpText(component, info, t)
expected_output = """
NAME
double - Returns the input multiplied by 2.
SYNOPSIS
double [--count=COUNT]
DESCRIPTION
Returns the input multiplied by 2.
FLAGS
--count
Input number that you want to double.
NOTES
You could also use flags syntax for POSITIONAL ARGUMENTS
"""
self.assertEqual(textwrap.dedent(expected_output).lstrip('\n'), help_output)
class UsageTest(testutils.BaseTestCase):
def testUsageOutput(self):
component = tc.NoDefaults()
t = trace.FireTrace(component, name='NoDefaults')
usage_output = helptext.UsageText(component, trace=t, verbose=False)
expected_output = '''
Usage: NoDefaults <commands>
available commands: double | triple
For detailed information on this command, run:
NoDefaults --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputVerbose(self):
component = tc.NoDefaults()
t = trace.FireTrace(component, name='NoDefaults')
usage_output = helptext.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: NoDefaults <commands>
available commands: double | triple
For detailed information on this command, run:
NoDefaults --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputMethod(self):
component = tc.NoDefaults().double
t = trace.FireTrace(component, name='NoDefaults')
t.AddAccessedProperty(component, 'double', ['double'], None, None)
usage_output = helptext.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: NoDefaults double COUNT
For detailed information on this command, run:
NoDefaults double --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputFunctionWithHelp(self):
component = tc.function_with_help
t = trace.FireTrace(component, name='function_with_help')
usage_output = helptext.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: function_with_help <flags>
Available flags: --help
For detailed information on this command, run:
function_with_help -- --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputFunctionWithDocstring(self):
component = tc.multiplier_with_docstring
t = trace.FireTrace(component, name='multiplier_with_docstring')
usage_output = helptext.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: multiplier_with_docstring NUM <flags>
Available flags: --rate
For detailed information on this command, run:
multiplier_with_docstring --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
@testutils.skip('The functionality is not implemented yet')
def testUsageOutputCallable(self):
# This is both a group and a command!
component = tc.CallableWithKeywordArgument
t = trace.FireTrace(component, name='CallableWithKeywordArgument')
usage_output = helptext.UsageText(component, trace=t, verbose=True)
# TODO(zuhaohen): We need to handle the case for keyword args as well
# i.e. __call__ method of CallableWithKeywordArgument
expected_output = '''
Usage: CallableWithKeywordArgument <commands>
Available commands: print_msg
For detailed information on this command, run:
CallableWithKeywordArgument -- --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputConstructorWithParameter(self):
component = tc.InstanceVars
t = trace.FireTrace(component, name='InstanceVars')
usage_output = helptext.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: InstanceVars ARG1 ARG2
For detailed information on this command, run:
InstanceVars --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
if __name__ == '__main__':
testutils.main()
+2 -379
View File
@@ -38,7 +38,6 @@ import inspect
from fire import completion
from fire import docstrings
from fire import inspectutils
from fire import value_types
def _NormalizeField(field):
@@ -119,22 +118,13 @@ def HelpString(component, trace=None, verbose=False):
String suitable for display giving information about the component.
"""
info = inspectutils.Info(component)
# TODO(dbieber): Stop using UsageString in favor of UsageText.
info['usage'] = UsageString(component, trace, verbose)
info['docstring_info'] = docstrings.parse(info['docstring'])
is_error_screen = False
if trace:
is_error_screen = trace.HasError()
if is_error_screen:
# TODO(dbieber): Call UsageText instead of CommonHelpText once ready.
return _CommonHelpText(info, trace)
else:
return _HelpText(info, trace)
return _HelpText(info, trace)
def _CommonHelpText(info, trace=None):
def _HelpText(info, trace=None):
"""Returns help text.
This was a copy of previous HelpString function and will be removed once the
@@ -146,8 +136,6 @@ def _CommonHelpText(info, trace=None):
Returns:
String suitable for display giving information about the component.
"""
# TODO(joejoevictor): Currently this is just a copy of existing
# HelpString method. We will reimplement this further in later CLs.
fields = _GetFields(trace)
try:
@@ -193,371 +181,6 @@ def GetCurrentCommand(trace=None):
return current_command
def HelpText(component, info, trace=None, verbose=False):
if inspect.isroutine(component) or inspect.isclass(component):
return HelpTextForFunction(component, info, trace)
else:
return HelpTextForObject(component, info, trace, verbose)
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 HelpTextForObject(component, info, trace=None, verbose=False):
"""Generates help text for python objects.
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.
"""
output_template = """NAME
{current_command} - {command_summary}
SYNOPSIS
{synopsis}
DESCRIPTION
{command_description}
{detail_section}
"""
current_command = GetCurrentCommand(trace)
docstring_info = info['docstring_info']
command_summary = docstring_info.summary if docstring_info.summary else ''
if docstring_info.description:
command_description = docstring_info.description
else:
command_description = ''
groups = []
commands = []
values = []
members = completion._Members(component, verbose) # pylint: disable=protected-access
for member_name, member in members:
if value_types.IsGroup(member):
groups.append((member_name, member))
if value_types.IsCommand(member):
commands.append((member_name, member))
if value_types.IsValue(member):
values.append((member_name, member))
possible_actions = []
# TODO(joejoevictor): Add global flags to here. Also, if it's a callable,
# there will be additional flags.
possible_flags = ''
detail_section_string = ''
item_template = """
{name}
{command_summary}
"""
if groups:
# TODO(joejoevictor): Add missing GROUPS section handling
possible_actions.append('GROUP')
if commands:
possible_actions.append('COMMAND')
commands_str_template = """
COMMANDS
COMMAND is one of the followings:
{items}
"""
command_item_strings = []
for command_name, command in commands:
command_docstring_info = docstrings.parse(
inspectutils.Info(command)['docstring'])
command_item_strings.append(
item_template.format(
name=command_name,
command_summary=command_docstring_info.summary))
detail_section_string += commands_str_template.format(
items=('\n'.join(command_item_strings)).rstrip('\n'))
if values:
possible_actions.append('VALUES')
values_str_template = """
VALUES
VALUE is one of the followings:
{items}
"""
value_item_strings = []
for value_name, value in values:
del value
init_docstring_info = docstrings.parse(
inspectutils.Info(component.__class__.__init__)['docstring'])
for arg_info in init_docstring_info.args:
if arg_info.name == value_name:
value_item_strings.append(
item_template.format(
name=value_name, command_summary=arg_info.description))
detail_section_string += values_str_template.format(
items=('\n'.join(value_item_strings)).rstrip('\n'))
possible_actions_string = ' ' + (' | '.join(possible_actions))
synopsis_template = '{current_command}{possible_actions}{possible_flags}'
synopsis_string = synopsis_template.format(
current_command=current_command,
possible_actions=possible_actions_string,
possible_flags=possible_flags)
return output_template.format(
current_command=current_command,
command_summary=command_summary,
synopsis=synopsis_string,
command_description=command_description,
detail_section=detail_section_string)
def UsageText(component, trace=None, verbose=False):
if inspect.isroutine(component) or inspect.isclass(component):
return UsageTextForFunction(component, trace)
else:
return UsageTextForObject(component, trace, verbose)
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 UsageTextForObject(component, trace=None, verbose=False):
"""Returns help text for usage screen for objects.
Construct help text for usage screen to inform the user about error occurred
and correct syntax for invoking the object.
Args:
component: The component to determine the usage text for.
trace: The Fire trace object containing all metadata of current execution.
verbose: Whether to include private members in the usage text.
Returns:
String suitable for display in error screen.
"""
output_template = """Usage: {current_command} <{possible_actions}>
{availability_lines}
For detailed information on this command, run:
{current_command} --help
"""
if trace:
command = trace.GetCommand()
else:
command = None
if not command:
command = ''
groups = []
commands = []
values = []
members = completion._Members(component, verbose) # pylint: disable=protected-access
for member_name, member in members:
if value_types.IsGroup(member):
groups.append(member_name)
if value_types.IsCommand(member):
commands.append(member_name)
if value_types.IsValue(member):
values.append(member_name)
possible_actions = []
availability_lines = []
availability_lint_format = '{header:20s}{choices}'
if groups:
possible_actions.append('groups')
groups_string = ' | '.join(groups)
groups_text = availability_lint_format.format(
header='available groups:',
choices=groups_string)
availability_lines.append(groups_text)
if commands:
possible_actions.append('commands')
commands_string = ' | '.join(commands)
commands_text = availability_lint_format.format(
header='available commands:',
choices=commands_string)
availability_lines.append(commands_text)
if values:
possible_actions.append('values')
values_string = ' | '.join(values)
values_text = availability_lint_format.format(
header='available values:',
choices=values_string)
availability_lines.append(values_text)
possible_actions_string = '|'.join(possible_actions)
availability_lines_string = '\n'.join(availability_lines)
return output_template.format(
current_command=command,
possible_actions=possible_actions_string,
availability_lines=availability_lines_string)
def _HelpText(info, trace=None):
"""Returns help text for extensive help screen.
Construct help text for help screen when user explicitly requesting help by
having -h, --help in the command sequence.
Args:
info: The IR object containing metadata of an object.
trace: The Fire trace object containing all metadata of current execution.
Returns:
String suitable for display in extensive help screen.
"""
# TODO(joejoevictor): Implement real help text construction.
return _CommonHelpText(info, trace)
def _UsageStringFromFullArgSpec(command, spec):
"""Get a usage string from the FullArgSpec for the given command.
-178
View File
@@ -19,14 +19,10 @@ from __future__ import division
from __future__ import print_function
import os
import textwrap
from fire import docstrings
from fire import helputils
from fire import inspectutils
from fire import test_components as tc
from fire import testutils
from fire import trace
import six
@@ -131,179 +127,5 @@ class HelpUtilsTest(testutils.BaseTestCase):
self.assertIn('Line: ', helpstring)
class HelpScreenTest(testutils.BaseTestCase):
def testHelpScreen(self):
component = tc.ClassWithDocstring()
t = trace.FireTrace(component, name='ClassWithDocstring')
info = inspectutils.Info(component)
info['docstring_info'] = docstrings.parse(info['docstring'])
help_output = helputils.HelpText(component, info, t)
expected_output = """
NAME
ClassWithDocstring - Test class for testing help text output.
SYNOPSIS
ClassWithDocstring COMMAND | VALUES
DESCRIPTION
This is some detail description of this test class.
COMMANDS
COMMAND is one of the followings:
print_msg
Prints a message.
VALUES
VALUE is one of the followings:
message
The default message to print.
"""
self.assertEqual(textwrap.dedent(expected_output).lstrip('\n'), help_output)
def testHelpScreenWithLineBreak(self):
component = tc.ClassWithMultilineDocstring.example_generator
t = trace.FireTrace(component, name='example_generator')
info = inspectutils.Info(component)
info['docstring_info'] = docstrings.parse(info['docstring'])
help_output = helputils.HelpText(component, info, t)
expected_output = """
NAME
example_generator - Generators have a ``Yields`` section instead of a ``Returns`` section.
SYNOPSIS
example_generator N
DESCRIPTION
Generators have a ``Yields`` section instead of a ``Returns`` section.
POSITIONAL ARGUMENTS
N
The upper limit of the range to generate, from 0 to `n` - 1.
NOTES
You could also use flags syntax for POSITIONAL ARGUMENTS
"""
self.assertEqual(textwrap.dedent(expected_output).lstrip('\n'), help_output)
class UsageTest(testutils.BaseTestCase):
def testUsageOutput(self):
component = tc.NoDefaults()
t = trace.FireTrace(component, name='NoDefaults')
usage_output = helputils.UsageText(component, trace=t, verbose=False)
expected_output = '''
Usage: NoDefaults <commands>
available commands: double | triple
For detailed information on this command, run:
NoDefaults --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputVerbose(self):
component = tc.NoDefaults()
t = trace.FireTrace(component, name='NoDefaults')
usage_output = helputils.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: NoDefaults <commands>
available commands: double | triple
For detailed information on this command, run:
NoDefaults --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputMethod(self):
component = tc.NoDefaults().double
t = trace.FireTrace(component, name='NoDefaults')
t.AddAccessedProperty(component, 'double', ['double'], None, None)
usage_output = helputils.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: NoDefaults double COUNT
For detailed information on this command, run:
NoDefaults double --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputFunctionWithHelp(self):
component = tc.function_with_help
t = trace.FireTrace(component, name='function_with_help')
usage_output = helputils.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: function_with_help <flags>
Available flags: --help
For detailed information on this command, run:
function_with_help -- --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputFunctionWithDocstring(self):
component = tc.multiplier_with_docstring
t = trace.FireTrace(component, name='multiplier_with_docstring')
usage_output = helputils.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: multiplier_with_docstring NUM <flags>
Available flags: --rate
For detailed information on this command, run:
multiplier_with_docstring --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
@testutils.skip('The functionality is not implemented yet')
def testUsageOutputCallable(self):
# This is both a group and a command!
component = tc.CallableWithKeywordArgument
t = trace.FireTrace(component, name='CallableWithKeywordArgument')
usage_output = helputils.UsageText(component, trace=t, verbose=True)
# TODO(zuhaohen): We need to handle the case for keyword args as well
# i.e. __call__ method of CallableWithKeywordArgument
expected_output = '''
Usage: CallableWithKeywordArgument <commands>
Available commands: print_msg
For detailed information on this command, run:
CallableWithKeywordArgument -- --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
def testUsageOutputConstructorWithParameter(self):
component = tc.InstanceVars
t = trace.FireTrace(component, name='InstanceVars')
usage_output = helputils.UsageText(component, trace=t, verbose=True)
expected_output = '''
Usage: InstanceVars ARG1 ARG2
For detailed information on this command, run:
InstanceVars --help
'''
self.assertEqual(
usage_output,
textwrap.dedent(expected_output).lstrip('\n'))
if __name__ == '__main__':
testutils.main()
+1 -1
View File
@@ -94,7 +94,7 @@ def _LiteralEval(value):
SyntaxError: If the value string has a syntax error.
"""
root = ast.parse(value, mode='eval')
if isinstance(root.body, ast.BinOp):
if isinstance(root.body, ast.BinOp): # pytype: disable=attribute-error
raise ValueError(value)
for node in ast.walk(root):
+9
View File
@@ -87,8 +87,17 @@ class NoDefaults(object):
class WithDefaults(object):
"""Class with functions that have default arguments."""
def double(self, count=0):
"""Returns the input multiplied by 2.
Args:
count: Input number that you want to double.
Returns:
A number that is the double of count.s
"""
return 2 * count
def triple(self, count=0):
+2
View File
@@ -65,7 +65,9 @@ class FireTrace(object):
def GetResult(self):
"""Returns the component from the last element of the trace."""
# pytype: disable=attribute-error
return self.GetLastHealthyElement().component
# pytype: enable=attribute-error
def GetLastHealthyElement(self):
"""Returns the last element of the trace that is not an error.
+4
View File
@@ -9,3 +9,7 @@ test = pytest
[tool:pytest]
addopts = --ignore=fire/test_components_py3.py --ignore=fire/parser_fuzz_test.py
[pytype]
inputs = .
output = .pytype
+1
View File
@@ -68,6 +68,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Operating System :: OS Independent',
'Operating System :: POSIX',