How to preserve newlines in argparse version output while letting argparse auto-format/wrap the remaining help message?
Question:
I wrote the following code.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--version', action='version',
version='%(prog)s 1.0nCopyright (c) 2016 Lone Learner')
parser.parse_args()
This produces the following output.
$ python foo.py --version
foo.py 1.0 Copyright (c) 2016 Lone Learner
You can see that the newline is lost. I wanted the copyright notice to appear on the next line.
How can I preserve the new lines in the version output message?
I still want argparse to compute how the output of python foo.py -h
should be laid out with all the auto-wrapping it does. But I want the version output to be a multiline output with the newlines intact.
Answers:
RawTextHelpFormatter
will turn off the automatic wrapping, allowing your explicit n
to appear. But it will affect all the help
lines. There’s no way of picking and choosing. Either accept the default wrapping, or put explicit newlines in all of your help lines.
You are getting to a level of pickiness about the help format that you need to study the HelpFormatter
code for yourself.
You’d be best off using a custom argparse.Action
for this.
import argparse
import os
import sys
class MultilineVersionAction(argparse.Action):
verbose_version = '1.0nCopyright (c) 2016 Lone Learner'
def __init__(self, option_strings, dest, nargs=None, **kwargs):
if nargs is not None:
raise ValueError('nargs not allowed')
# this is how argparse initialises `prog` by default
self.prog = os.path.basename(sys.argv[0])
super(MultilineVersionAction, self).__init__(option_strings, dest, nargs=0, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
parser.exit(message='{} {}n'.format(self.prog, self.verbose_version))
# ...
self.parser.add_argument('-v', '--version', action=MultilineVersionAction)
Just don’t use action='version'
. The same pattern can be repeated for -h
.
import os
import sys
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--version', action='store_true')
args = parser.parse_args()
if args.version:
prog = os.path.basename(__file__)
print('{} 1.0nCopyright (c) 2016 Lone Learner'.format(prog))
sys.exit()
There’s also argparse.RawDescriptionHelpFormatter
.
parser=argparse.ArgumentParser(add_help=True,
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""an
already-wrapped
description
string""")
It leaves the description
and epilog
alone, and wraps only argument help strings. The OP wanted the opposite.
While I agree with Hamish that you’re best off using a custom Action, it’s possible to leverage more of the argparse
machinery, and to integrate more fully with it as well. For example:
import argparse
import sys
__version__ = 1.0
__copyright__ = "Copyright Nobody <[email protected]>"
class _MyVersionAction(argparse._VersionAction):
"""Customized _VersionAction with RawDescription-formatted output."""
def __call__(self, parser, namespace, values, option_string=None):
version = self.version
if version is None: # I have no idea why this is here¹
version = parser.version # ← (no such thing)
formatter = argparse.RawDescriptionHelpFormatter(
prog=parser.prog)
formatter.add_text(version)
parser._print_message(formatter.format_help(), sys.stdout)
parser.exit()
parser = argparse.ArgumentParser()
parser.register('action', 'my_version', _MyVersionAction)
parser.add_argument(
'-V', '--version', action='my_version',
version=f"%(prog)s {__version__}n{__copyright__}")
parser.parse_args()
This _MyVersionAction
class subclasses argparse._VersionAction
, and is 90% a direct copy-paste of its code, warts and all.¹ (It doesn’t even define an __init__
, just inherits the parent’s.)
But it makes a minimal adjustment to the __call__
method, forcing its formatter to be argparse.RawDescriptionHelpFormatter
. The original code looked up the parser-wide one with parser._get_formatter()
, but we don’t want that here.
Then, after we create the parser instance, we register the action string 'my_version'
for _MyVersionAction
so it can be used in add_argument(..., action='my_version')
like any other action.
That allows you to, for example, use %(prog)s
in the version string, or consume other data from the parser instance. It’s also possible to reuse action='my_version'
in multiple programs, because the version string isn’t hardcoded. You just pass it to add_argument()
like always.
Output
$ python3 /tmp/argtest.py --help
usage: argtest.py [-h] [-V]
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
$ python3 /tmp/argtest.py --version
argtest.py 1.0
Copyright Nobody <[email protected]>
Notes
- Like the comment says, I have no idea why the action tries to get
parser.version
if self.version
isn’t set — there’s no supported API for setting a version
attribute on the parser
object. You’d have to forcibly set it by hand, with parser.version = X.Y
. I suspect that check is just code-cruft carried forward from an older version, or even from the optparse
days.
I wrote the following code.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--version', action='version',
version='%(prog)s 1.0nCopyright (c) 2016 Lone Learner')
parser.parse_args()
This produces the following output.
$ python foo.py --version
foo.py 1.0 Copyright (c) 2016 Lone Learner
You can see that the newline is lost. I wanted the copyright notice to appear on the next line.
How can I preserve the new lines in the version output message?
I still want argparse to compute how the output of python foo.py -h
should be laid out with all the auto-wrapping it does. But I want the version output to be a multiline output with the newlines intact.
RawTextHelpFormatter
will turn off the automatic wrapping, allowing your explicit n
to appear. But it will affect all the help
lines. There’s no way of picking and choosing. Either accept the default wrapping, or put explicit newlines in all of your help lines.
You are getting to a level of pickiness about the help format that you need to study the HelpFormatter
code for yourself.
You’d be best off using a custom argparse.Action
for this.
import argparse
import os
import sys
class MultilineVersionAction(argparse.Action):
verbose_version = '1.0nCopyright (c) 2016 Lone Learner'
def __init__(self, option_strings, dest, nargs=None, **kwargs):
if nargs is not None:
raise ValueError('nargs not allowed')
# this is how argparse initialises `prog` by default
self.prog = os.path.basename(sys.argv[0])
super(MultilineVersionAction, self).__init__(option_strings, dest, nargs=0, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
parser.exit(message='{} {}n'.format(self.prog, self.verbose_version))
# ...
self.parser.add_argument('-v', '--version', action=MultilineVersionAction)
Just don’t use action='version'
. The same pattern can be repeated for -h
.
import os
import sys
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--version', action='store_true')
args = parser.parse_args()
if args.version:
prog = os.path.basename(__file__)
print('{} 1.0nCopyright (c) 2016 Lone Learner'.format(prog))
sys.exit()
There’s also argparse.RawDescriptionHelpFormatter
.
parser=argparse.ArgumentParser(add_help=True,
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""an
already-wrapped
description
string""")
It leaves the description
and epilog
alone, and wraps only argument help strings. The OP wanted the opposite.
While I agree with Hamish that you’re best off using a custom Action, it’s possible to leverage more of the argparse
machinery, and to integrate more fully with it as well. For example:
import argparse
import sys
__version__ = 1.0
__copyright__ = "Copyright Nobody <[email protected]>"
class _MyVersionAction(argparse._VersionAction):
"""Customized _VersionAction with RawDescription-formatted output."""
def __call__(self, parser, namespace, values, option_string=None):
version = self.version
if version is None: # I have no idea why this is here¹
version = parser.version # ← (no such thing)
formatter = argparse.RawDescriptionHelpFormatter(
prog=parser.prog)
formatter.add_text(version)
parser._print_message(formatter.format_help(), sys.stdout)
parser.exit()
parser = argparse.ArgumentParser()
parser.register('action', 'my_version', _MyVersionAction)
parser.add_argument(
'-V', '--version', action='my_version',
version=f"%(prog)s {__version__}n{__copyright__}")
parser.parse_args()
This _MyVersionAction
class subclasses argparse._VersionAction
, and is 90% a direct copy-paste of its code, warts and all.¹ (It doesn’t even define an __init__
, just inherits the parent’s.)
But it makes a minimal adjustment to the __call__
method, forcing its formatter to be argparse.RawDescriptionHelpFormatter
. The original code looked up the parser-wide one with parser._get_formatter()
, but we don’t want that here.
Then, after we create the parser instance, we register the action string 'my_version'
for _MyVersionAction
so it can be used in add_argument(..., action='my_version')
like any other action.
That allows you to, for example, use %(prog)s
in the version string, or consume other data from the parser instance. It’s also possible to reuse action='my_version'
in multiple programs, because the version string isn’t hardcoded. You just pass it to add_argument()
like always.
Output
$ python3 /tmp/argtest.py --help
usage: argtest.py [-h] [-V]
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
$ python3 /tmp/argtest.py --version
argtest.py 1.0
Copyright Nobody <[email protected]>
Notes
- Like the comment says, I have no idea why the action tries to get
parser.version
ifself.version
isn’t set — there’s no supported API for setting aversion
attribute on theparser
object. You’d have to forcibly set it by hand, withparser.version = X.Y
. I suspect that check is just code-cruft carried forward from an older version, or even from theoptparse
days.