diff options
author | Benjamin Peterson <benjamin@python.org> | 2010-03-02 22:34:37 (GMT) |
---|---|---|
committer | Benjamin Peterson <benjamin@python.org> | 2010-03-02 22:34:37 (GMT) |
commit | 698a18aa9e8e0f3fca081dc12544002a4d0e83ec (patch) | |
tree | 691aee3a75f60be8994e663203b46180a8794e44 /Lib | |
parent | a988e422050246757a5a88fffe1174bdaa0aad25 (diff) | |
download | cpython-698a18aa9e8e0f3fca081dc12544002a4d0e83ec.zip cpython-698a18aa9e8e0f3fca081dc12544002a4d0e83ec.tar.gz cpython-698a18aa9e8e0f3fca081dc12544002a4d0e83ec.tar.bz2 |
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
........
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/argparse.py | 4704 | ||||
-rw-r--r-- | Lib/test/test_argparse.py | 8416 |
2 files changed, 6561 insertions, 6559 deletions
diff --git a/Lib/argparse.py b/Lib/argparse.py index 717b660..cde1fe9 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -1,2353 +1,2351 @@ -# -*- coding: utf-8 -*-
-
-# Copyright © 2006-2009 Steven J. Bethard <steven.bethard@gmail.com>.
-#
-# 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 <steven.bethard@gmail.com>. +# +# 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 61f8e46..849facb 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 <steven.bethard@gmail.com>.
-#
-# 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=<whatever was set>
- 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=<whatever was set>
- 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 <steven.bethard@gmail.com>. +# +# 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=<whatever was set> + 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=<whatever was set> + 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() |