Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 60 additions & 3 deletions Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,16 +297,18 @@ formatter_class
^^^^^^^^^^^^^^^

:class:`ArgumentParser` objects allow the help formatting to be customized by
specifying an alternate formatting class. Currently, there are four such
specifying an alternate formatting class. Currently, there are five such
classes:

.. class:: RawDescriptionHelpFormatter
RawTextHelpFormatter
ParagraphHelpFormatter
ArgumentDefaultsHelpFormatter
MetavarTypeHelpFormatter

:class:`RawDescriptionHelpFormatter` and :class:`RawTextHelpFormatter` give
more control over how textual descriptions are displayed.
:class:`RawDescriptionHelpFormatter`, :class:`RawTextHelpFormatter`, and
:class:`ParagraphHelpFormatter` give more control over how textual descriptions
are displayed.
By default, :class:`ArgumentParser` objects line-wrap the description_ and
epilog_ texts in command-line help messages::

Expand Down Expand Up @@ -361,6 +363,61 @@ including argument descriptions. However, multiple newlines are replaced with
one. If you wish to preserve multiple blank lines, add spaces between the
newlines.

:class:`ParagraphHelpFormatter` wraps description and help text like the
default formatter, while preserving paragraphs. Parapgraphs are separated by
blank lines. Blocks of text separated by a single return are merged into a
single paragraph.

Bullet lists are supported. Text for a bullet item is wrapped with appropriate
indentation. Bullet list items are recognized by markers such as "*", "-", "+",
or ">" characters, or by alphanumeric sequences followed by "." or ")".::

>>> parser = argparse.ArgumentParser(
... prog='PROG',
... formatter_class=argparse.ParagraphHelpFormatter,
... description="""
... The ParagraphHelpFormatter will wrap text within paragraphs
... when required to in order to make the text fit.
...
... Paragraphs are preserved.
...
... It also supports bulleted lists in a number of formats:
... * stars
... 1. numbers
... - ... and so on
... """)
>>> parser.add_argument(
... "argument",
... help="""
... Argument help text also supports flexible formatting,
... with word wrap:
... * See?
... """)
>>> parser.print_help()
usage: PROG [-h] argument

The ParagraphHelpFormatter will wrap text within paragraphs when required to in
order to make the text fit.

Paragraphs are preserved.

It also supports bulleted lists in a number of formats:
* stars
1. numbers
- ... and so on

positional arguments:
argument Argument help text also supports flexible formatting, with word
wrap:
* See?

options:
-h, --help show this help message and exit


.. versionadded:: 3.15
:class:`ParagraphHelpFormatter` class was added.

:class:`ArgumentDefaultsHelpFormatter` automatically adds information about
default values to each of the argument help messages::

Expand Down
83 changes: 82 additions & 1 deletion Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
'ArgumentDefaultsHelpFormatter',
'RawDescriptionHelpFormatter',
'RawTextHelpFormatter',
'ParagraphHelpFormatter',
'MetavarTypeHelpFormatter',
'Namespace',
'Action',
Expand Down Expand Up @@ -581,7 +582,10 @@ def _format_action(self, action):
help_lines = self._split_lines(help_text, help_width)
parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
for line in help_lines[1:]:
parts.append('%*s%s\n' % (help_position, '', line))
if line.strip():
parts.append('%*s%s\n' % (help_position, '', line))
else:
parts.append("\n")

# or add a newline if the description doesn't end with one
elif not action_header.endswith('\n'):
Expand Down Expand Up @@ -750,6 +754,83 @@ def _split_lines(self, text, width):
return text.splitlines()


class ParagraphHelpFormatter(HelpFormatter):
"""Help message formatter which respects paragraphs and bulleted lists.

Only the name of this class is considered a public API. All the methods
provided by the class are considered an implementation detail.
"""

def _split_lines(self, text, width):
return self._para_reformat(text, width)

def _fill_text(self, text, width, indent):
lines = self._para_reformat(text, width - len(indent))
linearray = [indent + line if line else "" for line in lines]
return "\n".join(linearray)

def _indents(self, line):
"""Return line indent level and "sub_indent" for bullet list text."""

indent = len(_re.match(r"( *)", line).group(1))
list_match = _re.match(r"( *)(([*\-+>]+|\w+\)|\w+\.) +)", line)
if list_match:
sub_indent = indent + len(list_match.group(2))
else:
sub_indent = indent

return (indent, sub_indent)

def _split_paragraphs(self, text):
"""Split text in to paragraphs of like-indented lines."""

import textwrap

text = textwrap.dedent(text).strip()
text = _re.sub("\n\n[\n]+", "\n\n", text)

last_sub_indent = None
paragraphs = list()
for line in text.splitlines():
(indent, sub_indent) = self._indents(line)
is_text = len(line.strip()) > 0

if is_text and indent == sub_indent == last_sub_indent:
paragraphs[-1] += " " + line
else:
paragraphs.append(line)

if is_text:
last_sub_indent = sub_indent
else:
last_sub_indent = None

return paragraphs

def _para_reformat(self, text, width):
"""Reformat text, by paragraph."""

import textwrap

lines = list()
for paragraph in self._split_paragraphs(text):

(indent, sub_indent) = self._indents(paragraph)

paragraph = self._whitespace_matcher.sub(" ", paragraph).strip()
new_lines = textwrap.wrap(
text=paragraph,
width=width,
initial_indent=" " * indent,
subsequent_indent=" " * sub_indent,
)

# Blank lines get eaten by textwrap, put it back
lines.extend(new_lines or [""])

return lines


class ArgumentDefaultsHelpFormatter(HelpFormatter):
"""Help message formatter which adds default values to argument help.

Expand Down
67 changes: 67 additions & 0 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -5409,6 +5409,73 @@ class TestHelpRawDescription(HelpTestCase):
version = ''


class TestHelpParagraph(HelpTestCase):
"""Test the ParagraphHelpFormatter"""

parser_signature = Sig(
prog='PROG', formatter_class=argparse.ParagraphHelpFormatter,
description='This text should be wrapped as appropriate to keep\n'
'things nice and very, very tidy.\n'
'\n'
'Paragraphs should be preserved.\n'
' * bullet list items\n'
' should wrap to an appropriate place,\n'
' should such wrapping be required.\n'
' * short bullet\n'
)

argument_signatures = [
Sig('--foo', help=' foo help should also\n'
'appear as given here\n'
'\n'
'along with a second paragraph, if called for\n'
' * bullet'),
Sig('spam', help='spam help'),
]
argument_group_signatures = [
(Sig('title', description='short help text\n'
'\n'
'Longer help text, containing useful\n'
'contextual information for the var in\n'
'question\n'
'* and a bullet\n'),
[Sig('--bar', help='bar help')]),
]
usage = '''\
usage: PROG [-h] [--foo FOO] [--bar BAR] spam
'''
help = usage + '''\

This text should be wrapped as appropriate to keep things nice and very, very
tidy.

Paragraphs should be preserved.
* bullet list items should wrap to an appropriate place, should such
wrapping be required.
* short bullet

positional arguments:
spam spam help

options:
-h, --help show this help message and exit
--foo FOO foo help should also appear as given here

along with a second paragraph, if called for
* bullet

title:
short help text

Longer help text, containing useful contextual information for the var in
question
* and a bullet

--bar BAR bar help
'''
version = ''


class TestHelpArgumentDefaults(HelpTestCase):
"""Test the ArgumentDefaultsHelpFormatter"""

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The :mod:`argparse` module has a new :class:`argparse.ParagraphHelpFormatter`
class that wraps help and description text while preserving paragraphs and
supporting bulleted lists.
Loading