8000 Include kwargs in help screens and usage screens. · shader/python-fire@c1a4acf · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit c1a4acf

Browse files
dbiebercopybara-github
authored andcommitted
Include kwargs in help screens and usage screens.
PiperOrigin-RevId: 259807919 Change-Id: I3fb16fa529e1e4085f2fecaf7c1b0d95368d9c20
1 parent ce030f6 commit c1a4acf

File tree

3 files changed

+89
-68
lines changed

3 files changed

+89
-68
lines changed

fire/helptext.py

Lines changed: 79 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ def _ArgsAndFlagsSections(info, spec, metadata):
168168
"""The "Args and Flags" sections of the help string."""
169169
args_with_no_defaults = spec.args[:len(spec.args) - len(spec.defaults)]
170170
args_with_defaults = spec.args[len(spec.args) - len(spec.defaults):]
171-
flags = args_with_defaults + spec.kwonlyargs
172171

173172
# Check if positional args are allowed. If not, require flag syntax for args.
174173
accepts_positional_args = metadata.get(decorators.ACCEPTS_POSITIONAL_ARGS)
@@ -197,10 +196,23 @@ def _ArgsAndFlagsSections(info, spec, metadata):
197196
('NOTES', 'You can also use flags syntax for POSITIONAL ARGUMENTS')
198197
)
199198

200-
flag_items = [
201-
_CreateFlagItem(flag, docstring_info)
202-
for flag in flags
199+
optional_flag_items = [
200+
_CreateFlagItem(flag, docstring_info, required=False)
201+
for flag in args_with_defaults
203202
]
203+
required_flag_items = [
204+
_CreateFlagItem(flag, docstring_info, required=True)
205+
for flag in spec.kwonlyargs
206+
]
207+
flag_items = optional_flag_items + required_flag_items
208+
209+
if spec.varkw:
210+
description = _GetArgDescription(spec.varkw, docstring_info)
211+
message = ('Additional flags are accepted.'
212+
if flag_items else
213+
'Flags are accepted.')
214+
item = _CreateItem(message, description, indent=4)
215+
flag_items.append(item)
204216

205217
if flag_items:
206218
flags_section = ('FLAGS', '\n'.join(flag_items))
@@ -262,7 +274,6 @@ def _GetArgsAndFlagsString(spec, metadata):
262274
"""
263275
args_with_no_defaults = spec.args[:len(spec.args) - len(spec.defaults)]
264276
args_with_defaults = spec.args[len(spec.args) - len(spec.defaults):]
265-
flags = args_with_defaults + spec.kwonlyargs
266277

267278
# Check if positional args are allowed. If not, require flag syntax for args.
268279
accepts_positional_args = metadata.get(decorators.ACCEPTS_POSITIONAL_ARGS)
@@ -279,13 +290,9 @@ def _GetArgsAndFlagsString(spec, metadata):
279290
for arg in args_with_no_defaults]
280291
arg_and_flag_strings.extend(arg_strings)
281292

282-
flag_string_template = '[--{flag_name}={flag_name_upper}]'
283-
if flags:
284-
for flag in flags:
285-
flag_string = flag_string_template.format(
286-
flag_name=flag,
287-
flag_name_upper=formatting.Underline(flag.upper()))
288-
arg_and_flag_strings.append(flag_string)
293+
# If there are any arguments that are treated as flags:
294+
if args_with_defaults or spec.kwonlyargs or spec.varkw:
295+
arg_and_flag_strings.append('<flags>')
289296

290297
if spec.varargs:
291298
varargs_string = '[{varargs}]...'.format(
@@ -367,48 +374,51 @@ def _CreateArgItem(arg, docstring_info):
367374
Returns:
368375
A string to be used in constructing the help screen for the function.
369376
"""
370-
description = None
371-
if docstring_info.args:
372-
for arg_in_docstring in docstring_info.args:
373-
if arg_in_docstring.name in (arg, '*' + arg, '**' + arg):
374-
description = arg_in_docstring.description
377+
description = _GetArgDescription(arg, docstring_info)
375378

376379
arg = arg.upper()
377-
if description:
378-
return _CreateItem(formatting.BoldUnderline(arg), description, indent=4)
379-
else:
380-
return formatting.BoldUnderline(arg)
380+
return _CreateItem(formatting.BoldUnderline(arg), description, indent=4)
381381

382382

383-
def _CreateFlagItem(flag, docstring_info):
383+
def _CreateFlagItem(flag, docstring_info, required=False):
384384
"""Returns a string describing a flag using information from the docstring.
385385
386386
Args:
387387
flag: The name of the flag.
388388
docstring_info: A docstrings.DocstringInfo namedtuple with information about
389389
the containing function's docstring.
390+
required: Whether the flag is required. Keyword-only arguments (only in
391+
Python 3) become required flags, whereas normal keyword arguments become
392+
optional flags.
390393
Returns:
391394
A string to be used in constructing the help screen for the function.
392395
"""
393-
description = None
394-
if docstring_info.args:
395-
for arg_in_docstring in docstring_info.args:
396-
if arg_in_docstring.name == flag:
397-
description = arg_in_docstring.description
398-
break
396+
description = _GetArgDescription(flag, docstring_info)
399397

400-
flag = '--{flag}'.format(flag=formatting.Underline(flag))
401-
if description:
402-
return _CreateItem(flag, description, indent=4)
403-
return flag
398+
flag_string_template = '--{flag_name}={flag_name_upper}'
399+
flag = flag_string_template.format(
400+
flag_name=flag,
401+
flag_name_upper=formatting.Underline(flag.upper()))
402+
if not required:
403+
flag += ' (optional)'
404+
return _CreateItem(flag, description, indent=4)
404405

405406

406407
def _CreateItem(name, description, indent=2):
408+
if not description:
409+
return name
407410
return """{name}
408411
{description}""".format(name=name,
409412
description=formatting.Indent(description, indent))
410413

411414

415+
def _GetArgDescription(name, docstring_info):
416+
if docstring_info.args:
417+
for arg_in_docstring in docstring_info.args:
418+
if arg_in_docstring.name in (name, '*' + name, '**' + name):
419+
return arg_in_docstring.description
420+
421+
412422
def _GroupUsageDetailsSection(groups):
413423
"""Creates a section tuple for the groups section of the usage details."""
414424
group_item_strings = []
@@ -417,9 +427,8 @@ def _GroupUsageDetailsSection(groups):
417427
group_item = group_name
418428
if 'docstring_info' in group_info:
419429
group_docstring_info = group_info['docstring_info']
420-
if group_docstring_info and group_docstring_info.summary:
421-
group_item = _CreateItem(group_name,
422-
group_docstring_info.summary)
430+
if group_docstring_info:
431+
group_item = _CreateItem(group_name, group_docstring_info.summary)
423432
group_item_strings.append(group_item)
424433
return ('GROUPS', _NewChoicesSection('GROUP', group_item_strings))
425434

@@ -432,9 +441,8 @@ def _CommandUsageDetailsSection(commands):
432441
command_item = command_name
433442
if 'docstring_info' in command_info:
434443
command_docstring_info = command_info['docstring_info']
435-
if command_docstring_info and command_docstring_info.summary:
436-
command_item = _CreateItem(command_name,
437-
command_docstring_info.summary)
444+
if command_docstring_info:
445+
command_item = _CreateItem(command_name, command_docstring_info.summary)
438446
command_item_strings.append(command_item)
439447
return ('COMMANDS', _NewChoicesSection('COMMAND', command_item_strings))
440448

@@ -502,14 +510,8 @@ def UsageTextForFunction(component, trace=None, verbose=False):
502510
command = ''
503511

504512
spec = inspectutils.GetFullArgSpec(component)
505-
args = spec.args
506-
if spec.defaults is None:
507-
num_defaults = 0
508-
else:
509-
num_defaults = len(spec.defaults)
510-
args_with_no_defaults = args[:len(args) - num_defaults]
511-
args_with_defaults = args[len(args) - num_defaults:]
512-
flags = args_with_defaults + spec.kwonlyargs
513+
args_with_no_defaults = spec.args[:len(spec.args) - len(spec.defaults)]
514+
args_with_defaults = spec.args[len(spec.args) - len(spec.defaults):]
513515

514516
# Check if positional args are allowed. If not, show flag syntax for args.
515517
metadata = decorators.GetMetadata(component)
@@ -520,13 +522,32 @@ def UsageTextForFunction(component, trace=None, verbose=False):
520522
else:
521523
items = [arg.upper() for arg in args_with_no_defaults]
522524

523-
if flags:
525+
# If there are any arguments that are treated as flags:
526+
if args_with_defaults or spec.kwonlyargs or spec.varkw:
524527
items.append('<flags>')
525-
availability_lines = (
526-
'\nAvailable flags: '
527-
+ ' | '.join('--' + flag for flag in flags) + '\n')
528-
else:
529-
availability_lines = ''
528+
529+
optional_flags = [('--' + flag) for flag in args_with_defaults]
530+
required_flags = [('--' + flag) for flag in spec.kwonlyargs]
531+
532+
# Flags section:
533+
availability_lines = []
534+
if optional_flags:
535+
availability_lines.append(
536+
_CreateAvailabilityLine(header='Optional flags:', items=optional_flags,
537+
header_indent=0))
538+
if required_flags:
539+
availability_lines.append(
540+
_CreateAvailabilityLine(header='Required flags:', items=required_flags,
541+
header_indent=0))
542+
if spec.varkw:
543+
additional_flags = ('Additional flags are accepted.'
544+
if optional_flags or required_flags else
545+
'Flags are accepted.')
546+
availability_lines.append(additional_flags + '\n')
547+
548+
if availability_lines:
549+
# Start the section with blank lines.
550+
availability_lines.insert(0, '\n')
530551

531552
if spec.varargs:
532553
items.append('[{varargs}]...'.format(varargs=spec.varargs.upper()))
@@ -538,7 +559,7 @@ def UsageTextForFunction(component, trace=None, verbose=False):
538559
return output_template.format(
539560
current_command=command,
540561
args_and_flags=args_and_flags,
541-
availability_lines=availability_lines,
562+
availability_lines=''.join(availability_lines),
542563
hyphen_hyphen=hyphen_hyphen)
543564

544565

@@ -576,25 +597,25 @@ def UsageTextForObject(component, trace=None, verbose=False):
576597
possible_actions.append('group')
577598
groups_text = _CreateAvailabilityLine(
578599
header='available groups:',
579-
items=groups)
600+
items=[name for name, _ in groups])
580601
availability_lines.append(groups_text)
581602
if commands:
582603
possible_actions.append('command')
583604
commands_text = _CreateAvailabilityLine(
584605
header='available commands:',
585-
items=commands)
606+
items=[name for name, _ in commands])
586607
availability_lines.append(commands_text)
587608
if values:
588609
possible_actions.append('value')
589610
values_text = _CreateAvailabilityLine(
590611
header='available values:',
591-
items=values)
612+
items=[name for name, _ in values])
592613
availability_lines.append(values_text)
593614
if indexes:
594615
possible_actions.append('index')
595616
indexes_text = _CreateAvailabilityLine(
596617
header='available indexes:',
597-
items=[(indexes, None)])
618+
items=indexes)
598619
availability_lines.append(indexes_text)
599620

600621
if possible_actions:
@@ -615,8 +636,7 @@ def _CreateAvailabilityLine(header, items,
615636
header_indent=2, items_indent=25,
616637
line_length=LINE_LENGTH):
617638
items_width = line_length - items_indent
618-
item_names = [item[0] for item in items]
619-
items_text = '\n'.join(formatting.WrappedJoin(item_names, width=items_width))
639+
items_text = '\n'.join(formatting.WrappedJoin(items, width=items_width))
620640
indented_items_text = formatting.Indent(items_text, spaces=items_indent)
621641
indented_header = formatting.Indent(header, spaces=header_indent)
622642
return indented_header + indented_items_text[len(indented_header):] + '\n'

fire/helptext_test.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ def testHelpTextFunctionWithDefaults(self):
7777
component=component,
7878
trace=trace.FireTrace(component, name='triple'))
7979
self.assertIn('NAME\n triple', help_screen)
80-
self.assertIn('SYNOPSIS\n triple [--count=COUNT]', help_screen)
80+
self.assertIn('SYNOPSIS\n triple <flags>', help_screen)
8181
self.assertNotIn('DESCRIPTION', help_screen)
82-
self.assertIn('FLAGS\n --count', help_screen)
82+
self.assertIn('FLAGS\n --count=COUNT (optional)', help_screen)
8383
self.assertNotIn('NOTES', help_screen)
8484

8585
def testHelpTextFunctionWithBuiltin(self):
@@ -215,13 +215,13 @@ def testHelpScreenForFunctionFunctionWithDefaultArgs(self):
215215
double - Returns the input multiplied by 2.
216216
217217
SYNOPSIS
218-
double [--count=COUNT]
218+
double <flags>
219219
220220
DESCRIPTION
221221
Returns the input multiplied by 2.
222222
223223
FLAGS
224-
--count
224+
--count=COUNT (optional)
225225
Input number that you want to double."""
226226
self.assertEqual(textwrap.dedent(expected_output).strip(),
227227
help_output.strip())
@@ -232,7 +232,7 @@ def testHelpTextUnderlineFlag(self):
232232
help_screen = helptext.HelpText(component, t)
233233
self.assertIn(formatting.Bold('NAME') + '\n triple', help_screen)
234234
self.assertIn(
235-
formatting.Bold('SYNOPSIS') + '\n triple [--count=COUNT]',
235+
formatting.Bold('SYNOPSIS') + '\n triple <flags>',
236236
help_screen)
237237
self.assertIn(
238238
formatting.Bold('FLAGS') + '\n --' + formatting.Underline('count'),
@@ -334,7 +334,7 @@ def testUsageOutputFunctionWithHelp(self):
334334
expected_output = '''
335335
Usage: function_with_help <flags>
336336
337-
Available flags: --help
337+
Optional flags: --help
338338
339339
For detailed information on this command, run:
340340
function_with_help -- --help'''
@@ -349,7 +349,7 @@ def testUsageOutputFunctionWithDocstring(self):
349349
expected_output = '''
350350
Usage: multiplier_with_docstring NUM <flags>
351351
352-
Available flags: --rate
352+
Optional flags: --rate
353353
354354
For detailed information on this command, run:
355355
multiplier_with_docstring --help'''

fire/test_components.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,5 +437,6 @@ def function_with_varargs(arg1, arg2, arg3=1, *varargs): # pylint: disable=keyw
437437
return varargs
438438

439439

440-
def function_with_keyword_arguments(**kwargs):
441-
return kwargs
440+
def function_with_keyword_arguments(arg1, arg2=3, **kwargs):
441+
del arg2 # Unused.
442+
return arg1, kwargs

0 commit comments

Comments
 (0)
0