Пример #1
0
  def PrintPositionalsAndFlagsSections(self, disable_header=False):
    """Prints the positionals and flags sections.

    Args:
      disable_header: Disable printing the section header if True.
    """
    if self._is_topic:
      return
    self._SetFlagSections()
    visible_positionals = self._FilterOutHidden(self.GetPositionalArgs())
    if visible_positionals:
      if not disable_header:
        self.PrintSectionHeader('POSITIONAL ARGUMENTS', sep=False)
      for arg in visible_positionals:
        self._out('\n{0}::\n'.format(
            usage_text.PositionalDisplayString(arg, markdown=True).lstrip()))
        self._out('\n{arghelp}\n'.format(arghelp=self.GetArgDetails(arg)))

    # List the sections in order.
    for heading, _, groups, attrs in self._flag_sections:
      self.PrintFlagSection(heading, groups, attrs,
                            disable_header=disable_header)

    if self._has_global_flags:
      if not disable_header:
        self.PrintSectionHeader('GLOBAL FLAGS', sep=False)
      self._out('\nRun *$ gcloud help* for a description of flags available to'
                '\nall commands.\n')
    def _PrintPositionalsAndFlagsSections(self):
        """Prints the positionals and flags sections."""
        visible_positionals = FilterOutSuppressed(
            self._command.ai.positional_args)
        if visible_positionals:
            self._Section('POSITIONAL ARGUMENTS', sep=False)
            for arg in visible_positionals:
                self._out('\n{0}::\n'.format(
                    usage_text.PositionalDisplayString(
                        arg, markdown=True).lstrip()))
                self._out('\n{arghelp}\n'.format(arghelp=self._Details(arg)))

        # Partition the flags into REQUIRED FLAGS, COMMON FLAGS, OPTIONAL_FLAGS and
        # GLOBAL FLAGS subsections.
        required_flags = []
        common_flags = []
        other_flags = []
        has_global_flags = False
        for arg in (self._command.ai.flag_args +
                    self._command.ai.ancestor_flag_args):
            if not self._IsSuppressed(arg):
                if arg.is_global and not self._is_top_element:
                    has_global_flags = True
                elif arg.required:
                    required_flags.append(arg)
                elif arg.is_common:
                    common_flags.append(arg)
                else:
                    other_flags.append(arg)

        other_flags_heading = 'FLAGS'
        if required_flags:
            other_flags_heading = 'OPTIONAL FLAGS'
        if common_flags:
            other_flags_heading = 'OTHER FLAGS'

        for flags, section in ((required_flags, 'REQUIRED FLAGS'),
                               (common_flags, 'COMMONLY USED FLAGS'),
                               (other_flags, other_flags_heading)):
            self._PrintFlagSection(flags, section)

        if has_global_flags:
            self._Section('GLOBAL FLAGS', sep=False)
            self._out(
                '\nRun *$ gcloud help* for a description of flags available to'
                '\nall commands.\n')
Пример #3
0
    def _PrintPositionalsAndFlagsSections(self, sections, has_global_flags):
        """Prints the positionals and flags sections."""
        visible_positionals = usage_text.FilterOutSuppressed(
            self._command.ai.positional_args)
        if visible_positionals:
            self._Section('POSITIONAL ARGUMENTS', sep=False)
            for arg in visible_positionals:
                self._out('\n{0}::\n'.format(
                    usage_text.PositionalDisplayString(
                        arg, markdown=True).lstrip()))
                self._out('\n{arghelp}\n'.format(arghelp=self._Details(arg)))

        # List the sections in order.
        for heading, _, groups, attrs in sections:
            self._PrintFlagSection(heading, groups, attrs)

        if has_global_flags:
            self._Section('GLOBAL FLAGS', sep=False)
            self._out(
                '\nRun *$ gcloud help* for a description of flags available to'
                '\nall commands.\n')
Пример #4
0
    def _PrintPositionalsAndFlagsSections(self):
        """Prints the positionals and flags sections."""
        if self._command.ai.positional_args:
            self._Section('POSITIONAL ARGUMENTS', sep=False)
            for arg in self._command.ai.positional_args:
                self._out('\n{0}::\n'.format(
                    usage_text.PositionalDisplayString(
                        arg, markdown=True).lstrip()))
                self._out('\n{arghelp}\n'.format(arghelp=self._Details(arg)))

        # Partition the flags into FLAGS, GROUP FLAGS and GLOBAL FLAGS subsections.
        command_flags = []
        group_flags = []
        global_flags = False
        for arg in self._command.ai.flag_args:
            if not self._IsSuppressed(arg):
                if self._IsGlobalFlag(arg) and not self._is_top_element:
                    global_flags = True
                elif self._IsGroupFlag(arg):
                    group_flags.append(arg)
                else:
                    command_flags.append(arg)
        for arg in self._command.ai.ancestor_flag_args:
            if not self._IsSuppressed(arg):
                if self._IsGlobalFlag(arg) and not self._is_top_element:
                    global_flags = True
                else:
                    group_flags.append(arg)

        for flags, section in ((command_flags, 'FLAGS'), (group_flags,
                                                          'GROUP FLAGS')):
            self._PrintFlagSection(flags, section)

        if global_flags:
            self._Section('GLOBAL FLAGS', sep=False)
            self._out(
                '\nRun *$ gcloud help* for a description of flags available to'
                '\nall commands.\n')
Пример #5
0
    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))
Пример #6
0
 def PrintPositionalDefinition(self, arg):
     self._out('\n{0}::\n'.format(
         usage_text.PositionalDisplayString(arg, markdown=True)))
     self._out('\n{arghelp}\n'.format(arghelp=self.GetArgDetails(arg)))
Пример #7
0
    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')
Пример #8
0
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
Пример #9
0
    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))
Пример #11
0
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 _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')