diff env/lib/python3.7/site-packages/humanfriendly/usage.py @ 5:9b1c78e6ba9c draft default tip

"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author shellac
date Mon, 01 Jun 2020 08:59:25 -0400
parents 79f47841a781
children
line wrap: on
line diff
--- a/env/lib/python3.7/site-packages/humanfriendly/usage.py	Thu May 14 16:47:39 2020 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,351 +0,0 @@
-# Human friendly input/output in Python.
-#
-# Author: Peter Odding <peter@peterodding.com>
-# Last Change: June 24, 2017
-# URL: https://humanfriendly.readthedocs.io
-
-"""
-Parsing and reformatting of usage messages.
-
-The :mod:`~humanfriendly.usage` module parses and reformats usage messages:
-
-- The :func:`format_usage()` function takes a usage message and inserts ANSI
-  escape sequences that highlight items of special significance like command
-  line options, meta variables, etc. The resulting usage message is (intended
-  to be) easier to read on a terminal.
-
-- The :func:`render_usage()` function takes a usage message and rewrites it to
-  reStructuredText_ suitable for inclusion in the documentation of a Python
-  package. This provides a DRY solution to keeping a single authoritative
-  definition of the usage message while making it easily available in
-  documentation. As a cherry on the cake it's not just a pre-formatted dump of
-  the usage message but a nicely formatted reStructuredText_ fragment.
-
-- The remaining functions in this module support the two functions above.
-
-Usage messages in general are free format of course, however the functions in
-this module assume a certain structure from usage messages in order to
-successfully parse and reformat them, refer to :func:`parse_usage()` for
-details.
-
-.. _DRY: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
-.. _reStructuredText: https://en.wikipedia.org/wiki/ReStructuredText
-"""
-
-# Standard library modules.
-import csv
-import functools
-import logging
-import re
-
-# Standard library module or external dependency (see setup.py).
-from importlib import import_module
-
-# Modules included in our package.
-from humanfriendly.compat import StringIO
-from humanfriendly.text import dedent, split_paragraphs, trim_empty_lines
-
-# Public identifiers that require documentation.
-__all__ = (
-    'find_meta_variables',
-    'format_usage',
-    'import_module',  # previously exported (backwards compatibility)
-    'inject_usage',
-    'parse_usage',
-    'render_usage',
-    'USAGE_MARKER',
-)
-
-USAGE_MARKER = "Usage:"
-"""The string that starts the first line of a usage message."""
-
-START_OF_OPTIONS_MARKER = "Supported options:"
-"""The string that marks the start of the documented command line options."""
-
-# Compiled regular expression used to tokenize usage messages.
-USAGE_PATTERN = re.compile(r'''
-    # Make sure whatever we're matching isn't preceded by a non-whitespace
-    # character.
-    (?<!\S)
-    (
-        # A short command line option or a long command line option
-        # (possibly including a meta variable for a value).
-        (-\w|--\w+(-\w+)*(=\S+)?)
-        # Or ...
-        |
-        # An environment variable.
-        \$[A-Za-z_][A-Za-z0-9_]*
-        # Or ...
-        |
-        # Might be a meta variable (usage() will figure it out).
-        [A-Z][A-Z0-9_]+
-    )
-''', re.VERBOSE)
-
-# Compiled regular expression used to recognize options.
-OPTION_PATTERN = re.compile(r'^(-\w|--\w+(-\w+)*(=\S+)?)$')
-
-# Initialize a logger for this module.
-logger = logging.getLogger(__name__)
-
-
-def format_usage(usage_text):
-    """
-    Highlight special items in a usage message.
-
-    :param usage_text: The usage message to process (a string).
-    :returns: The usage message with special items highlighted.
-
-    This function highlights the following special items:
-
-    - The initial line of the form "Usage: ..."
-    - Short and long command line options
-    - Environment variables
-    - Meta variables (see :func:`find_meta_variables()`)
-
-    All items are highlighted in the color defined by
-    :data:`.HIGHLIGHT_COLOR`.
-    """
-    # Ugly workaround to avoid circular import errors due to interdependencies
-    # between the humanfriendly.terminal and humanfriendly.usage modules.
-    from humanfriendly.terminal import ansi_wrap, HIGHLIGHT_COLOR
-    formatted_lines = []
-    meta_variables = find_meta_variables(usage_text)
-    for line in usage_text.strip().splitlines(True):
-        if line.startswith(USAGE_MARKER):
-            # Highlight the "Usage: ..." line in bold font and color.
-            formatted_lines.append(ansi_wrap(line, color=HIGHLIGHT_COLOR))
-        else:
-            # Highlight options, meta variables and environment variables.
-            formatted_lines.append(replace_special_tokens(
-                line, meta_variables,
-                lambda token: ansi_wrap(token, color=HIGHLIGHT_COLOR),
-            ))
-    return ''.join(formatted_lines)
-
-
-def find_meta_variables(usage_text):
-    """
-    Find the meta variables in the given usage message.
-
-    :param usage_text: The usage message to parse (a string).
-    :returns: A list of strings with any meta variables found in the usage
-              message.
-
-    When a command line option requires an argument, the convention is to
-    format such options as ``--option=ARG``. The text ``ARG`` in this example
-    is the meta variable.
-    """
-    meta_variables = set()
-    for match in USAGE_PATTERN.finditer(usage_text):
-        token = match.group(0)
-        if token.startswith('-'):
-            option, _, value = token.partition('=')
-            if value:
-                meta_variables.add(value)
-    return list(meta_variables)
-
-
-def parse_usage(text):
-    """
-    Parse a usage message by inferring its structure (and making some assumptions :-).
-
-    :param text: The usage message to parse (a string).
-    :returns: A tuple of two lists:
-
-              1. A list of strings with the paragraphs of the usage message's
-                 "introduction" (the paragraphs before the documentation of the
-                 supported command line options).
-
-              2. A list of strings with pairs of command line options and their
-                 descriptions: Item zero is a line listing a supported command
-                 line option, item one is the description of that command line
-                 option, item two is a line listing another supported command
-                 line option, etc.
-
-    Usage messages in general are free format of course, however
-    :func:`parse_usage()` assume a certain structure from usage messages in
-    order to successfully parse them:
-
-    - The usage message starts with a line ``Usage: ...`` that shows a symbolic
-      representation of the way the program is to be invoked.
-
-    - After some free form text a line ``Supported options:`` (surrounded by
-      empty lines) precedes the documentation of the supported command line
-      options.
-
-    - The command line options are documented as follows::
-
-        -v, --verbose
-
-          Make more noise.
-
-      So all of the variants of the command line option are shown together on a
-      separate line, followed by one or more paragraphs describing the option.
-
-    - There are several other minor assumptions, but to be honest I'm not sure if
-      anyone other than me is ever going to use this functionality, so for now I
-      won't list every intricate detail :-).
-
-      If you're curious anyway, refer to the usage message of the `humanfriendly`
-      package (defined in the :mod:`humanfriendly.cli` module) and compare it with
-      the usage message you see when you run ``humanfriendly --help`` and the
-      generated usage message embedded in the readme.
-
-      Feel free to request more detailed documentation if you're interested in
-      using the :mod:`humanfriendly.usage` module outside of the little ecosystem
-      of Python packages that I have been building over the past years.
-    """
-    introduction = []
-    documented_options = []
-    # Split the raw usage message into paragraphs.
-    paragraphs = split_paragraphs(text)
-    # Get the paragraphs that are part of the introduction.
-    while paragraphs:
-        # Check whether we've found the end of the introduction.
-        end_of_intro = (paragraphs[0] == START_OF_OPTIONS_MARKER)
-        # Append the current paragraph to the introduction.
-        introduction.append(paragraphs.pop(0))
-        # Stop after we've processed the complete introduction.
-        if end_of_intro:
-            break
-    logger.debug("Parsed introduction: %s", introduction)
-    # Parse the paragraphs that document command line options.
-    while paragraphs:
-        documented_options.append(dedent(paragraphs.pop(0)))
-        description = []
-        while paragraphs:
-            # Check if the next paragraph starts the documentation of another
-            # command line option. We split on a comma followed by a space so
-            # that our parsing doesn't trip up when the label used for an
-            # option's value contains commas.
-            tokens = [t.strip() for t in re.split(r',\s', paragraphs[0]) if t and not t.isspace()]
-            if all(OPTION_PATTERN.match(t) for t in tokens):
-                break
-            else:
-                description.append(paragraphs.pop(0))
-        # Join the description's paragraphs back together so we can remove
-        # common leading indentation.
-        documented_options.append(dedent('\n\n'.join(description)))
-    logger.debug("Parsed options: %s", documented_options)
-    return introduction, documented_options
-
-
-def render_usage(text):
-    """
-    Reformat a command line program's usage message to reStructuredText_.
-
-    :param text: The plain text usage message (a string).
-    :returns: The usage message rendered to reStructuredText_ (a string).
-    """
-    meta_variables = find_meta_variables(text)
-    introduction, options = parse_usage(text)
-    output = [render_paragraph(p, meta_variables) for p in introduction]
-    if options:
-        output.append('\n'.join([
-            '.. csv-table::',
-            '   :header: Option, Description',
-            '   :widths: 30, 70',
-            '',
-        ]))
-        csv_buffer = StringIO()
-        csv_writer = csv.writer(csv_buffer)
-        while options:
-            variants = options.pop(0)
-            description = options.pop(0)
-            csv_writer.writerow([
-                render_paragraph(variants, meta_variables),
-                ('\n\n'.join(render_paragraph(p, meta_variables) for p in split_paragraphs(description))).rstrip(),
-            ])
-        csv_lines = csv_buffer.getvalue().splitlines()
-        output.append('\n'.join('   %s' % l for l in csv_lines))
-    logger.debug("Rendered output: %s", output)
-    return '\n\n'.join(trim_empty_lines(o) for o in output)
-
-
-def inject_usage(module_name):
-    """
-    Use cog_ to inject a usage message into a reStructuredText_ file.
-
-    :param module_name: The name of the module whose ``__doc__`` attribute is
-                        the source of the usage message (a string).
-
-    This simple wrapper around :func:`render_usage()` makes it very easy to
-    inject a reformatted usage message into your documentation using cog_. To
-    use it you add a fragment like the following to your ``*.rst`` file::
-
-       .. [[[cog
-       .. from humanfriendly.usage import inject_usage
-       .. inject_usage('humanfriendly.cli')
-       .. ]]]
-       .. [[[end]]]
-
-    The lines in the fragment above are single line reStructuredText_ comments
-    that are not copied to the output. Their purpose is to instruct cog_ where
-    to inject the reformatted usage message. Once you've added these lines to
-    your ``*.rst`` file, updating the rendered usage message becomes really
-    simple thanks to cog_:
-
-    .. code-block:: sh
-
-       $ cog.py -r README.rst
-
-    This will inject or replace the rendered usage message in your
-    ``README.rst`` file with an up to date copy.
-
-    .. _cog: http://nedbatchelder.com/code/cog/
-    """
-    import cog
-    usage_text = import_module(module_name).__doc__
-    cog.out("\n" + render_usage(usage_text) + "\n\n")
-
-
-def render_paragraph(paragraph, meta_variables):
-    # Reformat the "Usage:" line to highlight "Usage:" in bold and show the
-    # remainder of the line as pre-formatted text.
-    if paragraph.startswith(USAGE_MARKER):
-        tokens = paragraph.split()
-        return "**%s** `%s`" % (tokens[0], ' '.join(tokens[1:]))
-    # Reformat the "Supported options:" line to highlight it in bold.
-    if paragraph == 'Supported options:':
-        return "**%s**" % paragraph
-    # Reformat shell transcripts into code blocks.
-    if re.match(r'^\s*\$\s+\S', paragraph):
-        # Split the paragraph into lines.
-        lines = paragraph.splitlines()
-        # Check if the paragraph is already indented.
-        if not paragraph[0].isspace():
-            # If the paragraph isn't already indented we'll indent it now.
-            lines = ['  %s' % line for line in lines]
-        lines.insert(0, '.. code-block:: sh')
-        lines.insert(1, '')
-        return "\n".join(lines)
-    # The following reformatting applies only to paragraphs which are not
-    # indented. Yes this is a hack - for now we assume that indented paragraphs
-    # are code blocks, even though this assumption can be wrong.
-    if not paragraph[0].isspace():
-        # Change UNIX style `quoting' so it doesn't trip up DocUtils.
-        paragraph = re.sub("`(.+?)'", r'"\1"', paragraph)
-        # Escape asterisks.
-        paragraph = paragraph.replace('*', r'\*')
-        # Reformat inline tokens.
-        paragraph = replace_special_tokens(
-            paragraph, meta_variables,
-            lambda token: '``%s``' % token,
-        )
-    return paragraph
-
-
-def replace_special_tokens(text, meta_variables, replace_fn):
-    return USAGE_PATTERN.sub(functools.partial(
-        replace_tokens_callback,
-        meta_variables=meta_variables,
-        replace_fn=replace_fn
-    ), text)
-
-
-def replace_tokens_callback(match, meta_variables, replace_fn):
-    token = match.group(0)
-    if not (re.match('^[A-Z][A-Z0-9_]+$', token) and token not in meta_variables):
-        token = replace_fn(token)
-    return token