def _PrintFlagSection(self, flags, section): if not flags: return self._Section(section, sep=False) for flag in sorted(flags, key=lambda f: f.option_strings): self._out('\n{0}::\n'.format( usage_text.FlagDisplayString(flag, markdown=True))) self._out('\n{arghelp}\n'.format(arghelp=self._Details(flag)))
def PrintFlagDefinition(self, flag, disable_header=False): """Prints a flags definition list item. Args: flag: The flag object to display. disable_header: Disable printing the section header if True. """ if not disable_header: self._out('\n') self._out('{0}::\n'.format( usage_text.FlagDisplayString(flag, markdown=True))) self._out('\n{arghelp}\n'.format(arghelp=self.GetArgDetails(flag)))
def _PrintSynopsisSection(self): """Prints the command line synopsis section.""" # MARKDOWN_CODE is the default SYNOPSIS font style. code = usage_text.MARKDOWN_CODE em = usage_text.MARKDOWN_ITALIC self._Section('SYNOPSIS') self._out('{code}{command}{code}'.format(code=code, command=self._command_name)) # Output the positional args up to the first REMAINDER or '-- *' args. The # rest will be picked up after the flag args are output. argparse does not # have an explicit '--' arg intercept, so we use the metavar value as a '--' # sentinel. positional_args = self._command.ai.positional_args[:] while positional_args: arg = positional_args[0] if arg.nargs == argparse.REMAINDER or arg.metavar.startswith( '-- '): break positional_args.pop(0) self._out(usage_text.PositionalDisplayString(arg, markdown=True)) if self._subcommands and self._subgroups: self._out(' ' + em + 'GROUP' + em + ' | ' + em + 'COMMAND' + em) elif self._subcommands: self._out(' ' + em + 'COMMAND' + em) elif self._subgroups: self._out(' ' + em + 'GROUP' + em) else: self._relative_offset = 2 # Place all flags into a dict. Flags that are in a mutually # exlusive group are mapped group_id -> [flags]. All other flags # are mapped dest -> [flag]. global_flags = False groups = collections.defaultdict(list) for flag in (self._command.ai.flag_args + self._command.ai.ancestor_flag_args): if self._IsGlobalFlag(flag) and not self._is_top_element: global_flags = True else: group_id = self._command.ai.mutex_groups.get( flag.dest, flag.dest) groups[group_id].append(flag) for group in sorted(groups.values(), key=lambda g: g[0].option_strings): if len(group) == 1: arg = group[0] if self._IsSuppressed(arg): continue msg = usage_text.FlagDisplayString(arg, markdown=True) if arg.required: self._out(' {msg}'.format(msg=msg)) else: self._out(' [{msg}]'.format(msg=msg)) else: group.sort(key=lambda f: f.option_strings) group = [ flag for flag in group if not self._IsSuppressed(flag) ] msg = ' | '.join( usage_text.FlagDisplayString(arg, markdown=True) for arg in group) self._out(' [{msg}]'.format(msg=msg)) if global_flags: self._out(' [' + em + 'GLOBAL-FLAG ...' + em + ']') # positional_args will only be non-empty if we had -- ... or REMAINDER left. for arg in positional_args: self._out(usage_text.PositionalDisplayString(arg, markdown=True))
def Markdown(command): """Generates a markdown help document for command. Args: command: calliope._CommandCommon, Help extracted from this calliope command or group. Returns: The generated markdown string. """ command.LoadAllSubElements() buf = StringIO.StringIO() out = buf.write detailed_help = getattr(command, 'detailed_help', {}) command_name = ' '.join(command.GetPath()) file_name = '_'.join(command.GetPath()) def UserInput(msg): """Returns msg with user input markdown. Args: msg: str, The user input string. Returns: The msg string with embedded user input markdown. """ return (usage_text.MARKDOWN_CODE + usage_text.MARKDOWN_ITALIC + msg + usage_text.MARKDOWN_ITALIC + usage_text.MARKDOWN_CODE) def Section(name, sep=True): """Prints the section header markdown for name. Args: name: str, The manpage section name. sep: boolean, Add trailing newline. """ out('\n\n== {name} ==\n'.format(name=name)) if sep: out('\n') def PrintSectionIfExists(name, default=None): """Print a section of the .help file, from a part of the detailed_help. Args: name: str, The manpage section name. default: str, Default help_stuff if section name is not defined. """ help_stuff = detailed_help.get(name, default) if not help_stuff: return if callable(help_stuff): help_message = help_stuff() else: help_message = help_stuff Section(name) out('{message}\n'.format( message=textwrap.dedent(help_message).strip())) def PrintCommandSection(name, subcommands): """Prints a group or command section. Args: name: str, The section name singular form. subcommands: dict, The subcommand dict. """ # Determine if the section has any content. content = '' for subcommand, help_info in sorted(subcommands.iteritems()): if command.IsHidden() or not help_info.is_hidden: # If this group is already hidden, we can safely include hidden # sub-items. Else, only include them if they are not hidden. content += '\n*link:{cmd}[{cmd}]*::\n\n{txt}\n'.format( cmd=subcommand, txt=help_info.help_text) if content: Section(name + 'S') out('{cmd} is one of the following:\n'.format(cmd=UserInput(name))) out(content) def Details(arg): """Returns the detailed help message for the given arg.""" help_stuff = getattr(arg, 'detailed_help', (arg.help or '') + '\n') if callable(help_stuff): help_message = help_stuff() else: help_message = help_stuff return textwrap.dedent(help_message).replace('\n\n', '\n+\n').strip() def Split(line): """Splits long example command lines. Args: line: str, The line to split. Returns: str, The split line. """ ret = '' m = SPLIT - FIRST_INDENT - SECTION_INDENT n = len(line) while n > m: indent = SUBSEQUENT_INDENT j = m noflag = 0 while True: if line[j] == ' ': # Break at a flag if possible. j += 1 if line[j] == '-': break # Look back one more operand to see if it's a flag. if noflag: j = noflag break noflag = j j -= 2 else: # Line is too long -- force an operand split with no indentation. j -= 1 if not j: j = m indent = 0 break ret += line[:j] + '\\\n' + ' ' * indent line = line[j:] n = len(line) m = SPLIT - SUBSEQUENT_INDENT - SECTION_INDENT return ret + line subcommands = command.GetSubCommandHelps() subgroups = command.GetSubGroupHelps() out('= {0}(1) =\n'.format(file_name.upper())) helptext = detailed_help.get('brief', command.short_help) if not helptext: helptext = '' elif len(helptext) > 1: if helptext[0].isupper() and not helptext[1].isupper(): helptext = helptext[0].lower() + helptext[1:] if helptext[-1] == '.': helptext = helptext[:-1] Section('NAME') out('{{command}} - {helptext}\n'.format(helptext=helptext)) # Post-processing will make the synopsis a hanging indent. # MARKDOWN_CODE is the default SYNOPSIS font style. code = usage_text.MARKDOWN_CODE em = usage_text.MARKDOWN_ITALIC Section('SYNOPSIS') out('{code}{command}{code}'.format(code=code, command=command_name)) # Output the positional args up to the first REMAINDER or '-- *' args. The # rest will be picked up after the flag args are output. argparse does not # have an explicit '--' arg intercept, so we use the metavar value as a '--' # sentinel. positional_args = command.ai.positional_args[:] while positional_args: arg = positional_args[0] if arg.nargs == argparse.REMAINDER or arg.metavar.startswith('-- '): break positional_args.pop(0) out(usage_text.PositionalDisplayString(arg, markdown=True)) # rel is the relative path offset used to generate ../* to the reference root. rel = 1 if subcommands and subgroups: out(' ' + em + 'GROUP' + em + ' | ' + em + 'COMMAND' + em) elif subcommands: out(' ' + em + 'COMMAND' + em) elif subgroups: out(' ' + em + 'GROUP' + em) else: rel = 2 # Places all flags into a dict. Flags that are in a mutually # exlusive group are mapped group_id -> [flags]. All other flags # are mapped dest -> [flag]. groups = collections.defaultdict(list) for flag in (command.ai.flag_args + [ arg for arg in command.ai.ancestor_flag_args if usage_text.ShouldPrintAncestorFlag(arg) ]): group_id = command.ai.mutex_groups.get(flag.dest, flag.dest) groups[group_id].append(flag) for group in sorted(groups.values(), key=lambda g: g[0].option_strings): if len(group) == 1: arg = group[0] if arg.help == argparse.SUPPRESS: continue msg = usage_text.FlagDisplayString(arg, markdown=True) if arg.required: out(' {msg}'.format(msg=msg)) else: out(' [{msg}]'.format(msg=msg)) else: group.sort(key=lambda f: f.option_strings) group = [flag for flag in group if flag.help != argparse.SUPPRESS] msg = ' | '.join( usage_text.FlagDisplayString(arg, markdown=True) for arg in group) # TODO(user): Figure out how to plumb through the required # attribute of a required flag. out(' [{msg}]'.format(msg=msg)) # positional_args will only be non-empty if we had -- ... or REMAINDER left. for arg in positional_args: out(usage_text.PositionalDisplayString(arg, markdown=True)) PrintSectionIfExists('DESCRIPTION', default=usage_text.ExpandHelpText( command, command.long_help)) PrintSectionIfExists('SEE ALSO') if command.ai.positional_args: Section('POSITIONAL ARGUMENTS', sep=False) for arg in command.ai.positional_args: out('\n{0}::\n'.format( usage_text.PositionalDisplayString(arg, markdown=True).lstrip())) out('\n{arghelp}\n'.format(arghelp=Details(arg))) Section('FLAGS', sep=False) root = ' ' not in command_name local_args = [] global_args = [] for arg in command.ai.flag_args: if arg.help != argparse.SUPPRESS: if not root and arg.option_strings[0] in ['--help', '-h']: global_args.append(arg) else: local_args.append(arg) for arg in command.ai.ancestor_flag_args: if arg.help != argparse.SUPPRESS: if usage_text.ShouldPrintAncestorFlag(arg): global_args.append(arg) local_args = sorted(local_args, key=lambda f: f.option_strings) global_args = sorted(global_args, key=lambda f: f.option_strings) if local_args and global_args: local_args.append([]) for arg in local_args + global_args: if not arg: out('\n=== GLOBAL FLAGS ===\n') continue out('\n{0}::\n'.format(usage_text.FlagDisplayString(arg, markdown=True))) out('\n{arghelp}\n'.format(arghelp=Details(arg))) if subgroups: PrintCommandSection('GROUP', subgroups) if subcommands: PrintCommandSection('COMMAND', subcommands) PrintSectionIfExists('EXAMPLES') if command.IsHidden() or command.ReleaseTrack(for_help=True).help_note: Section('NOTES') if command.IsHidden(): # This string must match gen-help-docs.sh to prevent the generated html # from being bundled. out('This command is an internal implementation detail and may change or ' 'disappear without notice.\n\n') if command.ReleaseTrack(for_help=True).help_note: out(command.ReleaseTrack(for_help=True).help_note + '\n\n') # This allows formatting to succeed if the help has any {somekey} in the text. doc = usage_text.ExpandHelpText(command, buf.getvalue()) # Split long $ ... example lines. pat = re.compile(r'(\$ .{%d,})$' % (SPLIT - FIRST_INDENT - SECTION_INDENT), re.M) pos = 0 rep = '' while True: match = pat.search(doc, pos) if not match: break rep += doc[pos:match.start(1)] + Split( doc[match.start(1):match.end(1)]) pos = match.end(1) if rep: doc = rep + doc[pos:] # $ command ... example refs. top = command.GetPath()[0] # This pattern matches "$ {top} {arg}*" where each arg is lower case and does # not start with example-, my-, or sample-. This follows the style guide rule # that user-supplied args to example commands contain upper case chars or # start with example-, my-, or sample-. pat = re.compile(r'\$ (' + top + '((?: (?!(example|my|sample)-)[a-z][-a-z]*)*))[ `\n]') pos = 0 rep = '' while True: match = pat.search(doc, pos) if not match: break cmd = match.group(1) i = cmd.find('set ') if i >= 0: # This terminates the command at the first positional ending with set. # This handles gcloud set and unset subcommands where 'set' and 'unset' # are the last command args, the remainder user-specified. i += 3 rem = cmd[i:] cmd = cmd[0:i] else: rem = '' ref = match.group(2) if ref: ref = ref[1:] ref = '/'.join(['..'] * (len(command.GetPath()) - rel) + ref.split(' ')) lnk = 'link:' + ref + '[' + cmd + ']' + rem rep += doc[pos:match.start(1)] + lnk pos = match.end(1) if rep: doc = rep + doc[pos:] # gcloud ...(1) man page refs. pat = re.compile(r'(\*?(' + top + r'((?:[-_ a-z])*))\*?)\(1\)') pos = 0 rep = '' while True: match = pat.search(doc, pos) if not match: break cmd = match.group(2).replace('_', ' ') ref = match.group(3).replace('_', ' ') if ref: ref = ref[1:] ref = '/'.join(['..'] * (len(command.GetPath()) - rel) + ref.split(' ')) lnk = '*link:' + ref + '[' + cmd + ']*' rep += doc[pos:match.start(2)] + lnk pos = match.end(1) if rep: doc = rep + doc[pos:] # ``*'' emphasis quotes => UserInput(*) pat = re.compile(r'(``([^`]*)\'\')') pos = 0 rep = '' while True: match = pat.search(doc, pos) if not match: break rep += doc[pos:match.start(1)] + UserInput(match.group(2)) pos = match.end(1) if rep: doc = rep + doc[pos:] return doc
def PrintSynopsisSection(self, disable_header=False): """Prints the command line synopsis section. Args: disable_header: Disable printing the section header if True. """ if self._is_topic: return self._SetFlagSections() # MARKDOWN_CODE is the default SYNOPSIS font style. code = base.MARKDOWN_CODE em = base.MARKDOWN_ITALIC if not disable_header: self.PrintSectionHeader('SYNOPSIS') self._out('{code}{command}{code}'.format(code=code, command=self._command_name)) # Output the positional args up to the first REMAINDER or '-- *' args. The # rest will be picked up after the flag args are output. argparse does not # have an explicit '--' arg intercept, so we use the metavar value as a '--' # sentinel. Any suppressed args are ingnored by a pre-pass. positional_args = self._FilterOutHidden(self.GetPositionalArgs()) while positional_args: arg = positional_args[0] if arg.nargs == argparse.REMAINDER or arg.metavar.startswith( '-- '): break positional_args.pop(0) self._out(' ' + usage_text.PositionalDisplayString(arg, markdown=True)) if self._subcommands and self._subgroups: self._out(' ' + em + 'GROUP' + em + ' | ' + em + 'COMMAND' + em) elif self._subcommands: self._out(' ' + em + 'COMMAND' + em) elif self._subgroups: self._out(' ' + em + 'GROUP' + em) # Generate the flag usage string with flags in section order. for _, _, groups, attrs in self._flag_sections: for group_id, group in sorted( groups.iteritems(), key=lambda x: self.FlagGroupSortKey(x[1])): flag = group[0] if len(group) == 1: msg = usage_text.FlagDisplayString( flag, markdown=True, inverted=getattr(flag, 'inverted_synopsis', False)) if not msg: continue if flag.required: self._out(' {msg}'.format(msg=msg)) else: self._out(' [{msg}]'.format(msg=msg)) else: group.sort(key=lambda f: f.option_strings) attr = attrs.get(group_id) if not attr or not attr.is_mutex: for flag in group: self._out(' [{0}]'.format( usage_text.FlagDisplayString(flag, markdown=True))) else: msg = ' | '.join( usage_text.FlagDisplayString(flag, markdown=True) for flag in group) if not msg: continue if attr.is_required: self._out(' ({msg})'.format(msg=msg)) else: self._out(' [{msg}]'.format(msg=msg)) if self._global_flags: self._out(' [' + em + self._top.upper() + '_WIDE_FLAG ...' + em + ']') # positional_args will only be non-empty if we had -- ... or REMAINDER left. for arg in self._FilterOutHidden(positional_args): self._out(' ' + usage_text.PositionalDisplayString(arg, markdown=True)) self._out('\n')
def _PrintSynopsisSection(self): """Prints the command line synopsis section.""" # MARKDOWN_CODE is the default SYNOPSIS font style. code = base.MARKDOWN_CODE em = base.MARKDOWN_ITALIC self._Section('SYNOPSIS') self._out('{code}{command}{code}'.format(code=code, command=self._command_name)) # Output the positional args up to the first REMAINDER or '-- *' args. The # rest will be picked up after the flag args are output. argparse does not # have an explicit '--' arg intercept, so we use the metavar value as a '--' # sentinel. Any suppressed args are ingnored by a pre-pass. positional_args = usage_text.FilterOutSuppressed( self._command.ai.positional_args) while positional_args: arg = positional_args[0] if arg.nargs == argparse.REMAINDER or arg.metavar.startswith( '-- '): break positional_args.pop(0) self._out(usage_text.PositionalDisplayString(arg, markdown=True)) if self._subcommands and self._subgroups: self._out(' ' + em + 'GROUP' + em + ' | ' + em + 'COMMAND' + em) elif self._subcommands: self._out(' ' + em + 'COMMAND' + em) elif self._subgroups: self._out(' ' + em + 'GROUP' + em) # Place all flags into a dict. Flags that are in a mutually # exclusive group are mapped group_id -> [flags]. All other flags # are mapped dest -> [flag]. global_flags = False groups = collections.defaultdict(list) for flag in (self._command.ai.flag_args + self._command.ai.ancestor_flag_args): if flag.is_global and not self._is_top_element: global_flags = True else: group_id = self._command.ai.mutex_groups.get( flag.dest, flag.dest) groups[group_id].append(flag) # Partition the non-GLOBAL flag groups dict into categorized sections. A # group is REQUIRED if any flag in it is required, categorized if any flag # in it is categorized, otherwise its OTHER. REQUIRED takes precedence # over categorized. categorized_groups = {} for group_id, flags in groups.iteritems(): for f in flags: if f.required: category = 'REQUIRED' elif f.category: category = f.category else: continue if category not in categorized_groups: categorized_groups[category] = {} categorized_groups[category][group_id] = flags break # Delete the categorized groups to get OTHER. for v in categorized_groups.values(): for g in v: del groups[g] category = 'OTHER' if category not in categorized_groups: categorized_groups[category] = {} for group_id, flags in groups.iteritems(): categorized_groups[category][group_id] = flags # Collect the sections in order: REQUIRED, COMMON, OTHER, and categorized. sections = [] for category in ('REQUIRED', base.COMMONLY_USED_FLAGS, 'OTHER'): if category in categorized_groups: sections.append(categorized_groups[category]) del categorized_groups[category] for _, v in sorted(categorized_groups.iteritems()): sections.append(v) # Generate the flag usage string with flags in section order. for section in sections: for group in sorted(section.values(), key=lambda g: g[0].option_strings): if len(group) == 1: arg = group[0] if usage_text.IsSuppressed(arg): continue msg = usage_text.FlagDisplayString(arg, markdown=True) if not msg: continue if arg.required: self._out(' {msg}'.format(msg=msg)) else: self._out(' [{msg}]'.format(msg=msg)) else: # Check if the inverted boolean name should be displayed. inverted = None if len(group) == 2: for arg in group: if getattr(arg, 'show_inverted', False): inverted = arg break if inverted: # The inverted arg replaces the boolean group which only contains # the arg and inverted arg. msg = usage_text.FlagDisplayString(inverted, markdown=True) else: group = usage_text.FilterOutSuppressed(group) group.sort(key=lambda f: f.option_strings) msg = ' | '.join( usage_text.FlagDisplayString(arg, markdown=True) for arg in group) if not msg: continue self._out(' [{msg}]'.format(msg=msg)) if global_flags: self._out(' [' + em + 'GLOBAL-FLAG ...' + em + ']') # positional_args will only be non-empty if we had -- ... or REMAINDER left. for arg in usage_text.FilterOutSuppressed(positional_args): self._out(usage_text.PositionalDisplayString(arg, markdown=True)) self._out('\n')
def _PrintSynopsisSection(self): """Prints the command line synopsis section.""" # MARKDOWN_CODE is the default SYNOPSIS font style. code = usage_text.MARKDOWN_CODE em = usage_text.MARKDOWN_ITALIC self._Section('SYNOPSIS') self._out('{code}{command}{code}'.format(code=code, command=self._command_name)) # Output the positional args up to the first REMAINDER or '-- *' args. The # rest will be picked up after the flag args are output. argparse does not # have an explicit '--' arg intercept, so we use the metavar value as a '--' # sentinel. # Any SUPPRESSed args are ingnored by a pre-pass. positional_args = FilterOutSuppressed( self._command.ai.positional_args[:]) while positional_args: arg = positional_args[0] if arg.nargs == argparse.REMAINDER or arg.metavar.startswith( '-- '): break positional_args.pop(0) self._out(usage_text.PositionalDisplayString(arg, markdown=True)) if self._subcommands and self._subgroups: self._out(' ' + em + 'GROUP' + em + ' | ' + em + 'COMMAND' + em) elif self._subcommands: self._out(' ' + em + 'COMMAND' + em) elif self._subgroups: self._out(' ' + em + 'GROUP' + em) # Place all flags into a dict. Flags that are in a mutually # exclusive group are mapped group_id -> [flags]. All other flags # are mapped dest -> [flag]. global_flags = False groups = collections.defaultdict(list) for flag in (self._command.ai.flag_args + self._command.ai.ancestor_flag_args): if flag.is_global and not self._is_top_element: global_flags = True else: group_id = self._command.ai.mutex_groups.get( flag.dest, flag.dest) groups[group_id].append(flag) # Split the groups dict into required, common, and then the rest of the # flags. A group is required if any flag in it is required and common if # any flag in it is common. Required takes precedence over common. required_groups = {} common_groups = {} for group_id, flags in groups.iteritems(): for f in flags: if f.required: required_groups[group_id] = flags break elif f.is_common: common_groups[group_id] = flags break for g in required_groups: del groups[g] for g in common_groups: del groups[g] # Generate the flag usage string with required flags first, then common # flags, then the rest of the flags. for section in [required_groups, common_groups, groups]: for group in sorted(section.values(), key=lambda g: g[0].option_strings): if len(group) == 1: arg = group[0] if self._IsSuppressed(arg): continue msg = usage_text.FlagDisplayString(arg, markdown=True) if not msg: continue if arg.required: self._out(' {msg}'.format(msg=msg)) else: self._out(' [{msg}]'.format(msg=msg)) else: group.sort(key=lambda f: f.option_strings) group = [ flag for flag in group if not self._IsSuppressed(flag) ] msg = ' | '.join( usage_text.FlagDisplayString(arg, markdown=True) for arg in group) if not msg: continue self._out(' [{msg}]'.format(msg=msg)) if global_flags: self._out(' [' + em + 'GLOBAL-FLAG ...' + em + ']') # positional_args will only be non-empty if we had -- ... or REMAINDER left. for arg in FilterOutSuppressed(positional_args): self._out(usage_text.PositionalDisplayString(arg, markdown=True))
def Markdown(command, post): """Generates a markdown help document for command. Args: command: calliope._CommandCommon, Help extracted from this calliope command or group. post: func(str), Markdown post-processor. """ buf = StringIO.StringIO() out = buf.write detailed_help = getattr(command, 'detailed_help', {}) command_name = ' '.join(command.GetPath()) file_name = '_'.join(command.GetPath()) def UserInput(msg): """Returns msg with user input markdown. Args: msg: str, The user input string. Returns: The msg string with embedded user input markdown. """ return (usage_text.MARKDOWN_CODE + usage_text.MARKDOWN_ITALIC + msg + usage_text.MARKDOWN_ITALIC + usage_text.MARKDOWN_CODE) def Section(name, sep=True): """Prints the section header markdown for name. Args: name: str, The manpage section name. sep: boolean, Add trailing newline. """ out('\n\n== {name} ==\n'.format(name=name)) if sep: out('\n') def PrintSectionIfExists(name, default=None): """Print a section of the .help file, from a part of the detailed_help. Args: name: str, The manpage section name. default: str, Default help_stuff if section name is not defined. """ help_stuff = detailed_help.get(name, default) if not help_stuff: return if callable(help_stuff): help_message = help_stuff() else: help_message = help_stuff Section(name) out('{message}\n'.format( message=textwrap.dedent(help_message).strip())) def PrintCommandSection(name, subcommands): """Prints a group or command section. Args: name: str, The section name singular form. subcommands: dict, The subcommand dict. """ # Determine if the section has any content. content = '' for subcommand, help_info in sorted(subcommands.iteritems()): if command.is_hidden or not help_info.is_hidden: # If this group is already hidden, we can safely include hidden # sub-items. Else, only include them if they are not hidden. content += '\n*link:{cmd}[{cmd}]*::\n\n{txt}\n'.format( cmd=subcommand, txt=help_info.help_text) if content: Section(name + 'S') out('{cmd} is one of the following:\n'.format(cmd=UserInput(name))) out(content) def Details(arg): """Returns the detailed help message for the given arg.""" help_stuff = getattr(arg, 'detailed_help', arg.help + '\n') if callable(help_stuff): help_message = help_stuff() else: help_message = help_stuff return textwrap.dedent(help_message).replace('\n\n', '\n+\n').strip() def Split(line): """Splits long example command lines. Args: line: str, The line to split. Returns: str, The split line. """ ret = '' m = SPLIT - FIRST_INDENT - SECTION_INDENT n = len(line) while n > m: indent = SUBSEQUENT_INDENT j = m noflag = 0 while 1: if line[j] == ' ': # Break at a flag if possible. j += 1 if line[j] == '-': break # Look back one more operand to see if it's a flag. if noflag: j = noflag break noflag = j j -= 2 else: # Line is too long -- force an operand split with no indentation. j -= 1 if not j: j = m indent = 0 break ret += line[:j] + '\\\n' + ' ' * indent line = line[j:] n = len(line) m = SPLIT - SUBSEQUENT_INDENT - SECTION_INDENT return ret + line subcommands = command.GetSubCommandHelps() subgroups = command.GetSubGroupHelps() out('= {0}(1) =\n'.format(file_name.upper())) helptext = detailed_help.get('brief', command.short_help) if not helptext: helptext = '' elif len(helptext) > 1: if helptext[0].isupper() and not helptext[1].isupper(): helptext = helptext[0].lower() + helptext[1:] if helptext[-1] == '.': helptext = helptext[:-1] Section('NAME') out('{{command}} - {helptext}\n'.format(helptext=helptext)) # Post-processing will make the synopsis a hanging indent. # MARKDOWN_CODE is the default SYNOPSIS font style. code = usage_text.MARKDOWN_CODE em = usage_text.MARKDOWN_ITALIC Section('SYNOPSIS') out('{code}{command}{code}'.format(code=code, command=command_name)) # pylint:disable=protected-access for arg in command._ai.positional_args: out(usage_text.PositionalDisplayString(arg, markdown=True)) # rel is the relative path offset used to generate ../* to the reference root. rel = 1 if subcommands and subgroups: out(' ' + em + 'GROUP' + em + ' | ' + em + 'COMMAND' + em) elif subcommands: out(' ' + em + 'COMMAND' + em) elif subgroups: out(' ' + em + 'GROUP' + em) else: rel = 2 # Places all flags into a dict. Flags that are in a mutually # exlusive group are mapped group_id -> [flags]. All other flags # are mapped dest -> [flag]. groups = collections.defaultdict(list) for flag in command._ai.flag_args + command._ai.ancestor_flag_args: group_id = command._ai.mutex_groups.get(flag.dest, flag.dest) groups[group_id].append(flag) for group in sorted(groups.values(), key=lambda g: g[0].option_strings): if len(group) == 1: arg = group[0] if arg.help == argparse.SUPPRESS: continue msg = usage_text.FlagDisplayString(arg, markdown=True) if arg.required: out(' {msg}'.format(msg=msg)) else: out(' [{msg}]'.format(msg=msg)) else: group.sort(key=lambda f: f.option_strings) group = [flag for flag in group if flag.help != argparse.SUPPRESS] msg = ' | '.join( usage_text.FlagDisplayString(arg, markdown=True) for arg in group) # TODO(user): Figure out how to plumb through the required # attribute of a required flag. out(' [{msg}]'.format(msg=msg)) # This is the end of the synopsis markdown. out('::\n--\n--\n') PrintSectionIfExists('DESCRIPTION', default=usage_text.ExpandHelpText( command, command.long_help)) PrintSectionIfExists('SEE ALSO') if command._ai.positional_args: Section('POSITIONAL ARGUMENTS', sep=False) for arg in command._ai.positional_args: out('\n{0}::\n'.format( usage_text.PositionalDisplayString(arg, markdown=True))) out('\n{arghelp}\n'.format(arghelp=Details(arg))) Section('FLAGS', sep=False) root = ' ' not in command_name local_args = [] global_args = [] for arg in command._ai.flag_args: if arg.help != argparse.SUPPRESS: if not root and arg.option_strings[0] in ['--help', '-h']: global_args.append(arg) else: local_args.append(arg) for arg in command._ai.ancestor_flag_args: if arg.help != argparse.SUPPRESS: global_args.append(arg) local_args = sorted(local_args, key=lambda f: f.option_strings) global_args = sorted(global_args, key=lambda f: f.option_strings) if local_args and global_args: local_args.append([]) for arg in local_args + global_args: if not arg: out('\n=== GLOBAL FLAGS ===\n') continue out('\n{0}::\n'.format(usage_text.FlagDisplayString(arg, markdown=True))) out('\n{arghelp}\n'.format(arghelp=Details(arg))) if subgroups: PrintCommandSection('GROUP', subgroups) if subcommands: PrintCommandSection('COMMAND', subcommands) PrintSectionIfExists('EXAMPLES') if command.is_hidden or command.release_stage: Section('NOTES') if command.is_hidden: # This string must match gen-help-docs.sh to prevent the generated html # from being bundled. out('This command is an internal implementation detail and may change or ' 'disappear without notice.\n\n') if command.release_stage: out(command.release_stage.note + '\n\n') top = command.GetPath()[0] # This allows formatting to succeed if the help has any {somekey} in the text. doc = usage_text.ExpandHelpText(command, buf.getvalue()) # Markdown fixups. # link:uri[text] not handled by markdown are captured by our doc generator. # Split long $ ... example lines. pat = re.compile(r'(\$ .{%d,})\n' % (SPLIT - FIRST_INDENT - SECTION_INDENT)) pos = 0 rep = '' while 1: match = pat.search(doc, pos) if not match: break rep += doc[pos:match.start(1)] + Split( doc[match.start(1):match.end(1)]) pos = match.end(1) if rep: doc = rep + doc[pos:] # [[ => [{empty}[ # This prevents [[XXX]...] being interpreted as an unrendered XXX attribute. pat = re.compile(r'(\[\[)') pos = 0 rep = '' while 1: match = pat.search(doc, pos) if not match: break rep += doc[pos:match.start(1)] + '[{empty}' # NOTE: This is different from the other edits here. We back up over the # second [ so that [[[ => [{empty}[{empty}[ etc. pos = match.end(1) - 1 if rep: doc = rep + doc[pos:] # $ command ... example refs. pat = re.compile(r'\$ (' + top + '((?: [a-z][-a-z]*)*))[ `\n]') pos = 0 rep = '' while 1: match = pat.search(doc, pos) if not match: break cmd = match.group(1) ref = match.group(2) if ref: ref = ref[1:] ref = '/'.join(['..'] * (len(command.GetPath()) - rel) + ref.split(' ')) lnk = 'link:' + ref + '[' + cmd + ']' rep += doc[pos:match.start(1)] + lnk pos = match.end(1) if rep: doc = rep + doc[pos:] # gcloud ...(1) man page refs. pat = re.compile(r'(\*?(' + top + r'((?:[-_ a-z])*))\*?)\(1\)') pos = 0 rep = '' while 1: match = pat.search(doc, pos) if not match: break cmd = match.group(2).replace('_', ' ') ref = match.group(3).replace('_', ' ') if ref: ref = ref[1:] ref = '/'.join(['..'] * (len(command.GetPath()) - rel) + ref.split(' ')) lnk = '*link:' + ref + '[' + cmd + ']*' rep += doc[pos:match.start(2)] + lnk pos = match.end(1) if rep: doc = rep + doc[pos:] # ``*'' emphasis quotes => UserInput(*) pat = re.compile(r'(``([^`]*)\'\')') pos = 0 rep = '' while 1: match = pat.search(doc, pos) if not match: break rep += doc[pos:match.start(1)] + UserInput(match.group(2)) pos = match.end(1) if rep: doc = rep + doc[pos:] post(doc)
def _PrintFlagDefinition(self, flag): """Prints a flags definition list item.""" self._out('\n{0}::\n'.format( usage_text.FlagDisplayString(flag, markdown=True))) self._out('\n{arghelp}\n'.format(arghelp=self._Details(flag)))
def _PrintSynopsisSection(self, sections, has_global_flags): """Prints the command line synopsis section.""" # MARKDOWN_CODE is the default SYNOPSIS font style. code = base.MARKDOWN_CODE em = base.MARKDOWN_ITALIC self._Section('SYNOPSIS') self._out('{code}{command}{code}'.format(code=code, command=self._command_name)) # Output the positional args up to the first REMAINDER or '-- *' args. The # rest will be picked up after the flag args are output. argparse does not # have an explicit '--' arg intercept, so we use the metavar value as a '--' # sentinel. Any suppressed args are ingnored by a pre-pass. positional_args = usage_text.FilterOutSuppressed( self._command.ai.positional_args) while positional_args: arg = positional_args[0] if arg.nargs == argparse.REMAINDER or arg.metavar.startswith( '-- '): break positional_args.pop(0) self._out(usage_text.PositionalDisplayString(arg, markdown=True)) if self._subcommands and self._subgroups: self._out(' ' + em + 'GROUP' + em + ' | ' + em + 'COMMAND' + em) elif self._subcommands: self._out(' ' + em + 'COMMAND' + em) elif self._subgroups: self._out(' ' + em + 'GROUP' + em) # Generate the flag usage string with flags in section order. for heading, _, groups in sections: for group in sorted(groups.values(), key=usage_text.FlagGroupSortKey): flag = group[0] if len(group) == 1: show_inverted = getattr(flag, 'show_inverted', None) if show_inverted: flag = show_inverted msg = usage_text.FlagDisplayString(flag, markdown=True) if not msg: continue if flag.required: self._out(' {msg}'.format(msg=msg)) else: self._out(' [{msg}]'.format(msg=msg)) else: group.sort(key=lambda f: f.option_strings) msg = ' | '.join( usage_text.FlagDisplayString(flag, markdown=True) for flag in group) if not msg: continue if heading == _REQUIRED_FLAGS_HEADING: self._out(' ({msg})'.format(msg=msg)) else: self._out(' [{msg}]'.format(msg=msg)) if has_global_flags: self._out(' [' + em + 'GLOBAL-FLAG ...' + em + ']') # positional_args will only be non-empty if we had -- ... or REMAINDER left. for arg in usage_text.FilterOutSuppressed(positional_args): self._out(usage_text.PositionalDisplayString(arg, markdown=True)) self._out('\n')