Python argparse: How to insert newline in the help text?
Question
I'm using argparse
in Python 2.7 for parsing input options. One of my options is a multiple choice. I want to make a list in its help text, e.g.
from argparse import ArgumentParser
parser = ArgumentParser(description='test')
parser.add_argument('-g', choices=['a', 'b', 'g', 'd', 'e'], default='a',
help="Some option, where\n"
" a = alpha\n"
" b = beta\n"
" g = gamma\n"
" d = delta\n"
" e = epsilon")
parser.parse_args()
However, argparse
strips all newlines and consecutive spaces. The result looks like
~/Downloads:52$ python2.7 x.py -h usage: x.py [-h] [-g {a,b,g,d,e}] test optional arguments: -h, --help show this help message and exit -g {a,b,g,d,e} Some option, where a = alpha b = beta g = gamma d = delta e = epsilon
How to insert newlines in the help text?
Solution
Try using RawTextHelpFormatter
:
from argparse import RawTextHelpFormatter
parser = ArgumentParser(description='test', formatter_class=RawTextHelpFormatter)
OTHER TIPS
If you just want to override the one option, you should not use RawTextHelpFormatter
. Instead subclass the HelpFormatter
and provide a special intro for the options that should be handled "raw" (I use "R|rest of help"
):
import argparse
class SmartFormatter(argparse.HelpFormatter):
def _split_lines(self, text, width):
if text.startswith('R|'):
return text[2:].splitlines()
# this is the RawTextHelpFormatter._split_lines
return argparse.HelpFormatter._split_lines(self, text, width)
And use it:
from argparse import ArgumentParser
parser = ArgumentParser(description='test', formatter_class=SmartFormatter)
parser.add_argument('-g', choices=['a', 'b', 'g', 'd', 'e'], default='a',
help="R|Some option, where\n"
" a = alpha\n"
" b = beta\n"
" g = gamma\n"
" d = delta\n"
" e = epsilon")
parser.parse_args()
Any other calls to .add_argument()
where the help does not start with R|
will be wrapped as normal.
This is part of my improvements on argparse. The full SmartFormatter also supports adding
the defaults to all options, and raw input of the utilities description. The full version
has its own _split_lines
method, so that any formatting done to e.g. version strings is preserved:
parser.add_argument('--version', '-v', action="version",
version="version...\n 42!")
Another easy way to do it is to include textwrap.
For example,
import argparse, textwrap
parser = argparse.ArgumentParser(description='some information',
usage='use "python %(prog)s --help" for more information',
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--argument', default=somedefault, type=sometype,
help= textwrap.dedent('''\
First line
Second line
More lines ... '''))
In this way, we can avoid the long empty space in front of each output line.
usage: use "python your_python_program.py --help" for more information
Prepare input file
optional arguments:
-h, --help show this help message and exit
--argument ARGUMENT
First line
Second line
More lines ...
I've faced similar issue (Python 2.7.6). I've tried to break down description section into several lines using RawTextHelpFormatter
:
parser = ArgumentParser(description="""First paragraph
Second paragraph
Third paragraph""",
usage='%(prog)s [OPTIONS]',
formatter_class=RawTextHelpFormatter)
options = parser.parse_args()
And got:
usage: play-with-argparse.py [OPTIONS] First paragraph Second paragraph Third paragraph optional arguments: -h, --help show this help message and exit
So RawTextHelpFormatter
is not a solution. Because it prints description as it appears in source code, preserving all whitespace characters (I want to keep extra tabs in my source code for readability but I don't want to print them all. Also raw formatter doesn't wrap line when it is too long, more than 80 characters for example).
Thanks to @Anton who inspired the right direction above. But that solution needs slight modification in order to format description section.
Anyway, custom formatter is needed. I extended existing HelpFormatter
class and overrode _fill_text
method like this:
import textwrap as _textwrap
class MultilineFormatter(argparse.HelpFormatter):
def _fill_text(self, text, width, indent):
text = self._whitespace_matcher.sub(' ', text).strip()
paragraphs = text.split('|n ')
multiline_text = ''
for paragraph in paragraphs:
formatted_paragraph = _textwrap.fill(paragraph, width, initial_indent=indent, subsequent_indent=indent) + '\n\n'
multiline_text = multiline_text + formatted_paragraph
return multiline_text
Compare with the original source code coming from argparse module:
def _fill_text(self, text, width, indent):
text = self._whitespace_matcher.sub(' ', text).strip()
return _textwrap.fill(text, width, initial_indent=indent,
subsequent_indent=indent)
In the original code the whole description is being wrapped. In custom formatter above the whole text is split into several chunks, and each of them is formatted independently.
So with aid of custom formatter:
parser = ArgumentParser(description= """First paragraph
|n
Second paragraph
|n
Third paragraph""",
usage='%(prog)s [OPTIONS]',
formatter_class=MultilineFormatter)
options = parser.parse_args()
the output is:
usage: play-with-argparse.py [OPTIONS] First paragraph Second paragraph Third paragraph optional arguments: -h, --help show this help message and exit
I wanted to have both manual line breaks in the description text, and auto wrapping of it; but none of the suggestions here worked for me - so I ended up modifying the SmartFormatter class given in the answers here; the issues with the argparse method names not being a public API notwithstanding, here is what I have (as a file called test.py
):
import argparse
from argparse import RawDescriptionHelpFormatter
# call with: python test.py -h
class SmartDescriptionFormatter(argparse.RawDescriptionHelpFormatter):
#def _split_lines(self, text, width): # RawTextHelpFormatter, although function name might change depending on Python
def _fill_text(self, text, width, indent): # RawDescriptionHelpFormatter, although function name might change depending on Python
#print("splot",text)
if text.startswith('R|'):
paragraphs = text[2:].splitlines()
rebroken = [argparse._textwrap.wrap(tpar, width) for tpar in paragraphs]
#print(rebroken)
rebrokenstr = []
for tlinearr in rebroken:
if (len(tlinearr) == 0):
rebrokenstr.append("")
else:
for tlinepiece in tlinearr:
rebrokenstr.append(tlinepiece)
#print(rebrokenstr)
return '\n'.join(rebrokenstr) #(argparse._textwrap.wrap(text[2:], width))
# this is the RawTextHelpFormatter._split_lines
#return argparse.HelpFormatter._split_lines(self, text, width)
return argparse.RawDescriptionHelpFormatter._fill_text(self, text, width, indent)
parser = argparse.ArgumentParser(formatter_class=SmartDescriptionFormatter, description="""R|Blahbla bla blah blahh/blahbla (bla blah-blabla) a blahblah bl a blaha-blah .blah blah
Blah blah bla blahblah, bla blahblah blah blah bl blblah bl blahb; blah bl blah bl bl a blah, bla blahb bl:
blah blahblah blah bl blah blahblah""")
options = parser.parse_args()
This is how it works in 2.7 and 3.4:
$ python test.py -h
usage: test.py [-h]
Blahbla bla blah blahh/blahbla (bla blah-blabla) a blahblah bl a blaha-blah
.blah blah
Blah blah bla blahblah, bla blahblah blah blah bl blblah bl blahb; blah bl
blah bl bl a blah, bla blahb bl:
blah blahblah blah bl blah blahblah
optional arguments:
-h, --help show this help message and exit
Starting from SmartFomatter described above, I ended to that solution:
class SmartFormatter(argparse.HelpFormatter):
'''
Custom Help Formatter used to split help text when '\n' was
inserted in it.
'''
def _split_lines(self, text, width):
r = []
for t in text.splitlines(): r.extend(argparse.HelpFormatter._split_lines(self, t, width))
return r
Note that strangely the formatter_class argument passed to top level parser is not inheritated by sub_parsers, one must pass it again for each created sub_parser.