From 698a18aa9e8e0f3fca081dc12544002a4d0e83ec Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 2 Mar 2010 22:34:37 +0000 Subject: [PATCH] Merged revisions 78586-78593 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk ........ r78586 | benjamin.peterson | 2010-03-02 16:03:03 -0600 (Tue, 02 Mar 2010) | 1 line remove coding cookie as mandated by PEP 8 ........ r78587 | benjamin.peterson | 2010-03-02 16:05:59 -0600 (Tue, 02 Mar 2010) | 1 line set svn:eol-style ........ r78588 | benjamin.peterson | 2010-03-02 16:08:40 -0600 (Tue, 02 Mar 2010) | 1 line remove another coding cookie ........ r78589 | georg.brandl | 2010-03-02 16:17:38 -0600 (Tue, 02 Mar 2010) | 1 line Add some x-refs. ........ r78590 | benjamin.peterson | 2010-03-02 16:20:10 -0600 (Tue, 02 Mar 2010) | 1 line enable running of argparse tests and fix two that failed in the new environment ........ r78591 | benjamin.peterson | 2010-03-02 16:23:33 -0600 (Tue, 02 Mar 2010) | 1 line prevent warning filter adjustment from altering other tests ........ r78592 | benjamin.peterson | 2010-03-02 16:24:30 -0600 (Tue, 02 Mar 2010) | 1 line use test_main() in __main__ section ........ r78593 | benjamin.peterson | 2010-03-02 16:26:25 -0600 (Tue, 02 Mar 2010) | 1 line convert deprecated fail* methods to assert* variants ........ --- Doc/library/argparse.rst | 3513 ++++++++-------- Lib/argparse.py | 4704 +++++++++++---------- Lib/test/test_argparse.py | 8416 +++++++++++++++++++------------------ 3 files changed, 8316 insertions(+), 8317 deletions(-) diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 78922b17b4b..7e74a68872d 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1,1758 +1,1755 @@ -:mod:`argparse` -- Parser for command line options, arguments and sub-commands -============================================================================== - -.. module:: argparse - :synopsis: Command-line option and argument parsing library. -.. moduleauthor:: Steven Bethard -.. versionadded:: 2.7 -.. sectionauthor:: Steven Bethard - - -The :mod:`argparse` module makes it easy to write user friendly command line -interfaces. You define what arguments your program requires, and -:mod:`argparse` will figure out how to parse those out of ``sys.argv``. The -:mod:`argparse` module also automatically generates help and usage messages -based on the arguments you have defined, and issues errors when users give your -program invalid arguments. - -Example -------- - -As an example, the following code is a Python program that takes a list of -integers and produces either the sum or the max:: - - import argparse - - parser = argparse.ArgumentParser(description='Process some integers.') - parser.add_argument('integers', metavar='N', type=int, nargs='+', - help='an integer for the accumulator') - parser.add_argument('--sum', dest='accumulate', action='store_const', - const=sum, default=max, - help='sum the integers (default: find the max)') - - args = parser.parse_args() - print args.accumulate(args.integers) - -Assuming the Python code above is saved into a file called ``prog.py``, it can -be run at the command line and provides useful help messages:: - - $ prog.py -h - usage: prog.py [-h] [--sum] N [N ...] - - Process some integers. - - positional arguments: - N an integer for the accumulator - - optional arguments: - -h, --help show this help message and exit - --sum sum the integers (default: find the max) - -When run with the appropriate arguments, it prints either the sum or the max of -the command-line integers:: - - $ prog.py 1 2 3 4 - 4 - - $ prog.py 1 2 3 4 --sum - 10 - -If invalid arguments are passed in, it will issue an error:: - - $ prog.py a b c - usage: prog.py [-h] [--sum] N [N ...] - prog.py: error: argument N: invalid int value: 'a' - -The following sections walk you through this example. - -Creating a parser -^^^^^^^^^^^^^^^^^ - -Pretty much every script that uses the :mod:`argparse` module will start out by -creating an :class:`ArgumentParser` object:: - - >>> parser = argparse.ArgumentParser(description='Process some integers.') - -The :class:`ArgumentParser` object will hold all the information necessary to -parse the command line into a more manageable form for your program. - - -Adding arguments -^^^^^^^^^^^^^^^^ - -Once you've created an :class:`ArgumentParser`, you'll want to fill it with -information about your program arguments. You typically do this by making calls -to the :meth:`add_argument` method. Generally, these calls tell the -:class:`ArgumentParser` how to take the strings on the command line and turn -them into objects for you. This information is stored and used when -:meth:`parse_args` is called. For example, if we add some arguments like this:: - - >>> parser.add_argument('integers', metavar='N', type=int, nargs='+', - ... help='an integer for the accumulator') - >>> parser.add_argument('--sum', dest='accumulate', action='store_const', - ... const=sum, default=max, - ... help='sum the integers (default: find the max)') - -when we later call :meth:`parse_args`, we can expect it to return an object -with two attributes, ``integers`` and ``accumulate``. The ``integers`` -attribute will be a list of one or more ints, and the ``accumulate`` attribute -will be either the ``sum`` function, if ``--sum`` was specified at the command -line, or the ``max`` function if it was not. - -Parsing arguments -^^^^^^^^^^^^^^^^^ - -Once an :class:`ArgumentParser` has been initialized with appropriate calls to -:meth:`add_argument`, it can be instructed to parse the command-line args by -calling the :meth:`parse_args` method. This will inspect the command-line, -convert each arg to the appropriate type and then invoke the appropriate -action. In most cases, this means a simple namespace object will be built up -from attributes parsed out of the command-line:: - - >>> parser.parse_args(['--sum', '7', '-1', '42']) - Namespace(accumulate=, integers=[7, -1, 42]) - -In a script, :meth:`parse_args` will typically be called with no arguments, and -the :class:`ArgumentParser` will automatically determine the command-line args -from ``sys.argv``. That's pretty much it. You're now ready to go write some -command line interfaces! - - -ArgumentParser objects ----------------------- - -.. class:: ArgumentParser([description], [epilog], [prog], [usage], [add_help], [argument_default], [parents], [prefix_chars], [conflict_handler], [formatter_class]) - - Create a new :class:`ArgumentParser` object. Each parameter has its own more - detailed description below, but in short they are: - - * description_ - Text to display before the argument help. - - * epilog_ - Text to display after the argument help. - - * add_help_ - Add a -h/--help option to the parser. (default: True) - - * argument_default_ - Set the global default value for arguments. - (default: None) - - * parents_ - A list of :class:ArgumentParser objects whose arguments should - also be included. - - * prefix_chars_ - The set of characters that prefix optional arguments. - (default: '-') - - * fromfile_prefix_chars_ - The set of characters that prefix files from - which additional arguments should be read. (default: None) - - * formatter_class_ - A class for customizing the help output. - - * conflict_handler_ - Usually unnecessary, defines strategy for resolving - conflicting optionals. - - * prog_ - Usually unnecessary, the name of the program - (default: ``sys.argv[0]``) - - * usage_ - Usually unnecessary, the string describing the program usage - (default: generated) - - The following sections describe how each of these are used. - - -description -^^^^^^^^^^^ - -Most calls to the ArgumentParser constructor will use the ``description=`` -keyword argument. This argument gives a brief description of what the program -does and how it works. In help messages, the description is displayed between -the command-line usage string and the help messages for the various arguments:: - - >>> parser = argparse.ArgumentParser(description='A foo that bars') - >>> parser.print_help() - usage: argparse.py [-h] - - A foo that bars - - optional arguments: - -h, --help show this help message and exit - -By default, the description will be line-wrapped so that it fits within the -given space. To change this behavior, see the formatter_class_ argument. - - -epilog -^^^^^^ - -Some programs like to display additional description of the program after the -description of the arguments. Such text can be specified using the ``epilog=`` -argument to ArgumentParser:: - - >>> parser = argparse.ArgumentParser( - ... description='A foo that bars', - ... epilog="And that's how you'd foo a bar") - >>> parser.print_help() - usage: argparse.py [-h] - - A foo that bars - - optional arguments: - -h, --help show this help message and exit - - And that's how you'd foo a bar - -As with the description_ argument, the ``epilog=`` text is by default -line-wrapped, but this behavior can be adjusted with the formatter_class_ -argument to ArgumentParser. - - -add_help -^^^^^^^^ - -By default, ArgumentParser objects add a ``-h/--help`` option which simply -displays the parser's help message. For example, consider a file named -``myprogram.py`` containing the following code:: - - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('--foo', help='foo help') - args = parser.parse_args() - -If ``-h`` or ``--help`` is supplied is at the command-line, the ArgumentParser -help will be printed:: - - $ python myprogram.py --help - usage: myprogram.py [-h] [--foo FOO] - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help - -Occasionally, it may be useful to disable the addition of this help option. -This can be achieved by passing ``False`` as the ``add_help=`` argument to -ArgumentParser:: - - >>> parser = argparse.ArgumentParser(prog='PROG', add_help=False) - >>> parser.add_argument('--foo', help='foo help') - >>> parser.print_help() - usage: PROG [--foo FOO] - - optional arguments: - --foo FOO foo help - - -prefix_chars -^^^^^^^^^^^^ - -Most command-line options will use ``'-'`` as the prefix, e.g. ``-f/--foo``. -Parsers that need to support additional prefix characters, e.g. for options -like ``+f`` or ``/foo``, may specify them using the ``prefix_chars=`` argument -to the ArgumentParser constructor:: - - >>> parser = argparse.ArgumentParser(prog='PROG', prefix_chars='-+') - >>> parser.add_argument('+f') - >>> parser.add_argument('++bar') - >>> parser.parse_args('+f X ++bar Y'.split()) - Namespace(bar='Y', f='X') - -The ``prefix_chars=`` argument defaults to ``'-'``. Supplying a set of -characters that does not include ``'-'`` will cause ``-f/--foo`` options to be -disallowed. - - -fromfile_prefix_chars -^^^^^^^^^^^^^^^^^^^^^ - -Sometimes, e.g. for particularly long argument lists, it may make sense to -keep the list of arguments in a file rather than typing it out at the command -line. If the ``fromfile_prefix_chars=`` argument is given to the ArgumentParser -constructor, then arguments that start with any of the specified characters -will be treated as files, and will be replaced by the arguments they contain. -For example:: - - >>> open('args.txt', 'w').write('-f\nbar') - >>> parser = argparse.ArgumentParser(fromfile_prefix_chars='@') - >>> parser.add_argument('-f') - >>> parser.parse_args(['-f', 'foo', '@args.txt']) - Namespace(f='bar') - -Arguments read from a file must by default be one per line (but see also -:meth:`convert_arg_line_to_args`) and are treated as if they were in the same -place as the original file referencing argument on the command line. So in the -example above, the expression ``['-f', 'foo', '@args.txt']`` is considered -equivalent to the expression ``['-f', 'foo', '-f', 'bar']``. - -The ``fromfile_prefix_chars=`` argument defaults to ``None``, meaning that -arguments will never be treated as file references. - -argument_default -^^^^^^^^^^^^^^^^ - -Generally, argument defaults are specified either by passing a default to -:meth:`add_argument` or by calling the :meth:`set_defaults` methods with a -specific set of name-value pairs. Sometimes however, it may be useful to -specify a single parser-wide default for arguments. This can be accomplished by -passing the ``argument_default=`` keyword argument to ArgumentParser. For -example, to globally suppress attribute creation on :meth:`parse_args` calls, -we supply ``argument_default=SUPPRESS``:: - - >>> parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS) - >>> parser.add_argument('--foo') - >>> parser.add_argument('bar', nargs='?') - >>> parser.parse_args(['--foo', '1', 'BAR']) - Namespace(bar='BAR', foo='1') - >>> parser.parse_args([]) - Namespace() - - -parents -^^^^^^^ - -Sometimes, several parsers share a common set of arguments. Rather than -repeating the definitions of these arguments, you can define a single parser -with all the shared arguments and then use the ``parents=`` argument to -ArgumentParser to have these "inherited". The ``parents=`` argument takes a -list of ArgumentParser objects, collects all the positional and optional -actions from them, and adds these actions to the ArgumentParser object being -constructed:: - - >>> parent_parser = argparse.ArgumentParser(add_help=False) - >>> parent_parser.add_argument('--parent', type=int) - - >>> foo_parser = argparse.ArgumentParser(parents=[parent_parser]) - >>> foo_parser.add_argument('foo') - >>> foo_parser.parse_args(['--parent', '2', 'XXX']) - Namespace(foo='XXX', parent=2) - - >>> bar_parser = argparse.ArgumentParser(parents=[parent_parser]) - >>> bar_parser.add_argument('--bar') - >>> bar_parser.parse_args(['--bar', 'YYY']) - Namespace(bar='YYY', parent=None) - -Note that most parent parsers will specify ``add_help=False``. Otherwise, the -ArgumentParser will see two ``-h/--help`` options (one in the parent and one in -the child) and raise an error. - - -formatter_class -^^^^^^^^^^^^^^^ - -ArgumentParser objects allow the help formatting to be customized by specifying -an alternate formatting class. Currently, there are three such classes: -``argparse.RawDescriptionHelpFormatter``, ``argparse.RawTextHelpFormatter`` and -``argparse.ArgumentDefaultsHelpFormatter``. The first two allow more control -over how textual descriptions are displayed, while the last automatically adds -information about argument default values. - -By default, ArgumentParser objects line-wrap the description_ and epilog_ texts -in command-line help messages:: - - >>> parser = argparse.ArgumentParser( - ... prog='PROG', - ... description='''this description - ... was indented weird - ... but that is okay''', - ... epilog=''' - ... likewise for this epilog whose whitespace will - ... be cleaned up and whose words will be wrapped - ... across a couple lines''') - >>> parser.print_help() - usage: PROG [-h] - - this description was indented weird but that is okay - - optional arguments: - -h, --help show this help message and exit - - likewise for this epilog whose whitespace will be cleaned up and whose words - will be wrapped across a couple lines - -When you have description_ and epilog_ that is already correctly formatted and -should not be line-wrapped, you can indicate this by passing -``argparse.RawDescriptionHelpFormatter`` as the ``formatter_class=`` argument -to ArgumentParser:: - - >>> parser = argparse.ArgumentParser( - ... prog='PROG', - ... formatter_class=argparse.RawDescriptionHelpFormatter, - ... description=textwrap.dedent('''\ - ... Please do not mess up this text! - ... -------------------------------- - ... I have indented it - ... exactly the way - ... I want it - ... ''')) - >>> parser.print_help() - usage: PROG [-h] - - Please do not mess up this text! - -------------------------------- - I have indented it - exactly the way - I want it - - optional arguments: - -h, --help show this help message and exit - -If you want to maintain whitespace for all sorts of help text (including -argument descriptions), you can use ``argparse.RawTextHelpFormatter``. - -The other formatter class available, -``argparse.ArgumentDefaultsHelpFormatter``, will add information about the -default value of each of the arguments:: - - >>> parser = argparse.ArgumentParser( - ... prog='PROG', - ... formatter_class=argparse.ArgumentDefaultsHelpFormatter) - >>> parser.add_argument('--foo', type=int, default=42, help='FOO!') - >>> parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!') - >>> parser.print_help() - usage: PROG [-h] [--foo FOO] [bar [bar ...]] - - positional arguments: - bar BAR! (default: [1, 2, 3]) - - optional arguments: - -h, --help show this help message and exit - --foo FOO FOO! (default: 42) - - -conflict_handler -^^^^^^^^^^^^^^^^ - -ArgumentParser objects do not allow two actions with the same option string. -By default, ArgumentParser objects will raise an exception if you try to create -an argument with an option string that is already in use:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-f', '--foo', help='old foo help') - >>> parser.add_argument('--foo', help='new foo help') - Traceback (most recent call last): - .. - ArgumentError: argument --foo: conflicting option string(s): --foo - -Sometimes (e.g. when using parents_) it may be useful to simply override any -older arguments with the same option string. To get this behavior, the value -``'resolve'`` can be supplied to the ``conflict_handler=`` argument of -ArgumentParser:: - - >>> parser = argparse.ArgumentParser(prog='PROG', conflict_handler='resolve') - >>> parser.add_argument('-f', '--foo', help='old foo help') - >>> parser.add_argument('--foo', help='new foo help') - >>> parser.print_help() - usage: PROG [-h] [-f FOO] [--foo FOO] - - optional arguments: - -h, --help show this help message and exit - -f FOO old foo help - --foo FOO new foo help - -Note that ArgumentParser objects only remove an action if all of its option -strings are overridden. So, in the example above, the old ``-f/--foo`` action -is retained as the ``-f`` action, because only the ``--foo`` option string was -overridden. - - -prog -^^^^ - -By default, ArgumentParser objects use ``sys.argv[0]`` to determine how to -display the name of the program in help messages. This default is almost always -what you want because it will make the help messages match what your users have -typed at the command line. For example, consider a file named ``myprogram.py`` -with the following code:: - - import argparse - parser = argparse.ArgumentParser() - parser.add_argument('--foo', help='foo help') - args = parser.parse_args() - -The help for this program will display ``myprogram.py`` as the program name -(regardless of where the program was invoked from):: - - $ python myprogram.py --help - usage: myprogram.py [-h] [--foo FOO] - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help - $ cd .. - $ python subdir\myprogram.py --help - usage: myprogram.py [-h] [--foo FOO] - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help - -To change this default behavior, another value can be supplied using the -``prog=`` argument to ArgumentParser:: - - >>> parser = argparse.ArgumentParser(prog='myprogram') - >>> parser.print_help() - usage: myprogram [-h] - - optional arguments: - -h, --help show this help message and exit - -Note that the program name, whether determined from ``sys.argv[0]`` or from the -``prog=`` argument, is available to help messages using the ``%(prog)s`` format -specifier. - -:: - - >>> parser = argparse.ArgumentParser(prog='myprogram') - >>> parser.add_argument('--foo', help='foo of the %(prog)s program') - >>> parser.print_help() - usage: myprogram [-h] [--foo FOO] - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo of the myprogram program - - -usage -^^^^^ - -By default, ArgumentParser objects calculate the usage message from the -arguments it contains:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('--foo', nargs='?', help='foo help') - >>> parser.add_argument('bar', nargs='+', help='bar help') - >>> parser.print_help() - usage: PROG [-h] [--foo [FOO]] bar [bar ...] - - positional arguments: - bar bar help - - optional arguments: - -h, --help show this help message and exit - --foo [FOO] foo help - -If the default usage message is not appropriate for your application, you can -supply your own usage message using the ``usage=`` keyword argument to -ArgumentParser:: - - >>> parser = argparse.ArgumentParser(prog='PROG', usage='%(prog)s [options]') - >>> parser.add_argument('--foo', nargs='?', help='foo help') - >>> parser.add_argument('bar', nargs='+', help='bar help') - >>> parser.print_help() - usage: PROG [options] - - positional arguments: - bar bar help - - optional arguments: - -h, --help show this help message and exit - --foo [FOO] foo help - -Note you can use the ``%(prog)s`` format specifier to fill in the program name -in your usage messages. - - -The add_argument() method -------------------------- - -.. method:: add_argument(name or flags..., [action], [nargs], [const], [default], [type], [choices], [required], [help], [metavar], [dest]) - - Define how a single command line argument should be parsed. Each parameter - has its own more detailed description below, but in short they are: - - * `name or flags`_ - Either a name or a list of option strings, e.g. ``foo`` - or ``-f, --foo`` - - * action_ - The basic type of action to be taken when this argument is - encountered at the command-line. - - * nargs_ - The number of command-line arguments that should be consumed. - - * const_ - A constant value required by some action_ and nargs_ selections. - - * default_ - The value produced if the argument is absent from the - command-line. - - * type_ - The type to which the command-line arg should be converted. - - * choices_ - A container of the allowable values for the argument. - - * required_ - Whether or not the command-line option may be omitted - (optionals only). - - * help_ - A brief description of what the argument does. - - * metavar_ - A name for the argument in usage messages. - - * dest_ - The name of the attribute to be added to the object returned by - :meth:`parse_args`. - - The following sections describe how each of these are used. - -name or flags -^^^^^^^^^^^^^ - -The :meth:`add_argument` method needs to know whether you're expecting an -optional argument, e.g. ``-f`` or ``--foo``, or a positional argument, e.g. a -list of filenames. The first arguments passed to :meth:`add_argument` must -therefore be either a series of flags, or a simple argument name. For example, -an optional argument could be created like:: - - >>> parser.add_argument('-f', '--foo') - -while a positional argument could be created like:: - - >>> parser.add_argument('bar') - -When :meth:`parse_args` is called, optional arguments will be identified by the -``-`` prefix, and the remaining arguments will be assumed to be positional:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-f', '--foo') - >>> parser.add_argument('bar') - >>> parser.parse_args(['BAR']) - Namespace(bar='BAR', foo=None) - >>> parser.parse_args(['BAR', '--foo', 'FOO']) - Namespace(bar='BAR', foo='FOO') - >>> parser.parse_args(['--foo', 'FOO']) - usage: PROG [-h] [-f FOO] bar - PROG: error: too few arguments - -action -^^^^^^ - -:class:`ArgumentParser` objects associate command-line args with actions. These -actions can do just about anything with the command-line args associated with -them, though most actions simply add an attribute to the object returned by -:meth:`parse_args`. When you specify a new argument using the -:meth:`add_argument` method, you can indicate how the command-line args should -be handled by specifying the ``action`` keyword argument. The supported actions -are: - -* ``'store'`` - This just stores the argument's value. This is the default - action. For example:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo') - >>> parser.parse_args('--foo 1'.split()) - Namespace(foo='1') - -* ``'store_const'`` - This stores the value specified by the const_ keyword - argument. Note that the const_ keyword argument defaults to ``None``, so - you'll almost always need to provide a value for it. The ``'store_const'`` - action is most commonly used with optional arguments that specify some sort - of flag. For example:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', action='store_const', const=42) - >>> parser.parse_args('--foo'.split()) - Namespace(foo=42) - -* ``'store_true'`` and ``'store_false'`` - These store the values ``True`` and - ``False`` respectively. These are basically special cases of - ``'store_const'``. For example:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', action='store_true') - >>> parser.add_argument('--bar', action='store_false') - >>> parser.parse_args('--foo --bar'.split()) - Namespace(bar=False, foo=True) - -* ``'append'`` - This stores a list, and appends each argument value to the - list. This is useful when you want to allow an option to be specified - multiple times. Example usage:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', action='append') - >>> parser.parse_args('--foo 1 --foo 2'.split()) - Namespace(foo=['1', '2']) - -* ``'append_const'`` - This stores a list, and appends the value specified by - the const_ keyword argument to the list. Note that the const_ keyword - argument defaults to ``None``, so you'll almost always need to provide a - value for it. The ``'append_const'`` action is typically useful when you - want multiple arguments to store constants to the same list, for example:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--str', dest='types', action='append_const', const=str) - >>> parser.add_argument('--int', dest='types', action='append_const', const=int) - >>> parser.parse_args('--str --int'.split()) - Namespace(types=[, ]) - -* ``'version'`` - This expects a ``version=`` keyword argument in the - :meth:`add_argument` call, and prints version information and exits when - invoked. - - >>> import argparse - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-v', '--version', action='version', version='%(prog)s 2.0') - >>> parser.parse_args(['-v']) - PROG 2.0 - -You can also specify an arbitrary action by passing an object that implements -the Action API. The easiest way to do this is to extend ``argparse.Action``, -supplying an appropriate ``__call__`` method. The ``__call__`` method accepts -four parameters: - -* ``parser`` - The ArgumentParser object which contains this action. - -* ``namespace`` - The namespace object that will be returned by - :meth:`parse_args`. Most actions add an attribute to this object. - -* ``values`` - The associated command-line args, with any type-conversions - applied. (Type-conversions are specified with the type_ keyword argument to - :meth:`add_argument`. - -* ``option_string`` - The option string that was used to invoke this action. - The ``option_string`` argument is optional, and will be absent if the action - is associated with a positional argument. - -So for example:: - - >>> class FooAction(argparse.Action): - ... def __call__(self, parser, namespace, values, option_string=None): - ... print '%r %r %r' % (namespace, values, option_string) - ... setattr(namespace, self.dest, values) - ... - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', action=FooAction) - >>> parser.add_argument('bar', action=FooAction) - >>> args = parser.parse_args('1 --foo 2'.split()) - Namespace(bar=None, foo=None) '1' None - Namespace(bar='1', foo=None) '2' '--foo' - >>> args - Namespace(bar='1', foo='2') - - -nargs -^^^^^ - -ArgumentParser objects usually associate a single command-line argument with a -single action to be taken. In the situations where you'd like to associate a -different number of command-line arguments with a single action, you can use -the ``nargs`` keyword argument to :meth:`add_argument`. The supported values -are: - -* N (an integer). N args from the command-line will be gathered together into - a list. For example:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', nargs=2) - >>> parser.add_argument('bar', nargs=1) - >>> parser.parse_args('c --foo a b'.split()) - Namespace(bar=['c'], foo=['a', 'b']) - - Note that ``nargs=1`` produces a list of one item. This is different from - the default, in which the item is produced by itself. - -* ``'?'``. One arg will be consumed from the command-line if possible, and - produced as a single item. If no command-line arg is present, the value from - default_ will be produced. Note that for optional arguments, there is an - additional case - the option string is present but not followed by a - command-line arg. In this case the value from const_ will be produced. Some - examples to illustrate this:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', nargs='?', const='c', default='d') - >>> parser.add_argument('bar', nargs='?', default='d') - >>> parser.parse_args('XX --foo YY'.split()) - Namespace(bar='XX', foo='YY') - >>> parser.parse_args('XX --foo'.split()) - Namespace(bar='XX', foo='c') - >>> parser.parse_args(''.split()) - Namespace(bar='d', foo='d') - - One of the more common uses of ``nargs='?'`` is to allow optional input and - output files:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin) - >>> parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout) - >>> parser.parse_args(['input.txt', 'output.txt']) - Namespace(infile=, outfile=) - >>> parser.parse_args([]) - Namespace(infile=', mode 'r' at 0x...>, outfile=', mode 'w' at 0x...>) - -* ``'*'``. All command-line args present are gathered into a list. Note that - it generally doesn't make much sense to have more than one positional - argument with ``nargs='*'``, but multiple optional arguments with - ``nargs='*'`` is possible. For example:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', nargs='*') - >>> parser.add_argument('--bar', nargs='*') - >>> parser.add_argument('baz', nargs='*') - >>> parser.parse_args('a b --foo x y --bar 1 2'.split()) - Namespace(bar=['1', '2'], baz=['a', 'b'], foo=['x', 'y']) - -* ``'+'``. Just like ``'*'``, all command-line args present are gathered into a - list. Additionally, an error message will be generated if there wasn't at - least one command-line arg present. For example:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('foo', nargs='+') - >>> parser.parse_args('a b'.split()) - Namespace(foo=['a', 'b']) - >>> parser.parse_args(''.split()) - usage: PROG [-h] foo [foo ...] - PROG: error: too few arguments - -If the ``nargs`` keyword argument is not provided, the number of args consumed -is determined by the action_. Generally this means a single command-line arg -will be consumed and a single item (not a list) will be produced. - - -const -^^^^^ - -The ``const`` argument of :meth:`add_argument` is used to hold constant values -that are not read from the command line but are required for the various -ArgumentParser actions. The two most common uses of it are: - -* When :meth:`add_argument` is called with ``action='store_const'`` or - ``action='append_const'``. These actions add the ``const`` value to one of - the attributes of the object returned by :meth:`parse_args`. See the action_ - description for examples. - -* When :meth:`add_argument` is called with option strings (like ``-f`` or - ``--foo``) and ``nargs='?'``. This creates an optional argument that can be - followed by zero or one command-line args. When parsing the command-line, if - the option string is encountered with no command-line arg following it, the - value of ``const`` will be assumed instead. See the nargs_ description for - examples. - -The ``const`` keyword argument defaults to ``None``. - - -default -^^^^^^^ - -All optional arguments and some positional arguments may be omitted at the -command-line. The ``default`` keyword argument of :meth:`add_argument`, whose -value defaults to ``None``, specifies what value should be used if the -command-line arg is not present. For optional arguments, the ``default`` value -is used when the option string was not present at the command line:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', default=42) - >>> parser.parse_args('--foo 2'.split()) - Namespace(foo='2') - >>> parser.parse_args(''.split()) - Namespace(foo=42) - -For positional arguments with nargs_ ``='?'`` or ``'*'``, the ``default`` value -is used when no command-line arg was present:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('foo', nargs='?', default=42) - >>> parser.parse_args('a'.split()) - Namespace(foo='a') - >>> parser.parse_args(''.split()) - Namespace(foo=42) - - -If you don't want to see an attribute when an option was not present at the -command line, you can supply ``default=argparse.SUPPRESS``:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', default=argparse.SUPPRESS) - >>> parser.parse_args([]) - Namespace() - >>> parser.parse_args(['--foo', '1']) - Namespace(foo='1') - - -type -^^^^ - -By default, ArgumentParser objects read command-line args in as simple strings. -However, quite often the command-line string should instead be interpreted as -another type, e.g. ``float``, ``int`` or ``file``. The ``type`` keyword -argument of :meth:`add_argument` allows any necessary type-checking and -type-conversions to be performed. Many common builtin types can be used -directly as the value of the ``type`` argument:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('foo', type=int) - >>> parser.add_argument('bar', type=file) - >>> parser.parse_args('2 temp.txt'.split()) - Namespace(bar=, foo=2) - -To ease the use of various types of files, the argparse module provides the -factory FileType which takes the ``mode=`` and ``bufsize=`` arguments of the -``file`` object. For example, ``FileType('w')`` can be used to create a -writable file:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('bar', type=argparse.FileType('w')) - >>> parser.parse_args(['out.txt']) - Namespace(bar=) - -If you need to do some special type-checking or type-conversions, you can -provide your own types by passing to ``type=`` a callable that takes a single -string argument and returns the type-converted value:: - - >>> def perfect_square(string): - ... value = int(string) - ... sqrt = math.sqrt(value) - ... if sqrt != int(sqrt): - ... msg = "%r is not a perfect square" % string - ... raise argparse.ArgumentTypeError(msg) - ... return value - ... - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('foo', type=perfect_square) - >>> parser.parse_args('9'.split()) - Namespace(foo=9) - >>> parser.parse_args('7'.split()) - usage: PROG [-h] foo - PROG: error: argument foo: '7' is not a perfect square - -Note that if your type-checking function is just checking for a particular set -of values, it may be more convenient to use the choices_ keyword argument:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('foo', type=int, choices=xrange(5, 10)) - >>> parser.parse_args('7'.split()) - Namespace(foo=7) - >>> parser.parse_args('11'.split()) - usage: PROG [-h] {5,6,7,8,9} - PROG: error: argument foo: invalid choice: 11 (choose from 5, 6, 7, 8, 9) - -See the choices_ section for more details. - - -choices -^^^^^^^ - -Some command-line args should be selected from a restricted set of values. -ArgumentParser objects can be told about such sets of values by passing a -container object as the ``choices`` keyword argument to :meth:`add_argument`. -When the command-line is parsed with :meth:`parse_args`, arg values will be -checked, and an error message will be displayed if the arg was not one of the -acceptable values:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('foo', choices='abc') - >>> parser.parse_args('c'.split()) - Namespace(foo='c') - >>> parser.parse_args('X'.split()) - usage: PROG [-h] {a,b,c} - PROG: error: argument foo: invalid choice: 'X' (choose from 'a', 'b', 'c') - -Note that inclusion in the ``choices`` container is checked after any type_ -conversions have been performed, so the type of the objects in the ``choices`` -container should match the type_ specified:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('foo', type=complex, choices=[1, 1j]) - >>> parser.parse_args('1j'.split()) - Namespace(foo=1j) - >>> parser.parse_args('-- -4'.split()) - usage: PROG [-h] {1,1j} - PROG: error: argument foo: invalid choice: (-4+0j) (choose from 1, 1j) - -Any object that supports the ``in`` operator can be passed as the ``choices`` -value, so ``dict`` objects, ``set`` objects, custom containers, etc. are all -supported. - - -required -^^^^^^^^ - -In general, the argparse module assumes that flags like ``-f`` and ``--bar`` -indicate *optional* arguments, which can always be omitted at the command-line. -To change this behavior, i.e. to make an option *required*, the value ``True`` -should be specified for the ``required=`` keyword argument to -:meth:`add_argument`:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', required=True) - >>> parser.parse_args(['--foo', 'BAR']) - Namespace(foo='BAR') - >>> parser.parse_args([]) - usage: argparse.py [-h] [--foo FOO] - argparse.py: error: option --foo is required - -As the example shows, if an option is marked as ``required``, :meth:`parse_args` -will report an error if that option is not present at the command line. - -**Warning:** Required options are generally considered bad form - normal users -expect *options* to be *optional*. You should avoid the use of required options -whenever possible. - - -help -^^^^ - -A great command-line interface isn't worth anything if your users can't figure -out which option does what. So for the end-users, ``help`` is probably the -most important argument to include in your :meth:`add_argument` calls. The -``help`` value should be a string containing a brief description of what the -argument specifies. When a user requests help (usually by using ``-h`` or -``--help`` at the command-line), these ``help`` descriptions will be displayed -with each argument:: - - >>> parser = argparse.ArgumentParser(prog='frobble') - >>> parser.add_argument('--foo', action='store_true', - ... help='foo the bars before frobbling') - >>> parser.add_argument('bar', nargs='+', - ... help='one of the bars to be frobbled') - >>> parser.parse_args('-h'.split()) - usage: frobble [-h] [--foo] bar [bar ...] - - positional arguments: - bar one of the bars to be frobbled - - optional arguments: - -h, --help show this help message and exit - --foo foo the bars before frobbling - -The ``help`` strings can include various format specifiers to avoid repetition -of things like the program name or the argument default_. The available -specifiers include the program name, ``%(prog)s`` and most keyword arguments to -:meth:`add_argument`, e.g. ``%(default)s``, ``%(type)s``, etc.:: - - >>> parser = argparse.ArgumentParser(prog='frobble') - >>> parser.add_argument('bar', nargs='?', type=int, default=42, - ... help='the bar to %(prog)s (default: %(default)s)') - >>> parser.print_help() - usage: frobble [-h] [bar] - - positional arguments: - bar the bar to frobble (default: 42) - - optional arguments: - -h, --help show this help message and exit - - -metavar -^^^^^^^ - -When ArgumentParser objects generate help messages, they need some way to refer -to each expected argument. By default, ArgumentParser objects use the dest_ -value as the "name" of each object. By default, for positional argument -actions, the dest_ value is used directly, and for optional argument actions, -the dest_ value is uppercased. So if we have a single positional argument with -``dest='bar'``, that argument will be referred to as ``bar``. And if we have a -single optional argument ``--foo`` that should be followed by a single -command-line arg, that arg will be referred to as ``FOO``. You can see this -behavior in the example below:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo') - >>> parser.add_argument('bar') - >>> parser.parse_args('X --foo Y'.split()) - Namespace(bar='X', foo='Y') - >>> parser.print_help() - usage: [-h] [--foo FOO] bar - - positional arguments: - bar - - optional arguments: - -h, --help show this help message and exit - --foo FOO - -If you would like to provide a different name for your argument in help -messages, you can supply a value for the ``metavar`` keyword argument to -:meth:`add_argument`:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', metavar='YYY') - >>> parser.add_argument('bar', metavar='XXX') - >>> parser.parse_args('X --foo Y'.split()) - Namespace(bar='X', foo='Y') - >>> parser.print_help() - usage: [-h] [--foo YYY] XXX - - positional arguments: - XXX - - optional arguments: - -h, --help show this help message and exit - --foo YYY - -Note that ``metavar`` only changes the *displayed* name - the name of the -attribute on the :meth:`parse_args` object is still determined by the dest_ -value. - -Different values of ``nargs`` may cause the metavar to be used multiple times. -If you'd like to specify a different display name for each of the arguments, -you can provide a tuple to ``metavar``:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-x', nargs=2) - >>> parser.add_argument('--foo', nargs=2, metavar=('bar', 'baz')) - >>> parser.print_help() - usage: PROG [-h] [-x X X] [--foo bar baz] - - optional arguments: - -h, --help show this help message and exit - -x X X - --foo bar baz - - -dest -^^^^ - -Most ArgumentParser actions add some value as an attribute of the object -returned by :meth:`parse_args`. The name of this attribute is determined by the -``dest`` keyword argument of :meth:`add_argument`. For positional argument -actions, ``dest`` is normally supplied as the first argument to -:meth:`add_argument`:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('bar') - >>> parser.parse_args('XXX'.split()) - Namespace(bar='XXX') - -For optional argument actions, the value of ``dest`` is normally inferred from -the option strings. ArgumentParser objects generate the value of ``dest`` by -taking the first long option string and stripping away the initial ``'--'`` -string. If no long option strings were supplied, ``dest`` will be derived from -the first short option string by stripping the initial ``'-'`` character. Any -internal ``'-'`` characters will be converted to ``'_'`` characters to make -sure the string is a valid attribute name. The examples below illustrate this -behavior:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('-f', '--foo-bar', '--foo') - >>> parser.add_argument('-x', '-y') - >>> parser.parse_args('-f 1 -x 2'.split()) - Namespace(foo_bar='1', x='2') - >>> parser.parse_args('--foo 1 -y 2'.split()) - Namespace(foo_bar='1', x='2') - -If you would like to use a different attribute name from the one automatically -inferred by the ArgumentParser, you can supply it with an explicit ``dest`` -parameter:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', dest='bar') - >>> parser.parse_args('--foo XXX'.split()) - Namespace(bar='XXX') - - -The parse_args() method ------------------------ - -.. method:: parse_args([args], [namespace]) - - Convert the strings to objects and assign them as attributes of the - namespace. Return the populated namespace. - - Previous calls to :meth:`add_argument` determine exactly what objects are - created and how they are assigned. See the documentation for - :meth:`add_argument` for details. - - By default, the arg strings are taken from ``sys.argv``, and a new empty - ``Namespace`` object is created for the attributes. - -Option value syntax -^^^^^^^^^^^^^^^^^^^ - -The :meth:`parse_args` method supports several ways of specifying the value of -an option (if it takes one). In the simplest case, the option and its value are -passed as two separate arguments:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-x') - >>> parser.add_argument('--foo') - >>> parser.parse_args('-x X'.split()) - Namespace(foo=None, x='X') - >>> parser.parse_args('--foo FOO'.split()) - Namespace(foo='FOO', x=None) - -For long options (options with names longer than a single character), you may -also pass the option and value as a single command line argument, using ``=`` -to separate them:: - - >>> parser.parse_args('--foo=FOO'.split()) - Namespace(foo='FOO', x=None) - -For short options (options only one character long), you may simply concatenate -the option and its value:: - - >>> parser.parse_args('-xX'.split()) - Namespace(foo=None, x='X') - -You can also combine several short options together, using only a single ``-`` -prefix, as long as only the last option (or none of them) requires a value:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-x', action='store_true') - >>> parser.add_argument('-y', action='store_true') - >>> parser.add_argument('-z') - >>> parser.parse_args('-xyzZ'.split()) - Namespace(x=True, y=True, z='Z') - - -Invalid arguments -^^^^^^^^^^^^^^^^^ - -While parsing the command-line, ``parse_args`` checks for a variety of errors, -including ambiguous options, invalid types, invalid options, wrong number of -positional arguments, etc. When it encounters such an error, it exits and -prints the error along with a usage message:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('--foo', type=int) - >>> parser.add_argument('bar', nargs='?') - - >>> # invalid type - >>> parser.parse_args(['--foo', 'spam']) - usage: PROG [-h] [--foo FOO] [bar] - PROG: error: argument --foo: invalid int value: 'spam' - - >>> # invalid option - >>> parser.parse_args(['--bar']) - usage: PROG [-h] [--foo FOO] [bar] - PROG: error: no such option: --bar - - >>> # wrong number of arguments - >>> parser.parse_args(['spam', 'badger']) - usage: PROG [-h] [--foo FOO] [bar] - PROG: error: extra arguments found: badger - - -Arguments containing ``"-"`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``parse_args`` method attempts to give errors whenever the user has clearly -made a mistake, but some situations are inherently ambiguous. For example, the -command-line arg ``'-1'`` could either be an attempt to specify an option or an -attempt to provide a positional argument. The ``parse_args`` method is cautious -here: positional arguments may only begin with ``'-'`` if they look like -negative numbers and there are no options in the parser that look like negative -numbers:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-x') - >>> parser.add_argument('foo', nargs='?') - - >>> # no negative number options, so -1 is a positional argument - >>> parser.parse_args(['-x', '-1']) - Namespace(foo=None, x='-1') - - >>> # no negative number options, so -1 and -5 are positional arguments - >>> parser.parse_args(['-x', '-1', '-5']) - Namespace(foo='-5', x='-1') - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-1', dest='one') - >>> parser.add_argument('foo', nargs='?') - - >>> # negative number options present, so -1 is an option - >>> parser.parse_args(['-1', 'X']) - Namespace(foo=None, one='X') - - >>> # negative number options present, so -2 is an option - >>> parser.parse_args(['-2']) - usage: PROG [-h] [-1 ONE] [foo] - PROG: error: no such option: -2 - - >>> # negative number options present, so both -1s are options - >>> parser.parse_args(['-1', '-1']) - usage: PROG [-h] [-1 ONE] [foo] - PROG: error: argument -1: expected one argument - -If you have positional arguments that must begin with ``'-'`` and don't look -like negative numbers, you can insert the pseudo-argument ``'--'`` which tells -``parse_args`` that everything after that is a positional argument:: - - >>> parser.parse_args(['--', '-f']) - Namespace(foo='-f', one=None) - - -Argument abbreviations -^^^^^^^^^^^^^^^^^^^^^^ - -The :meth:`parse_args` method allows you to abbreviate long options if the -abbreviation is unambiguous:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('-bacon') - >>> parser.add_argument('-badger') - >>> parser.parse_args('-bac MMM'.split()) - Namespace(bacon='MMM', badger=None) - >>> parser.parse_args('-bad WOOD'.split()) - Namespace(bacon=None, badger='WOOD') - >>> parser.parse_args('-ba BA'.split()) - usage: PROG [-h] [-bacon BACON] [-badger BADGER] - PROG: error: ambiguous option: -ba could match -badger, -bacon - -As you can see above, you will get an error if you pick a prefix that could -refer to more than one option. - - -Beyond ``sys.argv`` -^^^^^^^^^^^^^^^^^^^ - -Sometimes it may be useful to have an ArgumentParser parse args other than -those of ``sys.argv``. This can be accomplished by passing a list of strings -to ``parse_args``. You may have noticed that the examples in the argparse -documentation have made heavy use of this calling style - it is much easier -to use at the interactive prompt:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument( - ... 'integers', metavar='int', type=int, choices=xrange(10), - ... nargs='+', help='an integer in the range 0..9') - >>> parser.add_argument( - ... '--sum', dest='accumulate', action='store_const', const=sum, - ... default=max, help='sum the integers (default: find the max)') - >>> parser.parse_args(['1', '2', '3', '4']) - Namespace(accumulate=, integers=[1, 2, 3, 4]) - >>> parser.parse_args('1 2 3 4 --sum'.split()) - Namespace(accumulate=, integers=[1, 2, 3, 4]) - - -Custom namespaces -^^^^^^^^^^^^^^^^^ - -It may also be useful to have an ArgumentParser assign attributes to an already -existing object, rather than the newly-created Namespace object that is -normally used. This can be achieved by specifying the ``namespace=`` keyword -argument:: - - >>> class C(object): - ... pass - ... - >>> c = C() - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo') - >>> parser.parse_args(args=['--foo', 'BAR'], namespace=c) - >>> c.foo - 'BAR' - - -Other utilities ---------------- - -Sub-commands -^^^^^^^^^^^^ - -.. method:: add_subparsers() - - A lot of programs split up their functionality into a number of - sub-commands, for example, the ``svn`` program can invoke sub-commands like - ``svn checkout``, ``svn update``, ``svn commit``, etc. Splitting up - functionality this way can be a particularly good idea when a program - performs several different functions which require different kinds of - command-line arguments. ArgumentParser objects support the creation of such - sub-commands with the :meth:`add_subparsers` method. The - :meth:`add_subparsers` method is normally called with no arguments and - returns an special action object. This object has a single method, - ``add_parser``, which takes a command name and any ArgumentParser - constructor arguments, and returns an ArgumentParser object that can be - modified as usual. - - Some example usage:: - - >>> # create the top-level parser - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> parser.add_argument('--foo', action='store_true', help='foo help') - >>> subparsers = parser.add_subparsers(help='sub-command help') - >>> - >>> # create the parser for the "a" command - >>> parser_a = subparsers.add_parser('a', help='a help') - >>> parser_a.add_argument('bar', type=int, help='bar help') - >>> - >>> # create the parser for the "b" command - >>> parser_b = subparsers.add_parser('b', help='b help') - >>> parser_b.add_argument('--baz', choices='XYZ', help='baz help') - >>> - >>> # parse some arg lists - >>> parser.parse_args(['a', '12']) - Namespace(bar=12, foo=False) - >>> parser.parse_args(['--foo', 'b', '--baz', 'Z']) - Namespace(baz='Z', foo=True) - - Note that the object returned by :meth:`parse_args` will only contain - attributes for the main parser and the subparser that was selected by the - command line (and not any other subparsers). So in the example above, when - the ``"a"`` command is specified, only the ``foo`` and ``bar`` attributes - are present, and when the ``"b"`` command is specified, only the ``foo`` and - ``baz`` attributes are present. - - Similarly, when a help message is requested from a subparser, only the help - for that particular parser will be printed. The help message will not - include parent parser or sibling parser messages. (You can however supply a - help message for each subparser command by suppling the ``help=`` argument - to ``add_parser`` as above.) - - :: - - >>> parser.parse_args(['--help']) - usage: PROG [-h] [--foo] {a,b} ... - - positional arguments: - {a,b} sub-command help - a a help - b b help - - optional arguments: - -h, --help show this help message and exit - --foo foo help - - >>> parser.parse_args(['a', '--help']) - usage: PROG a [-h] bar - - positional arguments: - bar bar help - - optional arguments: - -h, --help show this help message and exit - - >>> parser.parse_args(['b', '--help']) - usage: PROG b [-h] [--baz {X,Y,Z}] - - optional arguments: - -h, --help show this help message and exit - --baz {X,Y,Z} baz help - - The :meth:`add_subparsers` method also supports ``title`` and - ``description`` keyword arguments. When either is present, the subparser's - commands will appear in their own group in the help output. For example:: - - >>> parser = argparse.ArgumentParser() - >>> subparsers = parser.add_subparsers(title='subcommands', - ... description='valid subcommands', - ... help='additional help') - >>> subparsers.add_parser('foo') - >>> subparsers.add_parser('bar') - >>> parser.parse_args(['-h']) - usage: [-h] {foo,bar} ... - - optional arguments: - -h, --help show this help message and exit - - subcommands: - valid subcommands - - {foo,bar} additional help - - - One particularly effective way of handling sub-commands is to combine the - use of the :meth:`add_subparsers` method with calls to :meth:`set_defaults` - so that each subparser knows which Python function it should execute. For - example:: - - >>> # sub-command functions - >>> def foo(args): - ... print args.x * args.y - ... - >>> def bar(args): - ... print '((%s))' % args.z - ... - >>> # create the top-level parser - >>> parser = argparse.ArgumentParser() - >>> subparsers = parser.add_subparsers() - >>> - >>> # create the parser for the "foo" command - >>> parser_foo = subparsers.add_parser('foo') - >>> parser_foo.add_argument('-x', type=int, default=1) - >>> parser_foo.add_argument('y', type=float) - >>> parser_foo.set_defaults(func=foo) - >>> - >>> # create the parser for the "bar" command - >>> parser_bar = subparsers.add_parser('bar') - >>> parser_bar.add_argument('z') - >>> parser_bar.set_defaults(func=bar) - >>> - >>> # parse the args and call whatever function was selected - >>> args = parser.parse_args('foo 1 -x 2'.split()) - >>> args.func(args) - 2.0 - >>> - >>> # parse the args and call whatever function was selected - >>> args = parser.parse_args('bar XYZYX'.split()) - >>> args.func(args) - ((XYZYX)) - - This way, you can let :meth:`parse_args` do all the work for you, and then - just call the appropriate function after the argument parsing is complete. - Associating functions with actions like this is typically the easiest way - to handle the different actions for each of your subparsers. However, if you - find it necessary to check the name of the subparser that was invoked, you - can always provide a ``dest`` keyword argument to the :meth:`add_subparsers` - call:: - - >>> parser = argparse.ArgumentParser() - >>> subparsers = parser.add_subparsers(dest='subparser_name') - >>> subparser1 = subparsers.add_parser('1') - >>> subparser1.add_argument('-x') - >>> subparser2 = subparsers.add_parser('2') - >>> subparser2.add_argument('y') - >>> parser.parse_args(['2', 'frobble']) - Namespace(subparser_name='2', y='frobble') - - -FileType objects -^^^^^^^^^^^^^^^^ - -.. class:: FileType(mode='r', bufsize=None) - - The :class:`FileType` factory creates objects that can be passed to the type - argument of :meth:`add_argument`. Arguments that have :class:`FileType` - objects as their type will open command-line args as files with the - requested modes and buffer sizes: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--output', type=argparse.FileType('wb', 0)) - >>> parser.parse_args(['--output', 'out']) - Namespace(output=) - - FileType objects understand the pseudo-argument ``'-'`` and automatically - convert this into ``sys.stdin`` for readable :class:`FileType` objects and - ``sys.stdout`` for writable :class:`FileType` objects: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('infile', type=argparse.FileType('r')) - >>> parser.parse_args(['-']) - Namespace(infile=', mode 'r' at 0x...>) - - -Argument groups -^^^^^^^^^^^^^^^ - -.. method:: add_argument_group([title], [description]) - - By default, ArgumentParser objects group command-line arguments into - "positional arguments" and "optional arguments" when displaying help - messages. When there is a better conceptual grouping of arguments than this - default one, appropriate groups can be created using the - :meth:`add_argument_group` method:: - - >>> parser = argparse.ArgumentParser(prog='PROG', add_help=False) - >>> group = parser.add_argument_group('group') - >>> group.add_argument('--foo', help='foo help') - >>> group.add_argument('bar', help='bar help') - >>> parser.print_help() - usage: PROG [--foo FOO] bar - - group: - bar bar help - --foo FOO foo help - - The :meth:`add_argument_group` method returns an argument group object which - has an :meth:`add_argument` method just like a regular ArgumentParser - objects. When an argument is added to the group, the parser treats it just - like a normal argument, but displays the argument in a separate group for - help messages. The :meth:`add_argument_group` method accepts ``title`` and - ``description`` arguments which can be used to customize this display:: - - >>> parser = argparse.ArgumentParser(prog='PROG', add_help=False) - >>> group1 = parser.add_argument_group('group1', 'group1 description') - >>> group1.add_argument('foo', help='foo help') - >>> group2 = parser.add_argument_group('group2', 'group2 description') - >>> group2.add_argument('--bar', help='bar help') - >>> parser.print_help() - usage: PROG [--bar BAR] foo - - group1: - group1 description - - foo foo help - - group2: - group2 description - - --bar BAR bar help - - Note that any arguments not in your user defined groups will end up back in - the usual "positional arguments" and "optional arguments" sections. - - -Mutual exclusion -^^^^^^^^^^^^^^^^ - -.. method:: add_mutually_exclusive_group([required=False]) - - Sometimes, you need to make sure that only one of a couple different options - is specified on the command line. You can create groups of such mutually - exclusive arguments using the :meth:`add_mutually_exclusive_group` method. - When :func:`parse_args` is called, argparse will make sure that only one of - the arguments in the mutually exclusive group was present on the command - line:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> group = parser.add_mutually_exclusive_group() - >>> group.add_argument('--foo', action='store_true') - >>> group.add_argument('--bar', action='store_false') - >>> parser.parse_args(['--foo']) - Namespace(bar=True, foo=True) - >>> parser.parse_args(['--bar']) - Namespace(bar=False, foo=False) - >>> parser.parse_args(['--foo', '--bar']) - usage: PROG [-h] [--foo | --bar] - PROG: error: argument --bar: not allowed with argument --foo - - The :meth:`add_mutually_exclusive_group` method also accepts a ``required`` - argument, to indicate that at least one of the mutually exclusive arguments - is required:: - - >>> parser = argparse.ArgumentParser(prog='PROG') - >>> group = parser.add_mutually_exclusive_group(required=True) - >>> group.add_argument('--foo', action='store_true') - >>> group.add_argument('--bar', action='store_false') - >>> parser.parse_args([]) - usage: PROG [-h] (--foo | --bar) - PROG: error: one of the arguments --foo --bar is required - - Note that currently mutually exclusive argument groups do not support the - ``title`` and ``description`` arguments of :meth:`add_argument_group`. This - may change in the future however, so you are *strongly* recommended to - specify ``required`` as a keyword argument if you use it. - - -Parser defaults -^^^^^^^^^^^^^^^ - -.. method:: set_defaults(**kwargs) - - Most of the time, the attributes of the object returned by - :meth:`parse_args` will be fully determined by inspecting the command-line - args and the argument actions described in your :meth:`add_argument` calls. - However, sometimes it may be useful to add some additional attributes that - are determined without any inspection of the command-line. The - :meth:`set_defaults` method allows you to do this:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('foo', type=int) - >>> parser.set_defaults(bar=42, baz='badger') - >>> parser.parse_args(['736']) - Namespace(bar=42, baz='badger', foo=736) - - Note that parser-level defaults always override argument-level defaults. So - if you set a parser-level default for a name that matches an argument, the - old argument default will no longer be used:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', default='bar') - >>> parser.set_defaults(foo='spam') - >>> parser.parse_args([]) - Namespace(foo='spam') - - Parser-level defaults can be particularly useful when you're working with - multiple parsers. See the :meth:`add_subparsers` method for an example of - this type. - -.. method:: get_default(dest) - - Get the default value for a namespace attribute, as set by either - :meth:`add_argument` or by :meth:`set_defaults`:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', default='badger') - >>> parser.get_default('foo') - 'badger' - - -Printing help -^^^^^^^^^^^^^ - -In most typical applications, :meth:`parse_args` will take care of formatting -and printing any usage or error messages. However, should you want to format or -print these on your own, several methods are available: - -.. method:: print_usage([file]): - - Print a brief description of how the :class:`ArgumentParser` should be - invoked on the command line. If ``file`` is not present, ``sys.stderr`` is - assumed. - -.. method:: print_help([file]): - - Print a help message, including the program usage and information about the - arguments registered with the :class:`ArgumentParser`. If ``file`` is not - present, ``sys.stderr`` is assumed. - -There are also variants of these methods that simply return a string instead of -printing it: - -.. method:: format_usage(): - - Return a string containing a brief description of how the - :class:`ArgumentParser` should be invoked on the command line. - -.. method:: format_help(): - - Return a string containing a help message, including the program usage and - information about the arguments registered with the :class:`ArgumentParser`. - - - -Partial parsing -^^^^^^^^^^^^^^^ - -.. method:: parse_known_args([args], [namespace]) - -Sometimes a script may only parse a few of the command line arguments, passing -the remaining arguments on to another script or program. In these cases, the -:meth:`parse_known_args` method can be useful. It works much like -:meth:`parse_args` except that it does not produce an error when extra -arguments are present. Instead, it returns a two item tuple containing the -populated namespace and the list of remaining argument strings. - -:: - - >>> parser = argparse.ArgumentParser() - >>> parser.add_argument('--foo', action='store_true') - >>> parser.add_argument('bar') - >>> parser.parse_known_args(['--foo', '--badger', 'BAR', 'spam']) - (Namespace(bar='BAR', foo=True), ['--badger', 'spam']) - - -Customizing file parsing -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. method:: convert_arg_line_to_args(arg_line) - - Arguments that are read from a file (see the ``fromfile_prefix_chars`` - keyword argument to the :class:`ArgumentParser` constructor) are read one - argument per line. If you need fancier parsing, then you can subclass the - :class:`ArgumentParser` and override the :meth:`convert_arg_line_to_args` - method. - - This method takes a single argument ``arg_line`` which is a string read from - the argument file. It returns a list of arguments parsed from this string. - The method is called once per line read from the argument file, in order. - - A useful override of this method is one that treats each space-separated - word as an argument:: - - def convert_arg_line_to_args(self, arg_line): - for arg in arg_line.split(): - if not arg.strip(): - continue - yield arg - - -Upgrading optparse code ------------------------ - -Originally, the argparse module had attempted to maintain compatibility with - optparse. However, optparse was difficult to extend transparently, - particularly with the changes required to support the new ``nargs=`` - specifiers and better usage messges. When most everything in optparse had - either been copy-pasted over or monkey-patched, it no longer seemed practical - to try to maintain the backwards compatibility. - -A partial upgrade path from optparse to argparse: - -* Replace all ``add_option()`` calls with :meth:`add_argument` calls. - -* Replace ``options, args = parser.parse_args()`` with - ``args = parser.parse_args()`` and add additional :meth:`add_argument` calls - for the positional arguments. - -* Replace callback actions and the ``callback_*`` keyword arguments with - ``type`` or ``action`` arguments. - -* Replace string names for ``type`` keyword arguments with the corresponding - type objects (e.g. int, float, complex, etc). - -* Replace ``Values`` with ``Namespace`` and ``OptionError/OptionValueError`` - with ``ArgumentError``. - -* Replace strings with implicit arguments such as ``%default`` or ``%prog`` - with the standard python syntax to use dictionaries to format strings, that - is, ``%(default)s`` and ``%(prog)s``. +:mod:`argparse` -- Parser for command line options, arguments and sub-commands +============================================================================== + +.. module:: argparse + :synopsis: Command-line option and argument parsing library. +.. moduleauthor:: Steven Bethard +.. versionadded:: 2.7 +.. sectionauthor:: Steven Bethard + + +The :mod:`argparse` module makes it easy to write user friendly command line +interfaces. You define what arguments your program requires, and :mod:`argparse` +will figure out how to parse those out of :data:`sys.argv`. The :mod:`argparse` +module also automatically generates help and usage messages based on the +arguments you have defined, and issues errors when users give your program +invalid arguments. + +Example +------- + +As an example, the following code is a Python program that takes a list of +integers and produces either the sum or the max:: + + import argparse + + parser = argparse.ArgumentParser(description='Process some integers.') + parser.add_argument('integers', metavar='N', type=int, nargs='+', + help='an integer for the accumulator') + parser.add_argument('--sum', dest='accumulate', action='store_const', + const=sum, default=max, + help='sum the integers (default: find the max)') + + args = parser.parse_args() + print args.accumulate(args.integers) + +Assuming the Python code above is saved into a file called ``prog.py``, it can +be run at the command line and provides useful help messages:: + + $ prog.py -h + usage: prog.py [-h] [--sum] N [N ...] + + Process some integers. + + positional arguments: + N an integer for the accumulator + + optional arguments: + -h, --help show this help message and exit + --sum sum the integers (default: find the max) + +When run with the appropriate arguments, it prints either the sum or the max of +the command-line integers:: + + $ prog.py 1 2 3 4 + 4 + + $ prog.py 1 2 3 4 --sum + 10 + +If invalid arguments are passed in, it will issue an error:: + + $ prog.py a b c + usage: prog.py [-h] [--sum] N [N ...] + prog.py: error: argument N: invalid int value: 'a' + +The following sections walk you through this example. + +Creating a parser +^^^^^^^^^^^^^^^^^ + +Pretty much every script that uses the :mod:`argparse` module will start out by +creating an :class:`ArgumentParser` object:: + + >>> parser = argparse.ArgumentParser(description='Process some integers.') + +The :class:`ArgumentParser` object will hold all the information necessary to +parse the command line into a more manageable form for your program. + + +Adding arguments +^^^^^^^^^^^^^^^^ + +Once you've created an :class:`ArgumentParser`, you'll want to fill it with +information about your program arguments. You typically do this by making calls +to the :meth:`add_argument` method. Generally, these calls tell the +:class:`ArgumentParser` how to take the strings on the command line and turn +them into objects for you. This information is stored and used when +:meth:`parse_args` is called. For example, if we add some arguments like this:: + + >>> parser.add_argument('integers', metavar='N', type=int, nargs='+', + ... help='an integer for the accumulator') + >>> parser.add_argument('--sum', dest='accumulate', action='store_const', + ... const=sum, default=max, + ... help='sum the integers (default: find the max)') + +when we later call :meth:`parse_args`, we can expect it to return an object with +two attributes, ``integers`` and ``accumulate``. The ``integers`` attribute +will be a list of one or more ints, and the ``accumulate`` attribute will be +either the :func:`sum` function, if ``--sum`` was specified at the command line, +or the :func:`max` function if it was not. + +Parsing arguments +^^^^^^^^^^^^^^^^^ + +Once an :class:`ArgumentParser` has been initialized with appropriate calls to +:meth:`add_argument`, it can be instructed to parse the command-line args by +calling the :meth:`parse_args` method. This will inspect the command-line, +convert each arg to the appropriate type and then invoke the appropriate action. +In most cases, this means a simple namespace object will be built up from +attributes parsed out of the command-line:: + + >>> parser.parse_args(['--sum', '7', '-1', '42']) + Namespace(accumulate=, integers=[7, -1, 42]) + +In a script, :meth:`parse_args` will typically be called with no arguments, and +the :class:`ArgumentParser` will automatically determine the command-line args +from :data:`sys.argv`. That's pretty much it. You're now ready to go write +some command line interfaces! + + +ArgumentParser objects +---------------------- + +.. class:: ArgumentParser([description], [epilog], [prog], [usage], [add_help], [argument_default], [parents], [prefix_chars], [conflict_handler], [formatter_class]) + + Create a new :class:`ArgumentParser` object. Each parameter has its own more + detailed description below, but in short they are: + + * description_ - Text to display before the argument help. + + * epilog_ - Text to display after the argument help. + + * add_help_ - Add a -h/--help option to the parser. (default: True) + + * argument_default_ - Set the global default value for arguments. + (default: None) + + * parents_ - A list of :class:ArgumentParser objects whose arguments should + also be included. + + * prefix_chars_ - The set of characters that prefix optional arguments. + (default: '-') + + * fromfile_prefix_chars_ - The set of characters that prefix files from + which additional arguments should be read. (default: None) + + * formatter_class_ - A class for customizing the help output. + + * conflict_handler_ - Usually unnecessary, defines strategy for resolving + conflicting optionals. + + * prog_ - Usually unnecessary, the name of the program + (default: ``sys.argv[0]``) + + * usage_ - Usually unnecessary, the string describing the program usage + (default: generated) + + The following sections describe how each of these are used. + + +description +^^^^^^^^^^^ + +Most calls to the ArgumentParser constructor will use the ``description=`` +keyword argument. This argument gives a brief description of what the program +does and how it works. In help messages, the description is displayed between +the command-line usage string and the help messages for the various arguments:: + + >>> parser = argparse.ArgumentParser(description='A foo that bars') + >>> parser.print_help() + usage: argparse.py [-h] + + A foo that bars + + optional arguments: + -h, --help show this help message and exit + +By default, the description will be line-wrapped so that it fits within the +given space. To change this behavior, see the formatter_class_ argument. + + +epilog +^^^^^^ + +Some programs like to display additional description of the program after the +description of the arguments. Such text can be specified using the ``epilog=`` +argument to :class:`ArgumentParser`:: + + >>> parser = argparse.ArgumentParser( + ... description='A foo that bars', + ... epilog="And that's how you'd foo a bar") + >>> parser.print_help() + usage: argparse.py [-h] + + A foo that bars + + optional arguments: + -h, --help show this help message and exit + + And that's how you'd foo a bar + +As with the description_ argument, the ``epilog=`` text is by default +line-wrapped, but this behavior can be adjusted with the formatter_class_ +argument to ArgumentParser. + + +add_help +^^^^^^^^ + +By default, ArgumentParser objects add a ``-h/--help`` option which simply +displays the parser's help message. For example, consider a file named +``myprogram.py`` containing the following code:: + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--foo', help='foo help') + args = parser.parse_args() + +If ``-h`` or ``--help`` is supplied is at the command-line, the ArgumentParser +help will be printed:: + + $ python myprogram.py --help + usage: myprogram.py [-h] [--foo FOO] + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help + +Occasionally, it may be useful to disable the addition of this help option. +This can be achieved by passing ``False`` as the ``add_help=`` argument to +ArgumentParser:: + + >>> parser = argparse.ArgumentParser(prog='PROG', add_help=False) + >>> parser.add_argument('--foo', help='foo help') + >>> parser.print_help() + usage: PROG [--foo FOO] + + optional arguments: + --foo FOO foo help + + +prefix_chars +^^^^^^^^^^^^ + +Most command-line options will use ``'-'`` as the prefix, e.g. ``-f/--foo``. +Parsers that need to support additional prefix characters, e.g. for options +like ``+f`` or ``/foo``, may specify them using the ``prefix_chars=`` argument +to the ArgumentParser constructor:: + + >>> parser = argparse.ArgumentParser(prog='PROG', prefix_chars='-+') + >>> parser.add_argument('+f') + >>> parser.add_argument('++bar') + >>> parser.parse_args('+f X ++bar Y'.split()) + Namespace(bar='Y', f='X') + +The ``prefix_chars=`` argument defaults to ``'-'``. Supplying a set of +characters that does not include ``'-'`` will cause ``-f/--foo`` options to be +disallowed. + + +fromfile_prefix_chars +^^^^^^^^^^^^^^^^^^^^^ + +Sometimes, e.g. for particularly long argument lists, it may make sense to keep +the list of arguments in a file rather than typing it out at the command line. +If the ``fromfile_prefix_chars=`` argument is given to the ArgumentParser +constructor, then arguments that start with any of the specified characters will +be treated as files, and will be replaced by the arguments they contain. For +example:: + + >>> open('args.txt', 'w').write('-f\nbar') + >>> parser = argparse.ArgumentParser(fromfile_prefix_chars='@') + >>> parser.add_argument('-f') + >>> parser.parse_args(['-f', 'foo', '@args.txt']) + Namespace(f='bar') + +Arguments read from a file must by default be one per line (but see also +:meth:`convert_arg_line_to_args`) and are treated as if they were in the same +place as the original file referencing argument on the command line. So in the +example above, the expression ``['-f', 'foo', '@args.txt']`` is considered +equivalent to the expression ``['-f', 'foo', '-f', 'bar']``. + +The ``fromfile_prefix_chars=`` argument defaults to ``None``, meaning that +arguments will never be treated as file references. + +argument_default +^^^^^^^^^^^^^^^^ + +Generally, argument defaults are specified either by passing a default to +:meth:`add_argument` or by calling the :meth:`set_defaults` methods with a +specific set of name-value pairs. Sometimes however, it may be useful to +specify a single parser-wide default for arguments. This can be accomplished by +passing the ``argument_default=`` keyword argument to ArgumentParser. For +example, to globally suppress attribute creation on :meth:`parse_args` calls, we +supply ``argument_default=SUPPRESS``:: + + >>> parser = argparse.ArgumentParser(argument_default=argparse.SUPPRESS) + >>> parser.add_argument('--foo') + >>> parser.add_argument('bar', nargs='?') + >>> parser.parse_args(['--foo', '1', 'BAR']) + Namespace(bar='BAR', foo='1') + >>> parser.parse_args([]) + Namespace() + + +parents +^^^^^^^ + +Sometimes, several parsers share a common set of arguments. Rather than +repeating the definitions of these arguments, you can define a single parser +with all the shared arguments and then use the ``parents=`` argument to +ArgumentParser to have these "inherited". The ``parents=`` argument takes a +list of ArgumentParser objects, collects all the positional and optional actions +from them, and adds these actions to the ArgumentParser object being +constructed:: + + >>> parent_parser = argparse.ArgumentParser(add_help=False) + >>> parent_parser.add_argument('--parent', type=int) + + >>> foo_parser = argparse.ArgumentParser(parents=[parent_parser]) + >>> foo_parser.add_argument('foo') + >>> foo_parser.parse_args(['--parent', '2', 'XXX']) + Namespace(foo='XXX', parent=2) + + >>> bar_parser = argparse.ArgumentParser(parents=[parent_parser]) + >>> bar_parser.add_argument('--bar') + >>> bar_parser.parse_args(['--bar', 'YYY']) + Namespace(bar='YYY', parent=None) + +Note that most parent parsers will specify ``add_help=False``. Otherwise, the +ArgumentParser will see two ``-h/--help`` options (one in the parent and one in +the child) and raise an error. + + +formatter_class +^^^^^^^^^^^^^^^ + +ArgumentParser objects allow the help formatting to be customized by specifying +an alternate formatting class. Currently, there are three such classes: +:class:`argparse.RawDescriptionHelpFormatter`, +:class:`argparse.RawTextHelpFormatter` and +:class:`argparse.ArgumentDefaultsHelpFormatter`. The first two allow more +control over how textual descriptions are displayed, while the last +automatically adds information about argument default values. + +By default, ArgumentParser objects line-wrap the description_ and epilog_ texts +in command-line help messages:: + + >>> parser = argparse.ArgumentParser( + ... prog='PROG', + ... description='''this description + ... was indented weird + ... but that is okay''', + ... epilog=''' + ... likewise for this epilog whose whitespace will + ... be cleaned up and whose words will be wrapped + ... across a couple lines''') + >>> parser.print_help() + usage: PROG [-h] + + this description was indented weird but that is okay + + optional arguments: + -h, --help show this help message and exit + + likewise for this epilog whose whitespace will be cleaned up and whose words + will be wrapped across a couple lines + +When you have description_ and epilog_ that is already correctly formatted and +should not be line-wrapped, you can indicate this by passing +``argparse.RawDescriptionHelpFormatter`` as the ``formatter_class=`` argument to +ArgumentParser:: + + >>> parser = argparse.ArgumentParser( + ... prog='PROG', + ... formatter_class=argparse.RawDescriptionHelpFormatter, + ... description=textwrap.dedent('''\ + ... Please do not mess up this text! + ... -------------------------------- + ... I have indented it + ... exactly the way + ... I want it + ... ''')) + >>> parser.print_help() + usage: PROG [-h] + + Please do not mess up this text! + -------------------------------- + I have indented it + exactly the way + I want it + + optional arguments: + -h, --help show this help message and exit + +If you want to maintain whitespace for all sorts of help text (including +argument descriptions), you can use ``argparse.RawTextHelpFormatter``. + +The other formatter class available, ``argparse.ArgumentDefaultsHelpFormatter``, +will add information about the default value of each of the arguments:: + + >>> parser = argparse.ArgumentParser( + ... prog='PROG', + ... formatter_class=argparse.ArgumentDefaultsHelpFormatter) + >>> parser.add_argument('--foo', type=int, default=42, help='FOO!') + >>> parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!') + >>> parser.print_help() + usage: PROG [-h] [--foo FOO] [bar [bar ...]] + + positional arguments: + bar BAR! (default: [1, 2, 3]) + + optional arguments: + -h, --help show this help message and exit + --foo FOO FOO! (default: 42) + + +conflict_handler +^^^^^^^^^^^^^^^^ + +ArgumentParser objects do not allow two actions with the same option string. By +default, ArgumentParser objects will raise an exception if you try to create an +argument with an option string that is already in use:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-f', '--foo', help='old foo help') + >>> parser.add_argument('--foo', help='new foo help') + Traceback (most recent call last): + .. + ArgumentError: argument --foo: conflicting option string(s): --foo + +Sometimes (e.g. when using parents_) it may be useful to simply override any +older arguments with the same option string. To get this behavior, the value +``'resolve'`` can be supplied to the ``conflict_handler=`` argument of +ArgumentParser:: + + >>> parser = argparse.ArgumentParser(prog='PROG', conflict_handler='resolve') + >>> parser.add_argument('-f', '--foo', help='old foo help') + >>> parser.add_argument('--foo', help='new foo help') + >>> parser.print_help() + usage: PROG [-h] [-f FOO] [--foo FOO] + + optional arguments: + -h, --help show this help message and exit + -f FOO old foo help + --foo FOO new foo help + +Note that ArgumentParser objects only remove an action if all of its option +strings are overridden. So, in the example above, the old ``-f/--foo`` action +is retained as the ``-f`` action, because only the ``--foo`` option string was +overridden. + + +prog +^^^^ + +By default, ArgumentParser objects use ``sys.argv[0]`` to determine how to +display the name of the program in help messages. This default is almost always +what you want because it will make the help messages match what your users have +typed at the command line. For example, consider a file named ``myprogram.py`` +with the following code:: + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--foo', help='foo help') + args = parser.parse_args() + +The help for this program will display ``myprogram.py`` as the program name +(regardless of where the program was invoked from):: + + $ python myprogram.py --help + usage: myprogram.py [-h] [--foo FOO] + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help + $ cd .. + $ python subdir\myprogram.py --help + usage: myprogram.py [-h] [--foo FOO] + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help + +To change this default behavior, another value can be supplied using the +``prog=`` argument to ArgumentParser:: + + >>> parser = argparse.ArgumentParser(prog='myprogram') + >>> parser.print_help() + usage: myprogram [-h] + + optional arguments: + -h, --help show this help message and exit + +Note that the program name, whether determined from ``sys.argv[0]`` or from the +``prog=`` argument, is available to help messages using the ``%(prog)s`` format +specifier. + +:: + + >>> parser = argparse.ArgumentParser(prog='myprogram') + >>> parser.add_argument('--foo', help='foo of the %(prog)s program') + >>> parser.print_help() + usage: myprogram [-h] [--foo FOO] + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo of the myprogram program + + +usage +^^^^^ + +By default, ArgumentParser objects calculate the usage message from the +arguments it contains:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('--foo', nargs='?', help='foo help') + >>> parser.add_argument('bar', nargs='+', help='bar help') + >>> parser.print_help() + usage: PROG [-h] [--foo [FOO]] bar [bar ...] + + positional arguments: + bar bar help + + optional arguments: + -h, --help show this help message and exit + --foo [FOO] foo help + +If the default usage message is not appropriate for your application, you can +supply your own usage message using the ``usage=`` keyword argument to +ArgumentParser:: + + >>> parser = argparse.ArgumentParser(prog='PROG', usage='%(prog)s [options]') + >>> parser.add_argument('--foo', nargs='?', help='foo help') + >>> parser.add_argument('bar', nargs='+', help='bar help') + >>> parser.print_help() + usage: PROG [options] + + positional arguments: + bar bar help + + optional arguments: + -h, --help show this help message and exit + --foo [FOO] foo help + +Note you can use the ``%(prog)s`` format specifier to fill in the program name +in your usage messages. + + +The add_argument() method +------------------------- + +.. method:: add_argument(name or flags..., [action], [nargs], [const], [default], [type], [choices], [required], [help], [metavar], [dest]) + + Define how a single command line argument should be parsed. Each parameter + has its own more detailed description below, but in short they are: + + * `name or flags`_ - Either a name or a list of option strings, e.g. ``foo`` + or ``-f, --foo`` + + * action_ - The basic type of action to be taken when this argument is + encountered at the command-line. + + * nargs_ - The number of command-line arguments that should be consumed. + + * const_ - A constant value required by some action_ and nargs_ selections. + + * default_ - The value produced if the argument is absent from the + command-line. + + * type_ - The type to which the command-line arg should be converted. + + * choices_ - A container of the allowable values for the argument. + + * required_ - Whether or not the command-line option may be omitted + (optionals only). + + * help_ - A brief description of what the argument does. + + * metavar_ - A name for the argument in usage messages. + + * dest_ - The name of the attribute to be added to the object returned by + :meth:`parse_args`. + + The following sections describe how each of these are used. + +name or flags +^^^^^^^^^^^^^ + +The :meth:`add_argument` method needs to know whether you're expecting an +optional argument, e.g. ``-f`` or ``--foo``, or a positional argument, e.g. a +list of filenames. The first arguments passed to :meth:`add_argument` must +therefore be either a series of flags, or a simple argument name. For example, +an optional argument could be created like:: + + >>> parser.add_argument('-f', '--foo') + +while a positional argument could be created like:: + + >>> parser.add_argument('bar') + +When :meth:`parse_args` is called, optional arguments will be identified by the +``-`` prefix, and the remaining arguments will be assumed to be positional:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-f', '--foo') + >>> parser.add_argument('bar') + >>> parser.parse_args(['BAR']) + Namespace(bar='BAR', foo=None) + >>> parser.parse_args(['BAR', '--foo', 'FOO']) + Namespace(bar='BAR', foo='FOO') + >>> parser.parse_args(['--foo', 'FOO']) + usage: PROG [-h] [-f FOO] bar + PROG: error: too few arguments + +action +^^^^^^ + +:class:`ArgumentParser` objects associate command-line args with actions. These +actions can do just about anything with the command-line args associated with +them, though most actions simply add an attribute to the object returned by +:meth:`parse_args`. When you specify a new argument using the +:meth:`add_argument` method, you can indicate how the command-line args should +be handled by specifying the ``action`` keyword argument. The supported actions +are: + +* ``'store'`` - This just stores the argument's value. This is the default + action. For example:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo') + >>> parser.parse_args('--foo 1'.split()) + Namespace(foo='1') + +* ``'store_const'`` - This stores the value specified by the const_ keyword + argument. Note that the const_ keyword argument defaults to ``None``, so + you'll almost always need to provide a value for it. The ``'store_const'`` + action is most commonly used with optional arguments that specify some sort + of flag. For example:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', action='store_const', const=42) + >>> parser.parse_args('--foo'.split()) + Namespace(foo=42) + +* ``'store_true'`` and ``'store_false'`` - These store the values ``True`` and + ``False`` respectively. These are basically special cases of + ``'store_const'``. For example:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', action='store_true') + >>> parser.add_argument('--bar', action='store_false') + >>> parser.parse_args('--foo --bar'.split()) + Namespace(bar=False, foo=True) + +* ``'append'`` - This stores a list, and appends each argument value to the + list. This is useful when you want to allow an option to be specified + multiple times. Example usage:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', action='append') + >>> parser.parse_args('--foo 1 --foo 2'.split()) + Namespace(foo=['1', '2']) + +* ``'append_const'`` - This stores a list, and appends the value specified by + the const_ keyword argument to the list. Note that the const_ keyword + argument defaults to ``None``, so you'll almost always need to provide a value + for it. The ``'append_const'`` action is typically useful when you want + multiple arguments to store constants to the same list, for example:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--str', dest='types', action='append_const', const=str) + >>> parser.add_argument('--int', dest='types', action='append_const', const=int) + >>> parser.parse_args('--str --int'.split()) + Namespace(types=[, ]) + +* ``'version'`` - This expects a ``version=`` keyword argument in the + :meth:`add_argument` call, and prints version information and exits when + invoked. + + >>> import argparse + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-v', '--version', action='version', version='%(prog)s 2.0') + >>> parser.parse_args(['-v']) + PROG 2.0 + +You can also specify an arbitrary action by passing an object that implements +the Action API. The easiest way to do this is to extend ``argparse.Action``, +supplying an appropriate :meth:`__call__` method. The ``__call__`` method +accepts four parameters: + +* ``parser`` - The ArgumentParser object which contains this action. + +* ``namespace`` - The namespace object that will be returned by + :meth:`parse_args`. Most actions add an attribute to this object. + +* ``values`` - The associated command-line args, with any type-conversions + applied. (Type-conversions are specified with the type_ keyword argument to + :meth:`add_argument`. + +* ``option_string`` - The option string that was used to invoke this action. + The ``option_string`` argument is optional, and will be absent if the action + is associated with a positional argument. + +So for example:: + + >>> class FooAction(argparse.Action): + ... def __call__(self, parser, namespace, values, option_string=None): + ... print '%r %r %r' % (namespace, values, option_string) + ... setattr(namespace, self.dest, values) + ... + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', action=FooAction) + >>> parser.add_argument('bar', action=FooAction) + >>> args = parser.parse_args('1 --foo 2'.split()) + Namespace(bar=None, foo=None) '1' None + Namespace(bar='1', foo=None) '2' '--foo' + >>> args + Namespace(bar='1', foo='2') + + +nargs +^^^^^ + +ArgumentParser objects usually associate a single command-line argument with a +single action to be taken. In the situations where you'd like to associate a +different number of command-line arguments with a single action, you can use the +``nargs`` keyword argument to :meth:`add_argument`. The supported values are: + +* N (an integer). N args from the command-line will be gathered together into a + list. For example:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', nargs=2) + >>> parser.add_argument('bar', nargs=1) + >>> parser.parse_args('c --foo a b'.split()) + Namespace(bar=['c'], foo=['a', 'b']) + + Note that ``nargs=1`` produces a list of one item. This is different from + the default, in which the item is produced by itself. + +* ``'?'``. One arg will be consumed from the command-line if possible, and + produced as a single item. If no command-line arg is present, the value from + default_ will be produced. Note that for optional arguments, there is an + additional case - the option string is present but not followed by a + command-line arg. In this case the value from const_ will be produced. Some + examples to illustrate this:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', nargs='?', const='c', default='d') + >>> parser.add_argument('bar', nargs='?', default='d') + >>> parser.parse_args('XX --foo YY'.split()) + Namespace(bar='XX', foo='YY') + >>> parser.parse_args('XX --foo'.split()) + Namespace(bar='XX', foo='c') + >>> parser.parse_args(''.split()) + Namespace(bar='d', foo='d') + + One of the more common uses of ``nargs='?'`` is to allow optional input and + output files:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin) + >>> parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout) + >>> parser.parse_args(['input.txt', 'output.txt']) + Namespace(infile=, outfile=) + >>> parser.parse_args([]) + Namespace(infile=', mode 'r' at 0x...>, outfile=', mode 'w' at 0x...>) + +* ``'*'``. All command-line args present are gathered into a list. Note that + it generally doesn't make much sense to have more than one positional argument + with ``nargs='*'``, but multiple optional arguments with ``nargs='*'`` is + possible. For example:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', nargs='*') + >>> parser.add_argument('--bar', nargs='*') + >>> parser.add_argument('baz', nargs='*') + >>> parser.parse_args('a b --foo x y --bar 1 2'.split()) + Namespace(bar=['1', '2'], baz=['a', 'b'], foo=['x', 'y']) + +* ``'+'``. Just like ``'*'``, all command-line args present are gathered into a + list. Additionally, an error message will be generated if there wasn't at + least one command-line arg present. For example:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('foo', nargs='+') + >>> parser.parse_args('a b'.split()) + Namespace(foo=['a', 'b']) + >>> parser.parse_args(''.split()) + usage: PROG [-h] foo [foo ...] + PROG: error: too few arguments + +If the ``nargs`` keyword argument is not provided, the number of args consumed +is determined by the action_. Generally this means a single command-line arg +will be consumed and a single item (not a list) will be produced. + + +const +^^^^^ + +The ``const`` argument of :meth:`add_argument` is used to hold constant values +that are not read from the command line but are required for the various +ArgumentParser actions. The two most common uses of it are: + +* When :meth:`add_argument` is called with ``action='store_const'`` or + ``action='append_const'``. These actions add the ``const`` value to one of + the attributes of the object returned by :meth:`parse_args`. See the action_ + description for examples. + +* When :meth:`add_argument` is called with option strings (like ``-f`` or + ``--foo``) and ``nargs='?'``. This creates an optional argument that can be + followed by zero or one command-line args. When parsing the command-line, if + the option string is encountered with no command-line arg following it, the + value of ``const`` will be assumed instead. See the nargs_ description for + examples. + +The ``const`` keyword argument defaults to ``None``. + + +default +^^^^^^^ + +All optional arguments and some positional arguments may be omitted at the +command-line. The ``default`` keyword argument of :meth:`add_argument`, whose +value defaults to ``None``, specifies what value should be used if the +command-line arg is not present. For optional arguments, the ``default`` value +is used when the option string was not present at the command line:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', default=42) + >>> parser.parse_args('--foo 2'.split()) + Namespace(foo='2') + >>> parser.parse_args(''.split()) + Namespace(foo=42) + +For positional arguments with nargs_ ``='?'`` or ``'*'``, the ``default`` value +is used when no command-line arg was present:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('foo', nargs='?', default=42) + >>> parser.parse_args('a'.split()) + Namespace(foo='a') + >>> parser.parse_args(''.split()) + Namespace(foo=42) + + +If you don't want to see an attribute when an option was not present at the +command line, you can supply ``default=argparse.SUPPRESS``:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', default=argparse.SUPPRESS) + >>> parser.parse_args([]) + Namespace() + >>> parser.parse_args(['--foo', '1']) + Namespace(foo='1') + + +type +^^^^ + +By default, ArgumentParser objects read command-line args in as simple strings. +However, quite often the command-line string should instead be interpreted as +another type, e.g. :class:`float`, :class:`int` or :class:`file`. The ``type`` +keyword argument of :meth:`add_argument` allows any necessary type-checking and +type-conversions to be performed. Many common builtin types can be used +directly as the value of the ``type`` argument:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('foo', type=int) + >>> parser.add_argument('bar', type=file) + >>> parser.parse_args('2 temp.txt'.split()) + Namespace(bar=, foo=2) + +To ease the use of various types of files, the argparse module provides the +factory FileType which takes the ``mode=`` and ``bufsize=`` arguments of the +``file`` object. For example, ``FileType('w')`` can be used to create a +writable file:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('bar', type=argparse.FileType('w')) + >>> parser.parse_args(['out.txt']) + Namespace(bar=) + +If you need to do some special type-checking or type-conversions, you can +provide your own types by passing to ``type=`` a callable that takes a single +string argument and returns the type-converted value:: + + >>> def perfect_square(string): + ... value = int(string) + ... sqrt = math.sqrt(value) + ... if sqrt != int(sqrt): + ... msg = "%r is not a perfect square" % string + ... raise argparse.ArgumentTypeError(msg) + ... return value + ... + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('foo', type=perfect_square) + >>> parser.parse_args('9'.split()) + Namespace(foo=9) + >>> parser.parse_args('7'.split()) + usage: PROG [-h] foo + PROG: error: argument foo: '7' is not a perfect square + +Note that if your type-checking function is just checking for a particular set +of values, it may be more convenient to use the choices_ keyword argument:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('foo', type=int, choices=xrange(5, 10)) + >>> parser.parse_args('7'.split()) + Namespace(foo=7) + >>> parser.parse_args('11'.split()) + usage: PROG [-h] {5,6,7,8,9} + PROG: error: argument foo: invalid choice: 11 (choose from 5, 6, 7, 8, 9) + +See the choices_ section for more details. + + +choices +^^^^^^^ + +Some command-line args should be selected from a restricted set of values. +ArgumentParser objects can be told about such sets of values by passing a +container object as the ``choices`` keyword argument to :meth:`add_argument`. +When the command-line is parsed with :meth:`parse_args`, arg values will be +checked, and an error message will be displayed if the arg was not one of the +acceptable values:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('foo', choices='abc') + >>> parser.parse_args('c'.split()) + Namespace(foo='c') + >>> parser.parse_args('X'.split()) + usage: PROG [-h] {a,b,c} + PROG: error: argument foo: invalid choice: 'X' (choose from 'a', 'b', 'c') + +Note that inclusion in the ``choices`` container is checked after any type_ +conversions have been performed, so the type of the objects in the ``choices`` +container should match the type_ specified:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('foo', type=complex, choices=[1, 1j]) + >>> parser.parse_args('1j'.split()) + Namespace(foo=1j) + >>> parser.parse_args('-- -4'.split()) + usage: PROG [-h] {1,1j} + PROG: error: argument foo: invalid choice: (-4+0j) (choose from 1, 1j) + +Any object that supports the ``in`` operator can be passed as the ``choices`` +value, so :class:`dict` objects, :class:`set` objects, custom containers, +etc. are all supported. + + +required +^^^^^^^^ + +In general, the argparse module assumes that flags like ``-f`` and ``--bar`` +indicate *optional* arguments, which can always be omitted at the command-line. +To change this behavior, i.e. to make an option *required*, the value ``True`` +should be specified for the ``required=`` keyword argument to +:meth:`add_argument`:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', required=True) + >>> parser.parse_args(['--foo', 'BAR']) + Namespace(foo='BAR') + >>> parser.parse_args([]) + usage: argparse.py [-h] [--foo FOO] + argparse.py: error: option --foo is required + +As the example shows, if an option is marked as ``required``, :meth:`parse_args` +will report an error if that option is not present at the command line. + +**Warning:** Required options are generally considered bad form - normal users +expect *options* to be *optional*. You should avoid the use of required options +whenever possible. + + +help +^^^^ + +A great command-line interface isn't worth anything if your users can't figure +out which option does what. So for the end-users, ``help`` is probably the most +important argument to include in your :meth:`add_argument` calls. The ``help`` +value should be a string containing a brief description of what the argument +specifies. When a user requests help (usually by using ``-h`` or ``--help`` at +the command-line), these ``help`` descriptions will be displayed with each +argument:: + + >>> parser = argparse.ArgumentParser(prog='frobble') + >>> parser.add_argument('--foo', action='store_true', + ... help='foo the bars before frobbling') + >>> parser.add_argument('bar', nargs='+', + ... help='one of the bars to be frobbled') + >>> parser.parse_args('-h'.split()) + usage: frobble [-h] [--foo] bar [bar ...] + + positional arguments: + bar one of the bars to be frobbled + + optional arguments: + -h, --help show this help message and exit + --foo foo the bars before frobbling + +The ``help`` strings can include various format specifiers to avoid repetition +of things like the program name or the argument default_. The available +specifiers include the program name, ``%(prog)s`` and most keyword arguments to +:meth:`add_argument`, e.g. ``%(default)s``, ``%(type)s``, etc.:: + + >>> parser = argparse.ArgumentParser(prog='frobble') + >>> parser.add_argument('bar', nargs='?', type=int, default=42, + ... help='the bar to %(prog)s (default: %(default)s)') + >>> parser.print_help() + usage: frobble [-h] [bar] + + positional arguments: + bar the bar to frobble (default: 42) + + optional arguments: + -h, --help show this help message and exit + + +metavar +^^^^^^^ + +When ArgumentParser objects generate help messages, they need some way to refer +to each expected argument. By default, ArgumentParser objects use the dest_ +value as the "name" of each object. By default, for positional argument +actions, the dest_ value is used directly, and for optional argument actions, +the dest_ value is uppercased. So if we have a single positional argument with +``dest='bar'``, that argument will be referred to as ``bar``. And if we have a +single optional argument ``--foo`` that should be followed by a single +command-line arg, that arg will be referred to as ``FOO``. You can see this +behavior in the example below:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo') + >>> parser.add_argument('bar') + >>> parser.parse_args('X --foo Y'.split()) + Namespace(bar='X', foo='Y') + >>> parser.print_help() + usage: [-h] [--foo FOO] bar + + positional arguments: + bar + + optional arguments: + -h, --help show this help message and exit + --foo FOO + +If you would like to provide a different name for your argument in help +messages, you can supply a value for the ``metavar`` keyword argument to +:meth:`add_argument`:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', metavar='YYY') + >>> parser.add_argument('bar', metavar='XXX') + >>> parser.parse_args('X --foo Y'.split()) + Namespace(bar='X', foo='Y') + >>> parser.print_help() + usage: [-h] [--foo YYY] XXX + + positional arguments: + XXX + + optional arguments: + -h, --help show this help message and exit + --foo YYY + +Note that ``metavar`` only changes the *displayed* name - the name of the +attribute on the :meth:`parse_args` object is still determined by the dest_ +value. + +Different values of ``nargs`` may cause the metavar to be used multiple times. +If you'd like to specify a different display name for each of the arguments, you +can provide a tuple to ``metavar``:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-x', nargs=2) + >>> parser.add_argument('--foo', nargs=2, metavar=('bar', 'baz')) + >>> parser.print_help() + usage: PROG [-h] [-x X X] [--foo bar baz] + + optional arguments: + -h, --help show this help message and exit + -x X X + --foo bar baz + + +dest +^^^^ + +Most ArgumentParser actions add some value as an attribute of the object +returned by :meth:`parse_args`. The name of this attribute is determined by the +``dest`` keyword argument of :meth:`add_argument`. For positional argument +actions, ``dest`` is normally supplied as the first argument to +:meth:`add_argument`:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('bar') + >>> parser.parse_args('XXX'.split()) + Namespace(bar='XXX') + +For optional argument actions, the value of ``dest`` is normally inferred from +the option strings. ArgumentParser objects generate the value of ``dest`` by +taking the first long option string and stripping away the initial ``'--'`` +string. If no long option strings were supplied, ``dest`` will be derived from +the first short option string by stripping the initial ``'-'`` character. Any +internal ``'-'`` characters will be converted to ``'_'`` characters to make sure +the string is a valid attribute name. The examples below illustrate this +behavior:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('-f', '--foo-bar', '--foo') + >>> parser.add_argument('-x', '-y') + >>> parser.parse_args('-f 1 -x 2'.split()) + Namespace(foo_bar='1', x='2') + >>> parser.parse_args('--foo 1 -y 2'.split()) + Namespace(foo_bar='1', x='2') + +If you would like to use a different attribute name from the one automatically +inferred by the ArgumentParser, you can supply it with an explicit ``dest`` +parameter:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', dest='bar') + >>> parser.parse_args('--foo XXX'.split()) + Namespace(bar='XXX') + + +The parse_args() method +----------------------- + +.. method:: parse_args([args], [namespace]) + + Convert the strings to objects and assign them as attributes of the + namespace. Return the populated namespace. + + Previous calls to :meth:`add_argument` determine exactly what objects are + created and how they are assigned. See the documentation for + :meth:`add_argument` for details. + + By default, the arg strings are taken from :data:`sys.argv`, and a new empty + ``Namespace`` object is created for the attributes. + +Option value syntax +^^^^^^^^^^^^^^^^^^^ + +The :meth:`parse_args` method supports several ways of specifying the value of +an option (if it takes one). In the simplest case, the option and its value are +passed as two separate arguments:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-x') + >>> parser.add_argument('--foo') + >>> parser.parse_args('-x X'.split()) + Namespace(foo=None, x='X') + >>> parser.parse_args('--foo FOO'.split()) + Namespace(foo='FOO', x=None) + +For long options (options with names longer than a single character), you may +also pass the option and value as a single command line argument, using ``=`` to +separate them:: + + >>> parser.parse_args('--foo=FOO'.split()) + Namespace(foo='FOO', x=None) + +For short options (options only one character long), you may simply concatenate +the option and its value:: + + >>> parser.parse_args('-xX'.split()) + Namespace(foo=None, x='X') + +You can also combine several short options together, using only a single ``-`` +prefix, as long as only the last option (or none of them) requires a value:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-x', action='store_true') + >>> parser.add_argument('-y', action='store_true') + >>> parser.add_argument('-z') + >>> parser.parse_args('-xyzZ'.split()) + Namespace(x=True, y=True, z='Z') + + +Invalid arguments +^^^^^^^^^^^^^^^^^ + +While parsing the command-line, ``parse_args`` checks for a variety of errors, +including ambiguous options, invalid types, invalid options, wrong number of +positional arguments, etc. When it encounters such an error, it exits and +prints the error along with a usage message:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('--foo', type=int) + >>> parser.add_argument('bar', nargs='?') + + >>> # invalid type + >>> parser.parse_args(['--foo', 'spam']) + usage: PROG [-h] [--foo FOO] [bar] + PROG: error: argument --foo: invalid int value: 'spam' + + >>> # invalid option + >>> parser.parse_args(['--bar']) + usage: PROG [-h] [--foo FOO] [bar] + PROG: error: no such option: --bar + + >>> # wrong number of arguments + >>> parser.parse_args(['spam', 'badger']) + usage: PROG [-h] [--foo FOO] [bar] + PROG: error: extra arguments found: badger + + +Arguments containing ``"-"`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``parse_args`` method attempts to give errors whenever the user has clearly +made a mistake, but some situations are inherently ambiguous. For example, the +command-line arg ``'-1'`` could either be an attempt to specify an option or an +attempt to provide a positional argument. The ``parse_args`` method is cautious +here: positional arguments may only begin with ``'-'`` if they look like +negative numbers and there are no options in the parser that look like negative +numbers:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-x') + >>> parser.add_argument('foo', nargs='?') + + >>> # no negative number options, so -1 is a positional argument + >>> parser.parse_args(['-x', '-1']) + Namespace(foo=None, x='-1') + + >>> # no negative number options, so -1 and -5 are positional arguments + >>> parser.parse_args(['-x', '-1', '-5']) + Namespace(foo='-5', x='-1') + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-1', dest='one') + >>> parser.add_argument('foo', nargs='?') + + >>> # negative number options present, so -1 is an option + >>> parser.parse_args(['-1', 'X']) + Namespace(foo=None, one='X') + + >>> # negative number options present, so -2 is an option + >>> parser.parse_args(['-2']) + usage: PROG [-h] [-1 ONE] [foo] + PROG: error: no such option: -2 + + >>> # negative number options present, so both -1s are options + >>> parser.parse_args(['-1', '-1']) + usage: PROG [-h] [-1 ONE] [foo] + PROG: error: argument -1: expected one argument + +If you have positional arguments that must begin with ``'-'`` and don't look +like negative numbers, you can insert the pseudo-argument ``'--'`` which tells +``parse_args`` that everything after that is a positional argument:: + + >>> parser.parse_args(['--', '-f']) + Namespace(foo='-f', one=None) + + +Argument abbreviations +^^^^^^^^^^^^^^^^^^^^^^ + +The :meth:`parse_args` method allows you to abbreviate long options if the +abbreviation is unambiguous:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('-bacon') + >>> parser.add_argument('-badger') + >>> parser.parse_args('-bac MMM'.split()) + Namespace(bacon='MMM', badger=None) + >>> parser.parse_args('-bad WOOD'.split()) + Namespace(bacon=None, badger='WOOD') + >>> parser.parse_args('-ba BA'.split()) + usage: PROG [-h] [-bacon BACON] [-badger BADGER] + PROG: error: ambiguous option: -ba could match -badger, -bacon + +As you can see above, you will get an error if you pick a prefix that could +refer to more than one option. + + +Beyond ``sys.argv`` +^^^^^^^^^^^^^^^^^^^ + +Sometimes it may be useful to have an ArgumentParser parse args other than those +of :data:`sys.argv`. This can be accomplished by passing a list of strings to +``parse_args``. You may have noticed that the examples in the argparse +documentation have made heavy use of this calling style - it is much easier to +use at the interactive prompt:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument( + ... 'integers', metavar='int', type=int, choices=xrange(10), + ... nargs='+', help='an integer in the range 0..9') + >>> parser.add_argument( + ... '--sum', dest='accumulate', action='store_const', const=sum, + ... default=max, help='sum the integers (default: find the max)') + >>> parser.parse_args(['1', '2', '3', '4']) + Namespace(accumulate=, integers=[1, 2, 3, 4]) + >>> parser.parse_args('1 2 3 4 --sum'.split()) + Namespace(accumulate=, integers=[1, 2, 3, 4]) + + +Custom namespaces +^^^^^^^^^^^^^^^^^ + +It may also be useful to have an ArgumentParser assign attributes to an already +existing object, rather than the newly-created Namespace object that is normally +used. This can be achieved by specifying the ``namespace=`` keyword argument:: + + >>> class C(object): + ... pass + ... + >>> c = C() + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo') + >>> parser.parse_args(args=['--foo', 'BAR'], namespace=c) + >>> c.foo + 'BAR' + + +Other utilities +--------------- + +Sub-commands +^^^^^^^^^^^^ + +.. method:: add_subparsers() + + A lot of programs split up their functionality into a number of sub-commands, + for example, the ``svn`` program can invoke sub-commands like ``svn + checkout``, ``svn update``, ``svn commit``, etc. Splitting up functionality + this way can be a particularly good idea when a program performs several + different functions which require different kinds of command-line arguments. + ArgumentParser objects support the creation of such sub-commands with the + :meth:`add_subparsers` method. The :meth:`add_subparsers` method is normally + called with no arguments and returns an special action object. This object + has a single method, ``add_parser``, which takes a command name and any + ArgumentParser constructor arguments, and returns an ArgumentParser object + that can be modified as usual. + + Some example usage:: + + >>> # create the top-level parser + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> parser.add_argument('--foo', action='store_true', help='foo help') + >>> subparsers = parser.add_subparsers(help='sub-command help') + >>> + >>> # create the parser for the "a" command + >>> parser_a = subparsers.add_parser('a', help='a help') + >>> parser_a.add_argument('bar', type=int, help='bar help') + >>> + >>> # create the parser for the "b" command + >>> parser_b = subparsers.add_parser('b', help='b help') + >>> parser_b.add_argument('--baz', choices='XYZ', help='baz help') + >>> + >>> # parse some arg lists + >>> parser.parse_args(['a', '12']) + Namespace(bar=12, foo=False) + >>> parser.parse_args(['--foo', 'b', '--baz', 'Z']) + Namespace(baz='Z', foo=True) + + Note that the object returned by :meth:`parse_args` will only contain + attributes for the main parser and the subparser that was selected by the + command line (and not any other subparsers). So in the example above, when + the ``"a"`` command is specified, only the ``foo`` and ``bar`` attributes are + present, and when the ``"b"`` command is specified, only the ``foo`` and + ``baz`` attributes are present. + + Similarly, when a help message is requested from a subparser, only the help + for that particular parser will be printed. The help message will not + include parent parser or sibling parser messages. (You can however supply a + help message for each subparser command by suppling the ``help=`` argument to + ``add_parser`` as above.) + + :: + + >>> parser.parse_args(['--help']) + usage: PROG [-h] [--foo] {a,b} ... + + positional arguments: + {a,b} sub-command help + a a help + b b help + + optional arguments: + -h, --help show this help message and exit + --foo foo help + + >>> parser.parse_args(['a', '--help']) + usage: PROG a [-h] bar + + positional arguments: + bar bar help + + optional arguments: + -h, --help show this help message and exit + + >>> parser.parse_args(['b', '--help']) + usage: PROG b [-h] [--baz {X,Y,Z}] + + optional arguments: + -h, --help show this help message and exit + --baz {X,Y,Z} baz help + + The :meth:`add_subparsers` method also supports ``title`` and ``description`` + keyword arguments. When either is present, the subparser's commands will + appear in their own group in the help output. For example:: + + >>> parser = argparse.ArgumentParser() + >>> subparsers = parser.add_subparsers(title='subcommands', + ... description='valid subcommands', + ... help='additional help') + >>> subparsers.add_parser('foo') + >>> subparsers.add_parser('bar') + >>> parser.parse_args(['-h']) + usage: [-h] {foo,bar} ... + + optional arguments: + -h, --help show this help message and exit + + subcommands: + valid subcommands + + {foo,bar} additional help + + + One particularly effective way of handling sub-commands is to combine the use + of the :meth:`add_subparsers` method with calls to :meth:`set_defaults` so + that each subparser knows which Python function it should execute. For + example:: + + >>> # sub-command functions + >>> def foo(args): + ... print args.x * args.y + ... + >>> def bar(args): + ... print '((%s))' % args.z + ... + >>> # create the top-level parser + >>> parser = argparse.ArgumentParser() + >>> subparsers = parser.add_subparsers() + >>> + >>> # create the parser for the "foo" command + >>> parser_foo = subparsers.add_parser('foo') + >>> parser_foo.add_argument('-x', type=int, default=1) + >>> parser_foo.add_argument('y', type=float) + >>> parser_foo.set_defaults(func=foo) + >>> + >>> # create the parser for the "bar" command + >>> parser_bar = subparsers.add_parser('bar') + >>> parser_bar.add_argument('z') + >>> parser_bar.set_defaults(func=bar) + >>> + >>> # parse the args and call whatever function was selected + >>> args = parser.parse_args('foo 1 -x 2'.split()) + >>> args.func(args) + 2.0 + >>> + >>> # parse the args and call whatever function was selected + >>> args = parser.parse_args('bar XYZYX'.split()) + >>> args.func(args) + ((XYZYX)) + + This way, you can let :meth:`parse_args` do all the work for you, and then + just call the appropriate function after the argument parsing is complete. + Associating functions with actions like this is typically the easiest way to + handle the different actions for each of your subparsers. However, if you + find it necessary to check the name of the subparser that was invoked, you + can always provide a ``dest`` keyword argument to the :meth:`add_subparsers` + call:: + + >>> parser = argparse.ArgumentParser() + >>> subparsers = parser.add_subparsers(dest='subparser_name') + >>> subparser1 = subparsers.add_parser('1') + >>> subparser1.add_argument('-x') + >>> subparser2 = subparsers.add_parser('2') + >>> subparser2.add_argument('y') + >>> parser.parse_args(['2', 'frobble']) + Namespace(subparser_name='2', y='frobble') + + +FileType objects +^^^^^^^^^^^^^^^^ + +.. class:: FileType(mode='r', bufsize=None) + + The :class:`FileType` factory creates objects that can be passed to the type + argument of :meth:`add_argument`. Arguments that have :class:`FileType` + objects as their type will open command-line args as files with the requested + modes and buffer sizes: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--output', type=argparse.FileType('wb', 0)) + >>> parser.parse_args(['--output', 'out']) + Namespace(output=) + + FileType objects understand the pseudo-argument ``'-'`` and automatically + convert this into ``sys.stdin`` for readable :class:`FileType` objects and + ``sys.stdout`` for writable :class:`FileType` objects: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('infile', type=argparse.FileType('r')) + >>> parser.parse_args(['-']) + Namespace(infile=', mode 'r' at 0x...>) + + +Argument groups +^^^^^^^^^^^^^^^ + +.. method:: add_argument_group([title], [description]) + + By default, ArgumentParser objects group command-line arguments into + "positional arguments" and "optional arguments" when displaying help + messages. When there is a better conceptual grouping of arguments than this + default one, appropriate groups can be created using the + :meth:`add_argument_group` method:: + + >>> parser = argparse.ArgumentParser(prog='PROG', add_help=False) + >>> group = parser.add_argument_group('group') + >>> group.add_argument('--foo', help='foo help') + >>> group.add_argument('bar', help='bar help') + >>> parser.print_help() + usage: PROG [--foo FOO] bar + + group: + bar bar help + --foo FOO foo help + + The :meth:`add_argument_group` method returns an argument group object which + has an :meth:`add_argument` method just like a regular ArgumentParser + objects. When an argument is added to the group, the parser treats it just + like a normal argument, but displays the argument in a separate group for + help messages. The :meth:`add_argument_group` method accepts ``title`` and + ``description`` arguments which can be used to customize this display:: + + >>> parser = argparse.ArgumentParser(prog='PROG', add_help=False) + >>> group1 = parser.add_argument_group('group1', 'group1 description') + >>> group1.add_argument('foo', help='foo help') + >>> group2 = parser.add_argument_group('group2', 'group2 description') + >>> group2.add_argument('--bar', help='bar help') + >>> parser.print_help() + usage: PROG [--bar BAR] foo + + group1: + group1 description + + foo foo help + + group2: + group2 description + + --bar BAR bar help + + Note that any arguments not in your user defined groups will end up back in + the usual "positional arguments" and "optional arguments" sections. + + +Mutual exclusion +^^^^^^^^^^^^^^^^ + +.. method:: add_mutually_exclusive_group([required=False]) + + Sometimes, you need to make sure that only one of a couple different options + is specified on the command line. You can create groups of such mutually + exclusive arguments using the :meth:`add_mutually_exclusive_group` method. + When :func:`parse_args` is called, argparse will make sure that only one of + the arguments in the mutually exclusive group was present on the command + line:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> group = parser.add_mutually_exclusive_group() + >>> group.add_argument('--foo', action='store_true') + >>> group.add_argument('--bar', action='store_false') + >>> parser.parse_args(['--foo']) + Namespace(bar=True, foo=True) + >>> parser.parse_args(['--bar']) + Namespace(bar=False, foo=False) + >>> parser.parse_args(['--foo', '--bar']) + usage: PROG [-h] [--foo | --bar] + PROG: error: argument --bar: not allowed with argument --foo + + The :meth:`add_mutually_exclusive_group` method also accepts a ``required`` + argument, to indicate that at least one of the mutually exclusive arguments + is required:: + + >>> parser = argparse.ArgumentParser(prog='PROG') + >>> group = parser.add_mutually_exclusive_group(required=True) + >>> group.add_argument('--foo', action='store_true') + >>> group.add_argument('--bar', action='store_false') + >>> parser.parse_args([]) + usage: PROG [-h] (--foo | --bar) + PROG: error: one of the arguments --foo --bar is required + + Note that currently mutually exclusive argument groups do not support the + ``title`` and ``description`` arguments of :meth:`add_argument_group`. This + may change in the future however, so you are *strongly* recommended to + specify ``required`` as a keyword argument if you use it. + + +Parser defaults +^^^^^^^^^^^^^^^ + +.. method:: set_defaults(**kwargs) + + Most of the time, the attributes of the object returned by :meth:`parse_args` + will be fully determined by inspecting the command-line args and the argument + actions described in your :meth:`add_argument` calls. However, sometimes it + may be useful to add some additional attributes that are determined without + any inspection of the command-line. The :meth:`set_defaults` method allows + you to do this:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('foo', type=int) + >>> parser.set_defaults(bar=42, baz='badger') + >>> parser.parse_args(['736']) + Namespace(bar=42, baz='badger', foo=736) + + Note that parser-level defaults always override argument-level defaults. So + if you set a parser-level default for a name that matches an argument, the + old argument default will no longer be used:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', default='bar') + >>> parser.set_defaults(foo='spam') + >>> parser.parse_args([]) + Namespace(foo='spam') + + Parser-level defaults can be particularly useful when you're working with + multiple parsers. See the :meth:`add_subparsers` method for an example of + this type. + +.. method:: get_default(dest) + + Get the default value for a namespace attribute, as set by either + :meth:`add_argument` or by :meth:`set_defaults`:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', default='badger') + >>> parser.get_default('foo') + 'badger' + + +Printing help +^^^^^^^^^^^^^ + +In most typical applications, :meth:`parse_args` will take care of formatting +and printing any usage or error messages. However, should you want to format or +print these on your own, several methods are available: + +.. method:: print_usage([file]): + + Print a brief description of how the :class:`ArgumentParser` should be + invoked on the command line. If ``file`` is not present, ``sys.stderr`` is + assumed. + +.. method:: print_help([file]): + + Print a help message, including the program usage and information about the + arguments registered with the :class:`ArgumentParser`. If ``file`` is not + present, ``sys.stderr`` is assumed. + +There are also variants of these methods that simply return a string instead of +printing it: + +.. method:: format_usage(): + + Return a string containing a brief description of how the + :class:`ArgumentParser` should be invoked on the command line. + +.. method:: format_help(): + + Return a string containing a help message, including the program usage and + information about the arguments registered with the :class:`ArgumentParser`. + + + +Partial parsing +^^^^^^^^^^^^^^^ + +.. method:: parse_known_args([args], [namespace]) + +Sometimes a script may only parse a few of the command line arguments, passing +the remaining arguments on to another script or program. In these cases, the +:meth:`parse_known_args` method can be useful. It works much like +:meth:`parse_args` except that it does not produce an error when extra arguments +are present. Instead, it returns a two item tuple containing the populated +namespace and the list of remaining argument strings. + +:: + + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--foo', action='store_true') + >>> parser.add_argument('bar') + >>> parser.parse_known_args(['--foo', '--badger', 'BAR', 'spam']) + (Namespace(bar='BAR', foo=True), ['--badger', 'spam']) + + +Customizing file parsing +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. method:: convert_arg_line_to_args(arg_line) + + Arguments that are read from a file (see the ``fromfile_prefix_chars`` + keyword argument to the :class:`ArgumentParser` constructor) are read one + argument per line. If you need fancier parsing, then you can subclass the + :class:`ArgumentParser` and override the :meth:`convert_arg_line_to_args` + method. + + This method takes a single argument ``arg_line`` which is a string read from + the argument file. It returns a list of arguments parsed from this string. + The method is called once per line read from the argument file, in order. + + A useful override of this method is one that treats each space-separated word + as an argument:: + + def convert_arg_line_to_args(self, arg_line): + for arg in arg_line.split(): + if not arg.strip(): + continue + yield arg + + +Upgrading optparse code +----------------------- + +Originally, the argparse module had attempted to maintain compatibility with +optparse. However, optparse was difficult to extend transparently, particularly +with the changes required to support the new ``nargs=`` specifiers and better +usage messges. When most everything in optparse had either been copy-pasted +over or monkey-patched, it no longer seemed practical to try to maintain the +backwards compatibility. + +A partial upgrade path from optparse to argparse: + +* Replace all ``add_option()`` calls with :meth:`add_argument` calls. + +* Replace ``options, args = parser.parse_args()`` with ``args = + parser.parse_args()`` and add additional :meth:`add_argument` calls for the + positional arguments. + +* Replace callback actions and the ``callback_*`` keyword arguments with + ``type`` or ``action`` arguments. + +* Replace string names for ``type`` keyword arguments with the corresponding + type objects (e.g. int, float, complex, etc). + +* Replace ``Values`` with ``Namespace`` and ``OptionError/OptionValueError`` + with ``ArgumentError``. + +* Replace strings with implicit arguments such as ``%default`` or ``%prog`` with + the standard python syntax to use dictionaries to format strings, that is, + ``%(default)s`` and ``%(prog)s``. diff --git a/Lib/argparse.py b/Lib/argparse.py index 717b660baae..cde1fe95b91 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1,2353 +1,2351 @@ -# -*- coding: utf-8 -*- - -# Copyright © 2006-2009 Steven J. Bethard . -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Command-line parsing library - -This module is an optparse-inspired command-line parsing library that: - - - handles both optional and positional arguments - - produces highly informative usage messages - - supports parsers that dispatch to sub-parsers - -The following is a simple usage example that sums integers from the -command-line and writes the result to a file:: - - parser = argparse.ArgumentParser( - description='sum the integers at the command line') - parser.add_argument( - 'integers', metavar='int', nargs='+', type=int, - help='an integer to be summed') - parser.add_argument( - '--log', default=sys.stdout, type=argparse.FileType('w'), - help='the file where the sum should be written') - args = parser.parse_args() - args.log.write('%s' % sum(args.integers)) - args.log.close() - -The module contains the following public classes: - - - ArgumentParser -- The main entry point for command-line parsing. As the - example above shows, the add_argument() method is used to populate - the parser with actions for optional and positional arguments. Then - the parse_args() method is invoked to convert the args at the - command-line into an object with attributes. - - - ArgumentError -- The exception raised by ArgumentParser objects when - there are errors with the parser's actions. Errors raised while - parsing the command-line are caught by ArgumentParser and emitted - as command-line messages. - - - FileType -- A factory for defining types of files to be created. As the - example above shows, instances of FileType are typically passed as - the type= argument of add_argument() calls. - - - Action -- The base class for parser actions. Typically actions are - selected by passing strings like 'store_true' or 'append_const' to - the action= argument of add_argument(). However, for greater - customization of ArgumentParser actions, subclasses of Action may - be defined and passed as the action= argument. - - - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, - ArgumentDefaultsHelpFormatter -- Formatter classes which - may be passed as the formatter_class= argument to the - ArgumentParser constructor. HelpFormatter is the default, - RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser - not to change the formatting for help text, and - ArgumentDefaultsHelpFormatter adds information about argument defaults - to the help. - -All other classes in this module are considered implementation details. -(Also note that HelpFormatter and RawDescriptionHelpFormatter are only -considered public as object names -- the API of the formatter objects is -still considered an implementation detail.) -""" - -__version__ = '1.1' -__all__ = [ - 'ArgumentParser', - 'ArgumentError', - 'Namespace', - 'Action', - 'FileType', - 'HelpFormatter', - 'RawDescriptionHelpFormatter', - 'RawTextHelpFormatter', - 'ArgumentDefaultsHelpFormatter', -] - - -import copy as _copy -import os as _os -import re as _re -import sys as _sys -import textwrap as _textwrap - -from gettext import gettext as _ - -try: - _set = set -except NameError: - from sets import Set as _set - -try: - _basestring = basestring -except NameError: - _basestring = str - -try: - _sorted = sorted -except NameError: - - def _sorted(iterable, reverse=False): - result = list(iterable) - result.sort() - if reverse: - result.reverse() - return result - - -def _callable(obj): - return hasattr(obj, '__call__') or hasattr(obj, '__bases__') - -# silence Python 2.6 buggy warnings about Exception.message -if _sys.version_info[:2] == (2, 6): - import warnings - warnings.filterwarnings( - action='ignore', - message='BaseException.message has been deprecated as of Python 2.6', - category=DeprecationWarning, - module='argparse') - - -SUPPRESS = '==SUPPRESS==' - -OPTIONAL = '?' -ZERO_OR_MORE = '*' -ONE_OR_MORE = '+' -PARSER = 'A...' -REMAINDER = '...' - -# ============================= -# Utility functions and classes -# ============================= - -class _AttributeHolder(object): - """Abstract base class that provides __repr__. - - The __repr__ method returns a string in the format:: - ClassName(attr=name, attr=name, ...) - The attributes are determined either by a class-level attribute, - '_kwarg_names', or by inspecting the instance __dict__. - """ - - def __repr__(self): - type_name = type(self).__name__ - arg_strings = [] - for arg in self._get_args(): - arg_strings.append(repr(arg)) - for name, value in self._get_kwargs(): - arg_strings.append('%s=%r' % (name, value)) - return '%s(%s)' % (type_name, ', '.join(arg_strings)) - - def _get_kwargs(self): - return _sorted(self.__dict__.items()) - - def _get_args(self): - return [] - - -def _ensure_value(namespace, name, value): - if getattr(namespace, name, None) is None: - setattr(namespace, name, value) - return getattr(namespace, name) - - -# =============== -# Formatting Help -# =============== - -class HelpFormatter(object): - """Formatter for generating usage messages and argument help strings. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def __init__(self, - prog, - indent_increment=2, - max_help_position=24, - width=None): - - # default setting for width - if width is None: - try: - width = int(_os.environ['COLUMNS']) - except (KeyError, ValueError): - width = 80 - width -= 2 - - self._prog = prog - self._indent_increment = indent_increment - self._max_help_position = max_help_position - self._width = width - - self._current_indent = 0 - self._level = 0 - self._action_max_length = 0 - - self._root_section = self._Section(self, None) - self._current_section = self._root_section - - self._whitespace_matcher = _re.compile(r'\s+') - self._long_break_matcher = _re.compile(r'\n\n\n+') - - # =============================== - # Section and indentation methods - # =============================== - def _indent(self): - self._current_indent += self._indent_increment - self._level += 1 - - def _dedent(self): - self._current_indent -= self._indent_increment - assert self._current_indent >= 0, 'Indent decreased below 0.' - self._level -= 1 - - class _Section(object): - - def __init__(self, formatter, parent, heading=None): - self.formatter = formatter - self.parent = parent - self.heading = heading - self.items = [] - - def format_help(self): - # format the indented section - if self.parent is not None: - self.formatter._indent() - join = self.formatter._join_parts - for func, args in self.items: - func(*args) - item_help = join([func(*args) for func, args in self.items]) - if self.parent is not None: - self.formatter._dedent() - - # return nothing if the section was empty - if not item_help: - return '' - - # add the heading if the section was non-empty - if self.heading is not SUPPRESS and self.heading is not None: - current_indent = self.formatter._current_indent - heading = '%*s%s:\n' % (current_indent, '', self.heading) - else: - heading = '' - - # join the section-initial newline, the heading and the help - return join(['\n', heading, item_help, '\n']) - - def _add_item(self, func, args): - self._current_section.items.append((func, args)) - - # ======================== - # Message building methods - # ======================== - def start_section(self, heading): - self._indent() - section = self._Section(self, self._current_section, heading) - self._add_item(section.format_help, []) - self._current_section = section - - def end_section(self): - self._current_section = self._current_section.parent - self._dedent() - - def add_text(self, text): - if text is not SUPPRESS and text is not None: - self._add_item(self._format_text, [text]) - - def add_usage(self, usage, actions, groups, prefix=None): - if usage is not SUPPRESS: - args = usage, actions, groups, prefix - self._add_item(self._format_usage, args) - - def add_argument(self, action): - if action.help is not SUPPRESS: - - # find all invocations - get_invocation = self._format_action_invocation - invocations = [get_invocation(action)] - for subaction in self._iter_indented_subactions(action): - invocations.append(get_invocation(subaction)) - - # update the maximum item length - invocation_length = max([len(s) for s in invocations]) - action_length = invocation_length + self._current_indent - self._action_max_length = max(self._action_max_length, - action_length) - - # add the item to the list - self._add_item(self._format_action, [action]) - - def add_arguments(self, actions): - for action in actions: - self.add_argument(action) - - # ======================= - # Help-formatting methods - # ======================= - def format_help(self): - help = self._root_section.format_help() - if help: - help = self._long_break_matcher.sub('\n\n', help) - help = help.strip('\n') + '\n' - return help - - def _join_parts(self, part_strings): - return ''.join([part - for part in part_strings - if part and part is not SUPPRESS]) - - def _format_usage(self, usage, actions, groups, prefix): - if prefix is None: - prefix = _('usage: ') - - # if usage is specified, use that - if usage is not None: - usage = usage % dict(prog=self._prog) - - # if no optionals or positionals are available, usage is just prog - elif usage is None and not actions: - usage = '%(prog)s' % dict(prog=self._prog) - - # if optionals and positionals are available, calculate usage - elif usage is None: - prog = '%(prog)s' % dict(prog=self._prog) - - # split optionals from positionals - optionals = [] - positionals = [] - for action in actions: - if action.option_strings: - optionals.append(action) - else: - positionals.append(action) - - # build full usage string - format = self._format_actions_usage - action_usage = format(optionals + positionals, groups) - usage = ' '.join([s for s in [prog, action_usage] if s]) - - # wrap the usage parts if it's too long - text_width = self._width - self._current_indent - if len(prefix) + len(usage) > text_width: - - # break usage into wrappable parts - part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' - opt_usage = format(optionals, groups) - pos_usage = format(positionals, groups) - opt_parts = _re.findall(part_regexp, opt_usage) - pos_parts = _re.findall(part_regexp, pos_usage) - assert ' '.join(opt_parts) == opt_usage - assert ' '.join(pos_parts) == pos_usage - - # helper for wrapping lines - def get_lines(parts, indent, prefix=None): - lines = [] - line = [] - if prefix is not None: - line_len = len(prefix) - 1 - else: - line_len = len(indent) - 1 - for part in parts: - if line_len + 1 + len(part) > text_width: - lines.append(indent + ' '.join(line)) - line = [] - line_len = len(indent) - 1 - line.append(part) - line_len += len(part) + 1 - if line: - lines.append(indent + ' '.join(line)) - if prefix is not None: - lines[0] = lines[0][len(indent):] - return lines - - # if prog is short, follow it with optionals or positionals - if len(prefix) + len(prog) <= 0.75 * text_width: - indent = ' ' * (len(prefix) + len(prog) + 1) - if opt_parts: - lines = get_lines([prog] + opt_parts, indent, prefix) - lines.extend(get_lines(pos_parts, indent)) - elif pos_parts: - lines = get_lines([prog] + pos_parts, indent, prefix) - else: - lines = [prog] - - # if prog is long, put it on its own line - else: - indent = ' ' * len(prefix) - parts = opt_parts + pos_parts - lines = get_lines(parts, indent) - if len(lines) > 1: - lines = [] - lines.extend(get_lines(opt_parts, indent)) - lines.extend(get_lines(pos_parts, indent)) - lines = [prog] + lines - - # join lines into usage - usage = '\n'.join(lines) - - # prefix with 'usage:' - return '%s%s\n\n' % (prefix, usage) - - def _format_actions_usage(self, actions, groups): - # find group indices and identify actions in groups - group_actions = _set() - inserts = {} - for group in groups: - try: - start = actions.index(group._group_actions[0]) - except ValueError: - continue - else: - end = start + len(group._group_actions) - if actions[start:end] == group._group_actions: - for action in group._group_actions: - group_actions.add(action) - if not group.required: - inserts[start] = '[' - inserts[end] = ']' - else: - inserts[start] = '(' - inserts[end] = ')' - for i in range(start + 1, end): - inserts[i] = '|' - - # collect all actions format strings - parts = [] - for i, action in enumerate(actions): - - # suppressed arguments are marked with None - # remove | separators for suppressed arguments - if action.help is SUPPRESS: - parts.append(None) - if inserts.get(i) == '|': - inserts.pop(i) - elif inserts.get(i + 1) == '|': - inserts.pop(i + 1) - - # produce all arg strings - elif not action.option_strings: - part = self._format_args(action, action.dest) - - # if it's in a group, strip the outer [] - if action in group_actions: - if part[0] == '[' and part[-1] == ']': - part = part[1:-1] - - # add the action string to the list - parts.append(part) - - # produce the first way to invoke the option in brackets - else: - option_string = action.option_strings[0] - - # if the Optional doesn't take a value, format is: - # -s or --long - if action.nargs == 0: - part = '%s' % option_string - - # if the Optional takes a value, format is: - # -s ARGS or --long ARGS - else: - default = action.dest.upper() - args_string = self._format_args(action, default) - part = '%s %s' % (option_string, args_string) - - # make it look optional if it's not required or in a group - if not action.required and action not in group_actions: - part = '[%s]' % part - - # add the action string to the list - parts.append(part) - - # insert things at the necessary indices - for i in _sorted(inserts, reverse=True): - parts[i:i] = [inserts[i]] - - # join all the action items with spaces - text = ' '.join([item for item in parts if item is not None]) - - # clean up separators for mutually exclusive groups - open = r'[\[(]' - close = r'[\])]' - text = _re.sub(r'(%s) ' % open, r'\1', text) - text = _re.sub(r' (%s)' % close, r'\1', text) - text = _re.sub(r'%s *%s' % (open, close), r'', text) - text = _re.sub(r'\(([^|]*)\)', r'\1', text) - text = text.strip() - - # return the text - return text - - def _format_text(self, text): - if '%(prog)' in text: - text = text % dict(prog=self._prog) - text_width = self._width - self._current_indent - indent = ' ' * self._current_indent - return self._fill_text(text, text_width, indent) + '\n\n' - - def _format_action(self, action): - # determine the required width and the entry label - help_position = min(self._action_max_length + 2, - self._max_help_position) - help_width = self._width - help_position - action_width = help_position - self._current_indent - 2 - action_header = self._format_action_invocation(action) - - # ho nelp; start on same line and add a final newline - if not action.help: - tup = self._current_indent, '', action_header - action_header = '%*s%s\n' % tup - - # short action name; start on the same line and pad two spaces - elif len(action_header) <= action_width: - tup = self._current_indent, '', action_width, action_header - action_header = '%*s%-*s ' % tup - indent_first = 0 - - # long action name; start on the next line - else: - tup = self._current_indent, '', action_header - action_header = '%*s%s\n' % tup - indent_first = help_position - - # collect the pieces of the action help - parts = [action_header] - - # if there was help for the action, add lines of help text - if action.help: - help_text = self._expand_help(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)) - - # or add a newline if the description doesn't end with one - elif not action_header.endswith('\n'): - parts.append('\n') - - # if there are any sub-actions, add their help as well - for subaction in self._iter_indented_subactions(action): - parts.append(self._format_action(subaction)) - - # return a single string - return self._join_parts(parts) - - def _format_action_invocation(self, action): - if not action.option_strings: - metavar, = self._metavar_formatter(action, action.dest)(1) - return metavar - - else: - parts = [] - - # if the Optional doesn't take a value, format is: - # -s, --long - if action.nargs == 0: - parts.extend(action.option_strings) - - # if the Optional takes a value, format is: - # -s ARGS, --long ARGS - else: - default = action.dest.upper() - args_string = self._format_args(action, default) - for option_string in action.option_strings: - parts.append('%s %s' % (option_string, args_string)) - - return ', '.join(parts) - - def _metavar_formatter(self, action, default_metavar): - if action.metavar is not None: - result = action.metavar - elif action.choices is not None: - choice_strs = [str(choice) for choice in action.choices] - result = '{%s}' % ','.join(choice_strs) - else: - result = default_metavar - - def format(tuple_size): - if isinstance(result, tuple): - return result - else: - return (result, ) * tuple_size - return format - - def _format_args(self, action, default_metavar): - get_metavar = self._metavar_formatter(action, default_metavar) - if action.nargs is None: - result = '%s' % get_metavar(1) - elif action.nargs == OPTIONAL: - result = '[%s]' % get_metavar(1) - elif action.nargs == ZERO_OR_MORE: - result = '[%s [%s ...]]' % get_metavar(2) - elif action.nargs == ONE_OR_MORE: - result = '%s [%s ...]' % get_metavar(2) - elif action.nargs == REMAINDER: - result = '...' - elif action.nargs == PARSER: - result = '%s ...' % get_metavar(1) - else: - formats = ['%s' for _ in range(action.nargs)] - result = ' '.join(formats) % get_metavar(action.nargs) - return result - - def _expand_help(self, action): - params = dict(vars(action), prog=self._prog) - for name in list(params): - if params[name] is SUPPRESS: - del params[name] - for name in list(params): - if hasattr(params[name], '__name__'): - params[name] = params[name].__name__ - if params.get('choices') is not None: - choices_str = ', '.join([str(c) for c in params['choices']]) - params['choices'] = choices_str - return self._get_help_string(action) % params - - def _iter_indented_subactions(self, action): - try: - get_subactions = action._get_subactions - except AttributeError: - pass - else: - self._indent() - for subaction in get_subactions(): - yield subaction - self._dedent() - - def _split_lines(self, text, width): - text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.wrap(text, width) - - 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) - - def _get_help_string(self, action): - return action.help - - -class RawDescriptionHelpFormatter(HelpFormatter): - """Help message formatter which retains any formatting in descriptions. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def _fill_text(self, text, width, indent): - return ''.join([indent + line for line in text.splitlines(True)]) - - -class RawTextHelpFormatter(RawDescriptionHelpFormatter): - """Help message formatter which retains formatting of all help text. - - 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 text.splitlines() - - -class ArgumentDefaultsHelpFormatter(HelpFormatter): - """Help message formatter which adds default values to argument help. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def _get_help_string(self, action): - help = action.help - if '%(default)' not in action.help: - if action.default is not SUPPRESS: - defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] - if action.option_strings or action.nargs in defaulting_nargs: - help += ' (default: %(default)s)' - return help - - -# ===================== -# Options and Arguments -# ===================== - -def _get_action_name(argument): - if argument is None: - return None - elif argument.option_strings: - return '/'.join(argument.option_strings) - elif argument.metavar not in (None, SUPPRESS): - return argument.metavar - elif argument.dest not in (None, SUPPRESS): - return argument.dest - else: - return None - - -class ArgumentError(Exception): - """An error from creating or using an argument (optional or positional). - - The string value of this exception is the message, augmented with - information about the argument that caused it. - """ - - def __init__(self, argument, message): - self.argument_name = _get_action_name(argument) - self.message = message - - def __str__(self): - if self.argument_name is None: - format = '%(message)s' - else: - format = 'argument %(argument_name)s: %(message)s' - return format % dict(message=self.message, - argument_name=self.argument_name) - - -class ArgumentTypeError(Exception): - """An error from trying to convert a command line string to a type.""" - pass - - -# ============== -# Action classes -# ============== - -class Action(_AttributeHolder): - """Information about how to convert command line strings to Python objects. - - Action objects are used by an ArgumentParser to represent the information - needed to parse a single argument from one or more strings from the - command line. The keyword arguments to the Action constructor are also - all attributes of Action instances. - - Keyword Arguments: - - - option_strings -- A list of command-line option strings which - should be associated with this action. - - - dest -- The name of the attribute to hold the created object(s) - - - nargs -- The number of command-line arguments that should be - consumed. By default, one argument will be consumed and a single - value will be produced. Other values include: - - N (an integer) consumes N arguments (and produces a list) - - '?' consumes zero or one arguments - - '*' consumes zero or more arguments (and produces a list) - - '+' consumes one or more arguments (and produces a list) - Note that the difference between the default and nargs=1 is that - with the default, a single value will be produced, while with - nargs=1, a list containing a single value will be produced. - - - const -- The value to be produced if the option is specified and the - option uses an action that takes no values. - - - default -- The value to be produced if the option is not specified. - - - type -- The type which the command-line arguments should be converted - to, should be one of 'string', 'int', 'float', 'complex' or a - callable object that accepts a single string argument. If None, - 'string' is assumed. - - - choices -- A container of values that should be allowed. If not None, - after a command-line argument has been converted to the appropriate - type, an exception will be raised if it is not a member of this - collection. - - - required -- True if the action must always be specified at the - command line. This is only meaningful for optional command-line - arguments. - - - help -- The help string describing the argument. - - - metavar -- The name to be used for the option's argument with the - help string. If None, the 'dest' value will be used as the name. - """ - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - self.option_strings = option_strings - self.dest = dest - self.nargs = nargs - self.const = const - self.default = default - self.type = type - self.choices = choices - self.required = required - self.help = help - self.metavar = metavar - - def _get_kwargs(self): - names = [ - 'option_strings', - 'dest', - 'nargs', - 'const', - 'default', - 'type', - 'choices', - 'help', - 'metavar', - ] - return [(name, getattr(self, name)) for name in names] - - def __call__(self, parser, namespace, values, option_string=None): - raise NotImplementedError(_('.__call__() not defined')) - - -class _StoreAction(Action): - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - if nargs == 0: - raise ValueError('nargs for store actions must be > 0; if you ' - 'have nothing to store, actions such as store ' - 'true or store const may be more appropriate') - if const is not None and nargs != OPTIONAL: - raise ValueError('nargs must be %r to supply const' % OPTIONAL) - super(_StoreAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, values) - - -class _StoreConstAction(Action): - - def __init__(self, - option_strings, - dest, - const, - default=None, - required=False, - help=None, - metavar=None): - super(_StoreConstAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=const, - default=default, - required=required, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, self.const) - - -class _StoreTrueAction(_StoreConstAction): - - def __init__(self, - option_strings, - dest, - default=False, - required=False, - help=None): - super(_StoreTrueAction, self).__init__( - option_strings=option_strings, - dest=dest, - const=True, - default=default, - required=required, - help=help) - - -class _StoreFalseAction(_StoreConstAction): - - def __init__(self, - option_strings, - dest, - default=True, - required=False, - help=None): - super(_StoreFalseAction, self).__init__( - option_strings=option_strings, - dest=dest, - const=False, - default=default, - required=required, - help=help) - - -class _AppendAction(Action): - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - if nargs == 0: - raise ValueError('nargs for append actions must be > 0; if arg ' - 'strings are not supplying the value to append, ' - 'the append const action may be more appropriate') - if const is not None and nargs != OPTIONAL: - raise ValueError('nargs must be %r to supply const' % OPTIONAL) - super(_AppendAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - items = _copy.copy(_ensure_value(namespace, self.dest, [])) - items.append(values) - setattr(namespace, self.dest, items) - - -class _AppendConstAction(Action): - - def __init__(self, - option_strings, - dest, - const, - default=None, - required=False, - help=None, - metavar=None): - super(_AppendConstAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=const, - default=default, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - items = _copy.copy(_ensure_value(namespace, self.dest, [])) - items.append(self.const) - setattr(namespace, self.dest, items) - - -class _CountAction(Action): - - def __init__(self, - option_strings, - dest, - default=None, - required=False, - help=None): - super(_CountAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - default=default, - required=required, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - new_count = _ensure_value(namespace, self.dest, 0) + 1 - setattr(namespace, self.dest, new_count) - - -class _HelpAction(Action): - - def __init__(self, - option_strings, - dest=SUPPRESS, - default=SUPPRESS, - help=None): - super(_HelpAction, self).__init__( - option_strings=option_strings, - dest=dest, - default=default, - nargs=0, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - parser.print_help() - parser.exit() - - -class _VersionAction(Action): - - def __init__(self, - option_strings, - version=None, - dest=SUPPRESS, - default=SUPPRESS, - help=None): - super(_VersionAction, self).__init__( - option_strings=option_strings, - dest=dest, - default=default, - nargs=0, - help=help) - self.version = version - - def __call__(self, parser, namespace, values, option_string=None): - version = self.version - if version is None: - version = parser.version - formatter = parser._get_formatter() - formatter.add_text(version) - parser.exit(message=formatter.format_help()) - - -class _SubParsersAction(Action): - - class _ChoicesPseudoAction(Action): - - def __init__(self, name, help): - sup = super(_SubParsersAction._ChoicesPseudoAction, self) - sup.__init__(option_strings=[], dest=name, help=help) - - def __init__(self, - option_strings, - prog, - parser_class, - dest=SUPPRESS, - help=None, - metavar=None): - - self._prog_prefix = prog - self._parser_class = parser_class - self._name_parser_map = {} - self._choices_actions = [] - - super(_SubParsersAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=PARSER, - choices=self._name_parser_map, - help=help, - metavar=metavar) - - def add_parser(self, name, **kwargs): - # set prog from the existing prefix - if kwargs.get('prog') is None: - kwargs['prog'] = '%s %s' % (self._prog_prefix, name) - - # create a pseudo-action to hold the choice help - if 'help' in kwargs: - help = kwargs.pop('help') - choice_action = self._ChoicesPseudoAction(name, help) - self._choices_actions.append(choice_action) - - # create the parser and add it to the map - parser = self._parser_class(**kwargs) - self._name_parser_map[name] = parser - return parser - - def _get_subactions(self): - return self._choices_actions - - def __call__(self, parser, namespace, values, option_string=None): - parser_name = values[0] - arg_strings = values[1:] - - # set the parser name if requested - if self.dest is not SUPPRESS: - setattr(namespace, self.dest, parser_name) - - # select the parser - try: - parser = self._name_parser_map[parser_name] - except KeyError: - tup = parser_name, ', '.join(self._name_parser_map) - msg = _('unknown parser %r (choices: %s)' % tup) - raise ArgumentError(self, msg) - - # parse all the remaining options into the namespace - parser.parse_args(arg_strings, namespace) - - -# ============== -# Type classes -# ============== - -class FileType(object): - """Factory for creating file object types - - Instances of FileType are typically passed as type= arguments to the - ArgumentParser add_argument() method. - - Keyword Arguments: - - mode -- A string indicating how the file is to be opened. Accepts the - same values as the builtin open() function. - - bufsize -- The file's desired buffer size. Accepts the same values as - the builtin open() function. - """ - - def __init__(self, mode='r', bufsize=None): - self._mode = mode - self._bufsize = bufsize - - def __call__(self, string): - # the special argument "-" means sys.std{in,out} - if string == '-': - if 'r' in self._mode: - return _sys.stdin - elif 'w' in self._mode: - return _sys.stdout - else: - msg = _('argument "-" with mode %r' % self._mode) - raise ValueError(msg) - - # all other arguments are used as file names - if self._bufsize: - return open(string, self._mode, self._bufsize) - else: - return open(string, self._mode) - - def __repr__(self): - args = [self._mode, self._bufsize] - args_str = ', '.join([repr(arg) for arg in args if arg is not None]) - return '%s(%s)' % (type(self).__name__, args_str) - -# =========================== -# Optional and Positional Parsing -# =========================== - -class Namespace(_AttributeHolder): - """Simple object for storing attributes. - - Implements equality by attribute names and values, and provides a simple - string representation. - """ - - def __init__(self, **kwargs): - for name in kwargs: - setattr(self, name, kwargs[name]) - - def __eq__(self, other): - return vars(self) == vars(other) - - def __ne__(self, other): - return not (self == other) - - def __contains__(self, key): - return key in self.__dict__ - - -class _ActionsContainer(object): - - def __init__(self, - description, - prefix_chars, - argument_default, - conflict_handler): - super(_ActionsContainer, self).__init__() - - self.description = description - self.argument_default = argument_default - self.prefix_chars = prefix_chars - self.conflict_handler = conflict_handler - - # set up registries - self._registries = {} - - # register actions - self.register('action', None, _StoreAction) - self.register('action', 'store', _StoreAction) - self.register('action', 'store_const', _StoreConstAction) - self.register('action', 'store_true', _StoreTrueAction) - self.register('action', 'store_false', _StoreFalseAction) - self.register('action', 'append', _AppendAction) - self.register('action', 'append_const', _AppendConstAction) - self.register('action', 'count', _CountAction) - self.register('action', 'help', _HelpAction) - self.register('action', 'version', _VersionAction) - self.register('action', 'parsers', _SubParsersAction) - - # raise an exception if the conflict handler is invalid - self._get_handler() - - # action storage - self._actions = [] - self._option_string_actions = {} - - # groups - self._action_groups = [] - self._mutually_exclusive_groups = [] - - # defaults storage - self._defaults = {} - - # determines whether an "option" looks like a negative number - self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') - - # whether or not there are any optionals that look like negative - # numbers -- uses a list so it can be shared and edited - self._has_negative_number_optionals = [] - - # ==================== - # Registration methods - # ==================== - def register(self, registry_name, value, object): - registry = self._registries.setdefault(registry_name, {}) - registry[value] = object - - def _registry_get(self, registry_name, value, default=None): - return self._registries[registry_name].get(value, default) - - # ================================== - # Namespace default accessor methods - # ================================== - def set_defaults(self, **kwargs): - self._defaults.update(kwargs) - - # if these defaults match any existing arguments, replace - # the previous default on the object with the new one - for action in self._actions: - if action.dest in kwargs: - action.default = kwargs[action.dest] - - def get_default(self, dest): - for action in self._actions: - if action.dest == dest and action.default is not None: - return action.default - return self._defaults.get(dest, None) - - - # ======================= - # Adding argument actions - # ======================= - def add_argument(self, *args, **kwargs): - """ - add_argument(dest, ..., name=value, ...) - add_argument(option_string, option_string, ..., name=value, ...) - """ - - # if no positional args are supplied or only one is supplied and - # it doesn't look like an option string, parse a positional - # argument - chars = self.prefix_chars - if not args or len(args) == 1 and args[0][0] not in chars: - if args and 'dest' in kwargs: - raise ValueError('dest supplied twice for positional argument') - kwargs = self._get_positional_kwargs(*args, **kwargs) - - # otherwise, we're adding an optional argument - else: - kwargs = self._get_optional_kwargs(*args, **kwargs) - - # if no default was supplied, use the parser-level default - if 'default' not in kwargs: - dest = kwargs['dest'] - if dest in self._defaults: - kwargs['default'] = self._defaults[dest] - elif self.argument_default is not None: - kwargs['default'] = self.argument_default - - # create the action object, and add it to the parser - action_class = self._pop_action_class(kwargs) - if not _callable(action_class): - raise ValueError('unknown action "%s"' % action_class) - action = action_class(**kwargs) - - # raise an error if the action type is not callable - type_func = self._registry_get('type', action.type, action.type) - if not _callable(type_func): - raise ValueError('%r is not callable' % type_func) - - return self._add_action(action) - - def add_argument_group(self, *args, **kwargs): - group = _ArgumentGroup(self, *args, **kwargs) - self._action_groups.append(group) - return group - - def add_mutually_exclusive_group(self, **kwargs): - group = _MutuallyExclusiveGroup(self, **kwargs) - self._mutually_exclusive_groups.append(group) - return group - - def _add_action(self, action): - # resolve any conflicts - self._check_conflict(action) - - # add to actions list - self._actions.append(action) - action.container = self - - # index the action by any option strings it has - for option_string in action.option_strings: - self._option_string_actions[option_string] = action - - # set the flag if any option strings look like negative numbers - for option_string in action.option_strings: - if self._negative_number_matcher.match(option_string): - if not self._has_negative_number_optionals: - self._has_negative_number_optionals.append(True) - - # return the created action - return action - - def _remove_action(self, action): - self._actions.remove(action) - - def _add_container_actions(self, container): - # collect groups by titles - title_group_map = {} - for group in self._action_groups: - if group.title in title_group_map: - msg = _('cannot merge actions - two groups are named %r') - raise ValueError(msg % (group.title)) - title_group_map[group.title] = group - - # map each action to its group - group_map = {} - for group in container._action_groups: - - # if a group with the title exists, use that, otherwise - # create a new group matching the container's group - if group.title not in title_group_map: - title_group_map[group.title] = self.add_argument_group( - title=group.title, - description=group.description, - conflict_handler=group.conflict_handler) - - # map the actions to their new group - for action in group._group_actions: - group_map[action] = title_group_map[group.title] - - # add container's mutually exclusive groups - # NOTE: if add_mutually_exclusive_group ever gains title= and - # description= then this code will need to be expanded as above - for group in container._mutually_exclusive_groups: - mutex_group = self.add_mutually_exclusive_group( - required=group.required) - - # map the actions to their new mutex group - for action in group._group_actions: - group_map[action] = mutex_group - - # add all actions to this container or their group - for action in container._actions: - group_map.get(action, self)._add_action(action) - - def _get_positional_kwargs(self, dest, **kwargs): - # make sure required is not specified - if 'required' in kwargs: - msg = _("'required' is an invalid argument for positionals") - raise TypeError(msg) - - # mark positional arguments as required if at least one is - # always required - if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: - kwargs['required'] = True - if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: - kwargs['required'] = True - - # return the keyword arguments with no option strings - return dict(kwargs, dest=dest, option_strings=[]) - - def _get_optional_kwargs(self, *args, **kwargs): - # determine short and long option strings - option_strings = [] - long_option_strings = [] - for option_string in args: - # error on strings that don't start with an appropriate prefix - if not option_string[0] in self.prefix_chars: - msg = _('invalid option string %r: ' - 'must start with a character %r') - tup = option_string, self.prefix_chars - raise ValueError(msg % tup) - - # strings starting with two prefix characters are long options - option_strings.append(option_string) - if option_string[0] in self.prefix_chars: - if len(option_string) > 1: - if option_string[1] in self.prefix_chars: - long_option_strings.append(option_string) - - # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' - dest = kwargs.pop('dest', None) - if dest is None: - if long_option_strings: - dest_option_string = long_option_strings[0] - else: - dest_option_string = option_strings[0] - dest = dest_option_string.lstrip(self.prefix_chars) - if not dest: - msg = _('dest= is required for options like %r') - raise ValueError(msg % option_string) - dest = dest.replace('-', '_') - - # return the updated keyword arguments - return dict(kwargs, dest=dest, option_strings=option_strings) - - def _pop_action_class(self, kwargs, default=None): - action = kwargs.pop('action', default) - return self._registry_get('action', action, action) - - def _get_handler(self): - # determine function from conflict handler string - handler_func_name = '_handle_conflict_%s' % self.conflict_handler - try: - return getattr(self, handler_func_name) - except AttributeError: - msg = _('invalid conflict_resolution value: %r') - raise ValueError(msg % self.conflict_handler) - - def _check_conflict(self, action): - - # find all options that conflict with this option - confl_optionals = [] - for option_string in action.option_strings: - if option_string in self._option_string_actions: - confl_optional = self._option_string_actions[option_string] - confl_optionals.append((option_string, confl_optional)) - - # resolve any conflicts - if confl_optionals: - conflict_handler = self._get_handler() - conflict_handler(action, confl_optionals) - - def _handle_conflict_error(self, action, conflicting_actions): - message = _('conflicting option string(s): %s') - conflict_string = ', '.join([option_string - for option_string, action - in conflicting_actions]) - raise ArgumentError(action, message % conflict_string) - - def _handle_conflict_resolve(self, action, conflicting_actions): - - # remove all conflicting options - for option_string, action in conflicting_actions: - - # remove the conflicting option - action.option_strings.remove(option_string) - self._option_string_actions.pop(option_string, None) - - # if the option now has no option string, remove it from the - # container holding it - if not action.option_strings: - action.container._remove_action(action) - - -class _ArgumentGroup(_ActionsContainer): - - def __init__(self, container, title=None, description=None, **kwargs): - # add any missing keyword arguments by checking the container - update = kwargs.setdefault - update('conflict_handler', container.conflict_handler) - update('prefix_chars', container.prefix_chars) - update('argument_default', container.argument_default) - super_init = super(_ArgumentGroup, self).__init__ - super_init(description=description, **kwargs) - - # group attributes - self.title = title - self._group_actions = [] - - # share most attributes with the container - self._registries = container._registries - self._actions = container._actions - self._option_string_actions = container._option_string_actions - self._defaults = container._defaults - self._has_negative_number_optionals = \ - container._has_negative_number_optionals - - def _add_action(self, action): - action = super(_ArgumentGroup, self)._add_action(action) - self._group_actions.append(action) - return action - - def _remove_action(self, action): - super(_ArgumentGroup, self)._remove_action(action) - self._group_actions.remove(action) - - -class _MutuallyExclusiveGroup(_ArgumentGroup): - - def __init__(self, container, required=False): - super(_MutuallyExclusiveGroup, self).__init__(container) - self.required = required - self._container = container - - def _add_action(self, action): - if action.required: - msg = _('mutually exclusive arguments must be optional') - raise ValueError(msg) - action = self._container._add_action(action) - self._group_actions.append(action) - return action - - def _remove_action(self, action): - self._container._remove_action(action) - self._group_actions.remove(action) - - -class ArgumentParser(_AttributeHolder, _ActionsContainer): - """Object for parsing command line strings into Python objects. - - Keyword Arguments: - - prog -- The name of the program (default: sys.argv[0]) - - usage -- A usage message (default: auto-generated from arguments) - - description -- A description of what the program does - - epilog -- Text following the argument descriptions - - parents -- Parsers whose arguments should be copied into this one - - formatter_class -- HelpFormatter class for printing help messages - - prefix_chars -- Characters that prefix optional arguments - - fromfile_prefix_chars -- Characters that prefix files containing - additional arguments - - argument_default -- The default value for all arguments - - conflict_handler -- String indicating how to handle conflicts - - add_help -- Add a -h/-help option - """ - - def __init__(self, - prog=None, - usage=None, - description=None, - epilog=None, - version=None, - parents=[], - formatter_class=HelpFormatter, - prefix_chars='-', - fromfile_prefix_chars=None, - argument_default=None, - conflict_handler='error', - add_help=True): - - if version is not None: - import warnings - warnings.warn( - """The "version" argument to ArgumentParser is deprecated. """ - """Please use """ - """"add_argument(..., action='version', version="N", ...)" """ - """instead""", DeprecationWarning) - - superinit = super(ArgumentParser, self).__init__ - superinit(description=description, - prefix_chars=prefix_chars, - argument_default=argument_default, - conflict_handler=conflict_handler) - - # default setting for prog - if prog is None: - prog = _os.path.basename(_sys.argv[0]) - - self.prog = prog - self.usage = usage - self.epilog = epilog - self.version = version - self.formatter_class = formatter_class - self.fromfile_prefix_chars = fromfile_prefix_chars - self.add_help = add_help - - add_group = self.add_argument_group - self._positionals = add_group(_('positional arguments')) - self._optionals = add_group(_('optional arguments')) - self._subparsers = None - - # register types - def identity(string): - return string - self.register('type', None, identity) - - # add help and version arguments if necessary - # (using explicit default to override global argument_default) - if self.add_help: - self.add_argument( - '-h', '--help', action='help', default=SUPPRESS, - help=_('show this help message and exit')) - if self.version: - self.add_argument( - '-v', '--version', action='version', default=SUPPRESS, - version=self.version, - help=_("show program's version number and exit")) - - # add parent arguments and defaults - for parent in parents: - self._add_container_actions(parent) - try: - defaults = parent._defaults - except AttributeError: - pass - else: - self._defaults.update(defaults) - - # ======================= - # Pretty __repr__ methods - # ======================= - def _get_kwargs(self): - names = [ - 'prog', - 'usage', - 'description', - 'version', - 'formatter_class', - 'conflict_handler', - 'add_help', - ] - return [(name, getattr(self, name)) for name in names] - - # ================================== - # Optional/Positional adding methods - # ================================== - def add_subparsers(self, **kwargs): - if self._subparsers is not None: - self.error(_('cannot have multiple subparser arguments')) - - # add the parser class to the arguments if it's not present - kwargs.setdefault('parser_class', type(self)) - - if 'title' in kwargs or 'description' in kwargs: - title = _(kwargs.pop('title', 'subcommands')) - description = _(kwargs.pop('description', None)) - self._subparsers = self.add_argument_group(title, description) - else: - self._subparsers = self._positionals - - # prog defaults to the usage message of this parser, skipping - # optional arguments and with no "usage:" prefix - if kwargs.get('prog') is None: - formatter = self._get_formatter() - positionals = self._get_positional_actions() - groups = self._mutually_exclusive_groups - formatter.add_usage(self.usage, positionals, groups, '') - kwargs['prog'] = formatter.format_help().strip() - - # create the parsers action and add it to the positionals list - parsers_class = self._pop_action_class(kwargs, 'parsers') - action = parsers_class(option_strings=[], **kwargs) - self._subparsers._add_action(action) - - # return the created parsers action - return action - - def _add_action(self, action): - if action.option_strings: - self._optionals._add_action(action) - else: - self._positionals._add_action(action) - return action - - def _get_optional_actions(self): - return [action - for action in self._actions - if action.option_strings] - - def _get_positional_actions(self): - return [action - for action in self._actions - if not action.option_strings] - - # ===================================== - # Command line argument parsing methods - # ===================================== - def parse_args(self, args=None, namespace=None): - args, argv = self.parse_known_args(args, namespace) - if argv: - msg = _('unrecognized arguments: %s') - self.error(msg % ' '.join(argv)) - return args - - def parse_known_args(self, args=None, namespace=None): - # args default to the system args - if args is None: - args = _sys.argv[1:] - - # default Namespace built from parser defaults - if namespace is None: - namespace = Namespace() - - # add any action defaults that aren't present - for action in self._actions: - if action.dest is not SUPPRESS: - if not hasattr(namespace, action.dest): - if action.default is not SUPPRESS: - default = action.default - if isinstance(action.default, _basestring): - default = self._get_value(action, default) - setattr(namespace, action.dest, default) - - # add any parser defaults that aren't present - for dest in self._defaults: - if not hasattr(namespace, dest): - setattr(namespace, dest, self._defaults[dest]) - - # parse the arguments and exit if there are any errors - try: - return self._parse_known_args(args, namespace) - except ArgumentError: - err = _sys.exc_info()[1] - self.error(str(err)) - - def _parse_known_args(self, arg_strings, namespace): - # replace arg strings that are file references - if self.fromfile_prefix_chars is not None: - arg_strings = self._read_args_from_files(arg_strings) - - # map all mutually exclusive arguments to the other arguments - # they can't occur with - action_conflicts = {} - for mutex_group in self._mutually_exclusive_groups: - group_actions = mutex_group._group_actions - for i, mutex_action in enumerate(mutex_group._group_actions): - conflicts = action_conflicts.setdefault(mutex_action, []) - conflicts.extend(group_actions[:i]) - conflicts.extend(group_actions[i + 1:]) - - # find all option indices, and determine the arg_string_pattern - # which has an 'O' if there is an option at an index, - # an 'A' if there is an argument, or a '-' if there is a '--' - option_string_indices = {} - arg_string_pattern_parts = [] - arg_strings_iter = iter(arg_strings) - for i, arg_string in enumerate(arg_strings_iter): - - # all args after -- are non-options - if arg_string == '--': - arg_string_pattern_parts.append('-') - for arg_string in arg_strings_iter: - arg_string_pattern_parts.append('A') - - # otherwise, add the arg to the arg strings - # and note the index if it was an option - else: - option_tuple = self._parse_optional(arg_string) - if option_tuple is None: - pattern = 'A' - else: - option_string_indices[i] = option_tuple - pattern = 'O' - arg_string_pattern_parts.append(pattern) - - # join the pieces together to form the pattern - arg_strings_pattern = ''.join(arg_string_pattern_parts) - - # converts arg strings to the appropriate and then takes the action - seen_actions = _set() - seen_non_default_actions = _set() - - def take_action(action, argument_strings, option_string=None): - seen_actions.add(action) - argument_values = self._get_values(action, argument_strings) - - # error if this argument is not allowed with other previously - # seen arguments, assuming that actions that use the default - # value don't really count as "present" - if argument_values is not action.default: - seen_non_default_actions.add(action) - for conflict_action in action_conflicts.get(action, []): - if conflict_action in seen_non_default_actions: - msg = _('not allowed with argument %s') - action_name = _get_action_name(conflict_action) - raise ArgumentError(action, msg % action_name) - - # take the action if we didn't receive a SUPPRESS value - # (e.g. from a default) - if argument_values is not SUPPRESS: - action(self, namespace, argument_values, option_string) - - # function to convert arg_strings into an optional action - def consume_optional(start_index): - - # get the optional identified at this index - option_tuple = option_string_indices[start_index] - action, option_string, explicit_arg = option_tuple - - # identify additional optionals in the same arg string - # (e.g. -xyz is the same as -x -y -z if no args are required) - match_argument = self._match_argument - action_tuples = [] - while True: - - # if we found no optional action, skip it - if action is None: - extras.append(arg_strings[start_index]) - return start_index + 1 - - # if there is an explicit argument, try to match the - # optional's string arguments to only this - if explicit_arg is not None: - arg_count = match_argument(action, 'A') - - # if the action is a single-dash option and takes no - # arguments, try to parse more single-dash options out - # of the tail of the option string - chars = self.prefix_chars - if arg_count == 0 and option_string[1] not in chars: - action_tuples.append((action, [], option_string)) - for char in self.prefix_chars: - option_string = char + explicit_arg[0] - explicit_arg = explicit_arg[1:] or None - optionals_map = self._option_string_actions - if option_string in optionals_map: - action = optionals_map[option_string] - break - else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - - # if the action expect exactly one argument, we've - # successfully matched the option; exit the loop - elif arg_count == 1: - stop = start_index + 1 - args = [explicit_arg] - action_tuples.append((action, args, option_string)) - break - - # error if a double-dash option did not use the - # explicit argument - else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - - # if there is no explicit argument, try to match the - # optional's string arguments with the following strings - # if successful, exit the loop - else: - start = start_index + 1 - selected_patterns = arg_strings_pattern[start:] - arg_count = match_argument(action, selected_patterns) - stop = start + arg_count - args = arg_strings[start:stop] - action_tuples.append((action, args, option_string)) - break - - # add the Optional to the list and return the index at which - # the Optional's string args stopped - assert action_tuples - for action, args, option_string in action_tuples: - take_action(action, args, option_string) - return stop - - # the list of Positionals left to be parsed; this is modified - # by consume_positionals() - positionals = self._get_positional_actions() - - # function to convert arg_strings into positional actions - def consume_positionals(start_index): - # match as many Positionals as possible - match_partial = self._match_arguments_partial - selected_pattern = arg_strings_pattern[start_index:] - arg_counts = match_partial(positionals, selected_pattern) - - # slice off the appropriate arg strings for each Positional - # and add the Positional and its args to the list - for action, arg_count in zip(positionals, arg_counts): - args = arg_strings[start_index: start_index + arg_count] - start_index += arg_count - take_action(action, args) - - # slice off the Positionals that we just parsed and return the - # index at which the Positionals' string args stopped - positionals[:] = positionals[len(arg_counts):] - return start_index - - # consume Positionals and Optionals alternately, until we have - # passed the last option string - extras = [] - start_index = 0 - if option_string_indices: - max_option_string_index = max(option_string_indices) - else: - max_option_string_index = -1 - while start_index <= max_option_string_index: - - # consume any Positionals preceding the next option - next_option_string_index = min([ - index - for index in option_string_indices - if index >= start_index]) - if start_index != next_option_string_index: - positionals_end_index = consume_positionals(start_index) - - # only try to parse the next optional if we didn't consume - # the option string during the positionals parsing - if positionals_end_index > start_index: - start_index = positionals_end_index - continue - else: - start_index = positionals_end_index - - # if we consumed all the positionals we could and we're not - # at the index of an option string, there were extra arguments - if start_index not in option_string_indices: - strings = arg_strings[start_index:next_option_string_index] - extras.extend(strings) - start_index = next_option_string_index - - # consume the next optional and any arguments for it - start_index = consume_optional(start_index) - - # consume any positionals following the last Optional - stop_index = consume_positionals(start_index) - - # if we didn't consume all the argument strings, there were extras - extras.extend(arg_strings[stop_index:]) - - # if we didn't use all the Positional objects, there were too few - # arg strings supplied. - if positionals: - self.error(_('too few arguments')) - - # make sure all required actions were present - for action in self._actions: - if action.required: - if action not in seen_actions: - name = _get_action_name(action) - self.error(_('argument %s is required') % name) - - # make sure all required groups had one option present - for group in self._mutually_exclusive_groups: - if group.required: - for action in group._group_actions: - if action in seen_non_default_actions: - break - - # if no actions were used, report the error - else: - names = [_get_action_name(action) - for action in group._group_actions - if action.help is not SUPPRESS] - msg = _('one of the arguments %s is required') - self.error(msg % ' '.join(names)) - - # return the updated namespace and the extra arguments - return namespace, extras - - def _read_args_from_files(self, arg_strings): - # expand arguments referencing files - new_arg_strings = [] - for arg_string in arg_strings: - - # for regular arguments, just add them back into the list - if arg_string[0] not in self.fromfile_prefix_chars: - new_arg_strings.append(arg_string) - - # replace arguments referencing files with the file content - else: - try: - args_file = open(arg_string[1:]) - try: - arg_strings = [] - for arg_line in args_file.read().splitlines(): - for arg in self.convert_arg_line_to_args(arg_line): - arg_strings.append(arg) - arg_strings = self._read_args_from_files(arg_strings) - new_arg_strings.extend(arg_strings) - finally: - args_file.close() - except IOError: - err = _sys.exc_info()[1] - self.error(str(err)) - - # return the modified argument list - return new_arg_strings - - def convert_arg_line_to_args(self, arg_line): - return [arg_line] - - def _match_argument(self, action, arg_strings_pattern): - # match the pattern for this action to the arg strings - nargs_pattern = self._get_nargs_pattern(action) - match = _re.match(nargs_pattern, arg_strings_pattern) - - # raise an exception if we weren't able to find a match - if match is None: - nargs_errors = { - None: _('expected one argument'), - OPTIONAL: _('expected at most one argument'), - ONE_OR_MORE: _('expected at least one argument'), - } - default = _('expected %s argument(s)') % action.nargs - msg = nargs_errors.get(action.nargs, default) - raise ArgumentError(action, msg) - - # return the number of arguments matched - return len(match.group(1)) - - def _match_arguments_partial(self, actions, arg_strings_pattern): - # progressively shorten the actions list by slicing off the - # final actions until we find a match - result = [] - for i in range(len(actions), 0, -1): - actions_slice = actions[:i] - pattern = ''.join([self._get_nargs_pattern(action) - for action in actions_slice]) - match = _re.match(pattern, arg_strings_pattern) - if match is not None: - result.extend([len(string) for string in match.groups()]) - break - - # return the list of arg string counts - return result - - def _parse_optional(self, arg_string): - # if it's an empty string, it was meant to be a positional - if not arg_string: - return None - - # if it doesn't start with a prefix, it was meant to be positional - if not arg_string[0] in self.prefix_chars: - return None - - # if the option string is present in the parser, return the action - if arg_string in self._option_string_actions: - action = self._option_string_actions[arg_string] - return action, arg_string, None - - # if it's just a single character, it was meant to be positional - if len(arg_string) == 1: - return None - - # if the option string before the "=" is present, return the action - if '=' in arg_string: - option_string, explicit_arg = arg_string.split('=', 1) - if option_string in self._option_string_actions: - action = self._option_string_actions[option_string] - return action, option_string, explicit_arg - - # search through all possible prefixes of the option string - # and all actions in the parser for possible interpretations - option_tuples = self._get_option_tuples(arg_string) - - # if multiple actions match, the option string was ambiguous - if len(option_tuples) > 1: - options = ', '.join([option_string - for action, option_string, explicit_arg in option_tuples]) - tup = arg_string, options - self.error(_('ambiguous option: %s could match %s') % tup) - - # if exactly one action matched, this segmentation is good, - # so return the parsed action - elif len(option_tuples) == 1: - option_tuple, = option_tuples - return option_tuple - - # if it was not found as an option, but it looks like a negative - # number, it was meant to be positional - # unless there are negative-number-like options - if self._negative_number_matcher.match(arg_string): - if not self._has_negative_number_optionals: - return None - - # if it contains a space, it was meant to be a positional - if ' ' in arg_string: - return None - - # it was meant to be an optional but there is no such option - # in this parser (though it might be a valid option in a subparser) - return None, arg_string, None - - def _get_option_tuples(self, option_string): - result = [] - - # option strings starting with two prefix characters are only - # split at the '=' - chars = self.prefix_chars - if option_string[0] in chars and option_string[1] in chars: - if '=' in option_string: - option_prefix, explicit_arg = option_string.split('=', 1) - else: - option_prefix = option_string - explicit_arg = None - for option_string in self._option_string_actions: - if option_string.startswith(option_prefix): - action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg - result.append(tup) - - # single character options can be concatenated with their arguments - # but multiple character options always have to have their argument - # separate - elif option_string[0] in chars and option_string[1] not in chars: - option_prefix = option_string - explicit_arg = None - short_option_prefix = option_string[:2] - short_explicit_arg = option_string[2:] - - for option_string in self._option_string_actions: - if option_string == short_option_prefix: - action = self._option_string_actions[option_string] - tup = action, option_string, short_explicit_arg - result.append(tup) - elif option_string.startswith(option_prefix): - action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg - result.append(tup) - - # shouldn't ever get here - else: - self.error(_('unexpected option string: %s') % option_string) - - # return the collected option tuples - return result - - def _get_nargs_pattern(self, action): - # in all examples below, we have to allow for '--' args - # which are represented as '-' in the pattern - nargs = action.nargs - - # the default (None) is assumed to be a single argument - if nargs is None: - nargs_pattern = '(-*A-*)' - - # allow zero or one arguments - elif nargs == OPTIONAL: - nargs_pattern = '(-*A?-*)' - - # allow zero or more arguments - elif nargs == ZERO_OR_MORE: - nargs_pattern = '(-*[A-]*)' - - # allow one or more arguments - elif nargs == ONE_OR_MORE: - nargs_pattern = '(-*A[A-]*)' - - # allow any number of options or arguments - elif nargs == REMAINDER: - nargs_pattern = '([-AO]*)' - - # allow one argument followed by any number of options or arguments - elif nargs == PARSER: - nargs_pattern = '(-*A[-AO]*)' - - # all others should be integers - else: - nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) - - # if this is an optional action, -- is not allowed - if action.option_strings: - nargs_pattern = nargs_pattern.replace('-*', '') - nargs_pattern = nargs_pattern.replace('-', '') - - # return the pattern - return nargs_pattern - - # ======================== - # Value conversion methods - # ======================== - def _get_values(self, action, arg_strings): - # for everything but PARSER args, strip out '--' - if action.nargs not in [PARSER, REMAINDER]: - arg_strings = [s for s in arg_strings if s != '--'] - - # optional argument produces a default when not present - if not arg_strings and action.nargs == OPTIONAL: - if action.option_strings: - value = action.const - else: - value = action.default - if isinstance(value, _basestring): - value = self._get_value(action, value) - self._check_value(action, value) - - # when nargs='*' on a positional, if there were no command-line - # args, use the default if it is anything other than None - elif (not arg_strings and action.nargs == ZERO_OR_MORE and - not action.option_strings): - if action.default is not None: - value = action.default - else: - value = arg_strings - self._check_value(action, value) - - # single argument or optional argument produces a single value - elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: - arg_string, = arg_strings - value = self._get_value(action, arg_string) - self._check_value(action, value) - - # REMAINDER arguments convert all values, checking none - elif action.nargs == REMAINDER: - value = [self._get_value(action, v) for v in arg_strings] - - # PARSER arguments convert all values, but check only the first - elif action.nargs == PARSER: - value = [self._get_value(action, v) for v in arg_strings] - self._check_value(action, value[0]) - - # all other types of nargs produce a list - else: - value = [self._get_value(action, v) for v in arg_strings] - for v in value: - self._check_value(action, v) - - # return the converted value - return value - - def _get_value(self, action, arg_string): - type_func = self._registry_get('type', action.type, action.type) - if not _callable(type_func): - msg = _('%r is not callable') - raise ArgumentError(action, msg % type_func) - - # convert the value to the appropriate type - try: - result = type_func(arg_string) - - # ArgumentTypeErrors indicate errors - except ArgumentTypeError: - name = getattr(action.type, '__name__', repr(action.type)) - msg = str(_sys.exc_info()[1]) - raise ArgumentError(action, msg) - - # TypeErrors or ValueErrors also indicate errors - except (TypeError, ValueError): - name = getattr(action.type, '__name__', repr(action.type)) - msg = _('invalid %s value: %r') - raise ArgumentError(action, msg % (name, arg_string)) - - # return the converted value - return result - - def _check_value(self, action, value): - # converted value must be one of the choices (if specified) - if action.choices is not None and value not in action.choices: - tup = value, ', '.join(map(repr, action.choices)) - msg = _('invalid choice: %r (choose from %s)') % tup - raise ArgumentError(action, msg) - - # ======================= - # Help-formatting methods - # ======================= - def format_usage(self): - formatter = self._get_formatter() - formatter.add_usage(self.usage, self._actions, - self._mutually_exclusive_groups) - return formatter.format_help() - - def format_help(self): - formatter = self._get_formatter() - - # usage - formatter.add_usage(self.usage, self._actions, - self._mutually_exclusive_groups) - - # description - formatter.add_text(self.description) - - # positionals, optionals and user-defined groups - for action_group in self._action_groups: - formatter.start_section(action_group.title) - formatter.add_text(action_group.description) - formatter.add_arguments(action_group._group_actions) - formatter.end_section() - - # epilog - formatter.add_text(self.epilog) - - # determine help from format above - return formatter.format_help() - - def format_version(self): - import warnings - warnings.warn( - 'The format_version method is deprecated -- the "version" ' - 'argument to ArgumentParser is no longer supported.', - DeprecationWarning) - formatter = self._get_formatter() - formatter.add_text(self.version) - return formatter.format_help() - - def _get_formatter(self): - return self.formatter_class(prog=self.prog) - - # ===================== - # Help-printing methods - # ===================== - def print_usage(self, file=None): - if file is None: - file = _sys.stdout - self._print_message(self.format_usage(), file) - - def print_help(self, file=None): - if file is None: - file = _sys.stdout - self._print_message(self.format_help(), file) - - def print_version(self, file=None): - import warnings - warnings.warn( - 'The print_version method is deprecated -- the "version" ' - 'argument to ArgumentParser is no longer supported.', - DeprecationWarning) - self._print_message(self.format_version(), file) - - def _print_message(self, message, file=None): - if message: - if file is None: - file = _sys.stderr - file.write(message) - - # =============== - # Exiting methods - # =============== - def exit(self, status=0, message=None): - if message: - self._print_message(message, _sys.stderr) - _sys.exit(status) - - def error(self, message): - """error(message: string) - - Prints a usage message incorporating the message to stderr and - exits. - - If you override this in a subclass, it should not return -- it - should either exit or raise an exception. - """ - self.print_usage(_sys.stderr) - self.exit(2, _('%s: error: %s\n') % (self.prog, message)) +# Copyright 2006-2009 Steven J. Bethard . +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Command-line parsing library + +This module is an optparse-inspired command-line parsing library that: + + - handles both optional and positional arguments + - produces highly informative usage messages + - supports parsers that dispatch to sub-parsers + +The following is a simple usage example that sums integers from the +command-line and writes the result to a file:: + + parser = argparse.ArgumentParser( + description='sum the integers at the command line') + parser.add_argument( + 'integers', metavar='int', nargs='+', type=int, + help='an integer to be summed') + parser.add_argument( + '--log', default=sys.stdout, type=argparse.FileType('w'), + help='the file where the sum should be written') + args = parser.parse_args() + args.log.write('%s' % sum(args.integers)) + args.log.close() + +The module contains the following public classes: + + - ArgumentParser -- The main entry point for command-line parsing. As the + example above shows, the add_argument() method is used to populate + the parser with actions for optional and positional arguments. Then + the parse_args() method is invoked to convert the args at the + command-line into an object with attributes. + + - ArgumentError -- The exception raised by ArgumentParser objects when + there are errors with the parser's actions. Errors raised while + parsing the command-line are caught by ArgumentParser and emitted + as command-line messages. + + - FileType -- A factory for defining types of files to be created. As the + example above shows, instances of FileType are typically passed as + the type= argument of add_argument() calls. + + - Action -- The base class for parser actions. Typically actions are + selected by passing strings like 'store_true' or 'append_const' to + the action= argument of add_argument(). However, for greater + customization of ArgumentParser actions, subclasses of Action may + be defined and passed as the action= argument. + + - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + ArgumentDefaultsHelpFormatter -- Formatter classes which + may be passed as the formatter_class= argument to the + ArgumentParser constructor. HelpFormatter is the default, + RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + not to change the formatting for help text, and + ArgumentDefaultsHelpFormatter adds information about argument defaults + to the help. + +All other classes in this module are considered implementation details. +(Also note that HelpFormatter and RawDescriptionHelpFormatter are only +considered public as object names -- the API of the formatter objects is +still considered an implementation detail.) +""" + +__version__ = '1.1' +__all__ = [ + 'ArgumentParser', + 'ArgumentError', + 'Namespace', + 'Action', + 'FileType', + 'HelpFormatter', + 'RawDescriptionHelpFormatter', + 'RawTextHelpFormatter', + 'ArgumentDefaultsHelpFormatter', +] + + +import copy as _copy +import os as _os +import re as _re +import sys as _sys +import textwrap as _textwrap + +from gettext import gettext as _ + +try: + _set = set +except NameError: + from sets import Set as _set + +try: + _basestring = basestring +except NameError: + _basestring = str + +try: + _sorted = sorted +except NameError: + + def _sorted(iterable, reverse=False): + result = list(iterable) + result.sort() + if reverse: + result.reverse() + return result + + +def _callable(obj): + return hasattr(obj, '__call__') or hasattr(obj, '__bases__') + +# silence Python 2.6 buggy warnings about Exception.message +if _sys.version_info[:2] == (2, 6): + import warnings + warnings.filterwarnings( + action='ignore', + message='BaseException.message has been deprecated as of Python 2.6', + category=DeprecationWarning, + module='argparse') + + +SUPPRESS = '==SUPPRESS==' + +OPTIONAL = '?' +ZERO_OR_MORE = '*' +ONE_OR_MORE = '+' +PARSER = 'A...' +REMAINDER = '...' + +# ============================= +# Utility functions and classes +# ============================= + +class _AttributeHolder(object): + """Abstract base class that provides __repr__. + + The __repr__ method returns a string in the format:: + ClassName(attr=name, attr=name, ...) + The attributes are determined either by a class-level attribute, + '_kwarg_names', or by inspecting the instance __dict__. + """ + + def __repr__(self): + type_name = type(self).__name__ + arg_strings = [] + for arg in self._get_args(): + arg_strings.append(repr(arg)) + for name, value in self._get_kwargs(): + arg_strings.append('%s=%r' % (name, value)) + return '%s(%s)' % (type_name, ', '.join(arg_strings)) + + def _get_kwargs(self): + return _sorted(self.__dict__.items()) + + def _get_args(self): + return [] + + +def _ensure_value(namespace, name, value): + if getattr(namespace, name, None) is None: + setattr(namespace, name, value) + return getattr(namespace, name) + + +# =============== +# Formatting Help +# =============== + +class HelpFormatter(object): + """Formatter for generating usage messages and argument help strings. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def __init__(self, + prog, + indent_increment=2, + max_help_position=24, + width=None): + + # default setting for width + if width is None: + try: + width = int(_os.environ['COLUMNS']) + except (KeyError, ValueError): + width = 80 + width -= 2 + + self._prog = prog + self._indent_increment = indent_increment + self._max_help_position = max_help_position + self._width = width + + self._current_indent = 0 + self._level = 0 + self._action_max_length = 0 + + self._root_section = self._Section(self, None) + self._current_section = self._root_section + + self._whitespace_matcher = _re.compile(r'\s+') + self._long_break_matcher = _re.compile(r'\n\n\n+') + + # =============================== + # Section and indentation methods + # =============================== + def _indent(self): + self._current_indent += self._indent_increment + self._level += 1 + + def _dedent(self): + self._current_indent -= self._indent_increment + assert self._current_indent >= 0, 'Indent decreased below 0.' + self._level -= 1 + + class _Section(object): + + def __init__(self, formatter, parent, heading=None): + self.formatter = formatter + self.parent = parent + self.heading = heading + self.items = [] + + def format_help(self): + # format the indented section + if self.parent is not None: + self.formatter._indent() + join = self.formatter._join_parts + for func, args in self.items: + func(*args) + item_help = join([func(*args) for func, args in self.items]) + if self.parent is not None: + self.formatter._dedent() + + # return nothing if the section was empty + if not item_help: + return '' + + # add the heading if the section was non-empty + if self.heading is not SUPPRESS and self.heading is not None: + current_indent = self.formatter._current_indent + heading = '%*s%s:\n' % (current_indent, '', self.heading) + else: + heading = '' + + # join the section-initial newline, the heading and the help + return join(['\n', heading, item_help, '\n']) + + def _add_item(self, func, args): + self._current_section.items.append((func, args)) + + # ======================== + # Message building methods + # ======================== + def start_section(self, heading): + self._indent() + section = self._Section(self, self._current_section, heading) + self._add_item(section.format_help, []) + self._current_section = section + + def end_section(self): + self._current_section = self._current_section.parent + self._dedent() + + def add_text(self, text): + if text is not SUPPRESS and text is not None: + self._add_item(self._format_text, [text]) + + def add_usage(self, usage, actions, groups, prefix=None): + if usage is not SUPPRESS: + args = usage, actions, groups, prefix + self._add_item(self._format_usage, args) + + def add_argument(self, action): + if action.help is not SUPPRESS: + + # find all invocations + get_invocation = self._format_action_invocation + invocations = [get_invocation(action)] + for subaction in self._iter_indented_subactions(action): + invocations.append(get_invocation(subaction)) + + # update the maximum item length + invocation_length = max([len(s) for s in invocations]) + action_length = invocation_length + self._current_indent + self._action_max_length = max(self._action_max_length, + action_length) + + # add the item to the list + self._add_item(self._format_action, [action]) + + def add_arguments(self, actions): + for action in actions: + self.add_argument(action) + + # ======================= + # Help-formatting methods + # ======================= + def format_help(self): + help = self._root_section.format_help() + if help: + help = self._long_break_matcher.sub('\n\n', help) + help = help.strip('\n') + '\n' + return help + + def _join_parts(self, part_strings): + return ''.join([part + for part in part_strings + if part and part is not SUPPRESS]) + + def _format_usage(self, usage, actions, groups, prefix): + if prefix is None: + prefix = _('usage: ') + + # if usage is specified, use that + if usage is not None: + usage = usage % dict(prog=self._prog) + + # if no optionals or positionals are available, usage is just prog + elif usage is None and not actions: + usage = '%(prog)s' % dict(prog=self._prog) + + # if optionals and positionals are available, calculate usage + elif usage is None: + prog = '%(prog)s' % dict(prog=self._prog) + + # split optionals from positionals + optionals = [] + positionals = [] + for action in actions: + if action.option_strings: + optionals.append(action) + else: + positionals.append(action) + + # build full usage string + format = self._format_actions_usage + action_usage = format(optionals + positionals, groups) + usage = ' '.join([s for s in [prog, action_usage] if s]) + + # wrap the usage parts if it's too long + text_width = self._width - self._current_indent + if len(prefix) + len(usage) > text_width: + + # break usage into wrappable parts + part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' + opt_usage = format(optionals, groups) + pos_usage = format(positionals, groups) + opt_parts = _re.findall(part_regexp, opt_usage) + pos_parts = _re.findall(part_regexp, pos_usage) + assert ' '.join(opt_parts) == opt_usage + assert ' '.join(pos_parts) == pos_usage + + # helper for wrapping lines + def get_lines(parts, indent, prefix=None): + lines = [] + line = [] + if prefix is not None: + line_len = len(prefix) - 1 + else: + line_len = len(indent) - 1 + for part in parts: + if line_len + 1 + len(part) > text_width: + lines.append(indent + ' '.join(line)) + line = [] + line_len = len(indent) - 1 + line.append(part) + line_len += len(part) + 1 + if line: + lines.append(indent + ' '.join(line)) + if prefix is not None: + lines[0] = lines[0][len(indent):] + return lines + + # if prog is short, follow it with optionals or positionals + if len(prefix) + len(prog) <= 0.75 * text_width: + indent = ' ' * (len(prefix) + len(prog) + 1) + if opt_parts: + lines = get_lines([prog] + opt_parts, indent, prefix) + lines.extend(get_lines(pos_parts, indent)) + elif pos_parts: + lines = get_lines([prog] + pos_parts, indent, prefix) + else: + lines = [prog] + + # if prog is long, put it on its own line + else: + indent = ' ' * len(prefix) + parts = opt_parts + pos_parts + lines = get_lines(parts, indent) + if len(lines) > 1: + lines = [] + lines.extend(get_lines(opt_parts, indent)) + lines.extend(get_lines(pos_parts, indent)) + lines = [prog] + lines + + # join lines into usage + usage = '\n'.join(lines) + + # prefix with 'usage:' + return '%s%s\n\n' % (prefix, usage) + + def _format_actions_usage(self, actions, groups): + # find group indices and identify actions in groups + group_actions = _set() + inserts = {} + for group in groups: + try: + start = actions.index(group._group_actions[0]) + except ValueError: + continue + else: + end = start + len(group._group_actions) + if actions[start:end] == group._group_actions: + for action in group._group_actions: + group_actions.add(action) + if not group.required: + inserts[start] = '[' + inserts[end] = ']' + else: + inserts[start] = '(' + inserts[end] = ')' + for i in range(start + 1, end): + inserts[i] = '|' + + # collect all actions format strings + parts = [] + for i, action in enumerate(actions): + + # suppressed arguments are marked with None + # remove | separators for suppressed arguments + if action.help is SUPPRESS: + parts.append(None) + if inserts.get(i) == '|': + inserts.pop(i) + elif inserts.get(i + 1) == '|': + inserts.pop(i + 1) + + # produce all arg strings + elif not action.option_strings: + part = self._format_args(action, action.dest) + + # if it's in a group, strip the outer [] + if action in group_actions: + if part[0] == '[' and part[-1] == ']': + part = part[1:-1] + + # add the action string to the list + parts.append(part) + + # produce the first way to invoke the option in brackets + else: + option_string = action.option_strings[0] + + # if the Optional doesn't take a value, format is: + # -s or --long + if action.nargs == 0: + part = '%s' % option_string + + # if the Optional takes a value, format is: + # -s ARGS or --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + part = '%s %s' % (option_string, args_string) + + # make it look optional if it's not required or in a group + if not action.required and action not in group_actions: + part = '[%s]' % part + + # add the action string to the list + parts.append(part) + + # insert things at the necessary indices + for i in _sorted(inserts, reverse=True): + parts[i:i] = [inserts[i]] + + # join all the action items with spaces + text = ' '.join([item for item in parts if item is not None]) + + # clean up separators for mutually exclusive groups + open = r'[\[(]' + close = r'[\])]' + text = _re.sub(r'(%s) ' % open, r'\1', text) + text = _re.sub(r' (%s)' % close, r'\1', text) + text = _re.sub(r'%s *%s' % (open, close), r'', text) + text = _re.sub(r'\(([^|]*)\)', r'\1', text) + text = text.strip() + + # return the text + return text + + def _format_text(self, text): + if '%(prog)' in text: + text = text % dict(prog=self._prog) + text_width = self._width - self._current_indent + indent = ' ' * self._current_indent + return self._fill_text(text, text_width, indent) + '\n\n' + + def _format_action(self, action): + # determine the required width and the entry label + help_position = min(self._action_max_length + 2, + self._max_help_position) + help_width = self._width - help_position + action_width = help_position - self._current_indent - 2 + action_header = self._format_action_invocation(action) + + # ho nelp; start on same line and add a final newline + if not action.help: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + + # short action name; start on the same line and pad two spaces + elif len(action_header) <= action_width: + tup = self._current_indent, '', action_width, action_header + action_header = '%*s%-*s ' % tup + indent_first = 0 + + # long action name; start on the next line + else: + tup = self._current_indent, '', action_header + action_header = '%*s%s\n' % tup + indent_first = help_position + + # collect the pieces of the action help + parts = [action_header] + + # if there was help for the action, add lines of help text + if action.help: + help_text = self._expand_help(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)) + + # or add a newline if the description doesn't end with one + elif not action_header.endswith('\n'): + parts.append('\n') + + # if there are any sub-actions, add their help as well + for subaction in self._iter_indented_subactions(action): + parts.append(self._format_action(subaction)) + + # return a single string + return self._join_parts(parts) + + def _format_action_invocation(self, action): + if not action.option_strings: + metavar, = self._metavar_formatter(action, action.dest)(1) + return metavar + + else: + parts = [] + + # if the Optional doesn't take a value, format is: + # -s, --long + if action.nargs == 0: + parts.extend(action.option_strings) + + # if the Optional takes a value, format is: + # -s ARGS, --long ARGS + else: + default = action.dest.upper() + args_string = self._format_args(action, default) + for option_string in action.option_strings: + parts.append('%s %s' % (option_string, args_string)) + + return ', '.join(parts) + + def _metavar_formatter(self, action, default_metavar): + if action.metavar is not None: + result = action.metavar + elif action.choices is not None: + choice_strs = [str(choice) for choice in action.choices] + result = '{%s}' % ','.join(choice_strs) + else: + result = default_metavar + + def format(tuple_size): + if isinstance(result, tuple): + return result + else: + return (result, ) * tuple_size + return format + + def _format_args(self, action, default_metavar): + get_metavar = self._metavar_formatter(action, default_metavar) + if action.nargs is None: + result = '%s' % get_metavar(1) + elif action.nargs == OPTIONAL: + result = '[%s]' % get_metavar(1) + elif action.nargs == ZERO_OR_MORE: + result = '[%s [%s ...]]' % get_metavar(2) + elif action.nargs == ONE_OR_MORE: + result = '%s [%s ...]' % get_metavar(2) + elif action.nargs == REMAINDER: + result = '...' + elif action.nargs == PARSER: + result = '%s ...' % get_metavar(1) + else: + formats = ['%s' for _ in range(action.nargs)] + result = ' '.join(formats) % get_metavar(action.nargs) + return result + + def _expand_help(self, action): + params = dict(vars(action), prog=self._prog) + for name in list(params): + if params[name] is SUPPRESS: + del params[name] + for name in list(params): + if hasattr(params[name], '__name__'): + params[name] = params[name].__name__ + if params.get('choices') is not None: + choices_str = ', '.join([str(c) for c in params['choices']]) + params['choices'] = choices_str + return self._get_help_string(action) % params + + def _iter_indented_subactions(self, action): + try: + get_subactions = action._get_subactions + except AttributeError: + pass + else: + self._indent() + for subaction in get_subactions(): + yield subaction + self._dedent() + + def _split_lines(self, text, width): + text = self._whitespace_matcher.sub(' ', text).strip() + return _textwrap.wrap(text, width) + + 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) + + def _get_help_string(self, action): + return action.help + + +class RawDescriptionHelpFormatter(HelpFormatter): + """Help message formatter which retains any formatting in descriptions. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _fill_text(self, text, width, indent): + return ''.join([indent + line for line in text.splitlines(True)]) + + +class RawTextHelpFormatter(RawDescriptionHelpFormatter): + """Help message formatter which retains formatting of all help text. + + 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 text.splitlines() + + +class ArgumentDefaultsHelpFormatter(HelpFormatter): + """Help message formatter which adds default values to argument help. + + Only the name of this class is considered a public API. All the methods + provided by the class are considered an implementation detail. + """ + + def _get_help_string(self, action): + help = action.help + if '%(default)' not in action.help: + if action.default is not SUPPRESS: + defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] + if action.option_strings or action.nargs in defaulting_nargs: + help += ' (default: %(default)s)' + return help + + +# ===================== +# Options and Arguments +# ===================== + +def _get_action_name(argument): + if argument is None: + return None + elif argument.option_strings: + return '/'.join(argument.option_strings) + elif argument.metavar not in (None, SUPPRESS): + return argument.metavar + elif argument.dest not in (None, SUPPRESS): + return argument.dest + else: + return None + + +class ArgumentError(Exception): + """An error from creating or using an argument (optional or positional). + + The string value of this exception is the message, augmented with + information about the argument that caused it. + """ + + def __init__(self, argument, message): + self.argument_name = _get_action_name(argument) + self.message = message + + def __str__(self): + if self.argument_name is None: + format = '%(message)s' + else: + format = 'argument %(argument_name)s: %(message)s' + return format % dict(message=self.message, + argument_name=self.argument_name) + + +class ArgumentTypeError(Exception): + """An error from trying to convert a command line string to a type.""" + pass + + +# ============== +# Action classes +# ============== + +class Action(_AttributeHolder): + """Information about how to convert command line strings to Python objects. + + Action objects are used by an ArgumentParser to represent the information + needed to parse a single argument from one or more strings from the + command line. The keyword arguments to the Action constructor are also + all attributes of Action instances. + + Keyword Arguments: + + - option_strings -- A list of command-line option strings which + should be associated with this action. + + - dest -- The name of the attribute to hold the created object(s) + + - nargs -- The number of command-line arguments that should be + consumed. By default, one argument will be consumed and a single + value will be produced. Other values include: + - N (an integer) consumes N arguments (and produces a list) + - '?' consumes zero or one arguments + - '*' consumes zero or more arguments (and produces a list) + - '+' consumes one or more arguments (and produces a list) + Note that the difference between the default and nargs=1 is that + with the default, a single value will be produced, while with + nargs=1, a list containing a single value will be produced. + + - const -- The value to be produced if the option is specified and the + option uses an action that takes no values. + + - default -- The value to be produced if the option is not specified. + + - type -- The type which the command-line arguments should be converted + to, should be one of 'string', 'int', 'float', 'complex' or a + callable object that accepts a single string argument. If None, + 'string' is assumed. + + - choices -- A container of values that should be allowed. If not None, + after a command-line argument has been converted to the appropriate + type, an exception will be raised if it is not a member of this + collection. + + - required -- True if the action must always be specified at the + command line. This is only meaningful for optional command-line + arguments. + + - help -- The help string describing the argument. + + - metavar -- The name to be used for the option's argument with the + help string. If None, the 'dest' value will be used as the name. + """ + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + self.option_strings = option_strings + self.dest = dest + self.nargs = nargs + self.const = const + self.default = default + self.type = type + self.choices = choices + self.required = required + self.help = help + self.metavar = metavar + + def _get_kwargs(self): + names = [ + 'option_strings', + 'dest', + 'nargs', + 'const', + 'default', + 'type', + 'choices', + 'help', + 'metavar', + ] + return [(name, getattr(self, name)) for name in names] + + def __call__(self, parser, namespace, values, option_string=None): + raise NotImplementedError(_('.__call__() not defined')) + + +class _StoreAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for store actions must be > 0; if you ' + 'have nothing to store, actions such as store ' + 'true or store const may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_StoreAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + + +class _StoreConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_StoreConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, self.const) + + +class _StoreTrueAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=False, + required=False, + help=None): + super(_StoreTrueAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=True, + default=default, + required=required, + help=help) + + +class _StoreFalseAction(_StoreConstAction): + + def __init__(self, + option_strings, + dest, + default=True, + required=False, + help=None): + super(_StoreFalseAction, self).__init__( + option_strings=option_strings, + dest=dest, + const=False, + default=default, + required=required, + help=help) + + +class _AppendAction(Action): + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None): + if nargs == 0: + raise ValueError('nargs for append actions must be > 0; if arg ' + 'strings are not supplying the value to append, ' + 'the append const action may be more appropriate') + if const is not None and nargs != OPTIONAL: + raise ValueError('nargs must be %r to supply const' % OPTIONAL) + super(_AppendAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(values) + setattr(namespace, self.dest, items) + + +class _AppendConstAction(Action): + + def __init__(self, + option_strings, + dest, + const, + default=None, + required=False, + help=None, + metavar=None): + super(_AppendConstAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + const=const, + default=default, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items.append(self.const) + setattr(namespace, self.dest, items) + + +class _CountAction(Action): + + def __init__(self, + option_strings, + dest, + default=None, + required=False, + help=None): + super(_CountAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=0, + default=default, + required=required, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + new_count = _ensure_value(namespace, self.dest, 0) + 1 + setattr(namespace, self.dest, new_count) + + +class _HelpAction(Action): + + def __init__(self, + option_strings, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_HelpAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + + def __call__(self, parser, namespace, values, option_string=None): + parser.print_help() + parser.exit() + + +class _VersionAction(Action): + + def __init__(self, + option_strings, + version=None, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_VersionAction, self).__init__( + option_strings=option_strings, + dest=dest, + default=default, + nargs=0, + help=help) + self.version = version + + def __call__(self, parser, namespace, values, option_string=None): + version = self.version + if version is None: + version = parser.version + formatter = parser._get_formatter() + formatter.add_text(version) + parser.exit(message=formatter.format_help()) + + +class _SubParsersAction(Action): + + class _ChoicesPseudoAction(Action): + + def __init__(self, name, help): + sup = super(_SubParsersAction._ChoicesPseudoAction, self) + sup.__init__(option_strings=[], dest=name, help=help) + + def __init__(self, + option_strings, + prog, + parser_class, + dest=SUPPRESS, + help=None, + metavar=None): + + self._prog_prefix = prog + self._parser_class = parser_class + self._name_parser_map = {} + self._choices_actions = [] + + super(_SubParsersAction, self).__init__( + option_strings=option_strings, + dest=dest, + nargs=PARSER, + choices=self._name_parser_map, + help=help, + metavar=metavar) + + def add_parser(self, name, **kwargs): + # set prog from the existing prefix + if kwargs.get('prog') is None: + kwargs['prog'] = '%s %s' % (self._prog_prefix, name) + + # create a pseudo-action to hold the choice help + if 'help' in kwargs: + help = kwargs.pop('help') + choice_action = self._ChoicesPseudoAction(name, help) + self._choices_actions.append(choice_action) + + # create the parser and add it to the map + parser = self._parser_class(**kwargs) + self._name_parser_map[name] = parser + return parser + + def _get_subactions(self): + return self._choices_actions + + def __call__(self, parser, namespace, values, option_string=None): + parser_name = values[0] + arg_strings = values[1:] + + # set the parser name if requested + if self.dest is not SUPPRESS: + setattr(namespace, self.dest, parser_name) + + # select the parser + try: + parser = self._name_parser_map[parser_name] + except KeyError: + tup = parser_name, ', '.join(self._name_parser_map) + msg = _('unknown parser %r (choices: %s)' % tup) + raise ArgumentError(self, msg) + + # parse all the remaining options into the namespace + parser.parse_args(arg_strings, namespace) + + +# ============== +# Type classes +# ============== + +class FileType(object): + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + """ + + def __init__(self, mode='r', bufsize=None): + self._mode = mode + self._bufsize = bufsize + + def __call__(self, string): + # the special argument "-" means sys.std{in,out} + if string == '-': + if 'r' in self._mode: + return _sys.stdin + elif 'w' in self._mode: + return _sys.stdout + else: + msg = _('argument "-" with mode %r' % self._mode) + raise ValueError(msg) + + # all other arguments are used as file names + if self._bufsize: + return open(string, self._mode, self._bufsize) + else: + return open(string, self._mode) + + def __repr__(self): + args = [self._mode, self._bufsize] + args_str = ', '.join([repr(arg) for arg in args if arg is not None]) + return '%s(%s)' % (type(self).__name__, args_str) + +# =========================== +# Optional and Positional Parsing +# =========================== + +class Namespace(_AttributeHolder): + """Simple object for storing attributes. + + Implements equality by attribute names and values, and provides a simple + string representation. + """ + + def __init__(self, **kwargs): + for name in kwargs: + setattr(self, name, kwargs[name]) + + def __eq__(self, other): + return vars(self) == vars(other) + + def __ne__(self, other): + return not (self == other) + + def __contains__(self, key): + return key in self.__dict__ + + +class _ActionsContainer(object): + + def __init__(self, + description, + prefix_chars, + argument_default, + conflict_handler): + super(_ActionsContainer, self).__init__() + + self.description = description + self.argument_default = argument_default + self.prefix_chars = prefix_chars + self.conflict_handler = conflict_handler + + # set up registries + self._registries = {} + + # register actions + self.register('action', None, _StoreAction) + self.register('action', 'store', _StoreAction) + self.register('action', 'store_const', _StoreConstAction) + self.register('action', 'store_true', _StoreTrueAction) + self.register('action', 'store_false', _StoreFalseAction) + self.register('action', 'append', _AppendAction) + self.register('action', 'append_const', _AppendConstAction) + self.register('action', 'count', _CountAction) + self.register('action', 'help', _HelpAction) + self.register('action', 'version', _VersionAction) + self.register('action', 'parsers', _SubParsersAction) + + # raise an exception if the conflict handler is invalid + self._get_handler() + + # action storage + self._actions = [] + self._option_string_actions = {} + + # groups + self._action_groups = [] + self._mutually_exclusive_groups = [] + + # defaults storage + self._defaults = {} + + # determines whether an "option" looks like a negative number + self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') + + # whether or not there are any optionals that look like negative + # numbers -- uses a list so it can be shared and edited + self._has_negative_number_optionals = [] + + # ==================== + # Registration methods + # ==================== + def register(self, registry_name, value, object): + registry = self._registries.setdefault(registry_name, {}) + registry[value] = object + + def _registry_get(self, registry_name, value, default=None): + return self._registries[registry_name].get(value, default) + + # ================================== + # Namespace default accessor methods + # ================================== + def set_defaults(self, **kwargs): + self._defaults.update(kwargs) + + # if these defaults match any existing arguments, replace + # the previous default on the object with the new one + for action in self._actions: + if action.dest in kwargs: + action.default = kwargs[action.dest] + + def get_default(self, dest): + for action in self._actions: + if action.dest == dest and action.default is not None: + return action.default + return self._defaults.get(dest, None) + + + # ======================= + # Adding argument actions + # ======================= + def add_argument(self, *args, **kwargs): + """ + add_argument(dest, ..., name=value, ...) + add_argument(option_string, option_string, ..., name=value, ...) + """ + + # if no positional args are supplied or only one is supplied and + # it doesn't look like an option string, parse a positional + # argument + chars = self.prefix_chars + if not args or len(args) == 1 and args[0][0] not in chars: + if args and 'dest' in kwargs: + raise ValueError('dest supplied twice for positional argument') + kwargs = self._get_positional_kwargs(*args, **kwargs) + + # otherwise, we're adding an optional argument + else: + kwargs = self._get_optional_kwargs(*args, **kwargs) + + # if no default was supplied, use the parser-level default + if 'default' not in kwargs: + dest = kwargs['dest'] + if dest in self._defaults: + kwargs['default'] = self._defaults[dest] + elif self.argument_default is not None: + kwargs['default'] = self.argument_default + + # create the action object, and add it to the parser + action_class = self._pop_action_class(kwargs) + if not _callable(action_class): + raise ValueError('unknown action "%s"' % action_class) + action = action_class(**kwargs) + + # raise an error if the action type is not callable + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + raise ValueError('%r is not callable' % type_func) + + return self._add_action(action) + + def add_argument_group(self, *args, **kwargs): + group = _ArgumentGroup(self, *args, **kwargs) + self._action_groups.append(group) + return group + + def add_mutually_exclusive_group(self, **kwargs): + group = _MutuallyExclusiveGroup(self, **kwargs) + self._mutually_exclusive_groups.append(group) + return group + + def _add_action(self, action): + # resolve any conflicts + self._check_conflict(action) + + # add to actions list + self._actions.append(action) + action.container = self + + # index the action by any option strings it has + for option_string in action.option_strings: + self._option_string_actions[option_string] = action + + # set the flag if any option strings look like negative numbers + for option_string in action.option_strings: + if self._negative_number_matcher.match(option_string): + if not self._has_negative_number_optionals: + self._has_negative_number_optionals.append(True) + + # return the created action + return action + + def _remove_action(self, action): + self._actions.remove(action) + + def _add_container_actions(self, container): + # collect groups by titles + title_group_map = {} + for group in self._action_groups: + if group.title in title_group_map: + msg = _('cannot merge actions - two groups are named %r') + raise ValueError(msg % (group.title)) + title_group_map[group.title] = group + + # map each action to its group + group_map = {} + for group in container._action_groups: + + # if a group with the title exists, use that, otherwise + # create a new group matching the container's group + if group.title not in title_group_map: + title_group_map[group.title] = self.add_argument_group( + title=group.title, + description=group.description, + conflict_handler=group.conflict_handler) + + # map the actions to their new group + for action in group._group_actions: + group_map[action] = title_group_map[group.title] + + # add container's mutually exclusive groups + # NOTE: if add_mutually_exclusive_group ever gains title= and + # description= then this code will need to be expanded as above + for group in container._mutually_exclusive_groups: + mutex_group = self.add_mutually_exclusive_group( + required=group.required) + + # map the actions to their new mutex group + for action in group._group_actions: + group_map[action] = mutex_group + + # add all actions to this container or their group + for action in container._actions: + group_map.get(action, self)._add_action(action) + + def _get_positional_kwargs(self, dest, **kwargs): + # make sure required is not specified + if 'required' in kwargs: + msg = _("'required' is an invalid argument for positionals") + raise TypeError(msg) + + # mark positional arguments as required if at least one is + # always required + if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: + kwargs['required'] = True + if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: + kwargs['required'] = True + + # return the keyword arguments with no option strings + return dict(kwargs, dest=dest, option_strings=[]) + + def _get_optional_kwargs(self, *args, **kwargs): + # determine short and long option strings + option_strings = [] + long_option_strings = [] + for option_string in args: + # error on strings that don't start with an appropriate prefix + if not option_string[0] in self.prefix_chars: + msg = _('invalid option string %r: ' + 'must start with a character %r') + tup = option_string, self.prefix_chars + raise ValueError(msg % tup) + + # strings starting with two prefix characters are long options + option_strings.append(option_string) + if option_string[0] in self.prefix_chars: + if len(option_string) > 1: + if option_string[1] in self.prefix_chars: + long_option_strings.append(option_string) + + # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' + dest = kwargs.pop('dest', None) + if dest is None: + if long_option_strings: + dest_option_string = long_option_strings[0] + else: + dest_option_string = option_strings[0] + dest = dest_option_string.lstrip(self.prefix_chars) + if not dest: + msg = _('dest= is required for options like %r') + raise ValueError(msg % option_string) + dest = dest.replace('-', '_') + + # return the updated keyword arguments + return dict(kwargs, dest=dest, option_strings=option_strings) + + def _pop_action_class(self, kwargs, default=None): + action = kwargs.pop('action', default) + return self._registry_get('action', action, action) + + def _get_handler(self): + # determine function from conflict handler string + handler_func_name = '_handle_conflict_%s' % self.conflict_handler + try: + return getattr(self, handler_func_name) + except AttributeError: + msg = _('invalid conflict_resolution value: %r') + raise ValueError(msg % self.conflict_handler) + + def _check_conflict(self, action): + + # find all options that conflict with this option + confl_optionals = [] + for option_string in action.option_strings: + if option_string in self._option_string_actions: + confl_optional = self._option_string_actions[option_string] + confl_optionals.append((option_string, confl_optional)) + + # resolve any conflicts + if confl_optionals: + conflict_handler = self._get_handler() + conflict_handler(action, confl_optionals) + + def _handle_conflict_error(self, action, conflicting_actions): + message = _('conflicting option string(s): %s') + conflict_string = ', '.join([option_string + for option_string, action + in conflicting_actions]) + raise ArgumentError(action, message % conflict_string) + + def _handle_conflict_resolve(self, action, conflicting_actions): + + # remove all conflicting options + for option_string, action in conflicting_actions: + + # remove the conflicting option + action.option_strings.remove(option_string) + self._option_string_actions.pop(option_string, None) + + # if the option now has no option string, remove it from the + # container holding it + if not action.option_strings: + action.container._remove_action(action) + + +class _ArgumentGroup(_ActionsContainer): + + def __init__(self, container, title=None, description=None, **kwargs): + # add any missing keyword arguments by checking the container + update = kwargs.setdefault + update('conflict_handler', container.conflict_handler) + update('prefix_chars', container.prefix_chars) + update('argument_default', container.argument_default) + super_init = super(_ArgumentGroup, self).__init__ + super_init(description=description, **kwargs) + + # group attributes + self.title = title + self._group_actions = [] + + # share most attributes with the container + self._registries = container._registries + self._actions = container._actions + self._option_string_actions = container._option_string_actions + self._defaults = container._defaults + self._has_negative_number_optionals = \ + container._has_negative_number_optionals + + def _add_action(self, action): + action = super(_ArgumentGroup, self)._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + super(_ArgumentGroup, self)._remove_action(action) + self._group_actions.remove(action) + + +class _MutuallyExclusiveGroup(_ArgumentGroup): + + def __init__(self, container, required=False): + super(_MutuallyExclusiveGroup, self).__init__(container) + self.required = required + self._container = container + + def _add_action(self, action): + if action.required: + msg = _('mutually exclusive arguments must be optional') + raise ValueError(msg) + action = self._container._add_action(action) + self._group_actions.append(action) + return action + + def _remove_action(self, action): + self._container._remove_action(action) + self._group_actions.remove(action) + + +class ArgumentParser(_AttributeHolder, _ActionsContainer): + """Object for parsing command line strings into Python objects. + + Keyword Arguments: + - prog -- The name of the program (default: sys.argv[0]) + - usage -- A usage message (default: auto-generated from arguments) + - description -- A description of what the program does + - epilog -- Text following the argument descriptions + - parents -- Parsers whose arguments should be copied into this one + - formatter_class -- HelpFormatter class for printing help messages + - prefix_chars -- Characters that prefix optional arguments + - fromfile_prefix_chars -- Characters that prefix files containing + additional arguments + - argument_default -- The default value for all arguments + - conflict_handler -- String indicating how to handle conflicts + - add_help -- Add a -h/-help option + """ + + def __init__(self, + prog=None, + usage=None, + description=None, + epilog=None, + version=None, + parents=[], + formatter_class=HelpFormatter, + prefix_chars='-', + fromfile_prefix_chars=None, + argument_default=None, + conflict_handler='error', + add_help=True): + + if version is not None: + import warnings + warnings.warn( + """The "version" argument to ArgumentParser is deprecated. """ + """Please use """ + """"add_argument(..., action='version', version="N", ...)" """ + """instead""", DeprecationWarning) + + superinit = super(ArgumentParser, self).__init__ + superinit(description=description, + prefix_chars=prefix_chars, + argument_default=argument_default, + conflict_handler=conflict_handler) + + # default setting for prog + if prog is None: + prog = _os.path.basename(_sys.argv[0]) + + self.prog = prog + self.usage = usage + self.epilog = epilog + self.version = version + self.formatter_class = formatter_class + self.fromfile_prefix_chars = fromfile_prefix_chars + self.add_help = add_help + + add_group = self.add_argument_group + self._positionals = add_group(_('positional arguments')) + self._optionals = add_group(_('optional arguments')) + self._subparsers = None + + # register types + def identity(string): + return string + self.register('type', None, identity) + + # add help and version arguments if necessary + # (using explicit default to override global argument_default) + if self.add_help: + self.add_argument( + '-h', '--help', action='help', default=SUPPRESS, + help=_('show this help message and exit')) + if self.version: + self.add_argument( + '-v', '--version', action='version', default=SUPPRESS, + version=self.version, + help=_("show program's version number and exit")) + + # add parent arguments and defaults + for parent in parents: + self._add_container_actions(parent) + try: + defaults = parent._defaults + except AttributeError: + pass + else: + self._defaults.update(defaults) + + # ======================= + # Pretty __repr__ methods + # ======================= + def _get_kwargs(self): + names = [ + 'prog', + 'usage', + 'description', + 'version', + 'formatter_class', + 'conflict_handler', + 'add_help', + ] + return [(name, getattr(self, name)) for name in names] + + # ================================== + # Optional/Positional adding methods + # ================================== + def add_subparsers(self, **kwargs): + if self._subparsers is not None: + self.error(_('cannot have multiple subparser arguments')) + + # add the parser class to the arguments if it's not present + kwargs.setdefault('parser_class', type(self)) + + if 'title' in kwargs or 'description' in kwargs: + title = _(kwargs.pop('title', 'subcommands')) + description = _(kwargs.pop('description', None)) + self._subparsers = self.add_argument_group(title, description) + else: + self._subparsers = self._positionals + + # prog defaults to the usage message of this parser, skipping + # optional arguments and with no "usage:" prefix + if kwargs.get('prog') is None: + formatter = self._get_formatter() + positionals = self._get_positional_actions() + groups = self._mutually_exclusive_groups + formatter.add_usage(self.usage, positionals, groups, '') + kwargs['prog'] = formatter.format_help().strip() + + # create the parsers action and add it to the positionals list + parsers_class = self._pop_action_class(kwargs, 'parsers') + action = parsers_class(option_strings=[], **kwargs) + self._subparsers._add_action(action) + + # return the created parsers action + return action + + def _add_action(self, action): + if action.option_strings: + self._optionals._add_action(action) + else: + self._positionals._add_action(action) + return action + + def _get_optional_actions(self): + return [action + for action in self._actions + if action.option_strings] + + def _get_positional_actions(self): + return [action + for action in self._actions + if not action.option_strings] + + # ===================================== + # Command line argument parsing methods + # ===================================== + def parse_args(self, args=None, namespace=None): + args, argv = self.parse_known_args(args, namespace) + if argv: + msg = _('unrecognized arguments: %s') + self.error(msg % ' '.join(argv)) + return args + + def parse_known_args(self, args=None, namespace=None): + # args default to the system args + if args is None: + args = _sys.argv[1:] + + # default Namespace built from parser defaults + if namespace is None: + namespace = Namespace() + + # add any action defaults that aren't present + for action in self._actions: + if action.dest is not SUPPRESS: + if not hasattr(namespace, action.dest): + if action.default is not SUPPRESS: + default = action.default + if isinstance(action.default, _basestring): + default = self._get_value(action, default) + setattr(namespace, action.dest, default) + + # add any parser defaults that aren't present + for dest in self._defaults: + if not hasattr(namespace, dest): + setattr(namespace, dest, self._defaults[dest]) + + # parse the arguments and exit if there are any errors + try: + return self._parse_known_args(args, namespace) + except ArgumentError: + err = _sys.exc_info()[1] + self.error(str(err)) + + def _parse_known_args(self, arg_strings, namespace): + # replace arg strings that are file references + if self.fromfile_prefix_chars is not None: + arg_strings = self._read_args_from_files(arg_strings) + + # map all mutually exclusive arguments to the other arguments + # they can't occur with + action_conflicts = {} + for mutex_group in self._mutually_exclusive_groups: + group_actions = mutex_group._group_actions + for i, mutex_action in enumerate(mutex_group._group_actions): + conflicts = action_conflicts.setdefault(mutex_action, []) + conflicts.extend(group_actions[:i]) + conflicts.extend(group_actions[i + 1:]) + + # find all option indices, and determine the arg_string_pattern + # which has an 'O' if there is an option at an index, + # an 'A' if there is an argument, or a '-' if there is a '--' + option_string_indices = {} + arg_string_pattern_parts = [] + arg_strings_iter = iter(arg_strings) + for i, arg_string in enumerate(arg_strings_iter): + + # all args after -- are non-options + if arg_string == '--': + arg_string_pattern_parts.append('-') + for arg_string in arg_strings_iter: + arg_string_pattern_parts.append('A') + + # otherwise, add the arg to the arg strings + # and note the index if it was an option + else: + option_tuple = self._parse_optional(arg_string) + if option_tuple is None: + pattern = 'A' + else: + option_string_indices[i] = option_tuple + pattern = 'O' + arg_string_pattern_parts.append(pattern) + + # join the pieces together to form the pattern + arg_strings_pattern = ''.join(arg_string_pattern_parts) + + # converts arg strings to the appropriate and then takes the action + seen_actions = _set() + seen_non_default_actions = _set() + + def take_action(action, argument_strings, option_string=None): + seen_actions.add(action) + argument_values = self._get_values(action, argument_strings) + + # error if this argument is not allowed with other previously + # seen arguments, assuming that actions that use the default + # value don't really count as "present" + if argument_values is not action.default: + seen_non_default_actions.add(action) + for conflict_action in action_conflicts.get(action, []): + if conflict_action in seen_non_default_actions: + msg = _('not allowed with argument %s') + action_name = _get_action_name(conflict_action) + raise ArgumentError(action, msg % action_name) + + # take the action if we didn't receive a SUPPRESS value + # (e.g. from a default) + if argument_values is not SUPPRESS: + action(self, namespace, argument_values, option_string) + + # function to convert arg_strings into an optional action + def consume_optional(start_index): + + # get the optional identified at this index + option_tuple = option_string_indices[start_index] + action, option_string, explicit_arg = option_tuple + + # identify additional optionals in the same arg string + # (e.g. -xyz is the same as -x -y -z if no args are required) + match_argument = self._match_argument + action_tuples = [] + while True: + + # if we found no optional action, skip it + if action is None: + extras.append(arg_strings[start_index]) + return start_index + 1 + + # if there is an explicit argument, try to match the + # optional's string arguments to only this + if explicit_arg is not None: + arg_count = match_argument(action, 'A') + + # if the action is a single-dash option and takes no + # arguments, try to parse more single-dash options out + # of the tail of the option string + chars = self.prefix_chars + if arg_count == 0 and option_string[1] not in chars: + action_tuples.append((action, [], option_string)) + for char in self.prefix_chars: + option_string = char + explicit_arg[0] + explicit_arg = explicit_arg[1:] or None + optionals_map = self._option_string_actions + if option_string in optionals_map: + action = optionals_map[option_string] + break + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if the action expect exactly one argument, we've + # successfully matched the option; exit the loop + elif arg_count == 1: + stop = start_index + 1 + args = [explicit_arg] + action_tuples.append((action, args, option_string)) + break + + # error if a double-dash option did not use the + # explicit argument + else: + msg = _('ignored explicit argument %r') + raise ArgumentError(action, msg % explicit_arg) + + # if there is no explicit argument, try to match the + # optional's string arguments with the following strings + # if successful, exit the loop + else: + start = start_index + 1 + selected_patterns = arg_strings_pattern[start:] + arg_count = match_argument(action, selected_patterns) + stop = start + arg_count + args = arg_strings[start:stop] + action_tuples.append((action, args, option_string)) + break + + # add the Optional to the list and return the index at which + # the Optional's string args stopped + assert action_tuples + for action, args, option_string in action_tuples: + take_action(action, args, option_string) + return stop + + # the list of Positionals left to be parsed; this is modified + # by consume_positionals() + positionals = self._get_positional_actions() + + # function to convert arg_strings into positional actions + def consume_positionals(start_index): + # match as many Positionals as possible + match_partial = self._match_arguments_partial + selected_pattern = arg_strings_pattern[start_index:] + arg_counts = match_partial(positionals, selected_pattern) + + # slice off the appropriate arg strings for each Positional + # and add the Positional and its args to the list + for action, arg_count in zip(positionals, arg_counts): + args = arg_strings[start_index: start_index + arg_count] + start_index += arg_count + take_action(action, args) + + # slice off the Positionals that we just parsed and return the + # index at which the Positionals' string args stopped + positionals[:] = positionals[len(arg_counts):] + return start_index + + # consume Positionals and Optionals alternately, until we have + # passed the last option string + extras = [] + start_index = 0 + if option_string_indices: + max_option_string_index = max(option_string_indices) + else: + max_option_string_index = -1 + while start_index <= max_option_string_index: + + # consume any Positionals preceding the next option + next_option_string_index = min([ + index + for index in option_string_indices + if index >= start_index]) + if start_index != next_option_string_index: + positionals_end_index = consume_positionals(start_index) + + # only try to parse the next optional if we didn't consume + # the option string during the positionals parsing + if positionals_end_index > start_index: + start_index = positionals_end_index + continue + else: + start_index = positionals_end_index + + # if we consumed all the positionals we could and we're not + # at the index of an option string, there were extra arguments + if start_index not in option_string_indices: + strings = arg_strings[start_index:next_option_string_index] + extras.extend(strings) + start_index = next_option_string_index + + # consume the next optional and any arguments for it + start_index = consume_optional(start_index) + + # consume any positionals following the last Optional + stop_index = consume_positionals(start_index) + + # if we didn't consume all the argument strings, there were extras + extras.extend(arg_strings[stop_index:]) + + # if we didn't use all the Positional objects, there were too few + # arg strings supplied. + if positionals: + self.error(_('too few arguments')) + + # make sure all required actions were present + for action in self._actions: + if action.required: + if action not in seen_actions: + name = _get_action_name(action) + self.error(_('argument %s is required') % name) + + # make sure all required groups had one option present + for group in self._mutually_exclusive_groups: + if group.required: + for action in group._group_actions: + if action in seen_non_default_actions: + break + + # if no actions were used, report the error + else: + names = [_get_action_name(action) + for action in group._group_actions + if action.help is not SUPPRESS] + msg = _('one of the arguments %s is required') + self.error(msg % ' '.join(names)) + + # return the updated namespace and the extra arguments + return namespace, extras + + def _read_args_from_files(self, arg_strings): + # expand arguments referencing files + new_arg_strings = [] + for arg_string in arg_strings: + + # for regular arguments, just add them back into the list + if arg_string[0] not in self.fromfile_prefix_chars: + new_arg_strings.append(arg_string) + + # replace arguments referencing files with the file content + else: + try: + args_file = open(arg_string[1:]) + try: + arg_strings = [] + for arg_line in args_file.read().splitlines(): + for arg in self.convert_arg_line_to_args(arg_line): + arg_strings.append(arg) + arg_strings = self._read_args_from_files(arg_strings) + new_arg_strings.extend(arg_strings) + finally: + args_file.close() + except IOError: + err = _sys.exc_info()[1] + self.error(str(err)) + + # return the modified argument list + return new_arg_strings + + def convert_arg_line_to_args(self, arg_line): + return [arg_line] + + def _match_argument(self, action, arg_strings_pattern): + # match the pattern for this action to the arg strings + nargs_pattern = self._get_nargs_pattern(action) + match = _re.match(nargs_pattern, arg_strings_pattern) + + # raise an exception if we weren't able to find a match + if match is None: + nargs_errors = { + None: _('expected one argument'), + OPTIONAL: _('expected at most one argument'), + ONE_OR_MORE: _('expected at least one argument'), + } + default = _('expected %s argument(s)') % action.nargs + msg = nargs_errors.get(action.nargs, default) + raise ArgumentError(action, msg) + + # return the number of arguments matched + return len(match.group(1)) + + def _match_arguments_partial(self, actions, arg_strings_pattern): + # progressively shorten the actions list by slicing off the + # final actions until we find a match + result = [] + for i in range(len(actions), 0, -1): + actions_slice = actions[:i] + pattern = ''.join([self._get_nargs_pattern(action) + for action in actions_slice]) + match = _re.match(pattern, arg_strings_pattern) + if match is not None: + result.extend([len(string) for string in match.groups()]) + break + + # return the list of arg string counts + return result + + def _parse_optional(self, arg_string): + # if it's an empty string, it was meant to be a positional + if not arg_string: + return None + + # if it doesn't start with a prefix, it was meant to be positional + if not arg_string[0] in self.prefix_chars: + return None + + # if the option string is present in the parser, return the action + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + + # if it's just a single character, it was meant to be positional + if len(arg_string) == 1: + return None + + # if the option string before the "=" is present, return the action + if '=' in arg_string: + option_string, explicit_arg = arg_string.split('=', 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + + # search through all possible prefixes of the option string + # and all actions in the parser for possible interpretations + option_tuples = self._get_option_tuples(arg_string) + + # if multiple actions match, the option string was ambiguous + if len(option_tuples) > 1: + options = ', '.join([option_string + for action, option_string, explicit_arg in option_tuples]) + tup = arg_string, options + self.error(_('ambiguous option: %s could match %s') % tup) + + # if exactly one action matched, this segmentation is good, + # so return the parsed action + elif len(option_tuples) == 1: + option_tuple, = option_tuples + return option_tuple + + # if it was not found as an option, but it looks like a negative + # number, it was meant to be positional + # unless there are negative-number-like options + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + + # if it contains a space, it was meant to be a positional + if ' ' in arg_string: + return None + + # it was meant to be an optional but there is no such option + # in this parser (though it might be a valid option in a subparser) + return None, arg_string, None + + def _get_option_tuples(self, option_string): + result = [] + + # option strings starting with two prefix characters are only + # split at the '=' + chars = self.prefix_chars + if option_string[0] in chars and option_string[1] in chars: + if '=' in option_string: + option_prefix, explicit_arg = option_string.split('=', 1) + else: + option_prefix = option_string + explicit_arg = None + for option_string in self._option_string_actions: + if option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # single character options can be concatenated with their arguments + # but multiple character options always have to have their argument + # separate + elif option_string[0] in chars and option_string[1] not in chars: + option_prefix = option_string + explicit_arg = None + short_option_prefix = option_string[:2] + short_explicit_arg = option_string[2:] + + for option_string in self._option_string_actions: + if option_string == short_option_prefix: + action = self._option_string_actions[option_string] + tup = action, option_string, short_explicit_arg + result.append(tup) + elif option_string.startswith(option_prefix): + action = self._option_string_actions[option_string] + tup = action, option_string, explicit_arg + result.append(tup) + + # shouldn't ever get here + else: + self.error(_('unexpected option string: %s') % option_string) + + # return the collected option tuples + return result + + def _get_nargs_pattern(self, action): + # in all examples below, we have to allow for '--' args + # which are represented as '-' in the pattern + nargs = action.nargs + + # the default (None) is assumed to be a single argument + if nargs is None: + nargs_pattern = '(-*A-*)' + + # allow zero or one arguments + elif nargs == OPTIONAL: + nargs_pattern = '(-*A?-*)' + + # allow zero or more arguments + elif nargs == ZERO_OR_MORE: + nargs_pattern = '(-*[A-]*)' + + # allow one or more arguments + elif nargs == ONE_OR_MORE: + nargs_pattern = '(-*A[A-]*)' + + # allow any number of options or arguments + elif nargs == REMAINDER: + nargs_pattern = '([-AO]*)' + + # allow one argument followed by any number of options or arguments + elif nargs == PARSER: + nargs_pattern = '(-*A[-AO]*)' + + # all others should be integers + else: + nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) + + # if this is an optional action, -- is not allowed + if action.option_strings: + nargs_pattern = nargs_pattern.replace('-*', '') + nargs_pattern = nargs_pattern.replace('-', '') + + # return the pattern + return nargs_pattern + + # ======================== + # Value conversion methods + # ======================== + def _get_values(self, action, arg_strings): + # for everything but PARSER args, strip out '--' + if action.nargs not in [PARSER, REMAINDER]: + arg_strings = [s for s in arg_strings if s != '--'] + + # optional argument produces a default when not present + if not arg_strings and action.nargs == OPTIONAL: + if action.option_strings: + value = action.const + else: + value = action.default + if isinstance(value, _basestring): + value = self._get_value(action, value) + self._check_value(action, value) + + # when nargs='*' on a positional, if there were no command-line + # args, use the default if it is anything other than None + elif (not arg_strings and action.nargs == ZERO_OR_MORE and + not action.option_strings): + if action.default is not None: + value = action.default + else: + value = arg_strings + self._check_value(action, value) + + # single argument or optional argument produces a single value + elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: + arg_string, = arg_strings + value = self._get_value(action, arg_string) + self._check_value(action, value) + + # REMAINDER arguments convert all values, checking none + elif action.nargs == REMAINDER: + value = [self._get_value(action, v) for v in arg_strings] + + # PARSER arguments convert all values, but check only the first + elif action.nargs == PARSER: + value = [self._get_value(action, v) for v in arg_strings] + self._check_value(action, value[0]) + + # all other types of nargs produce a list + else: + value = [self._get_value(action, v) for v in arg_strings] + for v in value: + self._check_value(action, v) + + # return the converted value + return value + + def _get_value(self, action, arg_string): + type_func = self._registry_get('type', action.type, action.type) + if not _callable(type_func): + msg = _('%r is not callable') + raise ArgumentError(action, msg % type_func) + + # convert the value to the appropriate type + try: + result = type_func(arg_string) + + # ArgumentTypeErrors indicate errors + except ArgumentTypeError: + name = getattr(action.type, '__name__', repr(action.type)) + msg = str(_sys.exc_info()[1]) + raise ArgumentError(action, msg) + + # TypeErrors or ValueErrors also indicate errors + except (TypeError, ValueError): + name = getattr(action.type, '__name__', repr(action.type)) + msg = _('invalid %s value: %r') + raise ArgumentError(action, msg % (name, arg_string)) + + # return the converted value + return result + + def _check_value(self, action, value): + # converted value must be one of the choices (if specified) + if action.choices is not None and value not in action.choices: + tup = value, ', '.join(map(repr, action.choices)) + msg = _('invalid choice: %r (choose from %s)') % tup + raise ArgumentError(action, msg) + + # ======================= + # Help-formatting methods + # ======================= + def format_usage(self): + formatter = self._get_formatter() + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + return formatter.format_help() + + def format_help(self): + formatter = self._get_formatter() + + # usage + formatter.add_usage(self.usage, self._actions, + self._mutually_exclusive_groups) + + # description + formatter.add_text(self.description) + + # positionals, optionals and user-defined groups + for action_group in self._action_groups: + formatter.start_section(action_group.title) + formatter.add_text(action_group.description) + formatter.add_arguments(action_group._group_actions) + formatter.end_section() + + # epilog + formatter.add_text(self.epilog) + + # determine help from format above + return formatter.format_help() + + def format_version(self): + import warnings + warnings.warn( + 'The format_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + formatter = self._get_formatter() + formatter.add_text(self.version) + return formatter.format_help() + + def _get_formatter(self): + return self.formatter_class(prog=self.prog) + + # ===================== + # Help-printing methods + # ===================== + def print_usage(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_usage(), file) + + def print_help(self, file=None): + if file is None: + file = _sys.stdout + self._print_message(self.format_help(), file) + + def print_version(self, file=None): + import warnings + warnings.warn( + 'The print_version method is deprecated -- the "version" ' + 'argument to ArgumentParser is no longer supported.', + DeprecationWarning) + self._print_message(self.format_version(), file) + + def _print_message(self, message, file=None): + if message: + if file is None: + file = _sys.stderr + file.write(message) + + # =============== + # Exiting methods + # =============== + def exit(self, status=0, message=None): + if message: + self._print_message(message, _sys.stderr) + _sys.exit(status) + + def error(self, message): + """error(message: string) + + Prints a usage message incorporating the message to stderr and + exits. + + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(_sys.stderr) + self.exit(2, _('%s: error: %s\n') % (self.prog, message)) diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 61f8e464eec..849facb3e7c 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1,4206 +1,4210 @@ -# -*- coding: utf-8 -*- - -# Copyright © 2006-2009 Steven J. Bethard . -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import codecs -import os -import shutil -import sys -import textwrap -import tempfile -import unittest -import argparse - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - -try: - set -except NameError: - from sets import Set as set - -try: - sorted -except NameError: - - def sorted(iterable, reverse=False): - result = list(iterable) - result.sort() - if reverse: - result.reverse() - return result - -# silence Python 2.6 buggy warnings about Exception.message -if sys.version_info[:2] == (2, 6): - import warnings - warnings.filterwarnings( - action='ignore', - message='BaseException.message has been deprecated as of Python 2.6', - category=DeprecationWarning) - -# silence warnings about version argument - these are expected -import warnings -warnings.filterwarnings( - action='ignore', - message='The "version" argument to ArgumentParser is deprecated.', - category=DeprecationWarning) -warnings.filterwarnings( - action='ignore', - message='The format_version method is deprecated', - category=DeprecationWarning) -warnings.filterwarnings( - action='ignore', - message='The print_version method is deprecated', - category=DeprecationWarning) - - -class TestCase(unittest.TestCase): - - def assertEqual(self, obj1, obj2): - if obj1 != obj2: - print('') - print(repr(obj1)) - print(repr(obj2)) - print(obj1) - print(obj2) - super(TestCase, self).assertEqual(obj1, obj2) - - -class TempDirMixin(object): - - def setUp(self): - self.temp_dir = tempfile.mkdtemp() - self.old_dir = os.getcwd() - os.chdir(self.temp_dir) - - def tearDown(self): - os.chdir(self.old_dir) - while True: - try: - shutil.rmtree(self.temp_dir) - except WindowsError: - continue - else: - break - - -class Sig(object): - - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - - -class NS(object): - - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __repr__(self): - sorted_items = sorted(self.__dict__.items()) - kwarg_str = ', '.join(['%s=%r' % tup for tup in sorted_items]) - return '%s(%s)' % (type(self).__name__, kwarg_str) - - def __eq__(self, other): - return vars(self) == vars(other) - - def __ne__(self, other): - return not (self == other) - - -class ArgumentParserError(Exception): - - def __init__(self, message, stdout=None, stderr=None, error_code=None): - Exception.__init__(self, message, stdout, stderr) - self.message = message - self.stdout = stdout - self.stderr = stderr - self.error_code = error_code - - -def stderr_to_parser_error(parse_args, *args, **kwargs): - # if this is being called recursively and stderr or stdout is already being - # redirected, simply call the function and let the enclosing function - # catch the exception - if isinstance(sys.stderr, StringIO) or isinstance(sys.stdout, StringIO): - return parse_args(*args, **kwargs) - - # if this is not being called recursively, redirect stderr and - # use it as the ArgumentParserError message - old_stdout = sys.stdout - old_stderr = sys.stderr - sys.stdout = StringIO() - sys.stderr = StringIO() - try: - try: - result = parse_args(*args, **kwargs) - for key in list(vars(result)): - if getattr(result, key) is sys.stdout: - setattr(result, key, old_stdout) - if getattr(result, key) is sys.stderr: - setattr(result, key, old_stderr) - return result - except SystemExit: - code = sys.exc_info()[1].code - stdout = sys.stdout.getvalue() - stderr = sys.stderr.getvalue() - raise ArgumentParserError("SystemExit", stdout, stderr, code) - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - - -class ErrorRaisingArgumentParser(argparse.ArgumentParser): - - def parse_args(self, *args, **kwargs): - parse_args = super(ErrorRaisingArgumentParser, self).parse_args - return stderr_to_parser_error(parse_args, *args, **kwargs) - - def exit(self, *args, **kwargs): - exit = super(ErrorRaisingArgumentParser, self).exit - return stderr_to_parser_error(exit, *args, **kwargs) - - def error(self, *args, **kwargs): - error = super(ErrorRaisingArgumentParser, self).error - return stderr_to_parser_error(error, *args, **kwargs) - - -class ParserTesterMetaclass(type): - """Adds parser tests using the class attributes. - - Classes of this type should specify the following attributes: - - argument_signatures -- a list of Sig objects which specify - the signatures of Argument objects to be created - failures -- a list of args lists that should cause the parser - to fail - successes -- a list of (initial_args, options, remaining_args) tuples - where initial_args specifies the string args to be parsed, - options is a dict that should match the vars() of the options - parsed out of initial_args, and remaining_args should be any - remaining unparsed arguments - """ - - def __init__(cls, name, bases, bodydict): - if name == 'ParserTestCase': - return - - # default parser signature is empty - if not hasattr(cls, 'parser_signature'): - cls.parser_signature = Sig() - if not hasattr(cls, 'parser_class'): - cls.parser_class = ErrorRaisingArgumentParser - - # --------------------------------------- - # functions for adding optional arguments - # --------------------------------------- - def no_groups(parser, argument_signatures): - """Add all arguments directly to the parser""" - for sig in argument_signatures: - parser.add_argument(*sig.args, **sig.kwargs) - - def one_group(parser, argument_signatures): - """Add all arguments under a single group in the parser""" - group = parser.add_argument_group('foo') - for sig in argument_signatures: - group.add_argument(*sig.args, **sig.kwargs) - - def many_groups(parser, argument_signatures): - """Add each argument in its own group to the parser""" - for i, sig in enumerate(argument_signatures): - group = parser.add_argument_group('foo:%i' % i) - group.add_argument(*sig.args, **sig.kwargs) - - # -------------------------- - # functions for parsing args - # -------------------------- - def listargs(parser, args): - """Parse the args by passing in a list""" - return parser.parse_args(args) - - def sysargs(parser, args): - """Parse the args by defaulting to sys.argv""" - old_sys_argv = sys.argv - sys.argv = [old_sys_argv[0]] + args - try: - return parser.parse_args() - finally: - sys.argv = old_sys_argv - - # class that holds the combination of one optional argument - # addition method and one arg parsing method - class AddTests(object): - - def __init__(self, tester_cls, add_arguments, parse_args): - self._add_arguments = add_arguments - self._parse_args = parse_args - - add_arguments_name = self._add_arguments.__name__ - parse_args_name = self._parse_args.__name__ - for test_func in [self.test_failures, self.test_successes]: - func_name = test_func.__name__ - names = func_name, add_arguments_name, parse_args_name - test_name = '_'.join(names) - - def wrapper(self, test_func=test_func): - test_func(self) - try: - wrapper.__name__ = test_name - except TypeError: - pass - setattr(tester_cls, test_name, wrapper) - - def _get_parser(self, tester): - args = tester.parser_signature.args - kwargs = tester.parser_signature.kwargs - parser = tester.parser_class(*args, **kwargs) - self._add_arguments(parser, tester.argument_signatures) - return parser - - def test_failures(self, tester): - parser = self._get_parser(tester) - for args_str in tester.failures: - args = args_str.split() - raises = tester.assertRaises - raises(ArgumentParserError, parser.parse_args, args) - - def test_successes(self, tester): - parser = self._get_parser(tester) - for args, expected_ns in tester.successes: - if isinstance(args, str): - args = args.split() - result_ns = self._parse_args(parser, args) - tester.assertEqual(expected_ns, result_ns) - - # add tests for each combination of an optionals adding method - # and an arg parsing method - for add_arguments in [no_groups, one_group, many_groups]: - for parse_args in [listargs, sysargs]: - AddTests(cls, add_arguments, parse_args) - -bases = TestCase, -ParserTestCase = ParserTesterMetaclass('ParserTestCase', bases, {}) - -# =============== -# Optionals tests -# =============== - -class TestOptionalsSingleDash(ParserTestCase): - """Test an Optional with a single-dash option string""" - - argument_signatures = [Sig('-x')] - failures = ['-x', 'a', '--foo', '-x --foo', '-x -y'] - successes = [ - ('', NS(x=None)), - ('-x a', NS(x='a')), - ('-xa', NS(x='a')), - ('-x -1', NS(x='-1')), - ('-x-1', NS(x='-1')), - ] - - -class TestOptionalsSingleDashCombined(ParserTestCase): - """Test an Optional with a single-dash option string""" - - argument_signatures = [ - Sig('-x', action='store_true'), - Sig('-yyy', action='store_const', const=42), - Sig('-z'), - ] - failures = ['a', '--foo', '-xa', '-x --foo', '-x -z', '-z -x', - '-yx', '-yz a', '-yyyx', '-yyyza', '-xyza'] - successes = [ - ('', NS(x=False, yyy=None, z=None)), - ('-x', NS(x=True, yyy=None, z=None)), - ('-za', NS(x=False, yyy=None, z='a')), - ('-z a', NS(x=False, yyy=None, z='a')), - ('-xza', NS(x=True, yyy=None, z='a')), - ('-xz a', NS(x=True, yyy=None, z='a')), - ('-x -za', NS(x=True, yyy=None, z='a')), - ('-x -z a', NS(x=True, yyy=None, z='a')), - ('-y', NS(x=False, yyy=42, z=None)), - ('-yyy', NS(x=False, yyy=42, z=None)), - ('-x -yyy -za', NS(x=True, yyy=42, z='a')), - ('-x -yyy -z a', NS(x=True, yyy=42, z='a')), - ] - - -class TestOptionalsSingleDashLong(ParserTestCase): - """Test an Optional with a multi-character single-dash option string""" - - argument_signatures = [Sig('-foo')] - failures = ['-foo', 'a', '--foo', '-foo --foo', '-foo -y', '-fooa'] - successes = [ - ('', NS(foo=None)), - ('-foo a', NS(foo='a')), - ('-foo -1', NS(foo='-1')), - ('-fo a', NS(foo='a')), - ('-f a', NS(foo='a')), - ] - - -class TestOptionalsSingleDashSubsetAmbiguous(ParserTestCase): - """Test Optionals where option strings are subsets of each other""" - - argument_signatures = [Sig('-f'), Sig('-foobar'), Sig('-foorab')] - failures = ['-f', '-foo', '-fo', '-foo b', '-foob', '-fooba', '-foora'] - successes = [ - ('', NS(f=None, foobar=None, foorab=None)), - ('-f a', NS(f='a', foobar=None, foorab=None)), - ('-fa', NS(f='a', foobar=None, foorab=None)), - ('-foa', NS(f='oa', foobar=None, foorab=None)), - ('-fooa', NS(f='ooa', foobar=None, foorab=None)), - ('-foobar a', NS(f=None, foobar='a', foorab=None)), - ('-foorab a', NS(f=None, foobar=None, foorab='a')), - ] - - -class TestOptionalsSingleDashAmbiguous(ParserTestCase): - """Test Optionals that partially match but are not subsets""" - - argument_signatures = [Sig('-foobar'), Sig('-foorab')] - failures = ['-f', '-f a', '-fa', '-foa', '-foo', '-fo', '-foo b'] - successes = [ - ('', NS(foobar=None, foorab=None)), - ('-foob a', NS(foobar='a', foorab=None)), - ('-foor a', NS(foobar=None, foorab='a')), - ('-fooba a', NS(foobar='a', foorab=None)), - ('-foora a', NS(foobar=None, foorab='a')), - ('-foobar a', NS(foobar='a', foorab=None)), - ('-foorab a', NS(foobar=None, foorab='a')), - ] - - -class TestOptionalsNumeric(ParserTestCase): - """Test an Optional with a short opt string""" - - argument_signatures = [Sig('-1', dest='one')] - failures = ['-1', 'a', '-1 --foo', '-1 -y', '-1 -1', '-1 -2'] - successes = [ - ('', NS(one=None)), - ('-1 a', NS(one='a')), - ('-1a', NS(one='a')), - ('-1-2', NS(one='-2')), - ] - - -class TestOptionalsDoubleDash(ParserTestCase): - """Test an Optional with a double-dash option string""" - - argument_signatures = [Sig('--foo')] - failures = ['--foo', '-f', '-f a', 'a', '--foo -x', '--foo --bar'] - successes = [ - ('', NS(foo=None)), - ('--foo a', NS(foo='a')), - ('--foo=a', NS(foo='a')), - ('--foo -2.5', NS(foo='-2.5')), - ('--foo=-2.5', NS(foo='-2.5')), - ] - - -class TestOptionalsDoubleDashPartialMatch(ParserTestCase): - """Tests partial matching with a double-dash option string""" - - argument_signatures = [ - Sig('--badger', action='store_true'), - Sig('--bat'), - ] - failures = ['--bar', '--b', '--ba', '--b=2', '--ba=4', '--badge 5'] - successes = [ - ('', NS(badger=False, bat=None)), - ('--bat X', NS(badger=False, bat='X')), - ('--bad', NS(badger=True, bat=None)), - ('--badg', NS(badger=True, bat=None)), - ('--badge', NS(badger=True, bat=None)), - ('--badger', NS(badger=True, bat=None)), - ] - - -class TestOptionalsDoubleDashPrefixMatch(ParserTestCase): - """Tests when one double-dash option string is a prefix of another""" - - argument_signatures = [ - Sig('--badger', action='store_true'), - Sig('--ba'), - ] - failures = ['--bar', '--b', '--ba', '--b=2', '--badge 5'] - successes = [ - ('', NS(badger=False, ba=None)), - ('--ba X', NS(badger=False, ba='X')), - ('--ba=X', NS(badger=False, ba='X')), - ('--bad', NS(badger=True, ba=None)), - ('--badg', NS(badger=True, ba=None)), - ('--badge', NS(badger=True, ba=None)), - ('--badger', NS(badger=True, ba=None)), - ] - - -class TestOptionalsSingleDoubleDash(ParserTestCase): - """Test an Optional with single- and double-dash option strings""" - - argument_signatures = [ - Sig('-f', action='store_true'), - Sig('--bar'), - Sig('-baz', action='store_const', const=42), - ] - failures = ['--bar', '-fbar', '-fbaz', '-bazf', '-b B', 'B'] - successes = [ - ('', NS(f=False, bar=None, baz=None)), - ('-f', NS(f=True, bar=None, baz=None)), - ('--ba B', NS(f=False, bar='B', baz=None)), - ('-f --bar B', NS(f=True, bar='B', baz=None)), - ('-f -b', NS(f=True, bar=None, baz=42)), - ('-ba -f', NS(f=True, bar=None, baz=42)), - ] - - -class TestOptionalsAlternatePrefixChars(ParserTestCase): - """Test an Optional with a double-dash option string""" - - parser_signature = Sig(prefix_chars='+:/', add_help=False) - argument_signatures = [ - Sig('+f', action='store_true'), - Sig('::bar'), - Sig('/baz', action='store_const', const=42), - ] - failures = ['--bar', '-fbar', '-b B', 'B', '-f', '--bar B', '-baz'] - successes = [ - ('', NS(f=False, bar=None, baz=None)), - ('+f', NS(f=True, bar=None, baz=None)), - ('::ba B', NS(f=False, bar='B', baz=None)), - ('+f ::bar B', NS(f=True, bar='B', baz=None)), - ('+f /b', NS(f=True, bar=None, baz=42)), - ('/ba +f', NS(f=True, bar=None, baz=42)), - ] - - -class TestOptionalsShortLong(ParserTestCase): - """Test a combination of single- and double-dash option strings""" - - argument_signatures = [ - Sig('-v', '--verbose', '-n', '--noisy', action='store_true'), - ] - failures = ['--x --verbose', '-N', 'a', '-v x'] - successes = [ - ('', NS(verbose=False)), - ('-v', NS(verbose=True)), - ('--verbose', NS(verbose=True)), - ('-n', NS(verbose=True)), - ('--noisy', NS(verbose=True)), - ] - - -class TestOptionalsDest(ParserTestCase): - """Tests various means of setting destination""" - - argument_signatures = [Sig('--foo-bar'), Sig('--baz', dest='zabbaz')] - failures = ['a'] - successes = [ - ('--foo-bar f', NS(foo_bar='f', zabbaz=None)), - ('--baz g', NS(foo_bar=None, zabbaz='g')), - ('--foo-bar h --baz i', NS(foo_bar='h', zabbaz='i')), - ('--baz j --foo-bar k', NS(foo_bar='k', zabbaz='j')), - ] - - -class TestOptionalsDefault(ParserTestCase): - """Tests specifying a default for an Optional""" - - argument_signatures = [Sig('-x'), Sig('-y', default=42)] - failures = ['a'] - successes = [ - ('', NS(x=None, y=42)), - ('-xx', NS(x='x', y=42)), - ('-yy', NS(x=None, y='y')), - ] - - -class TestOptionalsNargsDefault(ParserTestCase): - """Tests not specifying the number of args for an Optional""" - - argument_signatures = [Sig('-x')] - failures = ['a', '-x'] - successes = [ - ('', NS(x=None)), - ('-x a', NS(x='a')), - ] - - -class TestOptionalsNargs1(ParserTestCase): - """Tests specifying the 1 arg for an Optional""" - - argument_signatures = [Sig('-x', nargs=1)] - failures = ['a', '-x'] - successes = [ - ('', NS(x=None)), - ('-x a', NS(x=['a'])), - ] - - -class TestOptionalsNargs3(ParserTestCase): - """Tests specifying the 3 args for an Optional""" - - argument_signatures = [Sig('-x', nargs=3)] - failures = ['a', '-x', '-x a', '-x a b', 'a -x', 'a -x b'] - successes = [ - ('', NS(x=None)), - ('-x a b c', NS(x=['a', 'b', 'c'])), - ] - - -class TestOptionalsNargsOptional(ParserTestCase): - """Tests specifying an Optional arg for an Optional""" - - argument_signatures = [ - Sig('-w', nargs='?'), - Sig('-x', nargs='?', const=42), - Sig('-y', nargs='?', default='spam'), - Sig('-z', nargs='?', type=int, const='42', default='84'), - ] - failures = ['2'] - successes = [ - ('', NS(w=None, x=None, y='spam', z=84)), - ('-w', NS(w=None, x=None, y='spam', z=84)), - ('-w 2', NS(w='2', x=None, y='spam', z=84)), - ('-x', NS(w=None, x=42, y='spam', z=84)), - ('-x 2', NS(w=None, x='2', y='spam', z=84)), - ('-y', NS(w=None, x=None, y=None, z=84)), - ('-y 2', NS(w=None, x=None, y='2', z=84)), - ('-z', NS(w=None, x=None, y='spam', z=42)), - ('-z 2', NS(w=None, x=None, y='spam', z=2)), - ] - - -class TestOptionalsNargsZeroOrMore(ParserTestCase): - """Tests specifying an args for an Optional that accepts zero or more""" - - argument_signatures = [ - Sig('-x', nargs='*'), - Sig('-y', nargs='*', default='spam'), - ] - failures = ['a'] - successes = [ - ('', NS(x=None, y='spam')), - ('-x', NS(x=[], y='spam')), - ('-x a', NS(x=['a'], y='spam')), - ('-x a b', NS(x=['a', 'b'], y='spam')), - ('-y', NS(x=None, y=[])), - ('-y a', NS(x=None, y=['a'])), - ('-y a b', NS(x=None, y=['a', 'b'])), - ] - - -class TestOptionalsNargsOneOrMore(ParserTestCase): - """Tests specifying an args for an Optional that accepts one or more""" - - argument_signatures = [ - Sig('-x', nargs='+'), - Sig('-y', nargs='+', default='spam'), - ] - failures = ['a', '-x', '-y', 'a -x', 'a -y b'] - successes = [ - ('', NS(x=None, y='spam')), - ('-x a', NS(x=['a'], y='spam')), - ('-x a b', NS(x=['a', 'b'], y='spam')), - ('-y a', NS(x=None, y=['a'])), - ('-y a b', NS(x=None, y=['a', 'b'])), - ] - - -class TestOptionalsChoices(ParserTestCase): - """Tests specifying the choices for an Optional""" - - argument_signatures = [ - Sig('-f', choices='abc'), - Sig('-g', type=int, choices=range(5))] - failures = ['a', '-f d', '-fad', '-ga', '-g 6'] - successes = [ - ('', NS(f=None, g=None)), - ('-f a', NS(f='a', g=None)), - ('-f c', NS(f='c', g=None)), - ('-g 0', NS(f=None, g=0)), - ('-g 03', NS(f=None, g=3)), - ('-fb -g4', NS(f='b', g=4)), - ] - - -class TestOptionalsRequired(ParserTestCase): - """Tests the an optional action that is required""" - - argument_signatures = [ - Sig('-x', type=int, required=True), - ] - failures = ['a', ''] - successes = [ - ('-x 1', NS(x=1)), - ('-x42', NS(x=42)), - ] - - -class TestOptionalsActionStore(ParserTestCase): - """Tests the store action for an Optional""" - - argument_signatures = [Sig('-x', action='store')] - failures = ['a', 'a -x'] - successes = [ - ('', NS(x=None)), - ('-xfoo', NS(x='foo')), - ] - - -class TestOptionalsActionStoreConst(ParserTestCase): - """Tests the store_const action for an Optional""" - - argument_signatures = [Sig('-y', action='store_const', const=object)] - failures = ['a'] - successes = [ - ('', NS(y=None)), - ('-y', NS(y=object)), - ] - - -class TestOptionalsActionStoreFalse(ParserTestCase): - """Tests the store_false action for an Optional""" - - argument_signatures = [Sig('-z', action='store_false')] - failures = ['a', '-za', '-z a'] - successes = [ - ('', NS(z=True)), - ('-z', NS(z=False)), - ] - - -class TestOptionalsActionStoreTrue(ParserTestCase): - """Tests the store_true action for an Optional""" - - argument_signatures = [Sig('--apple', action='store_true')] - failures = ['a', '--apple=b', '--apple b'] - successes = [ - ('', NS(apple=False)), - ('--apple', NS(apple=True)), - ] - - -class TestOptionalsActionAppend(ParserTestCase): - """Tests the append action for an Optional""" - - argument_signatures = [Sig('--baz', action='append')] - failures = ['a', '--baz', 'a --baz', '--baz a b'] - successes = [ - ('', NS(baz=None)), - ('--baz a', NS(baz=['a'])), - ('--baz a --baz b', NS(baz=['a', 'b'])), - ] - - -class TestOptionalsActionAppendWithDefault(ParserTestCase): - """Tests the append action for an Optional""" - - argument_signatures = [Sig('--baz', action='append', default=['X'])] - failures = ['a', '--baz', 'a --baz', '--baz a b'] - successes = [ - ('', NS(baz=['X'])), - ('--baz a', NS(baz=['X', 'a'])), - ('--baz a --baz b', NS(baz=['X', 'a', 'b'])), - ] - - -class TestOptionalsActionAppendConst(ParserTestCase): - """Tests the append_const action for an Optional""" - - argument_signatures = [ - Sig('-b', action='append_const', const=Exception), - Sig('-c', action='append', dest='b'), - ] - failures = ['a', '-c', 'a -c', '-bx', '-b x'] - successes = [ - ('', NS(b=None)), - ('-b', NS(b=[Exception])), - ('-b -cx -b -cyz', NS(b=[Exception, 'x', Exception, 'yz'])), - ] - - -class TestOptionalsActionAppendConstWithDefault(ParserTestCase): - """Tests the append_const action for an Optional""" - - argument_signatures = [ - Sig('-b', action='append_const', const=Exception, default=['X']), - Sig('-c', action='append', dest='b'), - ] - failures = ['a', '-c', 'a -c', '-bx', '-b x'] - successes = [ - ('', NS(b=['X'])), - ('-b', NS(b=['X', Exception])), - ('-b -cx -b -cyz', NS(b=['X', Exception, 'x', Exception, 'yz'])), - ] - - -class TestOptionalsActionCount(ParserTestCase): - """Tests the count action for an Optional""" - - argument_signatures = [Sig('-x', action='count')] - failures = ['a', '-x a', '-x b', '-x a -x b'] - successes = [ - ('', NS(x=None)), - ('-x', NS(x=1)), - ] - - -# ================ -# Positional tests -# ================ - -class TestPositionalsNargsNone(ParserTestCase): - """Test a Positional that doesn't specify nargs""" - - argument_signatures = [Sig('foo')] - failures = ['', '-x', 'a b'] - successes = [ - ('a', NS(foo='a')), - ] - - -class TestPositionalsNargs1(ParserTestCase): - """Test a Positional that specifies an nargs of 1""" - - argument_signatures = [Sig('foo', nargs=1)] - failures = ['', '-x', 'a b'] - successes = [ - ('a', NS(foo=['a'])), - ] - - -class TestPositionalsNargs2(ParserTestCase): - """Test a Positional that specifies an nargs of 2""" - - argument_signatures = [Sig('foo', nargs=2)] - failures = ['', 'a', '-x', 'a b c'] - successes = [ - ('a b', NS(foo=['a', 'b'])), - ] - - -class TestPositionalsNargsZeroOrMore(ParserTestCase): - """Test a Positional that specifies unlimited nargs""" - - argument_signatures = [Sig('foo', nargs='*')] - failures = ['-x'] - successes = [ - ('', NS(foo=[])), - ('a', NS(foo=['a'])), - ('a b', NS(foo=['a', 'b'])), - ] - - -class TestPositionalsNargsZeroOrMoreDefault(ParserTestCase): - """Test a Positional that specifies unlimited nargs and a default""" - - argument_signatures = [Sig('foo', nargs='*', default='bar')] - failures = ['-x'] - successes = [ - ('', NS(foo='bar')), - ('a', NS(foo=['a'])), - ('a b', NS(foo=['a', 'b'])), - ] - - -class TestPositionalsNargsOneOrMore(ParserTestCase): - """Test a Positional that specifies one or more nargs""" - - argument_signatures = [Sig('foo', nargs='+')] - failures = ['', '-x'] - successes = [ - ('a', NS(foo=['a'])), - ('a b', NS(foo=['a', 'b'])), - ] - - -class TestPositionalsNargsOptional(ParserTestCase): - """Tests an Optional Positional""" - - argument_signatures = [Sig('foo', nargs='?')] - failures = ['-x', 'a b'] - successes = [ - ('', NS(foo=None)), - ('a', NS(foo='a')), - ] - - -class TestPositionalsNargsOptionalDefault(ParserTestCase): - """Tests an Optional Positional with a default value""" - - argument_signatures = [Sig('foo', nargs='?', default=42)] - failures = ['-x', 'a b'] - successes = [ - ('', NS(foo=42)), - ('a', NS(foo='a')), - ] - - -class TestPositionalsNargsOptionalConvertedDefault(ParserTestCase): - """Tests an Optional Positional with a default value - that needs to be converted to the appropriate type. - """ - - argument_signatures = [ - Sig('foo', nargs='?', type=int, default='42'), - ] - failures = ['-x', 'a b', '1 2'] - successes = [ - ('', NS(foo=42)), - ('1', NS(foo=1)), - ] - - -class TestPositionalsNargsNoneNone(ParserTestCase): - """Test two Positionals that don't specify nargs""" - - argument_signatures = [Sig('foo'), Sig('bar')] - failures = ['', '-x', 'a', 'a b c'] - successes = [ - ('a b', NS(foo='a', bar='b')), - ] - - -class TestPositionalsNargsNone1(ParserTestCase): - """Test a Positional with no nargs followed by one with 1""" - - argument_signatures = [Sig('foo'), Sig('bar', nargs=1)] - failures = ['', '--foo', 'a', 'a b c'] - successes = [ - ('a b', NS(foo='a', bar=['b'])), - ] - - -class TestPositionalsNargs2None(ParserTestCase): - """Test a Positional with 2 nargs followed by one with none""" - - argument_signatures = [Sig('foo', nargs=2), Sig('bar')] - failures = ['', '--foo', 'a', 'a b', 'a b c d'] - successes = [ - ('a b c', NS(foo=['a', 'b'], bar='c')), - ] - - -class TestPositionalsNargsNoneZeroOrMore(ParserTestCase): - """Test a Positional with no nargs followed by one with unlimited""" - - argument_signatures = [Sig('foo'), Sig('bar', nargs='*')] - failures = ['', '--foo'] - successes = [ - ('a', NS(foo='a', bar=[])), - ('a b', NS(foo='a', bar=['b'])), - ('a b c', NS(foo='a', bar=['b', 'c'])), - ] - - -class TestPositionalsNargsNoneOneOrMore(ParserTestCase): - """Test a Positional with no nargs followed by one with one or more""" - - argument_signatures = [Sig('foo'), Sig('bar', nargs='+')] - failures = ['', '--foo', 'a'] - successes = [ - ('a b', NS(foo='a', bar=['b'])), - ('a b c', NS(foo='a', bar=['b', 'c'])), - ] - - -class TestPositionalsNargsNoneOptional(ParserTestCase): - """Test a Positional with no nargs followed by one with an Optional""" - - argument_signatures = [Sig('foo'), Sig('bar', nargs='?')] - failures = ['', '--foo', 'a b c'] - successes = [ - ('a', NS(foo='a', bar=None)), - ('a b', NS(foo='a', bar='b')), - ] - - -class TestPositionalsNargsZeroOrMoreNone(ParserTestCase): - """Test a Positional with unlimited nargs followed by one with none""" - - argument_signatures = [Sig('foo', nargs='*'), Sig('bar')] - failures = ['', '--foo'] - successes = [ - ('a', NS(foo=[], bar='a')), - ('a b', NS(foo=['a'], bar='b')), - ('a b c', NS(foo=['a', 'b'], bar='c')), - ] - - -class TestPositionalsNargsOneOrMoreNone(ParserTestCase): - """Test a Positional with one or more nargs followed by one with none""" - - argument_signatures = [Sig('foo', nargs='+'), Sig('bar')] - failures = ['', '--foo', 'a'] - successes = [ - ('a b', NS(foo=['a'], bar='b')), - ('a b c', NS(foo=['a', 'b'], bar='c')), - ] - - -class TestPositionalsNargsOptionalNone(ParserTestCase): - """Test a Positional with an Optional nargs followed by one with none""" - - argument_signatures = [Sig('foo', nargs='?', default=42), Sig('bar')] - failures = ['', '--foo', 'a b c'] - successes = [ - ('a', NS(foo=42, bar='a')), - ('a b', NS(foo='a', bar='b')), - ] - - -class TestPositionalsNargs2ZeroOrMore(ParserTestCase): - """Test a Positional with 2 nargs followed by one with unlimited""" - - argument_signatures = [Sig('foo', nargs=2), Sig('bar', nargs='*')] - failures = ['', '--foo', 'a'] - successes = [ - ('a b', NS(foo=['a', 'b'], bar=[])), - ('a b c', NS(foo=['a', 'b'], bar=['c'])), - ] - - -class TestPositionalsNargs2OneOrMore(ParserTestCase): - """Test a Positional with 2 nargs followed by one with one or more""" - - argument_signatures = [Sig('foo', nargs=2), Sig('bar', nargs='+')] - failures = ['', '--foo', 'a', 'a b'] - successes = [ - ('a b c', NS(foo=['a', 'b'], bar=['c'])), - ] - - -class TestPositionalsNargs2Optional(ParserTestCase): - """Test a Positional with 2 nargs followed by one optional""" - - argument_signatures = [Sig('foo', nargs=2), Sig('bar', nargs='?')] - failures = ['', '--foo', 'a', 'a b c d'] - successes = [ - ('a b', NS(foo=['a', 'b'], bar=None)), - ('a b c', NS(foo=['a', 'b'], bar='c')), - ] - - -class TestPositionalsNargsZeroOrMore1(ParserTestCase): - """Test a Positional with unlimited nargs followed by one with 1""" - - argument_signatures = [Sig('foo', nargs='*'), Sig('bar', nargs=1)] - failures = ['', '--foo', ] - successes = [ - ('a', NS(foo=[], bar=['a'])), - ('a b', NS(foo=['a'], bar=['b'])), - ('a b c', NS(foo=['a', 'b'], bar=['c'])), - ] - - -class TestPositionalsNargsOneOrMore1(ParserTestCase): - """Test a Positional with one or more nargs followed by one with 1""" - - argument_signatures = [Sig('foo', nargs='+'), Sig('bar', nargs=1)] - failures = ['', '--foo', 'a'] - successes = [ - ('a b', NS(foo=['a'], bar=['b'])), - ('a b c', NS(foo=['a', 'b'], bar=['c'])), - ] - - -class TestPositionalsNargsOptional1(ParserTestCase): - """Test a Positional with an Optional nargs followed by one with 1""" - - argument_signatures = [Sig('foo', nargs='?'), Sig('bar', nargs=1)] - failures = ['', '--foo', 'a b c'] - successes = [ - ('a', NS(foo=None, bar=['a'])), - ('a b', NS(foo='a', bar=['b'])), - ] - - -class TestPositionalsNargsNoneZeroOrMore1(ParserTestCase): - """Test three Positionals: no nargs, unlimited nargs and 1 nargs""" - - argument_signatures = [ - Sig('foo'), - Sig('bar', nargs='*'), - Sig('baz', nargs=1), - ] - failures = ['', '--foo', 'a'] - successes = [ - ('a b', NS(foo='a', bar=[], baz=['b'])), - ('a b c', NS(foo='a', bar=['b'], baz=['c'])), - ] - - -class TestPositionalsNargsNoneOneOrMore1(ParserTestCase): - """Test three Positionals: no nargs, one or more nargs and 1 nargs""" - - argument_signatures = [ - Sig('foo'), - Sig('bar', nargs='+'), - Sig('baz', nargs=1), - ] - failures = ['', '--foo', 'a', 'b'] - successes = [ - ('a b c', NS(foo='a', bar=['b'], baz=['c'])), - ('a b c d', NS(foo='a', bar=['b', 'c'], baz=['d'])), - ] - - -class TestPositionalsNargsNoneOptional1(ParserTestCase): - """Test three Positionals: no nargs, optional narg and 1 nargs""" - - argument_signatures = [ - Sig('foo'), - Sig('bar', nargs='?', default=0.625), - Sig('baz', nargs=1), - ] - failures = ['', '--foo', 'a'] - successes = [ - ('a b', NS(foo='a', bar=0.625, baz=['b'])), - ('a b c', NS(foo='a', bar='b', baz=['c'])), - ] - - -class TestPositionalsNargsOptionalOptional(ParserTestCase): - """Test two optional nargs""" - - argument_signatures = [ - Sig('foo', nargs='?'), - Sig('bar', nargs='?', default=42), - ] - failures = ['--foo', 'a b c'] - successes = [ - ('', NS(foo=None, bar=42)), - ('a', NS(foo='a', bar=42)), - ('a b', NS(foo='a', bar='b')), - ] - - -class TestPositionalsNargsOptionalZeroOrMore(ParserTestCase): - """Test an Optional narg followed by unlimited nargs""" - - argument_signatures = [Sig('foo', nargs='?'), Sig('bar', nargs='*')] - failures = ['--foo'] - successes = [ - ('', NS(foo=None, bar=[])), - ('a', NS(foo='a', bar=[])), - ('a b', NS(foo='a', bar=['b'])), - ('a b c', NS(foo='a', bar=['b', 'c'])), - ] - - -class TestPositionalsNargsOptionalOneOrMore(ParserTestCase): - """Test an Optional narg followed by one or more nargs""" - - argument_signatures = [Sig('foo', nargs='?'), Sig('bar', nargs='+')] - failures = ['', '--foo'] - successes = [ - ('a', NS(foo=None, bar=['a'])), - ('a b', NS(foo='a', bar=['b'])), - ('a b c', NS(foo='a', bar=['b', 'c'])), - ] - - -class TestPositionalsChoicesString(ParserTestCase): - """Test a set of single-character choices""" - - argument_signatures = [Sig('spam', choices=set('abcdefg'))] - failures = ['', '--foo', 'h', '42', 'ef'] - successes = [ - ('a', NS(spam='a')), - ('g', NS(spam='g')), - ] - - -class TestPositionalsChoicesInt(ParserTestCase): - """Test a set of integer choices""" - - argument_signatures = [Sig('spam', type=int, choices=range(20))] - failures = ['', '--foo', 'h', '42', 'ef'] - successes = [ - ('4', NS(spam=4)), - ('15', NS(spam=15)), - ] - - -class TestPositionalsActionAppend(ParserTestCase): - """Test the 'append' action""" - - argument_signatures = [ - Sig('spam', action='append'), - Sig('spam', action='append', nargs=2), - ] - failures = ['', '--foo', 'a', 'a b', 'a b c d'] - successes = [ - ('a b c', NS(spam=['a', ['b', 'c']])), - ] - -# ======================================== -# Combined optionals and positionals tests -# ======================================== - -class TestOptionalsNumericAndPositionals(ParserTestCase): - """Tests negative number args when numeric options are present""" - - argument_signatures = [ - Sig('x', nargs='?'), - Sig('-4', dest='y', action='store_true'), - ] - failures = ['-2', '-315'] - successes = [ - ('', NS(x=None, y=False)), - ('a', NS(x='a', y=False)), - ('-4', NS(x=None, y=True)), - ('-4 a', NS(x='a', y=True)), - ] - - -class TestOptionalsAlmostNumericAndPositionals(ParserTestCase): - """Tests negative number args when almost numeric options are present""" - - argument_signatures = [ - Sig('x', nargs='?'), - Sig('-k4', dest='y', action='store_true'), - ] - failures = ['-k3'] - successes = [ - ('', NS(x=None, y=False)), - ('-2', NS(x='-2', y=False)), - ('a', NS(x='a', y=False)), - ('-k4', NS(x=None, y=True)), - ('-k4 a', NS(x='a', y=True)), - ] - - -class TestEmptyAndSpaceContainingArguments(ParserTestCase): - - argument_signatures = [ - Sig('x', nargs='?'), - Sig('-y', '--yyy', dest='y'), - ] - failures = ['-y'] - successes = [ - ([''], NS(x='', y=None)), - (['a badger'], NS(x='a badger', y=None)), - (['-a badger'], NS(x='-a badger', y=None)), - (['-y', ''], NS(x=None, y='')), - (['-y', 'a badger'], NS(x=None, y='a badger')), - (['-y', '-a badger'], NS(x=None, y='-a badger')), - (['--yyy=a badger'], NS(x=None, y='a badger')), - (['--yyy=-a badger'], NS(x=None, y='-a badger')), - ] - - -class TestPrefixCharacterOnlyArguments(ParserTestCase): - - parser_signature = Sig(prefix_chars='-+') - argument_signatures = [ - Sig('-', dest='x', nargs='?', const='badger'), - Sig('+', dest='y', type=int, default=42), - Sig('-+-', dest='z', action='store_true'), - ] - failures = ['-y', '+ -'] - successes = [ - ('', NS(x=None, y=42, z=False)), - ('-', NS(x='badger', y=42, z=False)), - ('- X', NS(x='X', y=42, z=False)), - ('+ -3', NS(x=None, y=-3, z=False)), - ('-+-', NS(x=None, y=42, z=True)), - ('- ===', NS(x='===', y=42, z=False)), - ] - - -class TestNargsZeroOrMore(ParserTestCase): - """Tests specifying an args for an Optional that accepts zero or more""" - - argument_signatures = [Sig('-x', nargs='*'), Sig('y', nargs='*')] - failures = [] - successes = [ - ('', NS(x=None, y=[])), - ('-x', NS(x=[], y=[])), - ('-x a', NS(x=['a'], y=[])), - ('-x a -- b', NS(x=['a'], y=['b'])), - ('a', NS(x=None, y=['a'])), - ('a -x', NS(x=[], y=['a'])), - ('a -x b', NS(x=['b'], y=['a'])), - ] - - -class TestNargsRemainder(ParserTestCase): - """Tests specifying a positional with nargs=REMAINDER""" - - argument_signatures = [Sig('x'), Sig('y', nargs='...'), Sig('-z')] - failures = ['', '-z', '-z Z'] - successes = [ - ('X', NS(x='X', y=[], z=None)), - ('-z Z X', NS(x='X', y=[], z='Z')), - ('X A B -z Z', NS(x='X', y=['A', 'B', '-z', 'Z'], z=None)), - ('X Y --foo', NS(x='X', y=['Y', '--foo'], z=None)), - ] - - -class TestOptionLike(ParserTestCase): - """Tests options that may or may not be arguments""" - - argument_signatures = [ - Sig('-x', type=float), - Sig('-3', type=float, dest='y'), - Sig('z', nargs='*'), - ] - failures = ['-x', '-y2.5', '-xa', '-x -a', - '-x -3', '-x -3.5', '-3 -3.5', - '-x -2.5', '-x -2.5 a', '-3 -.5', - 'a x -1', '-x -1 a', '-3 -1 a'] - successes = [ - ('', NS(x=None, y=None, z=[])), - ('-x 2.5', NS(x=2.5, y=None, z=[])), - ('-x 2.5 a', NS(x=2.5, y=None, z=['a'])), - ('-3.5', NS(x=None, y=0.5, z=[])), - ('-3-.5', NS(x=None, y=-0.5, z=[])), - ('-3 .5', NS(x=None, y=0.5, z=[])), - ('a -3.5', NS(x=None, y=0.5, z=['a'])), - ('a', NS(x=None, y=None, z=['a'])), - ('a -x 1', NS(x=1.0, y=None, z=['a'])), - ('-x 1 a', NS(x=1.0, y=None, z=['a'])), - ('-3 1 a', NS(x=None, y=1.0, z=['a'])), - ] - - -class TestDefaultSuppress(ParserTestCase): - """Test actions with suppressed defaults""" - - argument_signatures = [ - Sig('foo', nargs='?', default=argparse.SUPPRESS), - Sig('bar', nargs='*', default=argparse.SUPPRESS), - Sig('--baz', action='store_true', default=argparse.SUPPRESS), - ] - failures = ['-x'] - successes = [ - ('', NS()), - ('a', NS(foo='a')), - ('a b', NS(foo='a', bar=['b'])), - ('--baz', NS(baz=True)), - ('a --baz', NS(foo='a', baz=True)), - ('--baz a b', NS(foo='a', bar=['b'], baz=True)), - ] - - -class TestParserDefaultSuppress(ParserTestCase): - """Test actions with a parser-level default of SUPPRESS""" - - parser_signature = Sig(argument_default=argparse.SUPPRESS) - argument_signatures = [ - Sig('foo', nargs='?'), - Sig('bar', nargs='*'), - Sig('--baz', action='store_true'), - ] - failures = ['-x'] - successes = [ - ('', NS()), - ('a', NS(foo='a')), - ('a b', NS(foo='a', bar=['b'])), - ('--baz', NS(baz=True)), - ('a --baz', NS(foo='a', baz=True)), - ('--baz a b', NS(foo='a', bar=['b'], baz=True)), - ] - - -class TestParserDefault42(ParserTestCase): - """Test actions with a parser-level default of 42""" - - parser_signature = Sig(argument_default=42, version='1.0') - argument_signatures = [ - Sig('foo', nargs='?'), - Sig('bar', nargs='*'), - Sig('--baz', action='store_true'), - ] - failures = ['-x'] - successes = [ - ('', NS(foo=42, bar=42, baz=42)), - ('a', NS(foo='a', bar=42, baz=42)), - ('a b', NS(foo='a', bar=['b'], baz=42)), - ('--baz', NS(foo=42, bar=42, baz=True)), - ('a --baz', NS(foo='a', bar=42, baz=True)), - ('--baz a b', NS(foo='a', bar=['b'], baz=True)), - ] - - -class TestArgumentsFromFile(TempDirMixin, ParserTestCase): - """Test reading arguments from a file""" - - def setUp(self): - super(TestArgumentsFromFile, self).setUp() - file_texts = [ - ('hello', 'hello world!\n'), - ('recursive', '-a\n' - 'A\n' - '@hello'), - ('invalid', '@no-such-path\n'), - ] - for path, text in file_texts: - file = open(path, 'w') - file.write(text) - file.close() - - parser_signature = Sig(fromfile_prefix_chars='@') - argument_signatures = [ - Sig('-a'), - Sig('x'), - Sig('y', nargs='+'), - ] - failures = ['', '-b', 'X', '@invalid', '@missing'] - successes = [ - ('X Y', NS(a=None, x='X', y=['Y'])), - ('X -a A Y Z', NS(a='A', x='X', y=['Y', 'Z'])), - ('@hello X', NS(a=None, x='hello world!', y=['X'])), - ('X @hello', NS(a=None, x='X', y=['hello world!'])), - ('-a B @recursive Y Z', NS(a='A', x='hello world!', y=['Y', 'Z'])), - ('X @recursive Z -a B', NS(a='B', x='X', y=['hello world!', 'Z'])), - ] - - -class TestArgumentsFromFileConverter(TempDirMixin, ParserTestCase): - """Test reading arguments from a file""" - - def setUp(self): - super(TestArgumentsFromFileConverter, self).setUp() - file_texts = [ - ('hello', 'hello world!\n'), - ] - for path, text in file_texts: - file = open(path, 'w') - file.write(text) - file.close() - - class FromFileConverterArgumentParser(ErrorRaisingArgumentParser): - - def convert_arg_line_to_args(self, arg_line): - for arg in arg_line.split(): - if not arg.strip(): - continue - yield arg - parser_class = FromFileConverterArgumentParser - parser_signature = Sig(fromfile_prefix_chars='@') - argument_signatures = [ - Sig('y', nargs='+'), - ] - failures = [] - successes = [ - ('@hello X', NS(y=['hello', 'world!', 'X'])), - ] - - -# ===================== -# Type conversion tests -# ===================== - -class TestFileTypeRepr(TestCase): - - def test_r(self): - type = argparse.FileType('r') - self.assertEqual("FileType('r')", repr(type)) - - def test_wb_1(self): - type = argparse.FileType('wb', 1) - self.assertEqual("FileType('wb', 1)", repr(type)) - - -class RFile(object): - seen = {} - - def __init__(self, name): - self.name = name - - def __eq__(self, other): - if other in self.seen: - text = self.seen[other] - else: - text = self.seen[other] = other.read() - other.close() - if not isinstance(text, str): - text = text.decode('ascii') - return self.name == other.name == text - - -class TestFileTypeR(TempDirMixin, ParserTestCase): - """Test the FileType option/argument type for reading files""" - - def setUp(self): - super(TestFileTypeR, self).setUp() - for file_name in ['foo', 'bar']: - file = open(os.path.join(self.temp_dir, file_name), 'w') - file.write(file_name) - file.close() - - argument_signatures = [ - Sig('-x', type=argparse.FileType()), - Sig('spam', type=argparse.FileType('r')), - ] - failures = ['-x', ''] - successes = [ - ('foo', NS(x=None, spam=RFile('foo'))), - ('-x foo bar', NS(x=RFile('foo'), spam=RFile('bar'))), - ('bar -x foo', NS(x=RFile('foo'), spam=RFile('bar'))), - ('-x - -', NS(x=sys.stdin, spam=sys.stdin)), - ] - - -class TestFileTypeRB(TempDirMixin, ParserTestCase): - """Test the FileType option/argument type for reading files""" - - def setUp(self): - super(TestFileTypeRB, self).setUp() - for file_name in ['foo', 'bar']: - file = open(os.path.join(self.temp_dir, file_name), 'w') - file.write(file_name) - file.close() - - argument_signatures = [ - Sig('-x', type=argparse.FileType('rb')), - Sig('spam', type=argparse.FileType('rb')), - ] - failures = ['-x', ''] - successes = [ - ('foo', NS(x=None, spam=RFile('foo'))), - ('-x foo bar', NS(x=RFile('foo'), spam=RFile('bar'))), - ('bar -x foo', NS(x=RFile('foo'), spam=RFile('bar'))), - ('-x - -', NS(x=sys.stdin, spam=sys.stdin)), - ] - - -class WFile(object): - seen = set() - - def __init__(self, name): - self.name = name - - def __eq__(self, other): - if other not in self.seen: - text = 'Check that file is writable.' - if 'b' in other.mode: - text = text.encode('ascii') - other.write(text) - other.close() - self.seen.add(other) - return self.name == other.name - - -class TestFileTypeW(TempDirMixin, ParserTestCase): - """Test the FileType option/argument type for writing files""" - - argument_signatures = [ - Sig('-x', type=argparse.FileType('w')), - Sig('spam', type=argparse.FileType('w')), - ] - failures = ['-x', ''] - successes = [ - ('foo', NS(x=None, spam=WFile('foo'))), - ('-x foo bar', NS(x=WFile('foo'), spam=WFile('bar'))), - ('bar -x foo', NS(x=WFile('foo'), spam=WFile('bar'))), - ('-x - -', NS(x=sys.stdout, spam=sys.stdout)), - ] - - -class TestFileTypeWB(TempDirMixin, ParserTestCase): - - argument_signatures = [ - Sig('-x', type=argparse.FileType('wb')), - Sig('spam', type=argparse.FileType('wb')), - ] - failures = ['-x', ''] - successes = [ - ('foo', NS(x=None, spam=WFile('foo'))), - ('-x foo bar', NS(x=WFile('foo'), spam=WFile('bar'))), - ('bar -x foo', NS(x=WFile('foo'), spam=WFile('bar'))), - ('-x - -', NS(x=sys.stdout, spam=sys.stdout)), - ] - - -class TestTypeCallable(ParserTestCase): - """Test some callables as option/argument types""" - - argument_signatures = [ - Sig('--eggs', type=complex), - Sig('spam', type=float), - ] - failures = ['a', '42j', '--eggs a', '--eggs 2i'] - successes = [ - ('--eggs=42 42', NS(eggs=42, spam=42.0)), - ('--eggs 2j -- -1.5', NS(eggs=2j, spam=-1.5)), - ('1024.675', NS(eggs=None, spam=1024.675)), - ] - - -class TestTypeUserDefined(ParserTestCase): - """Test a user-defined option/argument type""" - - class MyType(TestCase): - - def __init__(self, value): - self.value = value - - def __eq__(self, other): - return (type(self), self.value) == (type(other), other.value) - - argument_signatures = [ - Sig('-x', type=MyType), - Sig('spam', type=MyType), - ] - failures = [] - successes = [ - ('a -x b', NS(x=MyType('b'), spam=MyType('a'))), - ('-xf g', NS(x=MyType('f'), spam=MyType('g'))), - ] - - -class TestTypeClassicClass(ParserTestCase): - """Test a classic class type""" - - class C: - - def __init__(self, value): - self.value = value - - def __eq__(self, other): - return (type(self), self.value) == (type(other), other.value) - - argument_signatures = [ - Sig('-x', type=C), - Sig('spam', type=C), - ] - failures = [] - successes = [ - ('a -x b', NS(x=C('b'), spam=C('a'))), - ('-xf g', NS(x=C('f'), spam=C('g'))), - ] - - -class TestTypeRegistration(TestCase): - """Test a user-defined type by registering it""" - - def test(self): - - def get_my_type(string): - return 'my_type{%s}' % string - - parser = argparse.ArgumentParser() - parser.register('type', 'my_type', get_my_type) - parser.add_argument('-x', type='my_type') - parser.add_argument('y', type='my_type') - - self.assertEqual(parser.parse_args('1'.split()), - NS(x=None, y='my_type{1}')) - self.assertEqual(parser.parse_args('-x 1 42'.split()), - NS(x='my_type{1}', y='my_type{42}')) - - -# ============ -# Action tests -# ============ - -class TestActionUserDefined(ParserTestCase): - """Test a user-defined option/argument action""" - - class OptionalAction(argparse.Action): - - def __call__(self, parser, namespace, value, option_string=None): - try: - # check destination and option string - assert self.dest == 'spam', 'dest: %s' % self.dest - assert option_string == '-s', 'flag: %s' % option_string - # when option is before argument, badger=2, and when - # option is after argument, badger= - expected_ns = NS(spam=0.25) - if value in [0.125, 0.625]: - expected_ns.badger = 2 - elif value in [2.0]: - expected_ns.badger = 84 - else: - raise AssertionError('value: %s' % value) - assert expected_ns == namespace, ('expected %s, got %s' % - (expected_ns, namespace)) - except AssertionError: - e = sys.exc_info()[1] - raise ArgumentParserError('opt_action failed: %s' % e) - setattr(namespace, 'spam', value) - - class PositionalAction(argparse.Action): - - def __call__(self, parser, namespace, value, option_string=None): - try: - assert option_string is None, ('option_string: %s' % - option_string) - # check destination - assert self.dest == 'badger', 'dest: %s' % self.dest - # when argument is before option, spam=0.25, and when - # option is after argument, spam= - expected_ns = NS(badger=2) - if value in [42, 84]: - expected_ns.spam = 0.25 - elif value in [1]: - expected_ns.spam = 0.625 - elif value in [2]: - expected_ns.spam = 0.125 - else: - raise AssertionError('value: %s' % value) - assert expected_ns == namespace, ('expected %s, got %s' % - (expected_ns, namespace)) - except AssertionError: - e = sys.exc_info()[1] - raise ArgumentParserError('arg_action failed: %s' % e) - setattr(namespace, 'badger', value) - - argument_signatures = [ - Sig('-s', dest='spam', action=OptionalAction, - type=float, default=0.25), - Sig('badger', action=PositionalAction, - type=int, nargs='?', default=2), - ] - failures = [] - successes = [ - ('-s0.125', NS(spam=0.125, badger=2)), - ('42', NS(spam=0.25, badger=42)), - ('-s 0.625 1', NS(spam=0.625, badger=1)), - ('84 -s2', NS(spam=2.0, badger=84)), - ] - - -class TestActionRegistration(TestCase): - """Test a user-defined action supplied by registering it""" - - class MyAction(argparse.Action): - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, 'foo[%s]' % values) - - def test(self): - - parser = argparse.ArgumentParser() - parser.register('action', 'my_action', self.MyAction) - parser.add_argument('badger', action='my_action') - - self.assertEqual(parser.parse_args(['1']), NS(badger='foo[1]')) - self.assertEqual(parser.parse_args(['42']), NS(badger='foo[42]')) - - -# ================ -# Subparsers tests -# ================ - -class TestAddSubparsers(TestCase): - """Test the add_subparsers method""" - - def assertArgumentParserError(self, *args, **kwargs): - self.assertRaises(ArgumentParserError, *args, **kwargs) - - def _get_parser(self, subparser_help=False): - # create a parser with a subparsers argument - parser = ErrorRaisingArgumentParser( - prog='PROG', description='main description') - parser.add_argument( - '--foo', action='store_true', help='foo help') - parser.add_argument( - 'bar', type=float, help='bar help') - - # check that only one subparsers argument can be added - subparsers = parser.add_subparsers(help='command help') - self.assertArgumentParserError(parser.add_subparsers) - - # add first sub-parser - parser1_kwargs = dict(description='1 description') - if subparser_help: - parser1_kwargs['help'] = '1 help' - parser1 = subparsers.add_parser('1', **parser1_kwargs) - parser1.add_argument('-w', type=int, help='w help') - parser1.add_argument('x', choices='abc', help='x help') - - # add second sub-parser - parser2_kwargs = dict(description='2 description') - if subparser_help: - parser2_kwargs['help'] = '2 help' - parser2 = subparsers.add_parser('2', **parser2_kwargs) - parser2.add_argument('-y', choices='123', help='y help') - parser2.add_argument('z', type=complex, nargs='*', help='z help') - - # return the main parser - return parser - - def setUp(self): - self.parser = self._get_parser() - self.command_help_parser = self._get_parser(subparser_help=True) - - def test_parse_args_failures(self): - # check some failure cases: - for args_str in ['', 'a', 'a a', '0.5 a', '0.5 1', - '0.5 1 -y', '0.5 2 -w']: - args = args_str.split() - self.assertArgumentParserError(self.parser.parse_args, args) - - def test_parse_args(self): - # check some non-failure cases: - self.assertEqual( - self.parser.parse_args('0.5 1 b -w 7'.split()), - NS(foo=False, bar=0.5, w=7, x='b'), - ) - self.assertEqual( - self.parser.parse_args('0.25 --foo 2 -y 2 3j -- -1j'.split()), - NS(foo=True, bar=0.25, y='2', z=[3j, -1j]), - ) - self.assertEqual( - self.parser.parse_args('--foo 0.125 1 c'.split()), - NS(foo=True, bar=0.125, w=None, x='c'), - ) - - def test_dest(self): - parser = ErrorRaisingArgumentParser() - parser.add_argument('--foo', action='store_true') - subparsers = parser.add_subparsers(dest='bar') - parser1 = subparsers.add_parser('1') - parser1.add_argument('baz') - self.assertEqual(NS(foo=False, bar='1', baz='2'), - parser.parse_args('1 2'.split())) - - def test_help(self): - self.assertEqual(self.parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') - self.assertEqual(self.parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... - - main description - - positional arguments: - bar bar help - {1,2} command help - - optional arguments: - -h, --help show this help message and exit - --foo foo help - ''')) - - def test_parser_command_help(self): - self.assertEqual(self.command_help_parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') - self.assertEqual(self.command_help_parser.format_help(), - textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... - - main description - - positional arguments: - bar bar help - {1,2} command help - 1 1 help - 2 2 help - - optional arguments: - -h, --help show this help message and exit - --foo foo help - ''')) - - def test_subparser_title_help(self): - parser = ErrorRaisingArgumentParser(prog='PROG', - description='main description') - parser.add_argument('--foo', action='store_true', help='foo help') - parser.add_argument('bar', help='bar help') - subparsers = parser.add_subparsers(title='subcommands', - description='command help', - help='additional text') - parser1 = subparsers.add_parser('1') - parser2 = subparsers.add_parser('2') - self.assertEqual(parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') - self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... - - main description - - positional arguments: - bar bar help - - optional arguments: - -h, --help show this help message and exit - --foo foo help - - subcommands: - command help - - {1,2} additional text - ''')) - - def _test_subparser_help(self, args_str, expected_help): - try: - self.parser.parse_args(args_str.split()) - except ArgumentParserError: - err = sys.exc_info()[1] - if err.stdout != expected_help: - print(repr(expected_help)) - print(repr(err.stdout)) - self.assertEqual(err.stdout, expected_help) - - def test_subparser1_help(self): - self._test_subparser_help('5.0 1 -h', textwrap.dedent('''\ - usage: PROG bar 1 [-h] [-w W] {a,b,c} - - 1 description - - positional arguments: - {a,b,c} x help - - optional arguments: - -h, --help show this help message and exit - -w W w help - ''')) - - def test_subparser2_help(self): - self._test_subparser_help('5.0 2 -h', textwrap.dedent('''\ - usage: PROG bar 2 [-h] [-y {1,2,3}] [z [z ...]] - - 2 description - - positional arguments: - z z help - - optional arguments: - -h, --help show this help message and exit - -y {1,2,3} y help - ''')) - -# ============ -# Groups tests -# ============ - -class TestPositionalsGroups(TestCase): - """Tests that order of group positionals matches construction order""" - - def test_nongroup_first(self): - parser = ErrorRaisingArgumentParser() - parser.add_argument('foo') - group = parser.add_argument_group('g') - group.add_argument('bar') - parser.add_argument('baz') - expected = NS(foo='1', bar='2', baz='3') - result = parser.parse_args('1 2 3'.split()) - self.assertEqual(expected, result) - - def test_group_first(self): - parser = ErrorRaisingArgumentParser() - group = parser.add_argument_group('xxx') - group.add_argument('foo') - parser.add_argument('bar') - parser.add_argument('baz') - expected = NS(foo='1', bar='2', baz='3') - result = parser.parse_args('1 2 3'.split()) - self.assertEqual(expected, result) - - def test_interleaved_groups(self): - parser = ErrorRaisingArgumentParser() - group = parser.add_argument_group('xxx') - parser.add_argument('foo') - group.add_argument('bar') - parser.add_argument('baz') - group = parser.add_argument_group('yyy') - group.add_argument('frell') - expected = NS(foo='1', bar='2', baz='3', frell='4') - result = parser.parse_args('1 2 3 4'.split()) - self.assertEqual(expected, result) - -# =================== -# Parent parser tests -# =================== - -class TestParentParsers(TestCase): - """Tests that parsers can be created with parent parsers""" - - def assertArgumentParserError(self, *args, **kwargs): - self.assertRaises(ArgumentParserError, *args, **kwargs) - - def setUp(self): - self.wxyz_parent = ErrorRaisingArgumentParser(add_help=False) - self.wxyz_parent.add_argument('--w') - x_group = self.wxyz_parent.add_argument_group('x') - x_group.add_argument('-y') - self.wxyz_parent.add_argument('z') - - self.abcd_parent = ErrorRaisingArgumentParser(add_help=False) - self.abcd_parent.add_argument('a') - self.abcd_parent.add_argument('-b') - c_group = self.abcd_parent.add_argument_group('c') - c_group.add_argument('--d') - - self.w_parent = ErrorRaisingArgumentParser(add_help=False) - self.w_parent.add_argument('--w') - - self.z_parent = ErrorRaisingArgumentParser(add_help=False) - self.z_parent.add_argument('z') - - # parents with mutually exclusive groups - self.ab_mutex_parent = ErrorRaisingArgumentParser(add_help=False) - group = self.ab_mutex_parent.add_mutually_exclusive_group() - group.add_argument('-a', action='store_true') - group.add_argument('-b', action='store_true') - - def test_single_parent(self): - parser = ErrorRaisingArgumentParser(parents=[self.wxyz_parent]) - self.assertEqual(parser.parse_args('-y 1 2 --w 3'.split()), - NS(w='3', y='1', z='2')) - - def test_single_parent_mutex(self): - self._test_mutex_ab(self.ab_mutex_parent.parse_args) - parser = ErrorRaisingArgumentParser(parents=[self.ab_mutex_parent]) - self._test_mutex_ab(parser.parse_args) - - def test_single_granparent_mutex(self): - parents = [self.ab_mutex_parent] - parser = ErrorRaisingArgumentParser(add_help=False, parents=parents) - parser = ErrorRaisingArgumentParser(parents=[parser]) - self._test_mutex_ab(parser.parse_args) - - def _test_mutex_ab(self, parse_args): - self.assertEqual(parse_args([]), NS(a=False, b=False)) - self.assertEqual(parse_args(['-a']), NS(a=True, b=False)) - self.assertEqual(parse_args(['-b']), NS(a=False, b=True)) - self.assertArgumentParserError(parse_args, ['-a', '-b']) - self.assertArgumentParserError(parse_args, ['-b', '-a']) - self.assertArgumentParserError(parse_args, ['-c']) - self.assertArgumentParserError(parse_args, ['-a', '-c']) - self.assertArgumentParserError(parse_args, ['-b', '-c']) - - def test_multiple_parents(self): - parents = [self.abcd_parent, self.wxyz_parent] - parser = ErrorRaisingArgumentParser(parents=parents) - self.assertEqual(parser.parse_args('--d 1 --w 2 3 4'.split()), - NS(a='3', b=None, d='1', w='2', y=None, z='4')) - - def test_multiple_parents_mutex(self): - parents = [self.ab_mutex_parent, self.wxyz_parent] - parser = ErrorRaisingArgumentParser(parents=parents) - self.assertEqual(parser.parse_args('-a --w 2 3'.split()), - NS(a=True, b=False, w='2', y=None, z='3')) - self.assertArgumentParserError( - parser.parse_args, '-a --w 2 3 -b'.split()) - self.assertArgumentParserError( - parser.parse_args, '-a -b --w 2 3'.split()) - - def test_conflicting_parents(self): - self.assertRaises( - argparse.ArgumentError, - argparse.ArgumentParser, - parents=[self.w_parent, self.wxyz_parent]) - - def test_conflicting_parents_mutex(self): - self.assertRaises( - argparse.ArgumentError, - argparse.ArgumentParser, - parents=[self.abcd_parent, self.ab_mutex_parent]) - - def test_same_argument_name_parents(self): - parents = [self.wxyz_parent, self.z_parent] - parser = ErrorRaisingArgumentParser(parents=parents) - self.assertEqual(parser.parse_args('1 2'.split()), - NS(w=None, y=None, z='2')) - - def test_subparser_parents(self): - parser = ErrorRaisingArgumentParser() - subparsers = parser.add_subparsers() - abcde_parser = subparsers.add_parser('bar', parents=[self.abcd_parent]) - abcde_parser.add_argument('e') - self.assertEqual(parser.parse_args('bar -b 1 --d 2 3 4'.split()), - NS(a='3', b='1', d='2', e='4')) - - def test_subparser_parents_mutex(self): - parser = ErrorRaisingArgumentParser() - subparsers = parser.add_subparsers() - parents = [self.ab_mutex_parent] - abc_parser = subparsers.add_parser('foo', parents=parents) - c_group = abc_parser.add_argument_group('c_group') - c_group.add_argument('c') - parents = [self.wxyz_parent, self.ab_mutex_parent] - wxyzabe_parser = subparsers.add_parser('bar', parents=parents) - wxyzabe_parser.add_argument('e') - self.assertEqual(parser.parse_args('foo -a 4'.split()), - NS(a=True, b=False, c='4')) - self.assertEqual(parser.parse_args('bar -b --w 2 3 4'.split()), - NS(a=False, b=True, w='2', y=None, z='3', e='4')) - self.assertArgumentParserError( - parser.parse_args, 'foo -a -b 4'.split()) - self.assertArgumentParserError( - parser.parse_args, 'bar -b -a 4'.split()) - - def test_parent_help(self): - parents = [self.abcd_parent, self.wxyz_parent] - parser = ErrorRaisingArgumentParser(parents=parents) - parser_help = parser.format_help() - self.assertEqual(parser_help, textwrap.dedent('''\ - usage: test_argparse.py [-h] [-b B] [--d D] [--w W] [-y Y] a z - - positional arguments: - a - z - - optional arguments: - -h, --help show this help message and exit - -b B - --w W - - c: - --d D - - x: - -y Y - ''')) - - def test_groups_parents(self): - parent = ErrorRaisingArgumentParser(add_help=False) - g = parent.add_argument_group(title='g', description='gd') - g.add_argument('-w') - g.add_argument('-x') - m = parent.add_mutually_exclusive_group() - m.add_argument('-y') - m.add_argument('-z') - parser = ErrorRaisingArgumentParser(parents=[parent]) - - self.assertRaises(ArgumentParserError, parser.parse_args, - ['-y', 'Y', '-z', 'Z']) - - parser_help = parser.format_help() - self.assertEqual(parser_help, textwrap.dedent('''\ - usage: test_argparse.py [-h] [-w W] [-x X] [-y Y | -z Z] - - optional arguments: - -h, --help show this help message and exit - -y Y - -z Z - - g: - gd - - -w W - -x X - ''')) - -# ============================== -# Mutually exclusive group tests -# ============================== - -class TestMutuallyExclusiveGroupErrors(TestCase): - - def test_invalid_add_argument_group(self): - parser = ErrorRaisingArgumentParser() - raises = self.assertRaises - raises(TypeError, parser.add_mutually_exclusive_group, title='foo') - - def test_invalid_add_argument(self): - parser = ErrorRaisingArgumentParser() - group = parser.add_mutually_exclusive_group() - add_argument = group.add_argument - raises = self.assertRaises - raises(ValueError, add_argument, '--foo', required=True) - raises(ValueError, add_argument, 'bar') - raises(ValueError, add_argument, 'bar', nargs='+') - raises(ValueError, add_argument, 'bar', nargs=1) - raises(ValueError, add_argument, 'bar', nargs=argparse.PARSER) - - -class MEMixin(object): - - def test_failures_when_not_required(self): - parse_args = self.get_parser(required=False).parse_args - error = ArgumentParserError - for args_string in self.failures: - self.assertRaises(error, parse_args, args_string.split()) - - def test_failures_when_required(self): - parse_args = self.get_parser(required=True).parse_args - error = ArgumentParserError - for args_string in self.failures + ['']: - self.assertRaises(error, parse_args, args_string.split()) - - def test_successes_when_not_required(self): - parse_args = self.get_parser(required=False).parse_args - successes = self.successes + self.successes_when_not_required - for args_string, expected_ns in successes: - actual_ns = parse_args(args_string.split()) - self.assertEqual(actual_ns, expected_ns) - - def test_successes_when_required(self): - parse_args = self.get_parser(required=True).parse_args - for args_string, expected_ns in self.successes: - actual_ns = parse_args(args_string.split()) - self.assertEqual(actual_ns, expected_ns) - - def test_usage_when_not_required(self): - format_usage = self.get_parser(required=False).format_usage - expected_usage = self.usage_when_not_required - self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) - - def test_usage_when_required(self): - format_usage = self.get_parser(required=True).format_usage - expected_usage = self.usage_when_required - self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) - - def test_help_when_not_required(self): - format_help = self.get_parser(required=False).format_help - help = self.usage_when_not_required + self.help - self.assertEqual(format_help(), textwrap.dedent(help)) - - def test_help_when_required(self): - format_help = self.get_parser(required=True).format_help - help = self.usage_when_required + self.help - self.assertEqual(format_help(), textwrap.dedent(help)) - - -class TestMutuallyExclusiveSimple(MEMixin, TestCase): - - def get_parser(self, required=None): - parser = ErrorRaisingArgumentParser(prog='PROG') - group = parser.add_mutually_exclusive_group(required=required) - group.add_argument('--bar', help='bar help') - group.add_argument('--baz', nargs='?', const='Z', help='baz help') - return parser - - failures = ['--bar X --baz Y', '--bar X --baz'] - successes = [ - ('--bar X', NS(bar='X', baz=None)), - ('--bar X --bar Z', NS(bar='Z', baz=None)), - ('--baz Y', NS(bar=None, baz='Y')), - ('--baz', NS(bar=None, baz='Z')), - ] - successes_when_not_required = [ - ('', NS(bar=None, baz=None)), - ] - - usage_when_not_required = '''\ - usage: PROG [-h] [--bar BAR | --baz [BAZ]] - ''' - usage_when_required = '''\ - usage: PROG [-h] (--bar BAR | --baz [BAZ]) - ''' - help = '''\ - - optional arguments: - -h, --help show this help message and exit - --bar BAR bar help - --baz [BAZ] baz help - ''' - - -class TestMutuallyExclusiveLong(MEMixin, TestCase): - - def get_parser(self, required=None): - parser = ErrorRaisingArgumentParser(prog='PROG') - parser.add_argument('--abcde', help='abcde help') - parser.add_argument('--fghij', help='fghij help') - group = parser.add_mutually_exclusive_group(required=required) - group.add_argument('--klmno', help='klmno help') - group.add_argument('--pqrst', help='pqrst help') - return parser - - failures = ['--klmno X --pqrst Y'] - successes = [ - ('--klmno X', NS(abcde=None, fghij=None, klmno='X', pqrst=None)), - ('--abcde Y --klmno X', - NS(abcde='Y', fghij=None, klmno='X', pqrst=None)), - ('--pqrst X', NS(abcde=None, fghij=None, klmno=None, pqrst='X')), - ('--pqrst X --fghij Y', - NS(abcde=None, fghij='Y', klmno=None, pqrst='X')), - ] - successes_when_not_required = [ - ('', NS(abcde=None, fghij=None, klmno=None, pqrst=None)), - ] - - usage_when_not_required = '''\ - usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] - [--klmno KLMNO | --pqrst PQRST] - ''' - usage_when_required = '''\ - usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] - (--klmno KLMNO | --pqrst PQRST) - ''' - help = '''\ - - optional arguments: - -h, --help show this help message and exit - --abcde ABCDE abcde help - --fghij FGHIJ fghij help - --klmno KLMNO klmno help - --pqrst PQRST pqrst help - ''' - - -class TestMutuallyExclusiveFirstSuppressed(MEMixin, TestCase): - - def get_parser(self, required): - parser = ErrorRaisingArgumentParser(prog='PROG') - group = parser.add_mutually_exclusive_group(required=required) - group.add_argument('-x', help=argparse.SUPPRESS) - group.add_argument('-y', action='store_false', help='y help') - return parser - - failures = ['-x X -y'] - successes = [ - ('-x X', NS(x='X', y=True)), - ('-x X -x Y', NS(x='Y', y=True)), - ('-y', NS(x=None, y=False)), - ] - successes_when_not_required = [ - ('', NS(x=None, y=True)), - ] - - usage_when_not_required = '''\ - usage: PROG [-h] [-y] - ''' - usage_when_required = '''\ - usage: PROG [-h] -y - ''' - help = '''\ - - optional arguments: - -h, --help show this help message and exit - -y y help - ''' - - -class TestMutuallyExclusiveManySuppressed(MEMixin, TestCase): - - def get_parser(self, required): - parser = ErrorRaisingArgumentParser(prog='PROG') - group = parser.add_mutually_exclusive_group(required=required) - add = group.add_argument - add('--spam', action='store_true', help=argparse.SUPPRESS) - add('--badger', action='store_false', help=argparse.SUPPRESS) - add('--bladder', help=argparse.SUPPRESS) - return parser - - failures = [ - '--spam --badger', - '--badger --bladder B', - '--bladder B --spam', - ] - successes = [ - ('--spam', NS(spam=True, badger=True, bladder=None)), - ('--badger', NS(spam=False, badger=False, bladder=None)), - ('--bladder B', NS(spam=False, badger=True, bladder='B')), - ('--spam --spam', NS(spam=True, badger=True, bladder=None)), - ] - successes_when_not_required = [ - ('', NS(spam=False, badger=True, bladder=None)), - ] - - usage_when_required = usage_when_not_required = '''\ - usage: PROG [-h] - ''' - help = '''\ - - optional arguments: - -h, --help show this help message and exit - ''' - - -class TestMutuallyExclusiveOptionalAndPositional(MEMixin, TestCase): - - def get_parser(self, required): - parser = ErrorRaisingArgumentParser(prog='PROG') - group = parser.add_mutually_exclusive_group(required=required) - group.add_argument('--foo', action='store_true', help='FOO') - group.add_argument('--spam', help='SPAM') - group.add_argument('badger', nargs='*', default='X', help='BADGER') - return parser - - failures = [ - '--foo --spam S', - '--spam S X', - 'X --foo', - 'X Y Z --spam S', - '--foo X Y', - ] - successes = [ - ('--foo', NS(foo=True, spam=None, badger='X')), - ('--spam S', NS(foo=False, spam='S', badger='X')), - ('X', NS(foo=False, spam=None, badger=['X'])), - ('X Y Z', NS(foo=False, spam=None, badger=['X', 'Y', 'Z'])), - ] - successes_when_not_required = [ - ('', NS(foo=False, spam=None, badger='X')), - ] - - usage_when_not_required = '''\ - usage: PROG [-h] [--foo | --spam SPAM | badger [badger ...]] - ''' - usage_when_required = '''\ - usage: PROG [-h] (--foo | --spam SPAM | badger [badger ...]) - ''' - help = '''\ - - positional arguments: - badger BADGER - - optional arguments: - -h, --help show this help message and exit - --foo FOO - --spam SPAM SPAM - ''' - - -class TestMutuallyExclusiveOptionalsMixed(MEMixin, TestCase): - - def get_parser(self, required): - parser = ErrorRaisingArgumentParser(prog='PROG') - parser.add_argument('-x', action='store_true', help='x help') - group = parser.add_mutually_exclusive_group(required=required) - group.add_argument('-a', action='store_true', help='a help') - group.add_argument('-b', action='store_true', help='b help') - parser.add_argument('-y', action='store_true', help='y help') - group.add_argument('-c', action='store_true', help='c help') - return parser - - failures = ['-a -b', '-b -c', '-a -c', '-a -b -c'] - successes = [ - ('-a', NS(a=True, b=False, c=False, x=False, y=False)), - ('-b', NS(a=False, b=True, c=False, x=False, y=False)), - ('-c', NS(a=False, b=False, c=True, x=False, y=False)), - ('-a -x', NS(a=True, b=False, c=False, x=True, y=False)), - ('-y -b', NS(a=False, b=True, c=False, x=False, y=True)), - ('-x -y -c', NS(a=False, b=False, c=True, x=True, y=True)), - ] - successes_when_not_required = [ - ('', NS(a=False, b=False, c=False, x=False, y=False)), - ('-x', NS(a=False, b=False, c=False, x=True, y=False)), - ('-y', NS(a=False, b=False, c=False, x=False, y=True)), - ] - - usage_when_required = usage_when_not_required = '''\ - usage: PROG [-h] [-x] [-a] [-b] [-y] [-c] - ''' - help = '''\ - - optional arguments: - -h, --help show this help message and exit - -x x help - -a a help - -b b help - -y y help - -c c help - ''' - - -class TestMutuallyExclusiveOptionalsAndPositionalsMixed(MEMixin, TestCase): - - def get_parser(self, required): - parser = ErrorRaisingArgumentParser(prog='PROG') - parser.add_argument('x', help='x help') - parser.add_argument('-y', action='store_true', help='y help') - group = parser.add_mutually_exclusive_group(required=required) - group.add_argument('a', nargs='?', help='a help') - group.add_argument('-b', action='store_true', help='b help') - group.add_argument('-c', action='store_true', help='c help') - return parser - - failures = ['X A -b', '-b -c', '-c X A'] - successes = [ - ('X A', NS(a='A', b=False, c=False, x='X', y=False)), - ('X -b', NS(a=None, b=True, c=False, x='X', y=False)), - ('X -c', NS(a=None, b=False, c=True, x='X', y=False)), - ('X A -y', NS(a='A', b=False, c=False, x='X', y=True)), - ('X -y -b', NS(a=None, b=True, c=False, x='X', y=True)), - ] - successes_when_not_required = [ - ('X', NS(a=None, b=False, c=False, x='X', y=False)), - ('X -y', NS(a=None, b=False, c=False, x='X', y=True)), - ] - - usage_when_required = usage_when_not_required = '''\ - usage: PROG [-h] [-y] [-b] [-c] x [a] - ''' - help = '''\ - - positional arguments: - x x help - a a help - - optional arguments: - -h, --help show this help message and exit - -y y help - -b b help - -c c help - ''' - -# ================================================= -# Mutually exclusive group in parent parser tests -# ================================================= - -class MEPBase(object): - - def get_parser(self, required=None): - parent = super(MEPBase, self).get_parser(required=required) - parser = ErrorRaisingArgumentParser( - prog=parent.prog, add_help=False, parents=[parent]) - return parser - - -class TestMutuallyExclusiveGroupErrorsParent( - MEPBase, TestMutuallyExclusiveGroupErrors): - pass - - -class TestMutuallyExclusiveSimpleParent( - MEPBase, TestMutuallyExclusiveSimple): - pass - - -class TestMutuallyExclusiveLongParent( - MEPBase, TestMutuallyExclusiveLong): - pass - - -class TestMutuallyExclusiveFirstSuppressedParent( - MEPBase, TestMutuallyExclusiveFirstSuppressed): - pass - - -class TestMutuallyExclusiveManySuppressedParent( - MEPBase, TestMutuallyExclusiveManySuppressed): - pass - - -class TestMutuallyExclusiveOptionalAndPositionalParent( - MEPBase, TestMutuallyExclusiveOptionalAndPositional): - pass - - -class TestMutuallyExclusiveOptionalsMixedParent( - MEPBase, TestMutuallyExclusiveOptionalsMixed): - pass - - -class TestMutuallyExclusiveOptionalsAndPositionalsMixedParent( - MEPBase, TestMutuallyExclusiveOptionalsAndPositionalsMixed): - pass - -# ================= -# Set default tests -# ================= - -class TestSetDefaults(TestCase): - - def test_set_defaults_no_args(self): - parser = ErrorRaisingArgumentParser() - parser.set_defaults(x='foo') - parser.set_defaults(y='bar', z=1) - self.assertEqual(NS(x='foo', y='bar', z=1), - parser.parse_args([])) - self.assertEqual(NS(x='foo', y='bar', z=1), - parser.parse_args([], NS())) - self.assertEqual(NS(x='baz', y='bar', z=1), - parser.parse_args([], NS(x='baz'))) - self.assertEqual(NS(x='baz', y='bar', z=2), - parser.parse_args([], NS(x='baz', z=2))) - - def test_set_defaults_with_args(self): - parser = ErrorRaisingArgumentParser() - parser.set_defaults(x='foo', y='bar') - parser.add_argument('-x', default='xfoox') - self.assertEqual(NS(x='xfoox', y='bar'), - parser.parse_args([])) - self.assertEqual(NS(x='xfoox', y='bar'), - parser.parse_args([], NS())) - self.assertEqual(NS(x='baz', y='bar'), - parser.parse_args([], NS(x='baz'))) - self.assertEqual(NS(x='1', y='bar'), - parser.parse_args('-x 1'.split())) - self.assertEqual(NS(x='1', y='bar'), - parser.parse_args('-x 1'.split(), NS())) - self.assertEqual(NS(x='1', y='bar'), - parser.parse_args('-x 1'.split(), NS(x='baz'))) - - def test_set_defaults_subparsers(self): - parser = ErrorRaisingArgumentParser() - parser.set_defaults(x='foo') - subparsers = parser.add_subparsers() - parser_a = subparsers.add_parser('a') - parser_a.set_defaults(y='bar') - self.assertEqual(NS(x='foo', y='bar'), - parser.parse_args('a'.split())) - - def test_set_defaults_parents(self): - parent = ErrorRaisingArgumentParser(add_help=False) - parent.set_defaults(x='foo') - parser = ErrorRaisingArgumentParser(parents=[parent]) - self.assertEqual(NS(x='foo'), parser.parse_args([])) - - def test_set_defaults_same_as_add_argument(self): - parser = ErrorRaisingArgumentParser() - parser.set_defaults(w='W', x='X', y='Y', z='Z') - parser.add_argument('-w') - parser.add_argument('-x', default='XX') - parser.add_argument('y', nargs='?') - parser.add_argument('z', nargs='?', default='ZZ') - - # defaults set previously - self.assertEqual(NS(w='W', x='XX', y='Y', z='ZZ'), - parser.parse_args([])) - - # reset defaults - parser.set_defaults(w='WW', x='X', y='YY', z='Z') - self.assertEqual(NS(w='WW', x='X', y='YY', z='Z'), - parser.parse_args([])) - - def test_set_defaults_same_as_add_argument_group(self): - parser = ErrorRaisingArgumentParser() - parser.set_defaults(w='W', x='X', y='Y', z='Z') - group = parser.add_argument_group('foo') - group.add_argument('-w') - group.add_argument('-x', default='XX') - group.add_argument('y', nargs='?') - group.add_argument('z', nargs='?', default='ZZ') - - - # defaults set previously - self.assertEqual(NS(w='W', x='XX', y='Y', z='ZZ'), - parser.parse_args([])) - - # reset defaults - parser.set_defaults(w='WW', x='X', y='YY', z='Z') - self.assertEqual(NS(w='WW', x='X', y='YY', z='Z'), - parser.parse_args([])) - -# ================= -# Get default tests -# ================= - -class TestGetDefault(TestCase): - - def test_get_default(self): - parser = ErrorRaisingArgumentParser() - self.assertEqual(None, parser.get_default("foo")) - self.assertEqual(None, parser.get_default("bar")) - - parser.add_argument("--foo") - self.assertEqual(None, parser.get_default("foo")) - self.assertEqual(None, parser.get_default("bar")) - - parser.add_argument("--bar", type=int, default=42) - self.assertEqual(None, parser.get_default("foo")) - self.assertEqual(42, parser.get_default("bar")) - - parser.set_defaults(foo="badger") - self.assertEqual("badger", parser.get_default("foo")) - self.assertEqual(42, parser.get_default("bar")) - -# ========================== -# Namespace 'contains' tests -# ========================== - -class TestNamespaceContainsSimple(TestCase): - - def test_empty(self): - ns = argparse.Namespace() - self.assertEquals('' in ns, False) - self.assertEquals('' not in ns, True) - self.assertEquals('x' in ns, False) - - def test_non_empty(self): - ns = argparse.Namespace(x=1, y=2) - self.assertEquals('x' in ns, True) - self.assertEquals('x' not in ns, False) - self.assertEquals('y' in ns, True) - self.assertEquals('' in ns, False) - self.assertEquals('xx' in ns, False) - self.assertEquals('z' in ns, False) - -# ===================== -# Help formatting tests -# ===================== - -class TestHelpFormattingMetaclass(type): - - def __init__(cls, name, bases, bodydict): - if name == 'HelpTestCase': - return - - class AddTests(object): - - def __init__(self, test_class, func_suffix, std_name): - self.func_suffix = func_suffix - self.std_name = std_name - - for test_func in [self.test_format, - self.test_print, - self.test_print_file]: - test_name = '%s_%s' % (test_func.__name__, func_suffix) - - def test_wrapper(self, test_func=test_func): - test_func(self) - try: - test_wrapper.__name__ = test_name - except TypeError: - pass - setattr(test_class, test_name, test_wrapper) - - def _get_parser(self, tester): - parser = argparse.ArgumentParser( - *tester.parser_signature.args, - **tester.parser_signature.kwargs) - for argument_sig in tester.argument_signatures: - parser.add_argument(*argument_sig.args, - **argument_sig.kwargs) - group_signatures = tester.argument_group_signatures - for group_sig, argument_sigs in group_signatures: - group = parser.add_argument_group(*group_sig.args, - **group_sig.kwargs) - for argument_sig in argument_sigs: - group.add_argument(*argument_sig.args, - **argument_sig.kwargs) - return parser - - def _test(self, tester, parser_text): - expected_text = getattr(tester, self.func_suffix) - expected_text = textwrap.dedent(expected_text) - if expected_text != parser_text: - print(repr(expected_text)) - print(repr(parser_text)) - for char1, char2 in zip(expected_text, parser_text): - if char1 != char2: - print('first diff: %r %r' % (char1, char2)) - break - tester.assertEqual(expected_text, parser_text) - - def test_format(self, tester): - parser = self._get_parser(tester) - format = getattr(parser, 'format_%s' % self.func_suffix) - self._test(tester, format()) - - def test_print(self, tester): - parser = self._get_parser(tester) - print_ = getattr(parser, 'print_%s' % self.func_suffix) - old_stream = getattr(sys, self.std_name) - setattr(sys, self.std_name, StringIO()) - try: - print_() - parser_text = getattr(sys, self.std_name).getvalue() - finally: - setattr(sys, self.std_name, old_stream) - self._test(tester, parser_text) - - def test_print_file(self, tester): - parser = self._get_parser(tester) - print_ = getattr(parser, 'print_%s' % self.func_suffix) - sfile = StringIO() - print_(sfile) - parser_text = sfile.getvalue() - self._test(tester, parser_text) - - # add tests for {format,print}_{usage,help,version} - for func_suffix, std_name in [('usage', 'stdout'), - ('help', 'stdout'), - ('version', 'stderr')]: - AddTests(cls, func_suffix, std_name) - -bases = TestCase, -HelpTestCase = TestHelpFormattingMetaclass('HelpTestCase', bases, {}) - - -class TestHelpBiggerOptionals(HelpTestCase): - """Make sure that argument help aligns when options are longer""" - - parser_signature = Sig(prog='PROG', description='DESCRIPTION', - epilog='EPILOG', version='0.1') - argument_signatures = [ - Sig('-x', action='store_true', help='X HELP'), - Sig('--y', help='Y HELP'), - Sig('foo', help='FOO HELP'), - Sig('bar', help='BAR HELP'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [-v] [-x] [--y Y] foo bar - ''' - help = usage + '''\ - - DESCRIPTION - - positional arguments: - foo FOO HELP - bar BAR HELP - - optional arguments: - -h, --help show this help message and exit - -v, --version show program's version number and exit - -x X HELP - --y Y Y HELP - - EPILOG - ''' - version = '''\ - 0.1 - ''' - - -class TestHelpBiggerOptionalGroups(HelpTestCase): - """Make sure that argument help aligns when options are longer""" - - parser_signature = Sig(prog='PROG', description='DESCRIPTION', - epilog='EPILOG', version='0.1') - argument_signatures = [ - Sig('-x', action='store_true', help='X HELP'), - Sig('--y', help='Y HELP'), - Sig('foo', help='FOO HELP'), - Sig('bar', help='BAR HELP'), - ] - argument_group_signatures = [ - (Sig('GROUP TITLE', description='GROUP DESCRIPTION'), [ - Sig('baz', help='BAZ HELP'), - Sig('-z', nargs='+', help='Z HELP')]), - ] - usage = '''\ - usage: PROG [-h] [-v] [-x] [--y Y] [-z Z [Z ...]] foo bar baz - ''' - help = usage + '''\ - - DESCRIPTION - - positional arguments: - foo FOO HELP - bar BAR HELP - - optional arguments: - -h, --help show this help message and exit - -v, --version show program's version number and exit - -x X HELP - --y Y Y HELP - - GROUP TITLE: - GROUP DESCRIPTION - - baz BAZ HELP - -z Z [Z ...] Z HELP - - EPILOG - ''' - version = '''\ - 0.1 - ''' - - -class TestHelpBiggerPositionals(HelpTestCase): - """Make sure that help aligns when arguments are longer""" - - parser_signature = Sig(usage='USAGE', description='DESCRIPTION') - argument_signatures = [ - Sig('-x', action='store_true', help='X HELP'), - Sig('--y', help='Y HELP'), - Sig('ekiekiekifekang', help='EKI HELP'), - Sig('bar', help='BAR HELP'), - ] - argument_group_signatures = [] - usage = '''\ - usage: USAGE - ''' - help = usage + '''\ - - DESCRIPTION - - positional arguments: - ekiekiekifekang EKI HELP - bar BAR HELP - - optional arguments: - -h, --help show this help message and exit - -x X HELP - --y Y Y HELP - ''' - - version = '' - - -class TestHelpReformatting(HelpTestCase): - """Make sure that text after short names starts on the first line""" - - parser_signature = Sig( - prog='PROG', - description=' oddly formatted\n' - 'description\n' - '\n' - 'that is so long that it should go onto multiple ' - 'lines when wrapped') - argument_signatures = [ - Sig('-x', metavar='XX', help='oddly\n' - ' formatted -x help'), - Sig('y', metavar='yyy', help='normal y help'), - ] - argument_group_signatures = [ - (Sig('title', description='\n' - ' oddly formatted group\n' - '\n' - 'description'), - [Sig('-a', action='store_true', - help=' oddly \n' - 'formatted -a help \n' - ' again, so long that it should be wrapped over ' - 'multiple lines')]), - ] - usage = '''\ - usage: PROG [-h] [-x XX] [-a] yyy - ''' - help = usage + '''\ - - oddly formatted description that is so long that it should go onto \ -multiple - lines when wrapped - - positional arguments: - yyy normal y help - - optional arguments: - -h, --help show this help message and exit - -x XX oddly formatted -x help - - title: - oddly formatted group description - - -a oddly formatted -a help again, so long that it should \ -be wrapped - over multiple lines - ''' - version = '' - - -class TestHelpWrappingShortNames(HelpTestCase): - """Make sure that text after short names starts on the first line""" - - parser_signature = Sig(prog='PROG', description= 'D\nD' * 30) - argument_signatures = [ - Sig('-x', metavar='XX', help='XHH HX' * 20), - Sig('y', metavar='yyy', help='YH YH' * 20), - ] - argument_group_signatures = [ - (Sig('ALPHAS'), [ - Sig('-a', action='store_true', help='AHHH HHA' * 10)]), - ] - usage = '''\ - usage: PROG [-h] [-x XX] [-a] yyy - ''' - help = usage + '''\ - - D DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD \ -DD DD DD - DD DD DD DD D - - positional arguments: - yyy YH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH \ -YHYH YHYH - YHYH YHYH YHYH YHYH YHYH YHYH YHYH YH - - optional arguments: - -h, --help show this help message and exit - -x XX XHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH \ -HXXHH HXXHH - HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HX - - ALPHAS: - -a AHHH HHAAHHH HHAAHHH HHAAHHH HHAAHHH HHAAHHH HHAAHHH \ -HHAAHHH - HHAAHHH HHAAHHH HHA - ''' - version = '' - - -class TestHelpWrappingLongNames(HelpTestCase): - """Make sure that text after long names starts on the next line""" - - parser_signature = Sig(usage='USAGE', description= 'D D' * 30, - version='V V'*30) - argument_signatures = [ - Sig('-x', metavar='X' * 25, help='XH XH' * 20), - Sig('y', metavar='y' * 25, help='YH YH' * 20), - ] - argument_group_signatures = [ - (Sig('ALPHAS'), [ - Sig('-a', metavar='A' * 25, help='AH AH' * 20), - Sig('z', metavar='z' * 25, help='ZH ZH' * 20)]), - ] - usage = '''\ - usage: USAGE - ''' - help = usage + '''\ - - D DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD \ -DD DD DD - DD DD DD DD D - - positional arguments: - yyyyyyyyyyyyyyyyyyyyyyyyy - YH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH \ -YHYH YHYH - YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YH - - optional arguments: - -h, --help show this help message and exit - -v, --version show program's version number and exit - -x XXXXXXXXXXXXXXXXXXXXXXXXX - XH XHXH XHXH XHXH XHXH XHXH XHXH XHXH XHXH \ -XHXH XHXH - XHXH XHXH XHXH XHXH XHXH XHXH XHXH XHXH XHXH XH - - ALPHAS: - -a AAAAAAAAAAAAAAAAAAAAAAAAA - AH AHAH AHAH AHAH AHAH AHAH AHAH AHAH AHAH \ -AHAH AHAH - AHAH AHAH AHAH AHAH AHAH AHAH AHAH AHAH AHAH AH - zzzzzzzzzzzzzzzzzzzzzzzzz - ZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH \ -ZHZH ZHZH - ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZH - ''' - version = '''\ - V VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV \ -VV VV VV - VV VV VV VV V - ''' - - -class TestHelpUsage(HelpTestCase): - """Test basic usage messages""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('-w', nargs='+', help='w'), - Sig('-x', nargs='*', help='x'), - Sig('a', help='a'), - Sig('b', help='b', nargs=2), - Sig('c', help='c', nargs='?'), - ] - argument_group_signatures = [ - (Sig('group'), [ - Sig('-y', nargs='?', help='y'), - Sig('-z', nargs=3, help='z'), - Sig('d', help='d', nargs='*'), - Sig('e', help='e', nargs='+'), - ]) - ] - usage = '''\ - usage: PROG [-h] [-w W [W ...]] [-x [X [X ...]]] [-y [Y]] [-z Z Z Z] - a b b [c] [d [d ...]] e [e ...] - ''' - help = usage + '''\ - - positional arguments: - a a - b b - c c - - optional arguments: - -h, --help show this help message and exit - -w W [W ...] w - -x [X [X ...]] x - - group: - -y [Y] y - -z Z Z Z z - d d - e e - ''' - version = '' - - -class TestHelpOnlyUserGroups(HelpTestCase): - """Test basic usage messages""" - - parser_signature = Sig(prog='PROG', add_help=False) - argument_signatures = [] - argument_group_signatures = [ - (Sig('xxxx'), [ - Sig('-x', help='x'), - Sig('a', help='a'), - ]), - (Sig('yyyy'), [ - Sig('b', help='b'), - Sig('-y', help='y'), - ]), - ] - usage = '''\ - usage: PROG [-x X] [-y Y] a b - ''' - help = usage + '''\ - - xxxx: - -x X x - a a - - yyyy: - b b - -y Y y - ''' - version = '' - - -class TestHelpUsageLongProg(HelpTestCase): - """Test usage messages where the prog is long""" - - parser_signature = Sig(prog='P' * 60) - argument_signatures = [ - Sig('-w', metavar='W'), - Sig('-x', metavar='X'), - Sig('a'), - Sig('b'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP - [-h] [-w W] [-x X] a b - ''' - help = usage + '''\ - - positional arguments: - a - b - - optional arguments: - -h, --help show this help message and exit - -w W - -x X - ''' - version = '' - - -class TestHelpUsageLongProgOptionsWrap(HelpTestCase): - """Test usage messages where the prog is long and the optionals wrap""" - - parser_signature = Sig(prog='P' * 60) - argument_signatures = [ - Sig('-w', metavar='W' * 25), - Sig('-x', metavar='X' * 25), - Sig('-y', metavar='Y' * 25), - Sig('-z', metavar='Z' * 25), - Sig('a'), - Sig('b'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP - [-h] [-w WWWWWWWWWWWWWWWWWWWWWWWWW] \ -[-x XXXXXXXXXXXXXXXXXXXXXXXXX] - [-y YYYYYYYYYYYYYYYYYYYYYYYYY] [-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] - a b - ''' - help = usage + '''\ - - positional arguments: - a - b - - optional arguments: - -h, --help show this help message and exit - -w WWWWWWWWWWWWWWWWWWWWWWWWW - -x XXXXXXXXXXXXXXXXXXXXXXXXX - -y YYYYYYYYYYYYYYYYYYYYYYYYY - -z ZZZZZZZZZZZZZZZZZZZZZZZZZ - ''' - version = '' - - -class TestHelpUsageLongProgPositionalsWrap(HelpTestCase): - """Test usage messages where the prog is long and the positionals wrap""" - - parser_signature = Sig(prog='P' * 60, add_help=False) - argument_signatures = [ - Sig('a' * 25), - Sig('b' * 25), - Sig('c' * 25), - ] - argument_group_signatures = [] - usage = '''\ - usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP - aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb - ccccccccccccccccccccccccc - ''' - help = usage + '''\ - - positional arguments: - aaaaaaaaaaaaaaaaaaaaaaaaa - bbbbbbbbbbbbbbbbbbbbbbbbb - ccccccccccccccccccccccccc - ''' - version = '' - - -class TestHelpUsageOptionalsWrap(HelpTestCase): - """Test usage messages where the optionals wrap""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('-w', metavar='W' * 25), - Sig('-x', metavar='X' * 25), - Sig('-y', metavar='Y' * 25), - Sig('-z', metavar='Z' * 25), - Sig('a'), - Sig('b'), - Sig('c'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [-w WWWWWWWWWWWWWWWWWWWWWWWWW] \ -[-x XXXXXXXXXXXXXXXXXXXXXXXXX] - [-y YYYYYYYYYYYYYYYYYYYYYYYYY] \ -[-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] - a b c - ''' - help = usage + '''\ - - positional arguments: - a - b - c - - optional arguments: - -h, --help show this help message and exit - -w WWWWWWWWWWWWWWWWWWWWWWWWW - -x XXXXXXXXXXXXXXXXXXXXXXXXX - -y YYYYYYYYYYYYYYYYYYYYYYYYY - -z ZZZZZZZZZZZZZZZZZZZZZZZZZ - ''' - version = '' - - -class TestHelpUsagePositionalsWrap(HelpTestCase): - """Test usage messages where the positionals wrap""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('-x'), - Sig('-y'), - Sig('-z'), - Sig('a' * 25), - Sig('b' * 25), - Sig('c' * 25), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [-x X] [-y Y] [-z Z] - aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb - ccccccccccccccccccccccccc - ''' - help = usage + '''\ - - positional arguments: - aaaaaaaaaaaaaaaaaaaaaaaaa - bbbbbbbbbbbbbbbbbbbbbbbbb - ccccccccccccccccccccccccc - - optional arguments: - -h, --help show this help message and exit - -x X - -y Y - -z Z - ''' - version = '' - - -class TestHelpUsageOptionalsPositionalsWrap(HelpTestCase): - """Test usage messages where the optionals and positionals wrap""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('-x', metavar='X' * 25), - Sig('-y', metavar='Y' * 25), - Sig('-z', metavar='Z' * 25), - Sig('a' * 25), - Sig('b' * 25), - Sig('c' * 25), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [-x XXXXXXXXXXXXXXXXXXXXXXXXX] \ -[-y YYYYYYYYYYYYYYYYYYYYYYYYY] - [-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] - aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb - ccccccccccccccccccccccccc - ''' - help = usage + '''\ - - positional arguments: - aaaaaaaaaaaaaaaaaaaaaaaaa - bbbbbbbbbbbbbbbbbbbbbbbbb - ccccccccccccccccccccccccc - - optional arguments: - -h, --help show this help message and exit - -x XXXXXXXXXXXXXXXXXXXXXXXXX - -y YYYYYYYYYYYYYYYYYYYYYYYYY - -z ZZZZZZZZZZZZZZZZZZZZZZZZZ - ''' - version = '' - - -class TestHelpUsageOptionalsOnlyWrap(HelpTestCase): - """Test usage messages where there are only optionals and they wrap""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('-x', metavar='X' * 25), - Sig('-y', metavar='Y' * 25), - Sig('-z', metavar='Z' * 25), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [-x XXXXXXXXXXXXXXXXXXXXXXXXX] \ -[-y YYYYYYYYYYYYYYYYYYYYYYYYY] - [-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] - ''' - help = usage + '''\ - - optional arguments: - -h, --help show this help message and exit - -x XXXXXXXXXXXXXXXXXXXXXXXXX - -y YYYYYYYYYYYYYYYYYYYYYYYYY - -z ZZZZZZZZZZZZZZZZZZZZZZZZZ - ''' - version = '' - - -class TestHelpUsagePositionalsOnlyWrap(HelpTestCase): - """Test usage messages where there are only positionals and they wrap""" - - parser_signature = Sig(prog='PROG', add_help=False) - argument_signatures = [ - Sig('a' * 25), - Sig('b' * 25), - Sig('c' * 25), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb - ccccccccccccccccccccccccc - ''' - help = usage + '''\ - - positional arguments: - aaaaaaaaaaaaaaaaaaaaaaaaa - bbbbbbbbbbbbbbbbbbbbbbbbb - ccccccccccccccccccccccccc - ''' - version = '' - - -class TestHelpVariableExpansion(HelpTestCase): - """Test that variables are expanded properly in help messages""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('-x', type=int, - help='x %(prog)s %(default)s %(type)s %%'), - Sig('-y', action='store_const', default=42, const='XXX', - help='y %(prog)s %(default)s %(const)s'), - Sig('--foo', choices='abc', - help='foo %(prog)s %(default)s %(choices)s'), - Sig('--bar', default='baz', choices=[1, 2], metavar='BBB', - help='bar %(prog)s %(default)s %(dest)s'), - Sig('spam', help='spam %(prog)s %(default)s'), - Sig('badger', default=0.5, help='badger %(prog)s %(default)s'), - ] - argument_group_signatures = [ - (Sig('group'), [ - Sig('-a', help='a %(prog)s %(default)s'), - Sig('-b', default=-1, help='b %(prog)s %(default)s'), - ]) - ] - usage = ('''\ - usage: PROG [-h] [-x X] [-y] [--foo {a,b,c}] [--bar BBB] [-a A] [-b B] - spam badger - ''') - help = usage + '''\ - - positional arguments: - spam spam PROG None - badger badger PROG 0.5 - - optional arguments: - -h, --help show this help message and exit - -x X x PROG None int % - -y y PROG 42 XXX - --foo {a,b,c} foo PROG None a, b, c - --bar BBB bar PROG baz bar - - group: - -a A a PROG None - -b B b PROG -1 - ''' - version = '' - - -class TestHelpVariableExpansionUsageSupplied(HelpTestCase): - """Test that variables are expanded properly when usage= is present""" - - parser_signature = Sig(prog='PROG', usage='%(prog)s FOO') - argument_signatures = [] - argument_group_signatures = [] - usage = ('''\ - usage: PROG FOO - ''') - help = usage + '''\ - - optional arguments: - -h, --help show this help message and exit - ''' - version = '' - - -class TestHelpVariableExpansionNoArguments(HelpTestCase): - """Test that variables are expanded properly with no arguments""" - - parser_signature = Sig(prog='PROG', add_help=False) - argument_signatures = [] - argument_group_signatures = [] - usage = ('''\ - usage: PROG - ''') - help = usage - version = '' - - -class TestHelpSuppressUsage(HelpTestCase): - """Test that items can be suppressed in usage messages""" - - parser_signature = Sig(prog='PROG', usage=argparse.SUPPRESS) - argument_signatures = [ - Sig('--foo', help='foo help'), - Sig('spam', help='spam help'), - ] - argument_group_signatures = [] - help = '''\ - positional arguments: - spam spam help - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help - ''' - usage = '' - version = '' - - -class TestHelpSuppressOptional(HelpTestCase): - """Test that optional arguments can be suppressed in help messages""" - - parser_signature = Sig(prog='PROG', add_help=False) - argument_signatures = [ - Sig('--foo', help=argparse.SUPPRESS), - Sig('spam', help='spam help'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG spam - ''' - help = usage + '''\ - - positional arguments: - spam spam help - ''' - version = '' - - -class TestHelpSuppressOptionalGroup(HelpTestCase): - """Test that optional groups can be suppressed in help messages""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('--foo', help='foo help'), - Sig('spam', help='spam help'), - ] - argument_group_signatures = [ - (Sig('group'), [Sig('--bar', help=argparse.SUPPRESS)]), - ] - usage = '''\ - usage: PROG [-h] [--foo FOO] spam - ''' - help = usage + '''\ - - positional arguments: - spam spam help - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help - ''' - version = '' - - -class TestHelpSuppressPositional(HelpTestCase): - """Test that positional arguments can be suppressed in help messages""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('--foo', help='foo help'), - Sig('spam', help=argparse.SUPPRESS), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [--foo FOO] - ''' - help = usage + '''\ - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help - ''' - version = '' - - -class TestHelpRequiredOptional(HelpTestCase): - """Test that required options don't look optional""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('--foo', required=True, help='foo help'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] --foo FOO - ''' - help = usage + '''\ - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help - ''' - version = '' - - -class TestHelpAlternatePrefixChars(HelpTestCase): - """Test that options display with different prefix characters""" - - parser_signature = Sig(prog='PROG', prefix_chars='^;', add_help=False) - argument_signatures = [ - Sig('^^foo', action='store_true', help='foo help'), - Sig(';b', ';;bar', help='bar help'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [^^foo] [;b BAR] - ''' - help = usage + '''\ - - optional arguments: - ^^foo foo help - ;b BAR, ;;bar BAR bar help - ''' - version = '' - - -class TestHelpNoHelpOptional(HelpTestCase): - """Test that the --help argument can be suppressed help messages""" - - parser_signature = Sig(prog='PROG', add_help=False) - argument_signatures = [ - Sig('--foo', help='foo help'), - Sig('spam', help='spam help'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [--foo FOO] spam - ''' - help = usage + '''\ - - positional arguments: - spam spam help - - optional arguments: - --foo FOO foo help - ''' - version = '' - - -class TestHelpVersionOptional(HelpTestCase): - """Test that the --version argument can be suppressed help messages""" - - parser_signature = Sig(prog='PROG', version='1.0') - argument_signatures = [ - Sig('--foo', help='foo help'), - Sig('spam', help='spam help'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [-v] [--foo FOO] spam - ''' - help = usage + '''\ - - positional arguments: - spam spam help - - optional arguments: - -h, --help show this help message and exit - -v, --version show program's version number and exit - --foo FOO foo help - ''' - version = '''\ - 1.0 - ''' - - -class TestHelpNone(HelpTestCase): - """Test that no errors occur if no help is specified""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('--foo'), - Sig('spam'), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [--foo FOO] spam - ''' - help = usage + '''\ - - positional arguments: - spam - - optional arguments: - -h, --help show this help message and exit - --foo FOO - ''' - version = '' - - -class TestHelpTupleMetavar(HelpTestCase): - """Test specifying metavar as a tuple""" - - parser_signature = Sig(prog='PROG') - argument_signatures = [ - Sig('-w', help='w', nargs='+', metavar=('W1', 'W2')), - Sig('-x', help='x', nargs='*', metavar=('X1', 'X2')), - Sig('-y', help='y', nargs=3, metavar=('Y1', 'Y2', 'Y3')), - Sig('-z', help='z', nargs='?', metavar=('Z1', )), - ] - argument_group_signatures = [] - usage = '''\ - usage: PROG [-h] [-w W1 [W2 ...]] [-x [X1 [X2 ...]]] [-y Y1 Y2 Y3] \ -[-z [Z1]] - ''' - help = usage + '''\ - - optional arguments: - -h, --help show this help message and exit - -w W1 [W2 ...] w - -x [X1 [X2 ...]] x - -y Y1 Y2 Y3 y - -z [Z1] z - ''' - version = '' - - -class TestHelpRawText(HelpTestCase): - """Test the RawTextHelpFormatter""" - - parser_signature = Sig( - prog='PROG', formatter_class=argparse.RawTextHelpFormatter, - description='Keep the formatting\n' - ' exactly as it is written\n' - '\n' - 'here\n') - - argument_signatures = [ - Sig('--foo', help=' foo help should also\n' - 'appear as given here'), - Sig('spam', help='spam help'), - ] - argument_group_signatures = [ - (Sig('title', description=' This text\n' - ' should be indented\n' - ' exactly like it is here\n'), - [Sig('--bar', help='bar help')]), - ] - usage = '''\ - usage: PROG [-h] [--foo FOO] [--bar BAR] spam - ''' - help = usage + '''\ - - Keep the formatting - exactly as it is written - - here - - positional arguments: - spam spam help - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help should also - appear as given here - - title: - This text - should be indented - exactly like it is here - - --bar BAR bar help - ''' - version = '' - - -class TestHelpRawDescription(HelpTestCase): - """Test the RawTextHelpFormatter""" - - parser_signature = Sig( - prog='PROG', formatter_class=argparse.RawDescriptionHelpFormatter, - description='Keep the formatting\n' - ' exactly as it is written\n' - '\n' - 'here\n') - - argument_signatures = [ - Sig('--foo', help=' foo help should not\n' - ' retain this odd formatting'), - Sig('spam', help='spam help'), - ] - argument_group_signatures = [ - (Sig('title', description=' This text\n' - ' should be indented\n' - ' exactly like it is here\n'), - [Sig('--bar', help='bar help')]), - ] - usage = '''\ - usage: PROG [-h] [--foo FOO] [--bar BAR] spam - ''' - help = usage + '''\ - - Keep the formatting - exactly as it is written - - here - - positional arguments: - spam spam help - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help should not retain this odd formatting - - title: - This text - should be indented - exactly like it is here - - --bar BAR bar help - ''' - version = '' - - -class TestHelpArgumentDefaults(HelpTestCase): - """Test the ArgumentDefaultsHelpFormatter""" - - parser_signature = Sig( - prog='PROG', formatter_class=argparse.ArgumentDefaultsHelpFormatter, - description='description') - - argument_signatures = [ - Sig('--foo', help='foo help - oh and by the way, %(default)s'), - Sig('--bar', action='store_true', help='bar help'), - Sig('spam', help='spam help'), - Sig('badger', nargs='?', default='wooden', help='badger help'), - ] - argument_group_signatures = [ - (Sig('title', description='description'), - [Sig('--baz', type=int, default=42, help='baz help')]), - ] - usage = '''\ - usage: PROG [-h] [--foo FOO] [--bar] [--baz BAZ] spam [badger] - ''' - help = usage + '''\ - - description - - positional arguments: - spam spam help - badger badger help (default: wooden) - - optional arguments: - -h, --help show this help message and exit - --foo FOO foo help - oh and by the way, None - --bar bar help (default: False) - - title: - description - - --baz BAZ baz help (default: 42) - ''' - version = '' - -# ===================================== -# Optional/Positional constructor tests -# ===================================== - -class TestInvalidArgumentConstructors(TestCase): - """Test a bunch of invalid Argument constructors""" - - def assertTypeError(self, *args, **kwargs): - parser = argparse.ArgumentParser() - self.assertRaises(TypeError, parser.add_argument, - *args, **kwargs) - - def assertValueError(self, *args, **kwargs): - parser = argparse.ArgumentParser() - self.assertRaises(ValueError, parser.add_argument, - *args, **kwargs) - - def test_invalid_keyword_arguments(self): - self.assertTypeError('-x', bar=None) - self.assertTypeError('-y', callback='foo') - self.assertTypeError('-y', callback_args=()) - self.assertTypeError('-y', callback_kwargs={}) - - def test_missing_destination(self): - self.assertTypeError() - for action in ['append', 'store']: - self.assertTypeError(action=action) - - def test_invalid_option_strings(self): - self.assertValueError('--') - self.assertValueError('---') - - def test_invalid_type(self): - self.assertValueError('--foo', type='int') - - def test_invalid_action(self): - self.assertValueError('-x', action='foo') - self.assertValueError('foo', action='baz') - parser = argparse.ArgumentParser() - try: - parser.add_argument("--foo", action="store-true") - except ValueError: - e = sys.exc_info()[1] - expected = 'unknown action' - msg = 'expected %r, found %r' % (expected, e) - self.assertTrue(expected in str(e), msg) - - def test_multiple_dest(self): - parser = argparse.ArgumentParser() - parser.add_argument(dest='foo') - try: - parser.add_argument('bar', dest='baz') - except ValueError: - e = sys.exc_info()[1] - expected = 'dest supplied twice for positional argument' - msg = 'expected %r, found %r' % (expected, e) - self.assertTrue(expected in str(e), msg) - - def test_no_argument_actions(self): - for action in ['store_const', 'store_true', 'store_false', - 'append_const', 'count']: - for attrs in [dict(type=int), dict(nargs='+'), - dict(choices='ab')]: - self.assertTypeError('-x', action=action, **attrs) - - def test_no_argument_no_const_actions(self): - # options with zero arguments - for action in ['store_true', 'store_false', 'count']: - - # const is always disallowed - self.assertTypeError('-x', const='foo', action=action) - - # nargs is always disallowed - self.assertTypeError('-x', nargs='*', action=action) - - def test_more_than_one_argument_actions(self): - for action in ['store', 'append']: - - # nargs=0 is disallowed - self.assertValueError('-x', nargs=0, action=action) - self.assertValueError('spam', nargs=0, action=action) - - # const is disallowed with non-optional arguments - for nargs in [1, '*', '+']: - self.assertValueError('-x', const='foo', - nargs=nargs, action=action) - self.assertValueError('spam', const='foo', - nargs=nargs, action=action) - - def test_required_const_actions(self): - for action in ['store_const', 'append_const']: - - # nargs is always disallowed - self.assertTypeError('-x', nargs='+', action=action) - - def test_parsers_action_missing_params(self): - self.assertTypeError('command', action='parsers') - self.assertTypeError('command', action='parsers', prog='PROG') - self.assertTypeError('command', action='parsers', - parser_class=argparse.ArgumentParser) - - def test_required_positional(self): - self.assertTypeError('foo', required=True) - - def test_user_defined_action(self): - - class Success(Exception): - pass - - class Action(object): - - def __init__(self, - option_strings, - dest, - const, - default, - required=False): - if dest == 'spam': - if const is Success: - if default is Success: - raise Success() - - def __call__(self, *args, **kwargs): - pass - - parser = argparse.ArgumentParser() - self.assertRaises(Success, parser.add_argument, '--spam', - action=Action, default=Success, const=Success) - self.assertRaises(Success, parser.add_argument, 'spam', - action=Action, default=Success, const=Success) - -# ================================ -# Actions returned by add_argument -# ================================ - -class TestActionsReturned(TestCase): - - def test_dest(self): - parser = argparse.ArgumentParser() - action = parser.add_argument('--foo') - self.assertEqual(action.dest, 'foo') - action = parser.add_argument('-b', '--bar') - self.assertEqual(action.dest, 'bar') - action = parser.add_argument('-x', '-y') - self.assertEqual(action.dest, 'x') - - def test_misc(self): - parser = argparse.ArgumentParser() - action = parser.add_argument('--foo', nargs='?', const=42, - default=84, type=int, choices=[1, 2], - help='FOO', metavar='BAR', dest='baz') - self.assertEqual(action.nargs, '?') - self.assertEqual(action.const, 42) - self.assertEqual(action.default, 84) - self.assertEqual(action.type, int) - self.assertEqual(action.choices, [1, 2]) - self.assertEqual(action.help, 'FOO') - self.assertEqual(action.metavar, 'BAR') - self.assertEqual(action.dest, 'baz') - - -# ================================ -# Argument conflict handling tests -# ================================ - -class TestConflictHandling(TestCase): - - def test_bad_type(self): - self.assertRaises(ValueError, argparse.ArgumentParser, - conflict_handler='foo') - - def test_conflict_error(self): - parser = argparse.ArgumentParser() - parser.add_argument('-x') - self.assertRaises(argparse.ArgumentError, - parser.add_argument, '-x') - parser.add_argument('--spam') - self.assertRaises(argparse.ArgumentError, - parser.add_argument, '--spam') - - def test_resolve_error(self): - get_parser = argparse.ArgumentParser - parser = get_parser(prog='PROG', conflict_handler='resolve') - - parser.add_argument('-x', help='OLD X') - parser.add_argument('-x', help='NEW X') - self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [-x X] - - optional arguments: - -h, --help show this help message and exit - -x X NEW X - ''')) - - parser.add_argument('--spam', metavar='OLD_SPAM') - parser.add_argument('--spam', metavar='NEW_SPAM') - self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [-x X] [--spam NEW_SPAM] - - optional arguments: - -h, --help show this help message and exit - -x X NEW X - --spam NEW_SPAM - ''')) - - -# ============================= -# Help and Version option tests -# ============================= - -class TestOptionalsHelpVersionActions(TestCase): - """Test the help and version actions""" - - def _get_error(self, func, *args, **kwargs): - try: - func(*args, **kwargs) - except ArgumentParserError: - return sys.exc_info()[1] - else: - self.assertRaises(ArgumentParserError, func, *args, **kwargs) - - def assertPrintHelpExit(self, parser, args_str): - self.assertEqual( - parser.format_help(), - self._get_error(parser.parse_args, args_str.split()).stdout) - - def assertPrintVersionExit(self, parser, args_str): - self.assertEqual( - parser.format_version(), - self._get_error(parser.parse_args, args_str.split()).stderr) - - def assertArgumentParserError(self, parser, *args): - self.assertRaises(ArgumentParserError, parser.parse_args, args) - - def test_version(self): - parser = ErrorRaisingArgumentParser(version='1.0') - self.assertPrintHelpExit(parser, '-h') - self.assertPrintHelpExit(parser, '--help') - self.assertPrintVersionExit(parser, '-v') - self.assertPrintVersionExit(parser, '--version') - - def test_version_format(self): - parser = ErrorRaisingArgumentParser(prog='PPP', version='%(prog)s 3.5') - msg = self._get_error(parser.parse_args, ['-v']).stderr - self.assertEqual('PPP 3.5\n', msg) - - def test_version_no_help(self): - parser = ErrorRaisingArgumentParser(add_help=False, version='1.0') - self.assertArgumentParserError(parser, '-h') - self.assertArgumentParserError(parser, '--help') - self.assertPrintVersionExit(parser, '-v') - self.assertPrintVersionExit(parser, '--version') - - def test_version_action(self): - parser = ErrorRaisingArgumentParser(prog='XXX') - parser.add_argument('-V', action='version', version='%(prog)s 3.7') - msg = self._get_error(parser.parse_args, ['-V']).stderr - self.assertEqual('XXX 3.7\n', msg) - - def test_no_help(self): - parser = ErrorRaisingArgumentParser(add_help=False) - self.assertArgumentParserError(parser, '-h') - self.assertArgumentParserError(parser, '--help') - self.assertArgumentParserError(parser, '-v') - self.assertArgumentParserError(parser, '--version') - - def test_alternate_help_version(self): - parser = ErrorRaisingArgumentParser() - parser.add_argument('-x', action='help') - parser.add_argument('-y', action='version') - self.assertPrintHelpExit(parser, '-x') - self.assertPrintVersionExit(parser, '-y') - self.assertArgumentParserError(parser, '-v') - self.assertArgumentParserError(parser, '--version') - - def test_help_version_extra_arguments(self): - parser = ErrorRaisingArgumentParser(version='1.0') - parser.add_argument('-x', action='store_true') - parser.add_argument('y') - - # try all combinations of valid prefixes and suffixes - valid_prefixes = ['', '-x', 'foo', '-x bar', 'baz -x'] - valid_suffixes = valid_prefixes + ['--bad-option', 'foo bar baz'] - for prefix in valid_prefixes: - for suffix in valid_suffixes: - format = '%s %%s %s' % (prefix, suffix) - self.assertPrintHelpExit(parser, format % '-h') - self.assertPrintHelpExit(parser, format % '--help') - self.assertPrintVersionExit(parser, format % '-v') - self.assertPrintVersionExit(parser, format % '--version') - - -# ====================== -# str() and repr() tests -# ====================== - -class TestStrings(TestCase): - """Test str() and repr() on Optionals and Positionals""" - - def assertStringEqual(self, obj, result_string): - for func in [str, repr]: - self.assertEqual(func(obj), result_string) - - def test_optional(self): - option = argparse.Action( - option_strings=['--foo', '-a', '-b'], - dest='b', - type='int', - nargs='+', - default=42, - choices=[1, 2, 3], - help='HELP', - metavar='METAVAR') - string = ( - "Action(option_strings=['--foo', '-a', '-b'], dest='b', " - "nargs='+', const=None, default=42, type='int', " - "choices=[1, 2, 3], help='HELP', metavar='METAVAR')") - self.assertStringEqual(option, string) - - def test_argument(self): - argument = argparse.Action( - option_strings=[], - dest='x', - type=float, - nargs='?', - default=2.5, - choices=[0.5, 1.5, 2.5], - help='H HH H', - metavar='MV MV MV') - string = ( - "Action(option_strings=[], dest='x', nargs='?', " - "const=None, default=2.5, type=%r, choices=[0.5, 1.5, 2.5], " - "help='H HH H', metavar='MV MV MV')" % float) - self.assertStringEqual(argument, string) - - def test_namespace(self): - ns = argparse.Namespace(foo=42, bar='spam') - string = "Namespace(bar='spam', foo=42)" - self.assertStringEqual(ns, string) - - def test_parser(self): - parser = argparse.ArgumentParser(prog='PROG') - string = ( - "ArgumentParser(prog='PROG', usage=None, description=None, " - "version=None, formatter_class=%r, conflict_handler='error', " - "add_help=True)" % argparse.HelpFormatter) - self.assertStringEqual(parser, string) - -# =============== -# Namespace tests -# =============== - -class TestNamespace(TestCase): - - def test_constructor(self): - ns = argparse.Namespace() - self.assertRaises(AttributeError, getattr, ns, 'x') - - ns = argparse.Namespace(a=42, b='spam') - self.assertEqual(ns.a, 42) - self.assertEqual(ns.b, 'spam') - - def test_equality(self): - ns1 = argparse.Namespace(a=1, b=2) - ns2 = argparse.Namespace(b=2, a=1) - ns3 = argparse.Namespace(a=1) - ns4 = argparse.Namespace(b=2) - - self.assertEqual(ns1, ns2) - self.assertNotEqual(ns1, ns3) - self.assertNotEqual(ns1, ns4) - self.assertNotEqual(ns2, ns3) - self.assertNotEqual(ns2, ns4) - self.assertTrue(ns1 != ns3) - self.assertTrue(ns1 != ns4) - self.assertTrue(ns2 != ns3) - self.assertTrue(ns2 != ns4) - - -# =================== -# File encoding tests -# =================== - -class TestEncoding(TestCase): - - def _test_module_encoding(self, path): - path, _ = os.path.splitext(path) - path += ".py" - codecs.open(path, 'r', 'utf8').read() - - def test_argparse_module_encoding(self): - self._test_module_encoding(argparse.__file__) - - def test_test_argparse_module_encoding(self): - self._test_module_encoding(__file__) - -# =================== -# ArgumentError tests -# =================== - -class TestArgumentError(TestCase): - - def test_argument_error(self): - msg = "my error here" - error = argparse.ArgumentError(None, msg) - self.assertEqual(str(error), msg) - -# ======================= -# ArgumentTypeError tests -# ======================= - -class TestArgumentError(TestCase): - - def test_argument_type_error(self): - - def spam(string): - raise argparse.ArgumentTypeError('spam!') - - parser = ErrorRaisingArgumentParser(prog='PROG', add_help=False) - parser.add_argument('x', type=spam) - try: - parser.parse_args(['XXX']) - except ArgumentParserError: - expected = 'usage: PROG x\nPROG: error: argument x: spam!\n' - msg = sys.exc_info()[1].stderr - self.assertEqual(expected, msg) - else: - self.fail() - -# ====================== -# parse_known_args tests -# ====================== - -class TestParseKnownArgs(TestCase): - - def test_optionals(self): - parser = argparse.ArgumentParser() - parser.add_argument('--foo') - args, extras = parser.parse_known_args('--foo F --bar --baz'.split()) - self.assertEqual(NS(foo='F'), args) - self.assertEqual(['--bar', '--baz'], extras) - - def test_mixed(self): - parser = argparse.ArgumentParser() - parser.add_argument('-v', nargs='?', const=1, type=int) - parser.add_argument('--spam', action='store_false') - parser.add_argument('badger') - - argv = ["B", "C", "--foo", "-v", "3", "4"] - args, extras = parser.parse_known_args(argv) - self.assertEqual(NS(v=3, spam=True, badger="B"), args) - self.assertEqual(["C", "--foo", "4"], extras) - -# ============================ -# from argparse import * tests -# ============================ - -class TestImportStar(TestCase): - - def test(self): - for name in argparse.__all__: - self.assertTrue(hasattr(argparse, name)) - - -if __name__ == '__main__': - unittest.main() +# Copyright 2006-2009 Steven J. Bethard . +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import codecs +import os +import shutil +import sys +import textwrap +import tempfile +import unittest +import warnings +import argparse + +from test import support + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +try: + set +except NameError: + from sets import Set as set + +try: + sorted +except NameError: + + def sorted(iterable, reverse=False): + result = list(iterable) + result.sort() + if reverse: + result.reverse() + return result + + +class TestCase(unittest.TestCase): + + def assertEqual(self, obj1, obj2): + if obj1 != obj2: + print('') + print(repr(obj1)) + print(repr(obj2)) + print(obj1) + print(obj2) + super(TestCase, self).assertEqual(obj1, obj2) + + +class TempDirMixin(object): + + def setUp(self): + self.temp_dir = tempfile.mkdtemp() + self.old_dir = os.getcwd() + os.chdir(self.temp_dir) + + def tearDown(self): + os.chdir(self.old_dir) + while True: + try: + shutil.rmtree(self.temp_dir) + except WindowsError: + continue + else: + break + + +class Sig(object): + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + +class NS(object): + + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + sorted_items = sorted(self.__dict__.items()) + kwarg_str = ', '.join(['%s=%r' % tup for tup in sorted_items]) + return '%s(%s)' % (type(self).__name__, kwarg_str) + + def __eq__(self, other): + return vars(self) == vars(other) + + def __ne__(self, other): + return not (self == other) + + +class ArgumentParserError(Exception): + + def __init__(self, message, stdout=None, stderr=None, error_code=None): + Exception.__init__(self, message, stdout, stderr) + self.message = message + self.stdout = stdout + self.stderr = stderr + self.error_code = error_code + + +def stderr_to_parser_error(parse_args, *args, **kwargs): + # if this is being called recursively and stderr or stdout is already being + # redirected, simply call the function and let the enclosing function + # catch the exception + if isinstance(sys.stderr, StringIO) or isinstance(sys.stdout, StringIO): + return parse_args(*args, **kwargs) + + # if this is not being called recursively, redirect stderr and + # use it as the ArgumentParserError message + old_stdout = sys.stdout + old_stderr = sys.stderr + sys.stdout = StringIO() + sys.stderr = StringIO() + try: + try: + result = parse_args(*args, **kwargs) + for key in list(vars(result)): + if getattr(result, key) is sys.stdout: + setattr(result, key, old_stdout) + if getattr(result, key) is sys.stderr: + setattr(result, key, old_stderr) + return result + except SystemExit: + code = sys.exc_info()[1].code + stdout = sys.stdout.getvalue() + stderr = sys.stderr.getvalue() + raise ArgumentParserError("SystemExit", stdout, stderr, code) + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + + +class ErrorRaisingArgumentParser(argparse.ArgumentParser): + + def parse_args(self, *args, **kwargs): + parse_args = super(ErrorRaisingArgumentParser, self).parse_args + return stderr_to_parser_error(parse_args, *args, **kwargs) + + def exit(self, *args, **kwargs): + exit = super(ErrorRaisingArgumentParser, self).exit + return stderr_to_parser_error(exit, *args, **kwargs) + + def error(self, *args, **kwargs): + error = super(ErrorRaisingArgumentParser, self).error + return stderr_to_parser_error(error, *args, **kwargs) + + +class ParserTesterMetaclass(type): + """Adds parser tests using the class attributes. + + Classes of this type should specify the following attributes: + + argument_signatures -- a list of Sig objects which specify + the signatures of Argument objects to be created + failures -- a list of args lists that should cause the parser + to fail + successes -- a list of (initial_args, options, remaining_args) tuples + where initial_args specifies the string args to be parsed, + options is a dict that should match the vars() of the options + parsed out of initial_args, and remaining_args should be any + remaining unparsed arguments + """ + + def __init__(cls, name, bases, bodydict): + if name == 'ParserTestCase': + return + + # default parser signature is empty + if not hasattr(cls, 'parser_signature'): + cls.parser_signature = Sig() + if not hasattr(cls, 'parser_class'): + cls.parser_class = ErrorRaisingArgumentParser + + # --------------------------------------- + # functions for adding optional arguments + # --------------------------------------- + def no_groups(parser, argument_signatures): + """Add all arguments directly to the parser""" + for sig in argument_signatures: + parser.add_argument(*sig.args, **sig.kwargs) + + def one_group(parser, argument_signatures): + """Add all arguments under a single group in the parser""" + group = parser.add_argument_group('foo') + for sig in argument_signatures: + group.add_argument(*sig.args, **sig.kwargs) + + def many_groups(parser, argument_signatures): + """Add each argument in its own group to the parser""" + for i, sig in enumerate(argument_signatures): + group = parser.add_argument_group('foo:%i' % i) + group.add_argument(*sig.args, **sig.kwargs) + + # -------------------------- + # functions for parsing args + # -------------------------- + def listargs(parser, args): + """Parse the args by passing in a list""" + return parser.parse_args(args) + + def sysargs(parser, args): + """Parse the args by defaulting to sys.argv""" + old_sys_argv = sys.argv + sys.argv = [old_sys_argv[0]] + args + try: + return parser.parse_args() + finally: + sys.argv = old_sys_argv + + # class that holds the combination of one optional argument + # addition method and one arg parsing method + class AddTests(object): + + def __init__(self, tester_cls, add_arguments, parse_args): + self._add_arguments = add_arguments + self._parse_args = parse_args + + add_arguments_name = self._add_arguments.__name__ + parse_args_name = self._parse_args.__name__ + for test_func in [self.test_failures, self.test_successes]: + func_name = test_func.__name__ + names = func_name, add_arguments_name, parse_args_name + test_name = '_'.join(names) + + def wrapper(self, test_func=test_func): + test_func(self) + try: + wrapper.__name__ = test_name + except TypeError: + pass + setattr(tester_cls, test_name, wrapper) + + def _get_parser(self, tester): + args = tester.parser_signature.args + kwargs = tester.parser_signature.kwargs + parser = tester.parser_class(*args, **kwargs) + self._add_arguments(parser, tester.argument_signatures) + return parser + + def test_failures(self, tester): + parser = self._get_parser(tester) + for args_str in tester.failures: + args = args_str.split() + raises = tester.assertRaises + raises(ArgumentParserError, parser.parse_args, args) + + def test_successes(self, tester): + parser = self._get_parser(tester) + for args, expected_ns in tester.successes: + if isinstance(args, str): + args = args.split() + result_ns = self._parse_args(parser, args) + tester.assertEqual(expected_ns, result_ns) + + # add tests for each combination of an optionals adding method + # and an arg parsing method + for add_arguments in [no_groups, one_group, many_groups]: + for parse_args in [listargs, sysargs]: + AddTests(cls, add_arguments, parse_args) + +bases = TestCase, +ParserTestCase = ParserTesterMetaclass('ParserTestCase', bases, {}) + +# =============== +# Optionals tests +# =============== + +class TestOptionalsSingleDash(ParserTestCase): + """Test an Optional with a single-dash option string""" + + argument_signatures = [Sig('-x')] + failures = ['-x', 'a', '--foo', '-x --foo', '-x -y'] + successes = [ + ('', NS(x=None)), + ('-x a', NS(x='a')), + ('-xa', NS(x='a')), + ('-x -1', NS(x='-1')), + ('-x-1', NS(x='-1')), + ] + + +class TestOptionalsSingleDashCombined(ParserTestCase): + """Test an Optional with a single-dash option string""" + + argument_signatures = [ + Sig('-x', action='store_true'), + Sig('-yyy', action='store_const', const=42), + Sig('-z'), + ] + failures = ['a', '--foo', '-xa', '-x --foo', '-x -z', '-z -x', + '-yx', '-yz a', '-yyyx', '-yyyza', '-xyza'] + successes = [ + ('', NS(x=False, yyy=None, z=None)), + ('-x', NS(x=True, yyy=None, z=None)), + ('-za', NS(x=False, yyy=None, z='a')), + ('-z a', NS(x=False, yyy=None, z='a')), + ('-xza', NS(x=True, yyy=None, z='a')), + ('-xz a', NS(x=True, yyy=None, z='a')), + ('-x -za', NS(x=True, yyy=None, z='a')), + ('-x -z a', NS(x=True, yyy=None, z='a')), + ('-y', NS(x=False, yyy=42, z=None)), + ('-yyy', NS(x=False, yyy=42, z=None)), + ('-x -yyy -za', NS(x=True, yyy=42, z='a')), + ('-x -yyy -z a', NS(x=True, yyy=42, z='a')), + ] + + +class TestOptionalsSingleDashLong(ParserTestCase): + """Test an Optional with a multi-character single-dash option string""" + + argument_signatures = [Sig('-foo')] + failures = ['-foo', 'a', '--foo', '-foo --foo', '-foo -y', '-fooa'] + successes = [ + ('', NS(foo=None)), + ('-foo a', NS(foo='a')), + ('-foo -1', NS(foo='-1')), + ('-fo a', NS(foo='a')), + ('-f a', NS(foo='a')), + ] + + +class TestOptionalsSingleDashSubsetAmbiguous(ParserTestCase): + """Test Optionals where option strings are subsets of each other""" + + argument_signatures = [Sig('-f'), Sig('-foobar'), Sig('-foorab')] + failures = ['-f', '-foo', '-fo', '-foo b', '-foob', '-fooba', '-foora'] + successes = [ + ('', NS(f=None, foobar=None, foorab=None)), + ('-f a', NS(f='a', foobar=None, foorab=None)), + ('-fa', NS(f='a', foobar=None, foorab=None)), + ('-foa', NS(f='oa', foobar=None, foorab=None)), + ('-fooa', NS(f='ooa', foobar=None, foorab=None)), + ('-foobar a', NS(f=None, foobar='a', foorab=None)), + ('-foorab a', NS(f=None, foobar=None, foorab='a')), + ] + + +class TestOptionalsSingleDashAmbiguous(ParserTestCase): + """Test Optionals that partially match but are not subsets""" + + argument_signatures = [Sig('-foobar'), Sig('-foorab')] + failures = ['-f', '-f a', '-fa', '-foa', '-foo', '-fo', '-foo b'] + successes = [ + ('', NS(foobar=None, foorab=None)), + ('-foob a', NS(foobar='a', foorab=None)), + ('-foor a', NS(foobar=None, foorab='a')), + ('-fooba a', NS(foobar='a', foorab=None)), + ('-foora a', NS(foobar=None, foorab='a')), + ('-foobar a', NS(foobar='a', foorab=None)), + ('-foorab a', NS(foobar=None, foorab='a')), + ] + + +class TestOptionalsNumeric(ParserTestCase): + """Test an Optional with a short opt string""" + + argument_signatures = [Sig('-1', dest='one')] + failures = ['-1', 'a', '-1 --foo', '-1 -y', '-1 -1', '-1 -2'] + successes = [ + ('', NS(one=None)), + ('-1 a', NS(one='a')), + ('-1a', NS(one='a')), + ('-1-2', NS(one='-2')), + ] + + +class TestOptionalsDoubleDash(ParserTestCase): + """Test an Optional with a double-dash option string""" + + argument_signatures = [Sig('--foo')] + failures = ['--foo', '-f', '-f a', 'a', '--foo -x', '--foo --bar'] + successes = [ + ('', NS(foo=None)), + ('--foo a', NS(foo='a')), + ('--foo=a', NS(foo='a')), + ('--foo -2.5', NS(foo='-2.5')), + ('--foo=-2.5', NS(foo='-2.5')), + ] + + +class TestOptionalsDoubleDashPartialMatch(ParserTestCase): + """Tests partial matching with a double-dash option string""" + + argument_signatures = [ + Sig('--badger', action='store_true'), + Sig('--bat'), + ] + failures = ['--bar', '--b', '--ba', '--b=2', '--ba=4', '--badge 5'] + successes = [ + ('', NS(badger=False, bat=None)), + ('--bat X', NS(badger=False, bat='X')), + ('--bad', NS(badger=True, bat=None)), + ('--badg', NS(badger=True, bat=None)), + ('--badge', NS(badger=True, bat=None)), + ('--badger', NS(badger=True, bat=None)), + ] + + +class TestOptionalsDoubleDashPrefixMatch(ParserTestCase): + """Tests when one double-dash option string is a prefix of another""" + + argument_signatures = [ + Sig('--badger', action='store_true'), + Sig('--ba'), + ] + failures = ['--bar', '--b', '--ba', '--b=2', '--badge 5'] + successes = [ + ('', NS(badger=False, ba=None)), + ('--ba X', NS(badger=False, ba='X')), + ('--ba=X', NS(badger=False, ba='X')), + ('--bad', NS(badger=True, ba=None)), + ('--badg', NS(badger=True, ba=None)), + ('--badge', NS(badger=True, ba=None)), + ('--badger', NS(badger=True, ba=None)), + ] + + +class TestOptionalsSingleDoubleDash(ParserTestCase): + """Test an Optional with single- and double-dash option strings""" + + argument_signatures = [ + Sig('-f', action='store_true'), + Sig('--bar'), + Sig('-baz', action='store_const', const=42), + ] + failures = ['--bar', '-fbar', '-fbaz', '-bazf', '-b B', 'B'] + successes = [ + ('', NS(f=False, bar=None, baz=None)), + ('-f', NS(f=True, bar=None, baz=None)), + ('--ba B', NS(f=False, bar='B', baz=None)), + ('-f --bar B', NS(f=True, bar='B', baz=None)), + ('-f -b', NS(f=True, bar=None, baz=42)), + ('-ba -f', NS(f=True, bar=None, baz=42)), + ] + + +class TestOptionalsAlternatePrefixChars(ParserTestCase): + """Test an Optional with a double-dash option string""" + + parser_signature = Sig(prefix_chars='+:/', add_help=False) + argument_signatures = [ + Sig('+f', action='store_true'), + Sig('::bar'), + Sig('/baz', action='store_const', const=42), + ] + failures = ['--bar', '-fbar', '-b B', 'B', '-f', '--bar B', '-baz'] + successes = [ + ('', NS(f=False, bar=None, baz=None)), + ('+f', NS(f=True, bar=None, baz=None)), + ('::ba B', NS(f=False, bar='B', baz=None)), + ('+f ::bar B', NS(f=True, bar='B', baz=None)), + ('+f /b', NS(f=True, bar=None, baz=42)), + ('/ba +f', NS(f=True, bar=None, baz=42)), + ] + + +class TestOptionalsShortLong(ParserTestCase): + """Test a combination of single- and double-dash option strings""" + + argument_signatures = [ + Sig('-v', '--verbose', '-n', '--noisy', action='store_true'), + ] + failures = ['--x --verbose', '-N', 'a', '-v x'] + successes = [ + ('', NS(verbose=False)), + ('-v', NS(verbose=True)), + ('--verbose', NS(verbose=True)), + ('-n', NS(verbose=True)), + ('--noisy', NS(verbose=True)), + ] + + +class TestOptionalsDest(ParserTestCase): + """Tests various means of setting destination""" + + argument_signatures = [Sig('--foo-bar'), Sig('--baz', dest='zabbaz')] + failures = ['a'] + successes = [ + ('--foo-bar f', NS(foo_bar='f', zabbaz=None)), + ('--baz g', NS(foo_bar=None, zabbaz='g')), + ('--foo-bar h --baz i', NS(foo_bar='h', zabbaz='i')), + ('--baz j --foo-bar k', NS(foo_bar='k', zabbaz='j')), + ] + + +class TestOptionalsDefault(ParserTestCase): + """Tests specifying a default for an Optional""" + + argument_signatures = [Sig('-x'), Sig('-y', default=42)] + failures = ['a'] + successes = [ + ('', NS(x=None, y=42)), + ('-xx', NS(x='x', y=42)), + ('-yy', NS(x=None, y='y')), + ] + + +class TestOptionalsNargsDefault(ParserTestCase): + """Tests not specifying the number of args for an Optional""" + + argument_signatures = [Sig('-x')] + failures = ['a', '-x'] + successes = [ + ('', NS(x=None)), + ('-x a', NS(x='a')), + ] + + +class TestOptionalsNargs1(ParserTestCase): + """Tests specifying the 1 arg for an Optional""" + + argument_signatures = [Sig('-x', nargs=1)] + failures = ['a', '-x'] + successes = [ + ('', NS(x=None)), + ('-x a', NS(x=['a'])), + ] + + +class TestOptionalsNargs3(ParserTestCase): + """Tests specifying the 3 args for an Optional""" + + argument_signatures = [Sig('-x', nargs=3)] + failures = ['a', '-x', '-x a', '-x a b', 'a -x', 'a -x b'] + successes = [ + ('', NS(x=None)), + ('-x a b c', NS(x=['a', 'b', 'c'])), + ] + + +class TestOptionalsNargsOptional(ParserTestCase): + """Tests specifying an Optional arg for an Optional""" + + argument_signatures = [ + Sig('-w', nargs='?'), + Sig('-x', nargs='?', const=42), + Sig('-y', nargs='?', default='spam'), + Sig('-z', nargs='?', type=int, const='42', default='84'), + ] + failures = ['2'] + successes = [ + ('', NS(w=None, x=None, y='spam', z=84)), + ('-w', NS(w=None, x=None, y='spam', z=84)), + ('-w 2', NS(w='2', x=None, y='spam', z=84)), + ('-x', NS(w=None, x=42, y='spam', z=84)), + ('-x 2', NS(w=None, x='2', y='spam', z=84)), + ('-y', NS(w=None, x=None, y=None, z=84)), + ('-y 2', NS(w=None, x=None, y='2', z=84)), + ('-z', NS(w=None, x=None, y='spam', z=42)), + ('-z 2', NS(w=None, x=None, y='spam', z=2)), + ] + + +class TestOptionalsNargsZeroOrMore(ParserTestCase): + """Tests specifying an args for an Optional that accepts zero or more""" + + argument_signatures = [ + Sig('-x', nargs='*'), + Sig('-y', nargs='*', default='spam'), + ] + failures = ['a'] + successes = [ + ('', NS(x=None, y='spam')), + ('-x', NS(x=[], y='spam')), + ('-x a', NS(x=['a'], y='spam')), + ('-x a b', NS(x=['a', 'b'], y='spam')), + ('-y', NS(x=None, y=[])), + ('-y a', NS(x=None, y=['a'])), + ('-y a b', NS(x=None, y=['a', 'b'])), + ] + + +class TestOptionalsNargsOneOrMore(ParserTestCase): + """Tests specifying an args for an Optional that accepts one or more""" + + argument_signatures = [ + Sig('-x', nargs='+'), + Sig('-y', nargs='+', default='spam'), + ] + failures = ['a', '-x', '-y', 'a -x', 'a -y b'] + successes = [ + ('', NS(x=None, y='spam')), + ('-x a', NS(x=['a'], y='spam')), + ('-x a b', NS(x=['a', 'b'], y='spam')), + ('-y a', NS(x=None, y=['a'])), + ('-y a b', NS(x=None, y=['a', 'b'])), + ] + + +class TestOptionalsChoices(ParserTestCase): + """Tests specifying the choices for an Optional""" + + argument_signatures = [ + Sig('-f', choices='abc'), + Sig('-g', type=int, choices=range(5))] + failures = ['a', '-f d', '-fad', '-ga', '-g 6'] + successes = [ + ('', NS(f=None, g=None)), + ('-f a', NS(f='a', g=None)), + ('-f c', NS(f='c', g=None)), + ('-g 0', NS(f=None, g=0)), + ('-g 03', NS(f=None, g=3)), + ('-fb -g4', NS(f='b', g=4)), + ] + + +class TestOptionalsRequired(ParserTestCase): + """Tests the an optional action that is required""" + + argument_signatures = [ + Sig('-x', type=int, required=True), + ] + failures = ['a', ''] + successes = [ + ('-x 1', NS(x=1)), + ('-x42', NS(x=42)), + ] + + +class TestOptionalsActionStore(ParserTestCase): + """Tests the store action for an Optional""" + + argument_signatures = [Sig('-x', action='store')] + failures = ['a', 'a -x'] + successes = [ + ('', NS(x=None)), + ('-xfoo', NS(x='foo')), + ] + + +class TestOptionalsActionStoreConst(ParserTestCase): + """Tests the store_const action for an Optional""" + + argument_signatures = [Sig('-y', action='store_const', const=object)] + failures = ['a'] + successes = [ + ('', NS(y=None)), + ('-y', NS(y=object)), + ] + + +class TestOptionalsActionStoreFalse(ParserTestCase): + """Tests the store_false action for an Optional""" + + argument_signatures = [Sig('-z', action='store_false')] + failures = ['a', '-za', '-z a'] + successes = [ + ('', NS(z=True)), + ('-z', NS(z=False)), + ] + + +class TestOptionalsActionStoreTrue(ParserTestCase): + """Tests the store_true action for an Optional""" + + argument_signatures = [Sig('--apple', action='store_true')] + failures = ['a', '--apple=b', '--apple b'] + successes = [ + ('', NS(apple=False)), + ('--apple', NS(apple=True)), + ] + + +class TestOptionalsActionAppend(ParserTestCase): + """Tests the append action for an Optional""" + + argument_signatures = [Sig('--baz', action='append')] + failures = ['a', '--baz', 'a --baz', '--baz a b'] + successes = [ + ('', NS(baz=None)), + ('--baz a', NS(baz=['a'])), + ('--baz a --baz b', NS(baz=['a', 'b'])), + ] + + +class TestOptionalsActionAppendWithDefault(ParserTestCase): + """Tests the append action for an Optional""" + + argument_signatures = [Sig('--baz', action='append', default=['X'])] + failures = ['a', '--baz', 'a --baz', '--baz a b'] + successes = [ + ('', NS(baz=['X'])), + ('--baz a', NS(baz=['X', 'a'])), + ('--baz a --baz b', NS(baz=['X', 'a', 'b'])), + ] + + +class TestOptionalsActionAppendConst(ParserTestCase): + """Tests the append_const action for an Optional""" + + argument_signatures = [ + Sig('-b', action='append_const', const=Exception), + Sig('-c', action='append', dest='b'), + ] + failures = ['a', '-c', 'a -c', '-bx', '-b x'] + successes = [ + ('', NS(b=None)), + ('-b', NS(b=[Exception])), + ('-b -cx -b -cyz', NS(b=[Exception, 'x', Exception, 'yz'])), + ] + + +class TestOptionalsActionAppendConstWithDefault(ParserTestCase): + """Tests the append_const action for an Optional""" + + argument_signatures = [ + Sig('-b', action='append_const', const=Exception, default=['X']), + Sig('-c', action='append', dest='b'), + ] + failures = ['a', '-c', 'a -c', '-bx', '-b x'] + successes = [ + ('', NS(b=['X'])), + ('-b', NS(b=['X', Exception])), + ('-b -cx -b -cyz', NS(b=['X', Exception, 'x', Exception, 'yz'])), + ] + + +class TestOptionalsActionCount(ParserTestCase): + """Tests the count action for an Optional""" + + argument_signatures = [Sig('-x', action='count')] + failures = ['a', '-x a', '-x b', '-x a -x b'] + successes = [ + ('', NS(x=None)), + ('-x', NS(x=1)), + ] + + +# ================ +# Positional tests +# ================ + +class TestPositionalsNargsNone(ParserTestCase): + """Test a Positional that doesn't specify nargs""" + + argument_signatures = [Sig('foo')] + failures = ['', '-x', 'a b'] + successes = [ + ('a', NS(foo='a')), + ] + + +class TestPositionalsNargs1(ParserTestCase): + """Test a Positional that specifies an nargs of 1""" + + argument_signatures = [Sig('foo', nargs=1)] + failures = ['', '-x', 'a b'] + successes = [ + ('a', NS(foo=['a'])), + ] + + +class TestPositionalsNargs2(ParserTestCase): + """Test a Positional that specifies an nargs of 2""" + + argument_signatures = [Sig('foo', nargs=2)] + failures = ['', 'a', '-x', 'a b c'] + successes = [ + ('a b', NS(foo=['a', 'b'])), + ] + + +class TestPositionalsNargsZeroOrMore(ParserTestCase): + """Test a Positional that specifies unlimited nargs""" + + argument_signatures = [Sig('foo', nargs='*')] + failures = ['-x'] + successes = [ + ('', NS(foo=[])), + ('a', NS(foo=['a'])), + ('a b', NS(foo=['a', 'b'])), + ] + + +class TestPositionalsNargsZeroOrMoreDefault(ParserTestCase): + """Test a Positional that specifies unlimited nargs and a default""" + + argument_signatures = [Sig('foo', nargs='*', default='bar')] + failures = ['-x'] + successes = [ + ('', NS(foo='bar')), + ('a', NS(foo=['a'])), + ('a b', NS(foo=['a', 'b'])), + ] + + +class TestPositionalsNargsOneOrMore(ParserTestCase): + """Test a Positional that specifies one or more nargs""" + + argument_signatures = [Sig('foo', nargs='+')] + failures = ['', '-x'] + successes = [ + ('a', NS(foo=['a'])), + ('a b', NS(foo=['a', 'b'])), + ] + + +class TestPositionalsNargsOptional(ParserTestCase): + """Tests an Optional Positional""" + + argument_signatures = [Sig('foo', nargs='?')] + failures = ['-x', 'a b'] + successes = [ + ('', NS(foo=None)), + ('a', NS(foo='a')), + ] + + +class TestPositionalsNargsOptionalDefault(ParserTestCase): + """Tests an Optional Positional with a default value""" + + argument_signatures = [Sig('foo', nargs='?', default=42)] + failures = ['-x', 'a b'] + successes = [ + ('', NS(foo=42)), + ('a', NS(foo='a')), + ] + + +class TestPositionalsNargsOptionalConvertedDefault(ParserTestCase): + """Tests an Optional Positional with a default value + that needs to be converted to the appropriate type. + """ + + argument_signatures = [ + Sig('foo', nargs='?', type=int, default='42'), + ] + failures = ['-x', 'a b', '1 2'] + successes = [ + ('', NS(foo=42)), + ('1', NS(foo=1)), + ] + + +class TestPositionalsNargsNoneNone(ParserTestCase): + """Test two Positionals that don't specify nargs""" + + argument_signatures = [Sig('foo'), Sig('bar')] + failures = ['', '-x', 'a', 'a b c'] + successes = [ + ('a b', NS(foo='a', bar='b')), + ] + + +class TestPositionalsNargsNone1(ParserTestCase): + """Test a Positional with no nargs followed by one with 1""" + + argument_signatures = [Sig('foo'), Sig('bar', nargs=1)] + failures = ['', '--foo', 'a', 'a b c'] + successes = [ + ('a b', NS(foo='a', bar=['b'])), + ] + + +class TestPositionalsNargs2None(ParserTestCase): + """Test a Positional with 2 nargs followed by one with none""" + + argument_signatures = [Sig('foo', nargs=2), Sig('bar')] + failures = ['', '--foo', 'a', 'a b', 'a b c d'] + successes = [ + ('a b c', NS(foo=['a', 'b'], bar='c')), + ] + + +class TestPositionalsNargsNoneZeroOrMore(ParserTestCase): + """Test a Positional with no nargs followed by one with unlimited""" + + argument_signatures = [Sig('foo'), Sig('bar', nargs='*')] + failures = ['', '--foo'] + successes = [ + ('a', NS(foo='a', bar=[])), + ('a b', NS(foo='a', bar=['b'])), + ('a b c', NS(foo='a', bar=['b', 'c'])), + ] + + +class TestPositionalsNargsNoneOneOrMore(ParserTestCase): + """Test a Positional with no nargs followed by one with one or more""" + + argument_signatures = [Sig('foo'), Sig('bar', nargs='+')] + failures = ['', '--foo', 'a'] + successes = [ + ('a b', NS(foo='a', bar=['b'])), + ('a b c', NS(foo='a', bar=['b', 'c'])), + ] + + +class TestPositionalsNargsNoneOptional(ParserTestCase): + """Test a Positional with no nargs followed by one with an Optional""" + + argument_signatures = [Sig('foo'), Sig('bar', nargs='?')] + failures = ['', '--foo', 'a b c'] + successes = [ + ('a', NS(foo='a', bar=None)), + ('a b', NS(foo='a', bar='b')), + ] + + +class TestPositionalsNargsZeroOrMoreNone(ParserTestCase): + """Test a Positional with unlimited nargs followed by one with none""" + + argument_signatures = [Sig('foo', nargs='*'), Sig('bar')] + failures = ['', '--foo'] + successes = [ + ('a', NS(foo=[], bar='a')), + ('a b', NS(foo=['a'], bar='b')), + ('a b c', NS(foo=['a', 'b'], bar='c')), + ] + + +class TestPositionalsNargsOneOrMoreNone(ParserTestCase): + """Test a Positional with one or more nargs followed by one with none""" + + argument_signatures = [Sig('foo', nargs='+'), Sig('bar')] + failures = ['', '--foo', 'a'] + successes = [ + ('a b', NS(foo=['a'], bar='b')), + ('a b c', NS(foo=['a', 'b'], bar='c')), + ] + + +class TestPositionalsNargsOptionalNone(ParserTestCase): + """Test a Positional with an Optional nargs followed by one with none""" + + argument_signatures = [Sig('foo', nargs='?', default=42), Sig('bar')] + failures = ['', '--foo', 'a b c'] + successes = [ + ('a', NS(foo=42, bar='a')), + ('a b', NS(foo='a', bar='b')), + ] + + +class TestPositionalsNargs2ZeroOrMore(ParserTestCase): + """Test a Positional with 2 nargs followed by one with unlimited""" + + argument_signatures = [Sig('foo', nargs=2), Sig('bar', nargs='*')] + failures = ['', '--foo', 'a'] + successes = [ + ('a b', NS(foo=['a', 'b'], bar=[])), + ('a b c', NS(foo=['a', 'b'], bar=['c'])), + ] + + +class TestPositionalsNargs2OneOrMore(ParserTestCase): + """Test a Positional with 2 nargs followed by one with one or more""" + + argument_signatures = [Sig('foo', nargs=2), Sig('bar', nargs='+')] + failures = ['', '--foo', 'a', 'a b'] + successes = [ + ('a b c', NS(foo=['a', 'b'], bar=['c'])), + ] + + +class TestPositionalsNargs2Optional(ParserTestCase): + """Test a Positional with 2 nargs followed by one optional""" + + argument_signatures = [Sig('foo', nargs=2), Sig('bar', nargs='?')] + failures = ['', '--foo', 'a', 'a b c d'] + successes = [ + ('a b', NS(foo=['a', 'b'], bar=None)), + ('a b c', NS(foo=['a', 'b'], bar='c')), + ] + + +class TestPositionalsNargsZeroOrMore1(ParserTestCase): + """Test a Positional with unlimited nargs followed by one with 1""" + + argument_signatures = [Sig('foo', nargs='*'), Sig('bar', nargs=1)] + failures = ['', '--foo', ] + successes = [ + ('a', NS(foo=[], bar=['a'])), + ('a b', NS(foo=['a'], bar=['b'])), + ('a b c', NS(foo=['a', 'b'], bar=['c'])), + ] + + +class TestPositionalsNargsOneOrMore1(ParserTestCase): + """Test a Positional with one or more nargs followed by one with 1""" + + argument_signatures = [Sig('foo', nargs='+'), Sig('bar', nargs=1)] + failures = ['', '--foo', 'a'] + successes = [ + ('a b', NS(foo=['a'], bar=['b'])), + ('a b c', NS(foo=['a', 'b'], bar=['c'])), + ] + + +class TestPositionalsNargsOptional1(ParserTestCase): + """Test a Positional with an Optional nargs followed by one with 1""" + + argument_signatures = [Sig('foo', nargs='?'), Sig('bar', nargs=1)] + failures = ['', '--foo', 'a b c'] + successes = [ + ('a', NS(foo=None, bar=['a'])), + ('a b', NS(foo='a', bar=['b'])), + ] + + +class TestPositionalsNargsNoneZeroOrMore1(ParserTestCase): + """Test three Positionals: no nargs, unlimited nargs and 1 nargs""" + + argument_signatures = [ + Sig('foo'), + Sig('bar', nargs='*'), + Sig('baz', nargs=1), + ] + failures = ['', '--foo', 'a'] + successes = [ + ('a b', NS(foo='a', bar=[], baz=['b'])), + ('a b c', NS(foo='a', bar=['b'], baz=['c'])), + ] + + +class TestPositionalsNargsNoneOneOrMore1(ParserTestCase): + """Test three Positionals: no nargs, one or more nargs and 1 nargs""" + + argument_signatures = [ + Sig('foo'), + Sig('bar', nargs='+'), + Sig('baz', nargs=1), + ] + failures = ['', '--foo', 'a', 'b'] + successes = [ + ('a b c', NS(foo='a', bar=['b'], baz=['c'])), + ('a b c d', NS(foo='a', bar=['b', 'c'], baz=['d'])), + ] + + +class TestPositionalsNargsNoneOptional1(ParserTestCase): + """Test three Positionals: no nargs, optional narg and 1 nargs""" + + argument_signatures = [ + Sig('foo'), + Sig('bar', nargs='?', default=0.625), + Sig('baz', nargs=1), + ] + failures = ['', '--foo', 'a'] + successes = [ + ('a b', NS(foo='a', bar=0.625, baz=['b'])), + ('a b c', NS(foo='a', bar='b', baz=['c'])), + ] + + +class TestPositionalsNargsOptionalOptional(ParserTestCase): + """Test two optional nargs""" + + argument_signatures = [ + Sig('foo', nargs='?'), + Sig('bar', nargs='?', default=42), + ] + failures = ['--foo', 'a b c'] + successes = [ + ('', NS(foo=None, bar=42)), + ('a', NS(foo='a', bar=42)), + ('a b', NS(foo='a', bar='b')), + ] + + +class TestPositionalsNargsOptionalZeroOrMore(ParserTestCase): + """Test an Optional narg followed by unlimited nargs""" + + argument_signatures = [Sig('foo', nargs='?'), Sig('bar', nargs='*')] + failures = ['--foo'] + successes = [ + ('', NS(foo=None, bar=[])), + ('a', NS(foo='a', bar=[])), + ('a b', NS(foo='a', bar=['b'])), + ('a b c', NS(foo='a', bar=['b', 'c'])), + ] + + +class TestPositionalsNargsOptionalOneOrMore(ParserTestCase): + """Test an Optional narg followed by one or more nargs""" + + argument_signatures = [Sig('foo', nargs='?'), Sig('bar', nargs='+')] + failures = ['', '--foo'] + successes = [ + ('a', NS(foo=None, bar=['a'])), + ('a b', NS(foo='a', bar=['b'])), + ('a b c', NS(foo='a', bar=['b', 'c'])), + ] + + +class TestPositionalsChoicesString(ParserTestCase): + """Test a set of single-character choices""" + + argument_signatures = [Sig('spam', choices=set('abcdefg'))] + failures = ['', '--foo', 'h', '42', 'ef'] + successes = [ + ('a', NS(spam='a')), + ('g', NS(spam='g')), + ] + + +class TestPositionalsChoicesInt(ParserTestCase): + """Test a set of integer choices""" + + argument_signatures = [Sig('spam', type=int, choices=range(20))] + failures = ['', '--foo', 'h', '42', 'ef'] + successes = [ + ('4', NS(spam=4)), + ('15', NS(spam=15)), + ] + + +class TestPositionalsActionAppend(ParserTestCase): + """Test the 'append' action""" + + argument_signatures = [ + Sig('spam', action='append'), + Sig('spam', action='append', nargs=2), + ] + failures = ['', '--foo', 'a', 'a b', 'a b c d'] + successes = [ + ('a b c', NS(spam=['a', ['b', 'c']])), + ] + +# ======================================== +# Combined optionals and positionals tests +# ======================================== + +class TestOptionalsNumericAndPositionals(ParserTestCase): + """Tests negative number args when numeric options are present""" + + argument_signatures = [ + Sig('x', nargs='?'), + Sig('-4', dest='y', action='store_true'), + ] + failures = ['-2', '-315'] + successes = [ + ('', NS(x=None, y=False)), + ('a', NS(x='a', y=False)), + ('-4', NS(x=None, y=True)), + ('-4 a', NS(x='a', y=True)), + ] + + +class TestOptionalsAlmostNumericAndPositionals(ParserTestCase): + """Tests negative number args when almost numeric options are present""" + + argument_signatures = [ + Sig('x', nargs='?'), + Sig('-k4', dest='y', action='store_true'), + ] + failures = ['-k3'] + successes = [ + ('', NS(x=None, y=False)), + ('-2', NS(x='-2', y=False)), + ('a', NS(x='a', y=False)), + ('-k4', NS(x=None, y=True)), + ('-k4 a', NS(x='a', y=True)), + ] + + +class TestEmptyAndSpaceContainingArguments(ParserTestCase): + + argument_signatures = [ + Sig('x', nargs='?'), + Sig('-y', '--yyy', dest='y'), + ] + failures = ['-y'] + successes = [ + ([''], NS(x='', y=None)), + (['a badger'], NS(x='a badger', y=None)), + (['-a badger'], NS(x='-a badger', y=None)), + (['-y', ''], NS(x=None, y='')), + (['-y', 'a badger'], NS(x=None, y='a badger')), + (['-y', '-a badger'], NS(x=None, y='-a badger')), + (['--yyy=a badger'], NS(x=None, y='a badger')), + (['--yyy=-a badger'], NS(x=None, y='-a badger')), + ] + + +class TestPrefixCharacterOnlyArguments(ParserTestCase): + + parser_signature = Sig(prefix_chars='-+') + argument_signatures = [ + Sig('-', dest='x', nargs='?', const='badger'), + Sig('+', dest='y', type=int, default=42), + Sig('-+-', dest='z', action='store_true'), + ] + failures = ['-y', '+ -'] + successes = [ + ('', NS(x=None, y=42, z=False)), + ('-', NS(x='badger', y=42, z=False)), + ('- X', NS(x='X', y=42, z=False)), + ('+ -3', NS(x=None, y=-3, z=False)), + ('-+-', NS(x=None, y=42, z=True)), + ('- ===', NS(x='===', y=42, z=False)), + ] + + +class TestNargsZeroOrMore(ParserTestCase): + """Tests specifying an args for an Optional that accepts zero or more""" + + argument_signatures = [Sig('-x', nargs='*'), Sig('y', nargs='*')] + failures = [] + successes = [ + ('', NS(x=None, y=[])), + ('-x', NS(x=[], y=[])), + ('-x a', NS(x=['a'], y=[])), + ('-x a -- b', NS(x=['a'], y=['b'])), + ('a', NS(x=None, y=['a'])), + ('a -x', NS(x=[], y=['a'])), + ('a -x b', NS(x=['b'], y=['a'])), + ] + + +class TestNargsRemainder(ParserTestCase): + """Tests specifying a positional with nargs=REMAINDER""" + + argument_signatures = [Sig('x'), Sig('y', nargs='...'), Sig('-z')] + failures = ['', '-z', '-z Z'] + successes = [ + ('X', NS(x='X', y=[], z=None)), + ('-z Z X', NS(x='X', y=[], z='Z')), + ('X A B -z Z', NS(x='X', y=['A', 'B', '-z', 'Z'], z=None)), + ('X Y --foo', NS(x='X', y=['Y', '--foo'], z=None)), + ] + + +class TestOptionLike(ParserTestCase): + """Tests options that may or may not be arguments""" + + argument_signatures = [ + Sig('-x', type=float), + Sig('-3', type=float, dest='y'), + Sig('z', nargs='*'), + ] + failures = ['-x', '-y2.5', '-xa', '-x -a', + '-x -3', '-x -3.5', '-3 -3.5', + '-x -2.5', '-x -2.5 a', '-3 -.5', + 'a x -1', '-x -1 a', '-3 -1 a'] + successes = [ + ('', NS(x=None, y=None, z=[])), + ('-x 2.5', NS(x=2.5, y=None, z=[])), + ('-x 2.5 a', NS(x=2.5, y=None, z=['a'])), + ('-3.5', NS(x=None, y=0.5, z=[])), + ('-3-.5', NS(x=None, y=-0.5, z=[])), + ('-3 .5', NS(x=None, y=0.5, z=[])), + ('a -3.5', NS(x=None, y=0.5, z=['a'])), + ('a', NS(x=None, y=None, z=['a'])), + ('a -x 1', NS(x=1.0, y=None, z=['a'])), + ('-x 1 a', NS(x=1.0, y=None, z=['a'])), + ('-3 1 a', NS(x=None, y=1.0, z=['a'])), + ] + + +class TestDefaultSuppress(ParserTestCase): + """Test actions with suppressed defaults""" + + argument_signatures = [ + Sig('foo', nargs='?', default=argparse.SUPPRESS), + Sig('bar', nargs='*', default=argparse.SUPPRESS), + Sig('--baz', action='store_true', default=argparse.SUPPRESS), + ] + failures = ['-x'] + successes = [ + ('', NS()), + ('a', NS(foo='a')), + ('a b', NS(foo='a', bar=['b'])), + ('--baz', NS(baz=True)), + ('a --baz', NS(foo='a', baz=True)), + ('--baz a b', NS(foo='a', bar=['b'], baz=True)), + ] + + +class TestParserDefaultSuppress(ParserTestCase): + """Test actions with a parser-level default of SUPPRESS""" + + parser_signature = Sig(argument_default=argparse.SUPPRESS) + argument_signatures = [ + Sig('foo', nargs='?'), + Sig('bar', nargs='*'), + Sig('--baz', action='store_true'), + ] + failures = ['-x'] + successes = [ + ('', NS()), + ('a', NS(foo='a')), + ('a b', NS(foo='a', bar=['b'])), + ('--baz', NS(baz=True)), + ('a --baz', NS(foo='a', baz=True)), + ('--baz a b', NS(foo='a', bar=['b'], baz=True)), + ] + + +class TestParserDefault42(ParserTestCase): + """Test actions with a parser-level default of 42""" + + parser_signature = Sig(argument_default=42, version='1.0') + argument_signatures = [ + Sig('foo', nargs='?'), + Sig('bar', nargs='*'), + Sig('--baz', action='store_true'), + ] + failures = ['-x'] + successes = [ + ('', NS(foo=42, bar=42, baz=42)), + ('a', NS(foo='a', bar=42, baz=42)), + ('a b', NS(foo='a', bar=['b'], baz=42)), + ('--baz', NS(foo=42, bar=42, baz=True)), + ('a --baz', NS(foo='a', bar=42, baz=True)), + ('--baz a b', NS(foo='a', bar=['b'], baz=True)), + ] + + +class TestArgumentsFromFile(TempDirMixin, ParserTestCase): + """Test reading arguments from a file""" + + def setUp(self): + super(TestArgumentsFromFile, self).setUp() + file_texts = [ + ('hello', 'hello world!\n'), + ('recursive', '-a\n' + 'A\n' + '@hello'), + ('invalid', '@no-such-path\n'), + ] + for path, text in file_texts: + file = open(path, 'w') + file.write(text) + file.close() + + parser_signature = Sig(fromfile_prefix_chars='@') + argument_signatures = [ + Sig('-a'), + Sig('x'), + Sig('y', nargs='+'), + ] + failures = ['', '-b', 'X', '@invalid', '@missing'] + successes = [ + ('X Y', NS(a=None, x='X', y=['Y'])), + ('X -a A Y Z', NS(a='A', x='X', y=['Y', 'Z'])), + ('@hello X', NS(a=None, x='hello world!', y=['X'])), + ('X @hello', NS(a=None, x='X', y=['hello world!'])), + ('-a B @recursive Y Z', NS(a='A', x='hello world!', y=['Y', 'Z'])), + ('X @recursive Z -a B', NS(a='B', x='X', y=['hello world!', 'Z'])), + ] + + +class TestArgumentsFromFileConverter(TempDirMixin, ParserTestCase): + """Test reading arguments from a file""" + + def setUp(self): + super(TestArgumentsFromFileConverter, self).setUp() + file_texts = [ + ('hello', 'hello world!\n'), + ] + for path, text in file_texts: + file = open(path, 'w') + file.write(text) + file.close() + + class FromFileConverterArgumentParser(ErrorRaisingArgumentParser): + + def convert_arg_line_to_args(self, arg_line): + for arg in arg_line.split(): + if not arg.strip(): + continue + yield arg + parser_class = FromFileConverterArgumentParser + parser_signature = Sig(fromfile_prefix_chars='@') + argument_signatures = [ + Sig('y', nargs='+'), + ] + failures = [] + successes = [ + ('@hello X', NS(y=['hello', 'world!', 'X'])), + ] + + +# ===================== +# Type conversion tests +# ===================== + +class TestFileTypeRepr(TestCase): + + def test_r(self): + type = argparse.FileType('r') + self.assertEqual("FileType('r')", repr(type)) + + def test_wb_1(self): + type = argparse.FileType('wb', 1) + self.assertEqual("FileType('wb', 1)", repr(type)) + + +class RFile(object): + seen = {} + + def __init__(self, name): + self.name = name + + def __eq__(self, other): + if other in self.seen: + text = self.seen[other] + else: + text = self.seen[other] = other.read() + other.close() + if not isinstance(text, str): + text = text.decode('ascii') + return self.name == other.name == text + + +class TestFileTypeR(TempDirMixin, ParserTestCase): + """Test the FileType option/argument type for reading files""" + + def setUp(self): + super(TestFileTypeR, self).setUp() + for file_name in ['foo', 'bar']: + file = open(os.path.join(self.temp_dir, file_name), 'w') + file.write(file_name) + file.close() + + argument_signatures = [ + Sig('-x', type=argparse.FileType()), + Sig('spam', type=argparse.FileType('r')), + ] + failures = ['-x', ''] + successes = [ + ('foo', NS(x=None, spam=RFile('foo'))), + ('-x foo bar', NS(x=RFile('foo'), spam=RFile('bar'))), + ('bar -x foo', NS(x=RFile('foo'), spam=RFile('bar'))), + ('-x - -', NS(x=sys.stdin, spam=sys.stdin)), + ] + + +class TestFileTypeRB(TempDirMixin, ParserTestCase): + """Test the FileType option/argument type for reading files""" + + def setUp(self): + super(TestFileTypeRB, self).setUp() + for file_name in ['foo', 'bar']: + file = open(os.path.join(self.temp_dir, file_name), 'w') + file.write(file_name) + file.close() + + argument_signatures = [ + Sig('-x', type=argparse.FileType('rb')), + Sig('spam', type=argparse.FileType('rb')), + ] + failures = ['-x', ''] + successes = [ + ('foo', NS(x=None, spam=RFile('foo'))), + ('-x foo bar', NS(x=RFile('foo'), spam=RFile('bar'))), + ('bar -x foo', NS(x=RFile('foo'), spam=RFile('bar'))), + ('-x - -', NS(x=sys.stdin, spam=sys.stdin)), + ] + + +class WFile(object): + seen = set() + + def __init__(self, name): + self.name = name + + def __eq__(self, other): + if other not in self.seen: + text = 'Check that file is writable.' + if 'b' in other.mode: + text = text.encode('ascii') + other.write(text) + other.close() + self.seen.add(other) + return self.name == other.name + + +class TestFileTypeW(TempDirMixin, ParserTestCase): + """Test the FileType option/argument type for writing files""" + + argument_signatures = [ + Sig('-x', type=argparse.FileType('w')), + Sig('spam', type=argparse.FileType('w')), + ] + failures = ['-x', ''] + successes = [ + ('foo', NS(x=None, spam=WFile('foo'))), + ('-x foo bar', NS(x=WFile('foo'), spam=WFile('bar'))), + ('bar -x foo', NS(x=WFile('foo'), spam=WFile('bar'))), + ('-x - -', NS(x=sys.stdout, spam=sys.stdout)), + ] + + +class TestFileTypeWB(TempDirMixin, ParserTestCase): + + argument_signatures = [ + Sig('-x', type=argparse.FileType('wb')), + Sig('spam', type=argparse.FileType('wb')), + ] + failures = ['-x', ''] + successes = [ + ('foo', NS(x=None, spam=WFile('foo'))), + ('-x foo bar', NS(x=WFile('foo'), spam=WFile('bar'))), + ('bar -x foo', NS(x=WFile('foo'), spam=WFile('bar'))), + ('-x - -', NS(x=sys.stdout, spam=sys.stdout)), + ] + + +class TestTypeCallable(ParserTestCase): + """Test some callables as option/argument types""" + + argument_signatures = [ + Sig('--eggs', type=complex), + Sig('spam', type=float), + ] + failures = ['a', '42j', '--eggs a', '--eggs 2i'] + successes = [ + ('--eggs=42 42', NS(eggs=42, spam=42.0)), + ('--eggs 2j -- -1.5', NS(eggs=2j, spam=-1.5)), + ('1024.675', NS(eggs=None, spam=1024.675)), + ] + + +class TestTypeUserDefined(ParserTestCase): + """Test a user-defined option/argument type""" + + class MyType(TestCase): + + def __init__(self, value): + self.value = value + + def __eq__(self, other): + return (type(self), self.value) == (type(other), other.value) + + argument_signatures = [ + Sig('-x', type=MyType), + Sig('spam', type=MyType), + ] + failures = [] + successes = [ + ('a -x b', NS(x=MyType('b'), spam=MyType('a'))), + ('-xf g', NS(x=MyType('f'), spam=MyType('g'))), + ] + + +class TestTypeClassicClass(ParserTestCase): + """Test a classic class type""" + + class C: + + def __init__(self, value): + self.value = value + + def __eq__(self, other): + return (type(self), self.value) == (type(other), other.value) + + argument_signatures = [ + Sig('-x', type=C), + Sig('spam', type=C), + ] + failures = [] + successes = [ + ('a -x b', NS(x=C('b'), spam=C('a'))), + ('-xf g', NS(x=C('f'), spam=C('g'))), + ] + + +class TestTypeRegistration(TestCase): + """Test a user-defined type by registering it""" + + def test(self): + + def get_my_type(string): + return 'my_type{%s}' % string + + parser = argparse.ArgumentParser() + parser.register('type', 'my_type', get_my_type) + parser.add_argument('-x', type='my_type') + parser.add_argument('y', type='my_type') + + self.assertEqual(parser.parse_args('1'.split()), + NS(x=None, y='my_type{1}')) + self.assertEqual(parser.parse_args('-x 1 42'.split()), + NS(x='my_type{1}', y='my_type{42}')) + + +# ============ +# Action tests +# ============ + +class TestActionUserDefined(ParserTestCase): + """Test a user-defined option/argument action""" + + class OptionalAction(argparse.Action): + + def __call__(self, parser, namespace, value, option_string=None): + try: + # check destination and option string + assert self.dest == 'spam', 'dest: %s' % self.dest + assert option_string == '-s', 'flag: %s' % option_string + # when option is before argument, badger=2, and when + # option is after argument, badger= + expected_ns = NS(spam=0.25) + if value in [0.125, 0.625]: + expected_ns.badger = 2 + elif value in [2.0]: + expected_ns.badger = 84 + else: + raise AssertionError('value: %s' % value) + assert expected_ns == namespace, ('expected %s, got %s' % + (expected_ns, namespace)) + except AssertionError: + e = sys.exc_info()[1] + raise ArgumentParserError('opt_action failed: %s' % e) + setattr(namespace, 'spam', value) + + class PositionalAction(argparse.Action): + + def __call__(self, parser, namespace, value, option_string=None): + try: + assert option_string is None, ('option_string: %s' % + option_string) + # check destination + assert self.dest == 'badger', 'dest: %s' % self.dest + # when argument is before option, spam=0.25, and when + # option is after argument, spam= + expected_ns = NS(badger=2) + if value in [42, 84]: + expected_ns.spam = 0.25 + elif value in [1]: + expected_ns.spam = 0.625 + elif value in [2]: + expected_ns.spam = 0.125 + else: + raise AssertionError('value: %s' % value) + assert expected_ns == namespace, ('expected %s, got %s' % + (expected_ns, namespace)) + except AssertionError: + e = sys.exc_info()[1] + raise ArgumentParserError('arg_action failed: %s' % e) + setattr(namespace, 'badger', value) + + argument_signatures = [ + Sig('-s', dest='spam', action=OptionalAction, + type=float, default=0.25), + Sig('badger', action=PositionalAction, + type=int, nargs='?', default=2), + ] + failures = [] + successes = [ + ('-s0.125', NS(spam=0.125, badger=2)), + ('42', NS(spam=0.25, badger=42)), + ('-s 0.625 1', NS(spam=0.625, badger=1)), + ('84 -s2', NS(spam=2.0, badger=84)), + ] + + +class TestActionRegistration(TestCase): + """Test a user-defined action supplied by registering it""" + + class MyAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, 'foo[%s]' % values) + + def test(self): + + parser = argparse.ArgumentParser() + parser.register('action', 'my_action', self.MyAction) + parser.add_argument('badger', action='my_action') + + self.assertEqual(parser.parse_args(['1']), NS(badger='foo[1]')) + self.assertEqual(parser.parse_args(['42']), NS(badger='foo[42]')) + + +# ================ +# Subparsers tests +# ================ + +class TestAddSubparsers(TestCase): + """Test the add_subparsers method""" + + def assertArgumentParserError(self, *args, **kwargs): + self.assertRaises(ArgumentParserError, *args, **kwargs) + + def _get_parser(self, subparser_help=False): + # create a parser with a subparsers argument + parser = ErrorRaisingArgumentParser( + prog='PROG', description='main description') + parser.add_argument( + '--foo', action='store_true', help='foo help') + parser.add_argument( + 'bar', type=float, help='bar help') + + # check that only one subparsers argument can be added + subparsers = parser.add_subparsers(help='command help') + self.assertArgumentParserError(parser.add_subparsers) + + # add first sub-parser + parser1_kwargs = dict(description='1 description') + if subparser_help: + parser1_kwargs['help'] = '1 help' + parser1 = subparsers.add_parser('1', **parser1_kwargs) + parser1.add_argument('-w', type=int, help='w help') + parser1.add_argument('x', choices='abc', help='x help') + + # add second sub-parser + parser2_kwargs = dict(description='2 description') + if subparser_help: + parser2_kwargs['help'] = '2 help' + parser2 = subparsers.add_parser('2', **parser2_kwargs) + parser2.add_argument('-y', choices='123', help='y help') + parser2.add_argument('z', type=complex, nargs='*', help='z help') + + # return the main parser + return parser + + def setUp(self): + self.parser = self._get_parser() + self.command_help_parser = self._get_parser(subparser_help=True) + + def test_parse_args_failures(self): + # check some failure cases: + for args_str in ['', 'a', 'a a', '0.5 a', '0.5 1', + '0.5 1 -y', '0.5 2 -w']: + args = args_str.split() + self.assertArgumentParserError(self.parser.parse_args, args) + + def test_parse_args(self): + # check some non-failure cases: + self.assertEqual( + self.parser.parse_args('0.5 1 b -w 7'.split()), + NS(foo=False, bar=0.5, w=7, x='b'), + ) + self.assertEqual( + self.parser.parse_args('0.25 --foo 2 -y 2 3j -- -1j'.split()), + NS(foo=True, bar=0.25, y='2', z=[3j, -1j]), + ) + self.assertEqual( + self.parser.parse_args('--foo 0.125 1 c'.split()), + NS(foo=True, bar=0.125, w=None, x='c'), + ) + + def test_dest(self): + parser = ErrorRaisingArgumentParser() + parser.add_argument('--foo', action='store_true') + subparsers = parser.add_subparsers(dest='bar') + parser1 = subparsers.add_parser('1') + parser1.add_argument('baz') + self.assertEqual(NS(foo=False, bar='1', baz='2'), + parser.parse_args('1 2'.split())) + + def test_help(self): + self.assertEqual(self.parser.format_usage(), + 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + self.assertEqual(self.parser.format_help(), textwrap.dedent('''\ + usage: PROG [-h] [--foo] bar {1,2} ... + + main description + + positional arguments: + bar bar help + {1,2} command help + + optional arguments: + -h, --help show this help message and exit + --foo foo help + ''')) + + def test_parser_command_help(self): + self.assertEqual(self.command_help_parser.format_usage(), + 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + self.assertEqual(self.command_help_parser.format_help(), + textwrap.dedent('''\ + usage: PROG [-h] [--foo] bar {1,2} ... + + main description + + positional arguments: + bar bar help + {1,2} command help + 1 1 help + 2 2 help + + optional arguments: + -h, --help show this help message and exit + --foo foo help + ''')) + + def test_subparser_title_help(self): + parser = ErrorRaisingArgumentParser(prog='PROG', + description='main description') + parser.add_argument('--foo', action='store_true', help='foo help') + parser.add_argument('bar', help='bar help') + subparsers = parser.add_subparsers(title='subcommands', + description='command help', + help='additional text') + parser1 = subparsers.add_parser('1') + parser2 = subparsers.add_parser('2') + self.assertEqual(parser.format_usage(), + 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + self.assertEqual(parser.format_help(), textwrap.dedent('''\ + usage: PROG [-h] [--foo] bar {1,2} ... + + main description + + positional arguments: + bar bar help + + optional arguments: + -h, --help show this help message and exit + --foo foo help + + subcommands: + command help + + {1,2} additional text + ''')) + + def _test_subparser_help(self, args_str, expected_help): + try: + self.parser.parse_args(args_str.split()) + except ArgumentParserError: + err = sys.exc_info()[1] + if err.stdout != expected_help: + print(repr(expected_help)) + print(repr(err.stdout)) + self.assertEqual(err.stdout, expected_help) + + def test_subparser1_help(self): + self._test_subparser_help('5.0 1 -h', textwrap.dedent('''\ + usage: PROG bar 1 [-h] [-w W] {a,b,c} + + 1 description + + positional arguments: + {a,b,c} x help + + optional arguments: + -h, --help show this help message and exit + -w W w help + ''')) + + def test_subparser2_help(self): + self._test_subparser_help('5.0 2 -h', textwrap.dedent('''\ + usage: PROG bar 2 [-h] [-y {1,2,3}] [z [z ...]] + + 2 description + + positional arguments: + z z help + + optional arguments: + -h, --help show this help message and exit + -y {1,2,3} y help + ''')) + +# ============ +# Groups tests +# ============ + +class TestPositionalsGroups(TestCase): + """Tests that order of group positionals matches construction order""" + + def test_nongroup_first(self): + parser = ErrorRaisingArgumentParser() + parser.add_argument('foo') + group = parser.add_argument_group('g') + group.add_argument('bar') + parser.add_argument('baz') + expected = NS(foo='1', bar='2', baz='3') + result = parser.parse_args('1 2 3'.split()) + self.assertEqual(expected, result) + + def test_group_first(self): + parser = ErrorRaisingArgumentParser() + group = parser.add_argument_group('xxx') + group.add_argument('foo') + parser.add_argument('bar') + parser.add_argument('baz') + expected = NS(foo='1', bar='2', baz='3') + result = parser.parse_args('1 2 3'.split()) + self.assertEqual(expected, result) + + def test_interleaved_groups(self): + parser = ErrorRaisingArgumentParser() + group = parser.add_argument_group('xxx') + parser.add_argument('foo') + group.add_argument('bar') + parser.add_argument('baz') + group = parser.add_argument_group('yyy') + group.add_argument('frell') + expected = NS(foo='1', bar='2', baz='3', frell='4') + result = parser.parse_args('1 2 3 4'.split()) + self.assertEqual(expected, result) + +# =================== +# Parent parser tests +# =================== + +class TestParentParsers(TestCase): + """Tests that parsers can be created with parent parsers""" + + def assertArgumentParserError(self, *args, **kwargs): + self.assertRaises(ArgumentParserError, *args, **kwargs) + + def setUp(self): + self.wxyz_parent = ErrorRaisingArgumentParser(add_help=False) + self.wxyz_parent.add_argument('--w') + x_group = self.wxyz_parent.add_argument_group('x') + x_group.add_argument('-y') + self.wxyz_parent.add_argument('z') + + self.abcd_parent = ErrorRaisingArgumentParser(add_help=False) + self.abcd_parent.add_argument('a') + self.abcd_parent.add_argument('-b') + c_group = self.abcd_parent.add_argument_group('c') + c_group.add_argument('--d') + + self.w_parent = ErrorRaisingArgumentParser(add_help=False) + self.w_parent.add_argument('--w') + + self.z_parent = ErrorRaisingArgumentParser(add_help=False) + self.z_parent.add_argument('z') + + # parents with mutually exclusive groups + self.ab_mutex_parent = ErrorRaisingArgumentParser(add_help=False) + group = self.ab_mutex_parent.add_mutually_exclusive_group() + group.add_argument('-a', action='store_true') + group.add_argument('-b', action='store_true') + + self.main_program = os.path.basename(sys.argv[0]) + + def test_single_parent(self): + parser = ErrorRaisingArgumentParser(parents=[self.wxyz_parent]) + self.assertEqual(parser.parse_args('-y 1 2 --w 3'.split()), + NS(w='3', y='1', z='2')) + + def test_single_parent_mutex(self): + self._test_mutex_ab(self.ab_mutex_parent.parse_args) + parser = ErrorRaisingArgumentParser(parents=[self.ab_mutex_parent]) + self._test_mutex_ab(parser.parse_args) + + def test_single_granparent_mutex(self): + parents = [self.ab_mutex_parent] + parser = ErrorRaisingArgumentParser(add_help=False, parents=parents) + parser = ErrorRaisingArgumentParser(parents=[parser]) + self._test_mutex_ab(parser.parse_args) + + def _test_mutex_ab(self, parse_args): + self.assertEqual(parse_args([]), NS(a=False, b=False)) + self.assertEqual(parse_args(['-a']), NS(a=True, b=False)) + self.assertEqual(parse_args(['-b']), NS(a=False, b=True)) + self.assertArgumentParserError(parse_args, ['-a', '-b']) + self.assertArgumentParserError(parse_args, ['-b', '-a']) + self.assertArgumentParserError(parse_args, ['-c']) + self.assertArgumentParserError(parse_args, ['-a', '-c']) + self.assertArgumentParserError(parse_args, ['-b', '-c']) + + def test_multiple_parents(self): + parents = [self.abcd_parent, self.wxyz_parent] + parser = ErrorRaisingArgumentParser(parents=parents) + self.assertEqual(parser.parse_args('--d 1 --w 2 3 4'.split()), + NS(a='3', b=None, d='1', w='2', y=None, z='4')) + + def test_multiple_parents_mutex(self): + parents = [self.ab_mutex_parent, self.wxyz_parent] + parser = ErrorRaisingArgumentParser(parents=parents) + self.assertEqual(parser.parse_args('-a --w 2 3'.split()), + NS(a=True, b=False, w='2', y=None, z='3')) + self.assertArgumentParserError( + parser.parse_args, '-a --w 2 3 -b'.split()) + self.assertArgumentParserError( + parser.parse_args, '-a -b --w 2 3'.split()) + + def test_conflicting_parents(self): + self.assertRaises( + argparse.ArgumentError, + argparse.ArgumentParser, + parents=[self.w_parent, self.wxyz_parent]) + + def test_conflicting_parents_mutex(self): + self.assertRaises( + argparse.ArgumentError, + argparse.ArgumentParser, + parents=[self.abcd_parent, self.ab_mutex_parent]) + + def test_same_argument_name_parents(self): + parents = [self.wxyz_parent, self.z_parent] + parser = ErrorRaisingArgumentParser(parents=parents) + self.assertEqual(parser.parse_args('1 2'.split()), + NS(w=None, y=None, z='2')) + + def test_subparser_parents(self): + parser = ErrorRaisingArgumentParser() + subparsers = parser.add_subparsers() + abcde_parser = subparsers.add_parser('bar', parents=[self.abcd_parent]) + abcde_parser.add_argument('e') + self.assertEqual(parser.parse_args('bar -b 1 --d 2 3 4'.split()), + NS(a='3', b='1', d='2', e='4')) + + def test_subparser_parents_mutex(self): + parser = ErrorRaisingArgumentParser() + subparsers = parser.add_subparsers() + parents = [self.ab_mutex_parent] + abc_parser = subparsers.add_parser('foo', parents=parents) + c_group = abc_parser.add_argument_group('c_group') + c_group.add_argument('c') + parents = [self.wxyz_parent, self.ab_mutex_parent] + wxyzabe_parser = subparsers.add_parser('bar', parents=parents) + wxyzabe_parser.add_argument('e') + self.assertEqual(parser.parse_args('foo -a 4'.split()), + NS(a=True, b=False, c='4')) + self.assertEqual(parser.parse_args('bar -b --w 2 3 4'.split()), + NS(a=False, b=True, w='2', y=None, z='3', e='4')) + self.assertArgumentParserError( + parser.parse_args, 'foo -a -b 4'.split()) + self.assertArgumentParserError( + parser.parse_args, 'bar -b -a 4'.split()) + + def test_parent_help(self): + parents = [self.abcd_parent, self.wxyz_parent] + parser = ErrorRaisingArgumentParser(parents=parents) + parser_help = parser.format_help() + self.assertEqual(parser_help, textwrap.dedent('''\ + usage: {} [-h] [-b B] [--d D] [--w W] [-y Y] a z + + positional arguments: + a + z + + optional arguments: + -h, --help show this help message and exit + -b B + --w W + + c: + --d D + + x: + -y Y + '''.format(self.main_program))) + + def test_groups_parents(self): + parent = ErrorRaisingArgumentParser(add_help=False) + g = parent.add_argument_group(title='g', description='gd') + g.add_argument('-w') + g.add_argument('-x') + m = parent.add_mutually_exclusive_group() + m.add_argument('-y') + m.add_argument('-z') + parser = ErrorRaisingArgumentParser(parents=[parent]) + + self.assertRaises(ArgumentParserError, parser.parse_args, + ['-y', 'Y', '-z', 'Z']) + + parser_help = parser.format_help() + self.assertEqual(parser_help, textwrap.dedent('''\ + usage: {} [-h] [-w W] [-x X] [-y Y | -z Z] + + optional arguments: + -h, --help show this help message and exit + -y Y + -z Z + + g: + gd + + -w W + -x X + '''.format(self.main_program))) + +# ============================== +# Mutually exclusive group tests +# ============================== + +class TestMutuallyExclusiveGroupErrors(TestCase): + + def test_invalid_add_argument_group(self): + parser = ErrorRaisingArgumentParser() + raises = self.assertRaises + raises(TypeError, parser.add_mutually_exclusive_group, title='foo') + + def test_invalid_add_argument(self): + parser = ErrorRaisingArgumentParser() + group = parser.add_mutually_exclusive_group() + add_argument = group.add_argument + raises = self.assertRaises + raises(ValueError, add_argument, '--foo', required=True) + raises(ValueError, add_argument, 'bar') + raises(ValueError, add_argument, 'bar', nargs='+') + raises(ValueError, add_argument, 'bar', nargs=1) + raises(ValueError, add_argument, 'bar', nargs=argparse.PARSER) + + +class MEMixin(object): + + def test_failures_when_not_required(self): + parse_args = self.get_parser(required=False).parse_args + error = ArgumentParserError + for args_string in self.failures: + self.assertRaises(error, parse_args, args_string.split()) + + def test_failures_when_required(self): + parse_args = self.get_parser(required=True).parse_args + error = ArgumentParserError + for args_string in self.failures + ['']: + self.assertRaises(error, parse_args, args_string.split()) + + def test_successes_when_not_required(self): + parse_args = self.get_parser(required=False).parse_args + successes = self.successes + self.successes_when_not_required + for args_string, expected_ns in successes: + actual_ns = parse_args(args_string.split()) + self.assertEqual(actual_ns, expected_ns) + + def test_successes_when_required(self): + parse_args = self.get_parser(required=True).parse_args + for args_string, expected_ns in self.successes: + actual_ns = parse_args(args_string.split()) + self.assertEqual(actual_ns, expected_ns) + + def test_usage_when_not_required(self): + format_usage = self.get_parser(required=False).format_usage + expected_usage = self.usage_when_not_required + self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) + + def test_usage_when_required(self): + format_usage = self.get_parser(required=True).format_usage + expected_usage = self.usage_when_required + self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) + + def test_help_when_not_required(self): + format_help = self.get_parser(required=False).format_help + help = self.usage_when_not_required + self.help + self.assertEqual(format_help(), textwrap.dedent(help)) + + def test_help_when_required(self): + format_help = self.get_parser(required=True).format_help + help = self.usage_when_required + self.help + self.assertEqual(format_help(), textwrap.dedent(help)) + + +class TestMutuallyExclusiveSimple(MEMixin, TestCase): + + def get_parser(self, required=None): + parser = ErrorRaisingArgumentParser(prog='PROG') + group = parser.add_mutually_exclusive_group(required=required) + group.add_argument('--bar', help='bar help') + group.add_argument('--baz', nargs='?', const='Z', help='baz help') + return parser + + failures = ['--bar X --baz Y', '--bar X --baz'] + successes = [ + ('--bar X', NS(bar='X', baz=None)), + ('--bar X --bar Z', NS(bar='Z', baz=None)), + ('--baz Y', NS(bar=None, baz='Y')), + ('--baz', NS(bar=None, baz='Z')), + ] + successes_when_not_required = [ + ('', NS(bar=None, baz=None)), + ] + + usage_when_not_required = '''\ + usage: PROG [-h] [--bar BAR | --baz [BAZ]] + ''' + usage_when_required = '''\ + usage: PROG [-h] (--bar BAR | --baz [BAZ]) + ''' + help = '''\ + + optional arguments: + -h, --help show this help message and exit + --bar BAR bar help + --baz [BAZ] baz help + ''' + + +class TestMutuallyExclusiveLong(MEMixin, TestCase): + + def get_parser(self, required=None): + parser = ErrorRaisingArgumentParser(prog='PROG') + parser.add_argument('--abcde', help='abcde help') + parser.add_argument('--fghij', help='fghij help') + group = parser.add_mutually_exclusive_group(required=required) + group.add_argument('--klmno', help='klmno help') + group.add_argument('--pqrst', help='pqrst help') + return parser + + failures = ['--klmno X --pqrst Y'] + successes = [ + ('--klmno X', NS(abcde=None, fghij=None, klmno='X', pqrst=None)), + ('--abcde Y --klmno X', + NS(abcde='Y', fghij=None, klmno='X', pqrst=None)), + ('--pqrst X', NS(abcde=None, fghij=None, klmno=None, pqrst='X')), + ('--pqrst X --fghij Y', + NS(abcde=None, fghij='Y', klmno=None, pqrst='X')), + ] + successes_when_not_required = [ + ('', NS(abcde=None, fghij=None, klmno=None, pqrst=None)), + ] + + usage_when_not_required = '''\ + usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] + [--klmno KLMNO | --pqrst PQRST] + ''' + usage_when_required = '''\ + usage: PROG [-h] [--abcde ABCDE] [--fghij FGHIJ] + (--klmno KLMNO | --pqrst PQRST) + ''' + help = '''\ + + optional arguments: + -h, --help show this help message and exit + --abcde ABCDE abcde help + --fghij FGHIJ fghij help + --klmno KLMNO klmno help + --pqrst PQRST pqrst help + ''' + + +class TestMutuallyExclusiveFirstSuppressed(MEMixin, TestCase): + + def get_parser(self, required): + parser = ErrorRaisingArgumentParser(prog='PROG') + group = parser.add_mutually_exclusive_group(required=required) + group.add_argument('-x', help=argparse.SUPPRESS) + group.add_argument('-y', action='store_false', help='y help') + return parser + + failures = ['-x X -y'] + successes = [ + ('-x X', NS(x='X', y=True)), + ('-x X -x Y', NS(x='Y', y=True)), + ('-y', NS(x=None, y=False)), + ] + successes_when_not_required = [ + ('', NS(x=None, y=True)), + ] + + usage_when_not_required = '''\ + usage: PROG [-h] [-y] + ''' + usage_when_required = '''\ + usage: PROG [-h] -y + ''' + help = '''\ + + optional arguments: + -h, --help show this help message and exit + -y y help + ''' + + +class TestMutuallyExclusiveManySuppressed(MEMixin, TestCase): + + def get_parser(self, required): + parser = ErrorRaisingArgumentParser(prog='PROG') + group = parser.add_mutually_exclusive_group(required=required) + add = group.add_argument + add('--spam', action='store_true', help=argparse.SUPPRESS) + add('--badger', action='store_false', help=argparse.SUPPRESS) + add('--bladder', help=argparse.SUPPRESS) + return parser + + failures = [ + '--spam --badger', + '--badger --bladder B', + '--bladder B --spam', + ] + successes = [ + ('--spam', NS(spam=True, badger=True, bladder=None)), + ('--badger', NS(spam=False, badger=False, bladder=None)), + ('--bladder B', NS(spam=False, badger=True, bladder='B')), + ('--spam --spam', NS(spam=True, badger=True, bladder=None)), + ] + successes_when_not_required = [ + ('', NS(spam=False, badger=True, bladder=None)), + ] + + usage_when_required = usage_when_not_required = '''\ + usage: PROG [-h] + ''' + help = '''\ + + optional arguments: + -h, --help show this help message and exit + ''' + + +class TestMutuallyExclusiveOptionalAndPositional(MEMixin, TestCase): + + def get_parser(self, required): + parser = ErrorRaisingArgumentParser(prog='PROG') + group = parser.add_mutually_exclusive_group(required=required) + group.add_argument('--foo', action='store_true', help='FOO') + group.add_argument('--spam', help='SPAM') + group.add_argument('badger', nargs='*', default='X', help='BADGER') + return parser + + failures = [ + '--foo --spam S', + '--spam S X', + 'X --foo', + 'X Y Z --spam S', + '--foo X Y', + ] + successes = [ + ('--foo', NS(foo=True, spam=None, badger='X')), + ('--spam S', NS(foo=False, spam='S', badger='X')), + ('X', NS(foo=False, spam=None, badger=['X'])), + ('X Y Z', NS(foo=False, spam=None, badger=['X', 'Y', 'Z'])), + ] + successes_when_not_required = [ + ('', NS(foo=False, spam=None, badger='X')), + ] + + usage_when_not_required = '''\ + usage: PROG [-h] [--foo | --spam SPAM | badger [badger ...]] + ''' + usage_when_required = '''\ + usage: PROG [-h] (--foo | --spam SPAM | badger [badger ...]) + ''' + help = '''\ + + positional arguments: + badger BADGER + + optional arguments: + -h, --help show this help message and exit + --foo FOO + --spam SPAM SPAM + ''' + + +class TestMutuallyExclusiveOptionalsMixed(MEMixin, TestCase): + + def get_parser(self, required): + parser = ErrorRaisingArgumentParser(prog='PROG') + parser.add_argument('-x', action='store_true', help='x help') + group = parser.add_mutually_exclusive_group(required=required) + group.add_argument('-a', action='store_true', help='a help') + group.add_argument('-b', action='store_true', help='b help') + parser.add_argument('-y', action='store_true', help='y help') + group.add_argument('-c', action='store_true', help='c help') + return parser + + failures = ['-a -b', '-b -c', '-a -c', '-a -b -c'] + successes = [ + ('-a', NS(a=True, b=False, c=False, x=False, y=False)), + ('-b', NS(a=False, b=True, c=False, x=False, y=False)), + ('-c', NS(a=False, b=False, c=True, x=False, y=False)), + ('-a -x', NS(a=True, b=False, c=False, x=True, y=False)), + ('-y -b', NS(a=False, b=True, c=False, x=False, y=True)), + ('-x -y -c', NS(a=False, b=False, c=True, x=True, y=True)), + ] + successes_when_not_required = [ + ('', NS(a=False, b=False, c=False, x=False, y=False)), + ('-x', NS(a=False, b=False, c=False, x=True, y=False)), + ('-y', NS(a=False, b=False, c=False, x=False, y=True)), + ] + + usage_when_required = usage_when_not_required = '''\ + usage: PROG [-h] [-x] [-a] [-b] [-y] [-c] + ''' + help = '''\ + + optional arguments: + -h, --help show this help message and exit + -x x help + -a a help + -b b help + -y y help + -c c help + ''' + + +class TestMutuallyExclusiveOptionalsAndPositionalsMixed(MEMixin, TestCase): + + def get_parser(self, required): + parser = ErrorRaisingArgumentParser(prog='PROG') + parser.add_argument('x', help='x help') + parser.add_argument('-y', action='store_true', help='y help') + group = parser.add_mutually_exclusive_group(required=required) + group.add_argument('a', nargs='?', help='a help') + group.add_argument('-b', action='store_true', help='b help') + group.add_argument('-c', action='store_true', help='c help') + return parser + + failures = ['X A -b', '-b -c', '-c X A'] + successes = [ + ('X A', NS(a='A', b=False, c=False, x='X', y=False)), + ('X -b', NS(a=None, b=True, c=False, x='X', y=False)), + ('X -c', NS(a=None, b=False, c=True, x='X', y=False)), + ('X A -y', NS(a='A', b=False, c=False, x='X', y=True)), + ('X -y -b', NS(a=None, b=True, c=False, x='X', y=True)), + ] + successes_when_not_required = [ + ('X', NS(a=None, b=False, c=False, x='X', y=False)), + ('X -y', NS(a=None, b=False, c=False, x='X', y=True)), + ] + + usage_when_required = usage_when_not_required = '''\ + usage: PROG [-h] [-y] [-b] [-c] x [a] + ''' + help = '''\ + + positional arguments: + x x help + a a help + + optional arguments: + -h, --help show this help message and exit + -y y help + -b b help + -c c help + ''' + +# ================================================= +# Mutually exclusive group in parent parser tests +# ================================================= + +class MEPBase(object): + + def get_parser(self, required=None): + parent = super(MEPBase, self).get_parser(required=required) + parser = ErrorRaisingArgumentParser( + prog=parent.prog, add_help=False, parents=[parent]) + return parser + + +class TestMutuallyExclusiveGroupErrorsParent( + MEPBase, TestMutuallyExclusiveGroupErrors): + pass + + +class TestMutuallyExclusiveSimpleParent( + MEPBase, TestMutuallyExclusiveSimple): + pass + + +class TestMutuallyExclusiveLongParent( + MEPBase, TestMutuallyExclusiveLong): + pass + + +class TestMutuallyExclusiveFirstSuppressedParent( + MEPBase, TestMutuallyExclusiveFirstSuppressed): + pass + + +class TestMutuallyExclusiveManySuppressedParent( + MEPBase, TestMutuallyExclusiveManySuppressed): + pass + + +class TestMutuallyExclusiveOptionalAndPositionalParent( + MEPBase, TestMutuallyExclusiveOptionalAndPositional): + pass + + +class TestMutuallyExclusiveOptionalsMixedParent( + MEPBase, TestMutuallyExclusiveOptionalsMixed): + pass + + +class TestMutuallyExclusiveOptionalsAndPositionalsMixedParent( + MEPBase, TestMutuallyExclusiveOptionalsAndPositionalsMixed): + pass + +# ================= +# Set default tests +# ================= + +class TestSetDefaults(TestCase): + + def test_set_defaults_no_args(self): + parser = ErrorRaisingArgumentParser() + parser.set_defaults(x='foo') + parser.set_defaults(y='bar', z=1) + self.assertEqual(NS(x='foo', y='bar', z=1), + parser.parse_args([])) + self.assertEqual(NS(x='foo', y='bar', z=1), + parser.parse_args([], NS())) + self.assertEqual(NS(x='baz', y='bar', z=1), + parser.parse_args([], NS(x='baz'))) + self.assertEqual(NS(x='baz', y='bar', z=2), + parser.parse_args([], NS(x='baz', z=2))) + + def test_set_defaults_with_args(self): + parser = ErrorRaisingArgumentParser() + parser.set_defaults(x='foo', y='bar') + parser.add_argument('-x', default='xfoox') + self.assertEqual(NS(x='xfoox', y='bar'), + parser.parse_args([])) + self.assertEqual(NS(x='xfoox', y='bar'), + parser.parse_args([], NS())) + self.assertEqual(NS(x='baz', y='bar'), + parser.parse_args([], NS(x='baz'))) + self.assertEqual(NS(x='1', y='bar'), + parser.parse_args('-x 1'.split())) + self.assertEqual(NS(x='1', y='bar'), + parser.parse_args('-x 1'.split(), NS())) + self.assertEqual(NS(x='1', y='bar'), + parser.parse_args('-x 1'.split(), NS(x='baz'))) + + def test_set_defaults_subparsers(self): + parser = ErrorRaisingArgumentParser() + parser.set_defaults(x='foo') + subparsers = parser.add_subparsers() + parser_a = subparsers.add_parser('a') + parser_a.set_defaults(y='bar') + self.assertEqual(NS(x='foo', y='bar'), + parser.parse_args('a'.split())) + + def test_set_defaults_parents(self): + parent = ErrorRaisingArgumentParser(add_help=False) + parent.set_defaults(x='foo') + parser = ErrorRaisingArgumentParser(parents=[parent]) + self.assertEqual(NS(x='foo'), parser.parse_args([])) + + def test_set_defaults_same_as_add_argument(self): + parser = ErrorRaisingArgumentParser() + parser.set_defaults(w='W', x='X', y='Y', z='Z') + parser.add_argument('-w') + parser.add_argument('-x', default='XX') + parser.add_argument('y', nargs='?') + parser.add_argument('z', nargs='?', default='ZZ') + + # defaults set previously + self.assertEqual(NS(w='W', x='XX', y='Y', z='ZZ'), + parser.parse_args([])) + + # reset defaults + parser.set_defaults(w='WW', x='X', y='YY', z='Z') + self.assertEqual(NS(w='WW', x='X', y='YY', z='Z'), + parser.parse_args([])) + + def test_set_defaults_same_as_add_argument_group(self): + parser = ErrorRaisingArgumentParser() + parser.set_defaults(w='W', x='X', y='Y', z='Z') + group = parser.add_argument_group('foo') + group.add_argument('-w') + group.add_argument('-x', default='XX') + group.add_argument('y', nargs='?') + group.add_argument('z', nargs='?', default='ZZ') + + + # defaults set previously + self.assertEqual(NS(w='W', x='XX', y='Y', z='ZZ'), + parser.parse_args([])) + + # reset defaults + parser.set_defaults(w='WW', x='X', y='YY', z='Z') + self.assertEqual(NS(w='WW', x='X', y='YY', z='Z'), + parser.parse_args([])) + +# ================= +# Get default tests +# ================= + +class TestGetDefault(TestCase): + + def test_get_default(self): + parser = ErrorRaisingArgumentParser() + self.assertEqual(None, parser.get_default("foo")) + self.assertEqual(None, parser.get_default("bar")) + + parser.add_argument("--foo") + self.assertEqual(None, parser.get_default("foo")) + self.assertEqual(None, parser.get_default("bar")) + + parser.add_argument("--bar", type=int, default=42) + self.assertEqual(None, parser.get_default("foo")) + self.assertEqual(42, parser.get_default("bar")) + + parser.set_defaults(foo="badger") + self.assertEqual("badger", parser.get_default("foo")) + self.assertEqual(42, parser.get_default("bar")) + +# ========================== +# Namespace 'contains' tests +# ========================== + +class TestNamespaceContainsSimple(TestCase): + + def test_empty(self): + ns = argparse.Namespace() + self.assertEquals('' in ns, False) + self.assertEquals('' not in ns, True) + self.assertEquals('x' in ns, False) + + def test_non_empty(self): + ns = argparse.Namespace(x=1, y=2) + self.assertEquals('x' in ns, True) + self.assertEquals('x' not in ns, False) + self.assertEquals('y' in ns, True) + self.assertEquals('' in ns, False) + self.assertEquals('xx' in ns, False) + self.assertEquals('z' in ns, False) + +# ===================== +# Help formatting tests +# ===================== + +class TestHelpFormattingMetaclass(type): + + def __init__(cls, name, bases, bodydict): + if name == 'HelpTestCase': + return + + class AddTests(object): + + def __init__(self, test_class, func_suffix, std_name): + self.func_suffix = func_suffix + self.std_name = std_name + + for test_func in [self.test_format, + self.test_print, + self.test_print_file]: + test_name = '%s_%s' % (test_func.__name__, func_suffix) + + def test_wrapper(self, test_func=test_func): + test_func(self) + try: + test_wrapper.__name__ = test_name + except TypeError: + pass + setattr(test_class, test_name, test_wrapper) + + def _get_parser(self, tester): + parser = argparse.ArgumentParser( + *tester.parser_signature.args, + **tester.parser_signature.kwargs) + for argument_sig in tester.argument_signatures: + parser.add_argument(*argument_sig.args, + **argument_sig.kwargs) + group_signatures = tester.argument_group_signatures + for group_sig, argument_sigs in group_signatures: + group = parser.add_argument_group(*group_sig.args, + **group_sig.kwargs) + for argument_sig in argument_sigs: + group.add_argument(*argument_sig.args, + **argument_sig.kwargs) + return parser + + def _test(self, tester, parser_text): + expected_text = getattr(tester, self.func_suffix) + expected_text = textwrap.dedent(expected_text) + if expected_text != parser_text: + print(repr(expected_text)) + print(repr(parser_text)) + for char1, char2 in zip(expected_text, parser_text): + if char1 != char2: + print('first diff: %r %r' % (char1, char2)) + break + tester.assertEqual(expected_text, parser_text) + + def test_format(self, tester): + parser = self._get_parser(tester) + format = getattr(parser, 'format_%s' % self.func_suffix) + self._test(tester, format()) + + def test_print(self, tester): + parser = self._get_parser(tester) + print_ = getattr(parser, 'print_%s' % self.func_suffix) + old_stream = getattr(sys, self.std_name) + setattr(sys, self.std_name, StringIO()) + try: + print_() + parser_text = getattr(sys, self.std_name).getvalue() + finally: + setattr(sys, self.std_name, old_stream) + self._test(tester, parser_text) + + def test_print_file(self, tester): + parser = self._get_parser(tester) + print_ = getattr(parser, 'print_%s' % self.func_suffix) + sfile = StringIO() + print_(sfile) + parser_text = sfile.getvalue() + self._test(tester, parser_text) + + # add tests for {format,print}_{usage,help,version} + for func_suffix, std_name in [('usage', 'stdout'), + ('help', 'stdout'), + ('version', 'stderr')]: + AddTests(cls, func_suffix, std_name) + +bases = TestCase, +HelpTestCase = TestHelpFormattingMetaclass('HelpTestCase', bases, {}) + + +class TestHelpBiggerOptionals(HelpTestCase): + """Make sure that argument help aligns when options are longer""" + + parser_signature = Sig(prog='PROG', description='DESCRIPTION', + epilog='EPILOG', version='0.1') + argument_signatures = [ + Sig('-x', action='store_true', help='X HELP'), + Sig('--y', help='Y HELP'), + Sig('foo', help='FOO HELP'), + Sig('bar', help='BAR HELP'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [-v] [-x] [--y Y] foo bar + ''' + help = usage + '''\ + + DESCRIPTION + + positional arguments: + foo FOO HELP + bar BAR HELP + + optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + -x X HELP + --y Y Y HELP + + EPILOG + ''' + version = '''\ + 0.1 + ''' + + +class TestHelpBiggerOptionalGroups(HelpTestCase): + """Make sure that argument help aligns when options are longer""" + + parser_signature = Sig(prog='PROG', description='DESCRIPTION', + epilog='EPILOG', version='0.1') + argument_signatures = [ + Sig('-x', action='store_true', help='X HELP'), + Sig('--y', help='Y HELP'), + Sig('foo', help='FOO HELP'), + Sig('bar', help='BAR HELP'), + ] + argument_group_signatures = [ + (Sig('GROUP TITLE', description='GROUP DESCRIPTION'), [ + Sig('baz', help='BAZ HELP'), + Sig('-z', nargs='+', help='Z HELP')]), + ] + usage = '''\ + usage: PROG [-h] [-v] [-x] [--y Y] [-z Z [Z ...]] foo bar baz + ''' + help = usage + '''\ + + DESCRIPTION + + positional arguments: + foo FOO HELP + bar BAR HELP + + optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + -x X HELP + --y Y Y HELP + + GROUP TITLE: + GROUP DESCRIPTION + + baz BAZ HELP + -z Z [Z ...] Z HELP + + EPILOG + ''' + version = '''\ + 0.1 + ''' + + +class TestHelpBiggerPositionals(HelpTestCase): + """Make sure that help aligns when arguments are longer""" + + parser_signature = Sig(usage='USAGE', description='DESCRIPTION') + argument_signatures = [ + Sig('-x', action='store_true', help='X HELP'), + Sig('--y', help='Y HELP'), + Sig('ekiekiekifekang', help='EKI HELP'), + Sig('bar', help='BAR HELP'), + ] + argument_group_signatures = [] + usage = '''\ + usage: USAGE + ''' + help = usage + '''\ + + DESCRIPTION + + positional arguments: + ekiekiekifekang EKI HELP + bar BAR HELP + + optional arguments: + -h, --help show this help message and exit + -x X HELP + --y Y Y HELP + ''' + + version = '' + + +class TestHelpReformatting(HelpTestCase): + """Make sure that text after short names starts on the first line""" + + parser_signature = Sig( + prog='PROG', + description=' oddly formatted\n' + 'description\n' + '\n' + 'that is so long that it should go onto multiple ' + 'lines when wrapped') + argument_signatures = [ + Sig('-x', metavar='XX', help='oddly\n' + ' formatted -x help'), + Sig('y', metavar='yyy', help='normal y help'), + ] + argument_group_signatures = [ + (Sig('title', description='\n' + ' oddly formatted group\n' + '\n' + 'description'), + [Sig('-a', action='store_true', + help=' oddly \n' + 'formatted -a help \n' + ' again, so long that it should be wrapped over ' + 'multiple lines')]), + ] + usage = '''\ + usage: PROG [-h] [-x XX] [-a] yyy + ''' + help = usage + '''\ + + oddly formatted description that is so long that it should go onto \ +multiple + lines when wrapped + + positional arguments: + yyy normal y help + + optional arguments: + -h, --help show this help message and exit + -x XX oddly formatted -x help + + title: + oddly formatted group description + + -a oddly formatted -a help again, so long that it should \ +be wrapped + over multiple lines + ''' + version = '' + + +class TestHelpWrappingShortNames(HelpTestCase): + """Make sure that text after short names starts on the first line""" + + parser_signature = Sig(prog='PROG', description= 'D\nD' * 30) + argument_signatures = [ + Sig('-x', metavar='XX', help='XHH HX' * 20), + Sig('y', metavar='yyy', help='YH YH' * 20), + ] + argument_group_signatures = [ + (Sig('ALPHAS'), [ + Sig('-a', action='store_true', help='AHHH HHA' * 10)]), + ] + usage = '''\ + usage: PROG [-h] [-x XX] [-a] yyy + ''' + help = usage + '''\ + + D DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD \ +DD DD DD + DD DD DD DD D + + positional arguments: + yyy YH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH \ +YHYH YHYH + YHYH YHYH YHYH YHYH YHYH YHYH YHYH YH + + optional arguments: + -h, --help show this help message and exit + -x XX XHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH \ +HXXHH HXXHH + HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HXXHH HX + + ALPHAS: + -a AHHH HHAAHHH HHAAHHH HHAAHHH HHAAHHH HHAAHHH HHAAHHH \ +HHAAHHH + HHAAHHH HHAAHHH HHA + ''' + version = '' + + +class TestHelpWrappingLongNames(HelpTestCase): + """Make sure that text after long names starts on the next line""" + + parser_signature = Sig(usage='USAGE', description= 'D D' * 30, + version='V V'*30) + argument_signatures = [ + Sig('-x', metavar='X' * 25, help='XH XH' * 20), + Sig('y', metavar='y' * 25, help='YH YH' * 20), + ] + argument_group_signatures = [ + (Sig('ALPHAS'), [ + Sig('-a', metavar='A' * 25, help='AH AH' * 20), + Sig('z', metavar='z' * 25, help='ZH ZH' * 20)]), + ] + usage = '''\ + usage: USAGE + ''' + help = usage + '''\ + + D DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD DD \ +DD DD DD + DD DD DD DD D + + positional arguments: + yyyyyyyyyyyyyyyyyyyyyyyyy + YH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH \ +YHYH YHYH + YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YHYH YH + + optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + -x XXXXXXXXXXXXXXXXXXXXXXXXX + XH XHXH XHXH XHXH XHXH XHXH XHXH XHXH XHXH \ +XHXH XHXH + XHXH XHXH XHXH XHXH XHXH XHXH XHXH XHXH XHXH XH + + ALPHAS: + -a AAAAAAAAAAAAAAAAAAAAAAAAA + AH AHAH AHAH AHAH AHAH AHAH AHAH AHAH AHAH \ +AHAH AHAH + AHAH AHAH AHAH AHAH AHAH AHAH AHAH AHAH AHAH AH + zzzzzzzzzzzzzzzzzzzzzzzzz + ZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH \ +ZHZH ZHZH + ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZHZH ZH + ''' + version = '''\ + V VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV VV \ +VV VV VV + VV VV VV VV V + ''' + + +class TestHelpUsage(HelpTestCase): + """Test basic usage messages""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('-w', nargs='+', help='w'), + Sig('-x', nargs='*', help='x'), + Sig('a', help='a'), + Sig('b', help='b', nargs=2), + Sig('c', help='c', nargs='?'), + ] + argument_group_signatures = [ + (Sig('group'), [ + Sig('-y', nargs='?', help='y'), + Sig('-z', nargs=3, help='z'), + Sig('d', help='d', nargs='*'), + Sig('e', help='e', nargs='+'), + ]) + ] + usage = '''\ + usage: PROG [-h] [-w W [W ...]] [-x [X [X ...]]] [-y [Y]] [-z Z Z Z] + a b b [c] [d [d ...]] e [e ...] + ''' + help = usage + '''\ + + positional arguments: + a a + b b + c c + + optional arguments: + -h, --help show this help message and exit + -w W [W ...] w + -x [X [X ...]] x + + group: + -y [Y] y + -z Z Z Z z + d d + e e + ''' + version = '' + + +class TestHelpOnlyUserGroups(HelpTestCase): + """Test basic usage messages""" + + parser_signature = Sig(prog='PROG', add_help=False) + argument_signatures = [] + argument_group_signatures = [ + (Sig('xxxx'), [ + Sig('-x', help='x'), + Sig('a', help='a'), + ]), + (Sig('yyyy'), [ + Sig('b', help='b'), + Sig('-y', help='y'), + ]), + ] + usage = '''\ + usage: PROG [-x X] [-y Y] a b + ''' + help = usage + '''\ + + xxxx: + -x X x + a a + + yyyy: + b b + -y Y y + ''' + version = '' + + +class TestHelpUsageLongProg(HelpTestCase): + """Test usage messages where the prog is long""" + + parser_signature = Sig(prog='P' * 60) + argument_signatures = [ + Sig('-w', metavar='W'), + Sig('-x', metavar='X'), + Sig('a'), + Sig('b'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + [-h] [-w W] [-x X] a b + ''' + help = usage + '''\ + + positional arguments: + a + b + + optional arguments: + -h, --help show this help message and exit + -w W + -x X + ''' + version = '' + + +class TestHelpUsageLongProgOptionsWrap(HelpTestCase): + """Test usage messages where the prog is long and the optionals wrap""" + + parser_signature = Sig(prog='P' * 60) + argument_signatures = [ + Sig('-w', metavar='W' * 25), + Sig('-x', metavar='X' * 25), + Sig('-y', metavar='Y' * 25), + Sig('-z', metavar='Z' * 25), + Sig('a'), + Sig('b'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + [-h] [-w WWWWWWWWWWWWWWWWWWWWWWWWW] \ +[-x XXXXXXXXXXXXXXXXXXXXXXXXX] + [-y YYYYYYYYYYYYYYYYYYYYYYYYY] [-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] + a b + ''' + help = usage + '''\ + + positional arguments: + a + b + + optional arguments: + -h, --help show this help message and exit + -w WWWWWWWWWWWWWWWWWWWWWWWWW + -x XXXXXXXXXXXXXXXXXXXXXXXXX + -y YYYYYYYYYYYYYYYYYYYYYYYYY + -z ZZZZZZZZZZZZZZZZZZZZZZZZZ + ''' + version = '' + + +class TestHelpUsageLongProgPositionalsWrap(HelpTestCase): + """Test usage messages where the prog is long and the positionals wrap""" + + parser_signature = Sig(prog='P' * 60, add_help=False) + argument_signatures = [ + Sig('a' * 25), + Sig('b' * 25), + Sig('c' * 25), + ] + argument_group_signatures = [] + usage = '''\ + usage: PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP + aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + ''' + help = usage + '''\ + + positional arguments: + aaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + ''' + version = '' + + +class TestHelpUsageOptionalsWrap(HelpTestCase): + """Test usage messages where the optionals wrap""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('-w', metavar='W' * 25), + Sig('-x', metavar='X' * 25), + Sig('-y', metavar='Y' * 25), + Sig('-z', metavar='Z' * 25), + Sig('a'), + Sig('b'), + Sig('c'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [-w WWWWWWWWWWWWWWWWWWWWWWWWW] \ +[-x XXXXXXXXXXXXXXXXXXXXXXXXX] + [-y YYYYYYYYYYYYYYYYYYYYYYYYY] \ +[-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] + a b c + ''' + help = usage + '''\ + + positional arguments: + a + b + c + + optional arguments: + -h, --help show this help message and exit + -w WWWWWWWWWWWWWWWWWWWWWWWWW + -x XXXXXXXXXXXXXXXXXXXXXXXXX + -y YYYYYYYYYYYYYYYYYYYYYYYYY + -z ZZZZZZZZZZZZZZZZZZZZZZZZZ + ''' + version = '' + + +class TestHelpUsagePositionalsWrap(HelpTestCase): + """Test usage messages where the positionals wrap""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('-x'), + Sig('-y'), + Sig('-z'), + Sig('a' * 25), + Sig('b' * 25), + Sig('c' * 25), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [-x X] [-y Y] [-z Z] + aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + ''' + help = usage + '''\ + + positional arguments: + aaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + + optional arguments: + -h, --help show this help message and exit + -x X + -y Y + -z Z + ''' + version = '' + + +class TestHelpUsageOptionalsPositionalsWrap(HelpTestCase): + """Test usage messages where the optionals and positionals wrap""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('-x', metavar='X' * 25), + Sig('-y', metavar='Y' * 25), + Sig('-z', metavar='Z' * 25), + Sig('a' * 25), + Sig('b' * 25), + Sig('c' * 25), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [-x XXXXXXXXXXXXXXXXXXXXXXXXX] \ +[-y YYYYYYYYYYYYYYYYYYYYYYYYY] + [-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] + aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + ''' + help = usage + '''\ + + positional arguments: + aaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + + optional arguments: + -h, --help show this help message and exit + -x XXXXXXXXXXXXXXXXXXXXXXXXX + -y YYYYYYYYYYYYYYYYYYYYYYYYY + -z ZZZZZZZZZZZZZZZZZZZZZZZZZ + ''' + version = '' + + +class TestHelpUsageOptionalsOnlyWrap(HelpTestCase): + """Test usage messages where there are only optionals and they wrap""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('-x', metavar='X' * 25), + Sig('-y', metavar='Y' * 25), + Sig('-z', metavar='Z' * 25), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [-x XXXXXXXXXXXXXXXXXXXXXXXXX] \ +[-y YYYYYYYYYYYYYYYYYYYYYYYYY] + [-z ZZZZZZZZZZZZZZZZZZZZZZZZZ] + ''' + help = usage + '''\ + + optional arguments: + -h, --help show this help message and exit + -x XXXXXXXXXXXXXXXXXXXXXXXXX + -y YYYYYYYYYYYYYYYYYYYYYYYYY + -z ZZZZZZZZZZZZZZZZZZZZZZZZZ + ''' + version = '' + + +class TestHelpUsagePositionalsOnlyWrap(HelpTestCase): + """Test usage messages where there are only positionals and they wrap""" + + parser_signature = Sig(prog='PROG', add_help=False) + argument_signatures = [ + Sig('a' * 25), + Sig('b' * 25), + Sig('c' * 25), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG aaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + ''' + help = usage + '''\ + + positional arguments: + aaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbb + ccccccccccccccccccccccccc + ''' + version = '' + + +class TestHelpVariableExpansion(HelpTestCase): + """Test that variables are expanded properly in help messages""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('-x', type=int, + help='x %(prog)s %(default)s %(type)s %%'), + Sig('-y', action='store_const', default=42, const='XXX', + help='y %(prog)s %(default)s %(const)s'), + Sig('--foo', choices='abc', + help='foo %(prog)s %(default)s %(choices)s'), + Sig('--bar', default='baz', choices=[1, 2], metavar='BBB', + help='bar %(prog)s %(default)s %(dest)s'), + Sig('spam', help='spam %(prog)s %(default)s'), + Sig('badger', default=0.5, help='badger %(prog)s %(default)s'), + ] + argument_group_signatures = [ + (Sig('group'), [ + Sig('-a', help='a %(prog)s %(default)s'), + Sig('-b', default=-1, help='b %(prog)s %(default)s'), + ]) + ] + usage = ('''\ + usage: PROG [-h] [-x X] [-y] [--foo {a,b,c}] [--bar BBB] [-a A] [-b B] + spam badger + ''') + help = usage + '''\ + + positional arguments: + spam spam PROG None + badger badger PROG 0.5 + + optional arguments: + -h, --help show this help message and exit + -x X x PROG None int % + -y y PROG 42 XXX + --foo {a,b,c} foo PROG None a, b, c + --bar BBB bar PROG baz bar + + group: + -a A a PROG None + -b B b PROG -1 + ''' + version = '' + + +class TestHelpVariableExpansionUsageSupplied(HelpTestCase): + """Test that variables are expanded properly when usage= is present""" + + parser_signature = Sig(prog='PROG', usage='%(prog)s FOO') + argument_signatures = [] + argument_group_signatures = [] + usage = ('''\ + usage: PROG FOO + ''') + help = usage + '''\ + + optional arguments: + -h, --help show this help message and exit + ''' + version = '' + + +class TestHelpVariableExpansionNoArguments(HelpTestCase): + """Test that variables are expanded properly with no arguments""" + + parser_signature = Sig(prog='PROG', add_help=False) + argument_signatures = [] + argument_group_signatures = [] + usage = ('''\ + usage: PROG + ''') + help = usage + version = '' + + +class TestHelpSuppressUsage(HelpTestCase): + """Test that items can be suppressed in usage messages""" + + parser_signature = Sig(prog='PROG', usage=argparse.SUPPRESS) + argument_signatures = [ + Sig('--foo', help='foo help'), + Sig('spam', help='spam help'), + ] + argument_group_signatures = [] + help = '''\ + positional arguments: + spam spam help + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help + ''' + usage = '' + version = '' + + +class TestHelpSuppressOptional(HelpTestCase): + """Test that optional arguments can be suppressed in help messages""" + + parser_signature = Sig(prog='PROG', add_help=False) + argument_signatures = [ + Sig('--foo', help=argparse.SUPPRESS), + Sig('spam', help='spam help'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG spam + ''' + help = usage + '''\ + + positional arguments: + spam spam help + ''' + version = '' + + +class TestHelpSuppressOptionalGroup(HelpTestCase): + """Test that optional groups can be suppressed in help messages""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('--foo', help='foo help'), + Sig('spam', help='spam help'), + ] + argument_group_signatures = [ + (Sig('group'), [Sig('--bar', help=argparse.SUPPRESS)]), + ] + usage = '''\ + usage: PROG [-h] [--foo FOO] spam + ''' + help = usage + '''\ + + positional arguments: + spam spam help + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help + ''' + version = '' + + +class TestHelpSuppressPositional(HelpTestCase): + """Test that positional arguments can be suppressed in help messages""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('--foo', help='foo help'), + Sig('spam', help=argparse.SUPPRESS), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [--foo FOO] + ''' + help = usage + '''\ + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help + ''' + version = '' + + +class TestHelpRequiredOptional(HelpTestCase): + """Test that required options don't look optional""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('--foo', required=True, help='foo help'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] --foo FOO + ''' + help = usage + '''\ + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help + ''' + version = '' + + +class TestHelpAlternatePrefixChars(HelpTestCase): + """Test that options display with different prefix characters""" + + parser_signature = Sig(prog='PROG', prefix_chars='^;', add_help=False) + argument_signatures = [ + Sig('^^foo', action='store_true', help='foo help'), + Sig(';b', ';;bar', help='bar help'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [^^foo] [;b BAR] + ''' + help = usage + '''\ + + optional arguments: + ^^foo foo help + ;b BAR, ;;bar BAR bar help + ''' + version = '' + + +class TestHelpNoHelpOptional(HelpTestCase): + """Test that the --help argument can be suppressed help messages""" + + parser_signature = Sig(prog='PROG', add_help=False) + argument_signatures = [ + Sig('--foo', help='foo help'), + Sig('spam', help='spam help'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [--foo FOO] spam + ''' + help = usage + '''\ + + positional arguments: + spam spam help + + optional arguments: + --foo FOO foo help + ''' + version = '' + + +class TestHelpVersionOptional(HelpTestCase): + """Test that the --version argument can be suppressed help messages""" + + parser_signature = Sig(prog='PROG', version='1.0') + argument_signatures = [ + Sig('--foo', help='foo help'), + Sig('spam', help='spam help'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [-v] [--foo FOO] spam + ''' + help = usage + '''\ + + positional arguments: + spam spam help + + optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + --foo FOO foo help + ''' + version = '''\ + 1.0 + ''' + + +class TestHelpNone(HelpTestCase): + """Test that no errors occur if no help is specified""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('--foo'), + Sig('spam'), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [--foo FOO] spam + ''' + help = usage + '''\ + + positional arguments: + spam + + optional arguments: + -h, --help show this help message and exit + --foo FOO + ''' + version = '' + + +class TestHelpTupleMetavar(HelpTestCase): + """Test specifying metavar as a tuple""" + + parser_signature = Sig(prog='PROG') + argument_signatures = [ + Sig('-w', help='w', nargs='+', metavar=('W1', 'W2')), + Sig('-x', help='x', nargs='*', metavar=('X1', 'X2')), + Sig('-y', help='y', nargs=3, metavar=('Y1', 'Y2', 'Y3')), + Sig('-z', help='z', nargs='?', metavar=('Z1', )), + ] + argument_group_signatures = [] + usage = '''\ + usage: PROG [-h] [-w W1 [W2 ...]] [-x [X1 [X2 ...]]] [-y Y1 Y2 Y3] \ +[-z [Z1]] + ''' + help = usage + '''\ + + optional arguments: + -h, --help show this help message and exit + -w W1 [W2 ...] w + -x [X1 [X2 ...]] x + -y Y1 Y2 Y3 y + -z [Z1] z + ''' + version = '' + + +class TestHelpRawText(HelpTestCase): + """Test the RawTextHelpFormatter""" + + parser_signature = Sig( + prog='PROG', formatter_class=argparse.RawTextHelpFormatter, + description='Keep the formatting\n' + ' exactly as it is written\n' + '\n' + 'here\n') + + argument_signatures = [ + Sig('--foo', help=' foo help should also\n' + 'appear as given here'), + Sig('spam', help='spam help'), + ] + argument_group_signatures = [ + (Sig('title', description=' This text\n' + ' should be indented\n' + ' exactly like it is here\n'), + [Sig('--bar', help='bar help')]), + ] + usage = '''\ + usage: PROG [-h] [--foo FOO] [--bar BAR] spam + ''' + help = usage + '''\ + + Keep the formatting + exactly as it is written + + here + + positional arguments: + spam spam help + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help should also + appear as given here + + title: + This text + should be indented + exactly like it is here + + --bar BAR bar help + ''' + version = '' + + +class TestHelpRawDescription(HelpTestCase): + """Test the RawTextHelpFormatter""" + + parser_signature = Sig( + prog='PROG', formatter_class=argparse.RawDescriptionHelpFormatter, + description='Keep the formatting\n' + ' exactly as it is written\n' + '\n' + 'here\n') + + argument_signatures = [ + Sig('--foo', help=' foo help should not\n' + ' retain this odd formatting'), + Sig('spam', help='spam help'), + ] + argument_group_signatures = [ + (Sig('title', description=' This text\n' + ' should be indented\n' + ' exactly like it is here\n'), + [Sig('--bar', help='bar help')]), + ] + usage = '''\ + usage: PROG [-h] [--foo FOO] [--bar BAR] spam + ''' + help = usage + '''\ + + Keep the formatting + exactly as it is written + + here + + positional arguments: + spam spam help + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help should not retain this odd formatting + + title: + This text + should be indented + exactly like it is here + + --bar BAR bar help + ''' + version = '' + + +class TestHelpArgumentDefaults(HelpTestCase): + """Test the ArgumentDefaultsHelpFormatter""" + + parser_signature = Sig( + prog='PROG', formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description='description') + + argument_signatures = [ + Sig('--foo', help='foo help - oh and by the way, %(default)s'), + Sig('--bar', action='store_true', help='bar help'), + Sig('spam', help='spam help'), + Sig('badger', nargs='?', default='wooden', help='badger help'), + ] + argument_group_signatures = [ + (Sig('title', description='description'), + [Sig('--baz', type=int, default=42, help='baz help')]), + ] + usage = '''\ + usage: PROG [-h] [--foo FOO] [--bar] [--baz BAZ] spam [badger] + ''' + help = usage + '''\ + + description + + positional arguments: + spam spam help + badger badger help (default: wooden) + + optional arguments: + -h, --help show this help message and exit + --foo FOO foo help - oh and by the way, None + --bar bar help (default: False) + + title: + description + + --baz BAZ baz help (default: 42) + ''' + version = '' + +# ===================================== +# Optional/Positional constructor tests +# ===================================== + +class TestInvalidArgumentConstructors(TestCase): + """Test a bunch of invalid Argument constructors""" + + def assertTypeError(self, *args, **kwargs): + parser = argparse.ArgumentParser() + self.assertRaises(TypeError, parser.add_argument, + *args, **kwargs) + + def assertValueError(self, *args, **kwargs): + parser = argparse.ArgumentParser() + self.assertRaises(ValueError, parser.add_argument, + *args, **kwargs) + + def test_invalid_keyword_arguments(self): + self.assertTypeError('-x', bar=None) + self.assertTypeError('-y', callback='foo') + self.assertTypeError('-y', callback_args=()) + self.assertTypeError('-y', callback_kwargs={}) + + def test_missing_destination(self): + self.assertTypeError() + for action in ['append', 'store']: + self.assertTypeError(action=action) + + def test_invalid_option_strings(self): + self.assertValueError('--') + self.assertValueError('---') + + def test_invalid_type(self): + self.assertValueError('--foo', type='int') + + def test_invalid_action(self): + self.assertValueError('-x', action='foo') + self.assertValueError('foo', action='baz') + parser = argparse.ArgumentParser() + try: + parser.add_argument("--foo", action="store-true") + except ValueError: + e = sys.exc_info()[1] + expected = 'unknown action' + msg = 'expected %r, found %r' % (expected, e) + self.assertTrue(expected in str(e), msg) + + def test_multiple_dest(self): + parser = argparse.ArgumentParser() + parser.add_argument(dest='foo') + try: + parser.add_argument('bar', dest='baz') + except ValueError: + e = sys.exc_info()[1] + expected = 'dest supplied twice for positional argument' + msg = 'expected %r, found %r' % (expected, e) + self.assertTrue(expected in str(e), msg) + + def test_no_argument_actions(self): + for action in ['store_const', 'store_true', 'store_false', + 'append_const', 'count']: + for attrs in [dict(type=int), dict(nargs='+'), + dict(choices='ab')]: + self.assertTypeError('-x', action=action, **attrs) + + def test_no_argument_no_const_actions(self): + # options with zero arguments + for action in ['store_true', 'store_false', 'count']: + + # const is always disallowed + self.assertTypeError('-x', const='foo', action=action) + + # nargs is always disallowed + self.assertTypeError('-x', nargs='*', action=action) + + def test_more_than_one_argument_actions(self): + for action in ['store', 'append']: + + # nargs=0 is disallowed + self.assertValueError('-x', nargs=0, action=action) + self.assertValueError('spam', nargs=0, action=action) + + # const is disallowed with non-optional arguments + for nargs in [1, '*', '+']: + self.assertValueError('-x', const='foo', + nargs=nargs, action=action) + self.assertValueError('spam', const='foo', + nargs=nargs, action=action) + + def test_required_const_actions(self): + for action in ['store_const', 'append_const']: + + # nargs is always disallowed + self.assertTypeError('-x', nargs='+', action=action) + + def test_parsers_action_missing_params(self): + self.assertTypeError('command', action='parsers') + self.assertTypeError('command', action='parsers', prog='PROG') + self.assertTypeError('command', action='parsers', + parser_class=argparse.ArgumentParser) + + def test_required_positional(self): + self.assertTypeError('foo', required=True) + + def test_user_defined_action(self): + + class Success(Exception): + pass + + class Action(object): + + def __init__(self, + option_strings, + dest, + const, + default, + required=False): + if dest == 'spam': + if const is Success: + if default is Success: + raise Success() + + def __call__(self, *args, **kwargs): + pass + + parser = argparse.ArgumentParser() + self.assertRaises(Success, parser.add_argument, '--spam', + action=Action, default=Success, const=Success) + self.assertRaises(Success, parser.add_argument, 'spam', + action=Action, default=Success, const=Success) + +# ================================ +# Actions returned by add_argument +# ================================ + +class TestActionsReturned(TestCase): + + def test_dest(self): + parser = argparse.ArgumentParser() + action = parser.add_argument('--foo') + self.assertEqual(action.dest, 'foo') + action = parser.add_argument('-b', '--bar') + self.assertEqual(action.dest, 'bar') + action = parser.add_argument('-x', '-y') + self.assertEqual(action.dest, 'x') + + def test_misc(self): + parser = argparse.ArgumentParser() + action = parser.add_argument('--foo', nargs='?', const=42, + default=84, type=int, choices=[1, 2], + help='FOO', metavar='BAR', dest='baz') + self.assertEqual(action.nargs, '?') + self.assertEqual(action.const, 42) + self.assertEqual(action.default, 84) + self.assertEqual(action.type, int) + self.assertEqual(action.choices, [1, 2]) + self.assertEqual(action.help, 'FOO') + self.assertEqual(action.metavar, 'BAR') + self.assertEqual(action.dest, 'baz') + + +# ================================ +# Argument conflict handling tests +# ================================ + +class TestConflictHandling(TestCase): + + def test_bad_type(self): + self.assertRaises(ValueError, argparse.ArgumentParser, + conflict_handler='foo') + + def test_conflict_error(self): + parser = argparse.ArgumentParser() + parser.add_argument('-x') + self.assertRaises(argparse.ArgumentError, + parser.add_argument, '-x') + parser.add_argument('--spam') + self.assertRaises(argparse.ArgumentError, + parser.add_argument, '--spam') + + def test_resolve_error(self): + get_parser = argparse.ArgumentParser + parser = get_parser(prog='PROG', conflict_handler='resolve') + + parser.add_argument('-x', help='OLD X') + parser.add_argument('-x', help='NEW X') + self.assertEqual(parser.format_help(), textwrap.dedent('''\ + usage: PROG [-h] [-x X] + + optional arguments: + -h, --help show this help message and exit + -x X NEW X + ''')) + + parser.add_argument('--spam', metavar='OLD_SPAM') + parser.add_argument('--spam', metavar='NEW_SPAM') + self.assertEqual(parser.format_help(), textwrap.dedent('''\ + usage: PROG [-h] [-x X] [--spam NEW_SPAM] + + optional arguments: + -h, --help show this help message and exit + -x X NEW X + --spam NEW_SPAM + ''')) + + +# ============================= +# Help and Version option tests +# ============================= + +class TestOptionalsHelpVersionActions(TestCase): + """Test the help and version actions""" + + def _get_error(self, func, *args, **kwargs): + try: + func(*args, **kwargs) + except ArgumentParserError: + return sys.exc_info()[1] + else: + self.assertRaises(ArgumentParserError, func, *args, **kwargs) + + def assertPrintHelpExit(self, parser, args_str): + self.assertEqual( + parser.format_help(), + self._get_error(parser.parse_args, args_str.split()).stdout) + + def assertPrintVersionExit(self, parser, args_str): + self.assertEqual( + parser.format_version(), + self._get_error(parser.parse_args, args_str.split()).stderr) + + def assertArgumentParserError(self, parser, *args): + self.assertRaises(ArgumentParserError, parser.parse_args, args) + + def test_version(self): + parser = ErrorRaisingArgumentParser(version='1.0') + self.assertPrintHelpExit(parser, '-h') + self.assertPrintHelpExit(parser, '--help') + self.assertPrintVersionExit(parser, '-v') + self.assertPrintVersionExit(parser, '--version') + + def test_version_format(self): + parser = ErrorRaisingArgumentParser(prog='PPP', version='%(prog)s 3.5') + msg = self._get_error(parser.parse_args, ['-v']).stderr + self.assertEqual('PPP 3.5\n', msg) + + def test_version_no_help(self): + parser = ErrorRaisingArgumentParser(add_help=False, version='1.0') + self.assertArgumentParserError(parser, '-h') + self.assertArgumentParserError(parser, '--help') + self.assertPrintVersionExit(parser, '-v') + self.assertPrintVersionExit(parser, '--version') + + def test_version_action(self): + parser = ErrorRaisingArgumentParser(prog='XXX') + parser.add_argument('-V', action='version', version='%(prog)s 3.7') + msg = self._get_error(parser.parse_args, ['-V']).stderr + self.assertEqual('XXX 3.7\n', msg) + + def test_no_help(self): + parser = ErrorRaisingArgumentParser(add_help=False) + self.assertArgumentParserError(parser, '-h') + self.assertArgumentParserError(parser, '--help') + self.assertArgumentParserError(parser, '-v') + self.assertArgumentParserError(parser, '--version') + + def test_alternate_help_version(self): + parser = ErrorRaisingArgumentParser() + parser.add_argument('-x', action='help') + parser.add_argument('-y', action='version') + self.assertPrintHelpExit(parser, '-x') + self.assertPrintVersionExit(parser, '-y') + self.assertArgumentParserError(parser, '-v') + self.assertArgumentParserError(parser, '--version') + + def test_help_version_extra_arguments(self): + parser = ErrorRaisingArgumentParser(version='1.0') + parser.add_argument('-x', action='store_true') + parser.add_argument('y') + + # try all combinations of valid prefixes and suffixes + valid_prefixes = ['', '-x', 'foo', '-x bar', 'baz -x'] + valid_suffixes = valid_prefixes + ['--bad-option', 'foo bar baz'] + for prefix in valid_prefixes: + for suffix in valid_suffixes: + format = '%s %%s %s' % (prefix, suffix) + self.assertPrintHelpExit(parser, format % '-h') + self.assertPrintHelpExit(parser, format % '--help') + self.assertPrintVersionExit(parser, format % '-v') + self.assertPrintVersionExit(parser, format % '--version') + + +# ====================== +# str() and repr() tests +# ====================== + +class TestStrings(TestCase): + """Test str() and repr() on Optionals and Positionals""" + + def assertStringEqual(self, obj, result_string): + for func in [str, repr]: + self.assertEqual(func(obj), result_string) + + def test_optional(self): + option = argparse.Action( + option_strings=['--foo', '-a', '-b'], + dest='b', + type='int', + nargs='+', + default=42, + choices=[1, 2, 3], + help='HELP', + metavar='METAVAR') + string = ( + "Action(option_strings=['--foo', '-a', '-b'], dest='b', " + "nargs='+', const=None, default=42, type='int', " + "choices=[1, 2, 3], help='HELP', metavar='METAVAR')") + self.assertStringEqual(option, string) + + def test_argument(self): + argument = argparse.Action( + option_strings=[], + dest='x', + type=float, + nargs='?', + default=2.5, + choices=[0.5, 1.5, 2.5], + help='H HH H', + metavar='MV MV MV') + string = ( + "Action(option_strings=[], dest='x', nargs='?', " + "const=None, default=2.5, type=%r, choices=[0.5, 1.5, 2.5], " + "help='H HH H', metavar='MV MV MV')" % float) + self.assertStringEqual(argument, string) + + def test_namespace(self): + ns = argparse.Namespace(foo=42, bar='spam') + string = "Namespace(bar='spam', foo=42)" + self.assertStringEqual(ns, string) + + def test_parser(self): + parser = argparse.ArgumentParser(prog='PROG') + string = ( + "ArgumentParser(prog='PROG', usage=None, description=None, " + "version=None, formatter_class=%r, conflict_handler='error', " + "add_help=True)" % argparse.HelpFormatter) + self.assertStringEqual(parser, string) + +# =============== +# Namespace tests +# =============== + +class TestNamespace(TestCase): + + def test_constructor(self): + ns = argparse.Namespace() + self.assertRaises(AttributeError, getattr, ns, 'x') + + ns = argparse.Namespace(a=42, b='spam') + self.assertEqual(ns.a, 42) + self.assertEqual(ns.b, 'spam') + + def test_equality(self): + ns1 = argparse.Namespace(a=1, b=2) + ns2 = argparse.Namespace(b=2, a=1) + ns3 = argparse.Namespace(a=1) + ns4 = argparse.Namespace(b=2) + + self.assertEqual(ns1, ns2) + self.assertNotEqual(ns1, ns3) + self.assertNotEqual(ns1, ns4) + self.assertNotEqual(ns2, ns3) + self.assertNotEqual(ns2, ns4) + self.assertTrue(ns1 != ns3) + self.assertTrue(ns1 != ns4) + self.assertTrue(ns2 != ns3) + self.assertTrue(ns2 != ns4) + + +# =================== +# File encoding tests +# =================== + +class TestEncoding(TestCase): + + def _test_module_encoding(self, path): + path, _ = os.path.splitext(path) + path += ".py" + codecs.open(path, 'r', 'utf8').read() + + def test_argparse_module_encoding(self): + self._test_module_encoding(argparse.__file__) + + def test_test_argparse_module_encoding(self): + self._test_module_encoding(__file__) + +# =================== +# ArgumentError tests +# =================== + +class TestArgumentError(TestCase): + + def test_argument_error(self): + msg = "my error here" + error = argparse.ArgumentError(None, msg) + self.assertEqual(str(error), msg) + +# ======================= +# ArgumentTypeError tests +# ======================= + +class TestArgumentError(TestCase): + + def test_argument_type_error(self): + + def spam(string): + raise argparse.ArgumentTypeError('spam!') + + parser = ErrorRaisingArgumentParser(prog='PROG', add_help=False) + parser.add_argument('x', type=spam) + try: + parser.parse_args(['XXX']) + except ArgumentParserError: + expected = 'usage: PROG x\nPROG: error: argument x: spam!\n' + msg = sys.exc_info()[1].stderr + self.assertEqual(expected, msg) + else: + self.fail() + +# ====================== +# parse_known_args tests +# ====================== + +class TestParseKnownArgs(TestCase): + + def test_optionals(self): + parser = argparse.ArgumentParser() + parser.add_argument('--foo') + args, extras = parser.parse_known_args('--foo F --bar --baz'.split()) + self.assertEqual(NS(foo='F'), args) + self.assertEqual(['--bar', '--baz'], extras) + + def test_mixed(self): + parser = argparse.ArgumentParser() + parser.add_argument('-v', nargs='?', const=1, type=int) + parser.add_argument('--spam', action='store_false') + parser.add_argument('badger') + + argv = ["B", "C", "--foo", "-v", "3", "4"] + args, extras = parser.parse_known_args(argv) + self.assertEqual(NS(v=3, spam=True, badger="B"), args) + self.assertEqual(["C", "--foo", "4"], extras) + +# ============================ +# from argparse import * tests +# ============================ + +class TestImportStar(TestCase): + + def test(self): + for name in argparse.__all__: + self.assertTrue(hasattr(argparse, name)) + +def test_main(): + with warnings.catch_warnings(): + # silence Python 2.6 buggy warnings about Exception.message + warnings.filterwarnings( + action='ignore', + message='BaseException.message has been deprecated as of' + 'Python 2.6', + category=DeprecationWarning) + # silence warnings about version argument - these are expected + warnings.filterwarnings( + action='ignore', + message='The "version" argument to ArgumentParser is deprecated.', + category=DeprecationWarning) + warnings.filterwarnings( + action='ignore', + message='The format_version method is deprecated', + category=DeprecationWarning) + warnings.filterwarnings( + action='ignore', + message='The print_version method is deprecated', + category=DeprecationWarning) + + support.run_unittest(__name__) + + +if __name__ == '__main__': + test_main()