From 15822cfefbc1107ee870760fc2de5d347dd2dbe8 Mon Sep 17 00:00:00 2001 From: "steven.bethard" Date: Sat, 12 Sep 2009 15:27:12 +0000 Subject: Set svn:eol-style=native for all .py files. --- argparse.py | 4432 ++++++++++++++--------------- doc/source/conf.py | 394 +-- setup.py | 104 +- test/test_argparse.py | 7348 ++++++++++++++++++++++++------------------------- 4 files changed, 6139 insertions(+), 6139 deletions(-) diff --git a/argparse.py b/argparse.py index 27929ee..8950cc7 100644 --- a/argparse.py +++ b/argparse.py @@ -1,2216 +1,2216 @@ -# -*- coding: utf-8 -*- - -# Copyright © 2006-2009 Steven J. Bethard . -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Command-line parsing library - -This module is an optparse-inspired command-line parsing library that: - - - handles both optional and positional arguments - - produces highly informative usage messages - - supports parsers that dispatch to sub-parsers - -The following is a simple usage example that sums integers from the -command-line and writes the result to a file:: - - parser = argparse.ArgumentParser( - description='sum the integers at the command line') - parser.add_argument( - 'integers', metavar='int', nargs='+', type=int, - help='an integer to be summed') - parser.add_argument( - '--log', default=sys.stdout, type=argparse.FileType('w'), - help='the file where the sum should be written') - args = parser.parse_args() - args.log.write('%s' % sum(args.integers)) - args.log.close() - -The module contains the following public classes: - - - ArgumentParser -- The main entry point for command-line parsing. As the - example above shows, the add_argument() method is used to populate - the parser with actions for optional and positional arguments. Then - the parse_args() method is invoked to convert the args at the - command-line into an object with attributes. - - - ArgumentError -- The exception raised by ArgumentParser objects when - there are errors with the parser's actions. Errors raised while - parsing the command-line are caught by ArgumentParser and emitted - as command-line messages. - - - FileType -- A factory for defining types of files to be created. As the - example above shows, instances of FileType are typically passed as - the type= argument of add_argument() calls. - - - Action -- The base class for parser actions. Typically actions are - selected by passing strings like 'store_true' or 'append_const' to - the action= argument of add_argument(). However, for greater - customization of ArgumentParser actions, subclasses of Action may - be defined and passed as the action= argument. - - - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, - ArgumentDefaultsHelpFormatter -- Formatter classes which - may be passed as the formatter_class= argument to the - ArgumentParser constructor. HelpFormatter is the default, - RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser - not to change the formatting for help text, and - ArgumentDefaultsHelpFormatter adds information about argument defaults - to the help. - -All other classes in this module are considered implementation details. -(Also note that HelpFormatter and RawDescriptionHelpFormatter are only -considered public as object names -- the API of the formatter objects is -still considered an implementation detail.) -""" - -__version__ = '1.0.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 - - -SUPPRESS = '==SUPPRESS==' - -OPTIONAL = '?' -ZERO_OR_MORE = '*' -ONE_OR_MORE = '+' -PARSER = '==PARSER==' - -# ============================= -# 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() % dict(prog=self._prog) - 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 no optionals or positionals are available, usage is just prog - if usage is None and not actions: - usage = '%(prog)s' - - # if optionals and positionals are available, calculate usage - elif usage is None: - usage = '%(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) - - # determine width of "usage: PROG" and width of text - prefix_width = len(prefix) + len(usage) + 1 - prefix_indent = self._current_indent + prefix_width - text_width = self._width - self._current_indent - - # put them on one line if they're short enough - format = self._format_actions_usage - action_usage = format(optionals + positionals, groups) - if prefix_width + len(action_usage) + 1 < text_width: - usage = '%s %s' % (usage, action_usage) - - # if they're long, wrap optionals and positionals individually - else: - optional_usage = format(optionals, groups) - positional_usage = format(positionals, groups) - indent = ' ' * prefix_indent - - # usage is made of PROG, optionals and positionals - parts = [usage, ' '] - - # options always get added right after PROG - if optional_usage: - parts.append(_textwrap.fill( - optional_usage, text_width, - initial_indent=indent, - subsequent_indent=indent).lstrip()) - - # if there were options, put arguments on the next line - # otherwise, start them right after PROG - if positional_usage: - part = _textwrap.fill( - positional_usage, text_width, - initial_indent=indent, - subsequent_indent=indent).lstrip() - if optional_usage: - part = '\n' + indent + part - parts.append(part) - usage = ''.join(parts) - - # 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): - 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 is 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] - 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) - -# ============== -# 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 must be > 0') - 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 must be > 0') - 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, - dest=SUPPRESS, - default=SUPPRESS, - help=None): - super(_VersionAction, 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_version() - parser.exit() - - -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) - - -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 settings 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] - - # ======================= - # 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: - 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) - action = action_class(**kwargs) - 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 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 one-or-fewer-character option strings - if len(option_string) < 2: - msg = _('invalid option string %r: ' - 'must be at least two characters long') - raise ValueError(msg % option_string) - - # 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) - - # error on strings that are all prefix characters - if not (_set(option_string) - _set(self.prefix_chars)): - msg = _('invalid option string %r: ' - 'must contain characters other than %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 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) - 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 - - version -- Add a -v/--version option with the given version string - - 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): - - 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, - 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 = args_file.read().splitlines() - 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 _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 it's just dashes, it was meant to be positional - if not arg_string.strip('-'): - 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 - - # 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 one argument followed by any number of options or arguments - elif nargs is 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 is not PARSER: - 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) - - # PARSER arguments convert all values, but check only the first - elif action.nargs is 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 hasattr(type_func, '__call__'): - msg = _('%r is not callable') - raise ArgumentError(action, msg % type_func) - - # convert the value to the appropriate type - try: - result = type_func(arg_string) - - # TypeErrors or ValueErrors 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): - 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): - self._print_message(self.format_usage(), file) - - def print_help(self, file=None): - self._print_message(self.format_help(), file) - - def print_version(self, file=None): - 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: - _sys.stderr.write(message) - _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)) +# -*- coding: utf-8 -*- + +# Copyright © 2006-2009 Steven J. Bethard . +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Command-line parsing library + +This module is an optparse-inspired command-line parsing library that: + + - handles both optional and positional arguments + - produces highly informative usage messages + - supports parsers that dispatch to sub-parsers + +The following is a simple usage example that sums integers from the +command-line and writes the result to a file:: + + parser = argparse.ArgumentParser( + description='sum the integers at the command line') + parser.add_argument( + 'integers', metavar='int', nargs='+', type=int, + help='an integer to be summed') + parser.add_argument( + '--log', default=sys.stdout, type=argparse.FileType('w'), + help='the file where the sum should be written') + args = parser.parse_args() + args.log.write('%s' % sum(args.integers)) + args.log.close() + +The module contains the following public classes: + + - ArgumentParser -- The main entry point for command-line parsing. As the + example above shows, the add_argument() method is used to populate + the parser with actions for optional and positional arguments. Then + the parse_args() method is invoked to convert the args at the + command-line into an object with attributes. + + - ArgumentError -- The exception raised by ArgumentParser objects when + there are errors with the parser's actions. Errors raised while + parsing the command-line are caught by ArgumentParser and emitted + as command-line messages. + + - FileType -- A factory for defining types of files to be created. As the + example above shows, instances of FileType are typically passed as + the type= argument of add_argument() calls. + + - Action -- The base class for parser actions. Typically actions are + selected by passing strings like 'store_true' or 'append_const' to + the action= argument of add_argument(). However, for greater + customization of ArgumentParser actions, subclasses of Action may + be defined and passed as the action= argument. + + - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, + ArgumentDefaultsHelpFormatter -- Formatter classes which + may be passed as the formatter_class= argument to the + ArgumentParser constructor. HelpFormatter is the default, + RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser + not to change the formatting for help text, and + ArgumentDefaultsHelpFormatter adds information about argument defaults + to the help. + +All other classes in this module are considered implementation details. +(Also note that HelpFormatter and RawDescriptionHelpFormatter are only +considered public as object names -- the API of the formatter objects is +still considered an implementation detail.) +""" + +__version__ = '1.0.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 + + +SUPPRESS = '==SUPPRESS==' + +OPTIONAL = '?' +ZERO_OR_MORE = '*' +ONE_OR_MORE = '+' +PARSER = '==PARSER==' + +# ============================= +# 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() % dict(prog=self._prog) + 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 no optionals or positionals are available, usage is just prog + if usage is None and not actions: + usage = '%(prog)s' + + # if optionals and positionals are available, calculate usage + elif usage is None: + usage = '%(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) + + # determine width of "usage: PROG" and width of text + prefix_width = len(prefix) + len(usage) + 1 + prefix_indent = self._current_indent + prefix_width + text_width = self._width - self._current_indent + + # put them on one line if they're short enough + format = self._format_actions_usage + action_usage = format(optionals + positionals, groups) + if prefix_width + len(action_usage) + 1 < text_width: + usage = '%s %s' % (usage, action_usage) + + # if they're long, wrap optionals and positionals individually + else: + optional_usage = format(optionals, groups) + positional_usage = format(positionals, groups) + indent = ' ' * prefix_indent + + # usage is made of PROG, optionals and positionals + parts = [usage, ' '] + + # options always get added right after PROG + if optional_usage: + parts.append(_textwrap.fill( + optional_usage, text_width, + initial_indent=indent, + subsequent_indent=indent).lstrip()) + + # if there were options, put arguments on the next line + # otherwise, start them right after PROG + if positional_usage: + part = _textwrap.fill( + positional_usage, text_width, + initial_indent=indent, + subsequent_indent=indent).lstrip() + if optional_usage: + part = '\n' + indent + part + parts.append(part) + usage = ''.join(parts) + + # 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): + 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 is 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] + 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) + +# ============== +# 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 must be > 0') + 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 must be > 0') + 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, + dest=SUPPRESS, + default=SUPPRESS, + help=None): + super(_VersionAction, 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_version() + parser.exit() + + +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) + + +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 settings 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] + + # ======================= + # 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: + 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) + action = action_class(**kwargs) + 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 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 one-or-fewer-character option strings + if len(option_string) < 2: + msg = _('invalid option string %r: ' + 'must be at least two characters long') + raise ValueError(msg % option_string) + + # 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) + + # error on strings that are all prefix characters + if not (_set(option_string) - _set(self.prefix_chars)): + msg = _('invalid option string %r: ' + 'must contain characters other than %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 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) + 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 + - version -- Add a -v/--version option with the given version string + - 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): + + 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, + 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 = args_file.read().splitlines() + 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 _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 it's just dashes, it was meant to be positional + if not arg_string.strip('-'): + 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 + + # 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 one argument followed by any number of options or arguments + elif nargs is 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 is not PARSER: + 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) + + # PARSER arguments convert all values, but check only the first + elif action.nargs is 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 hasattr(type_func, '__call__'): + msg = _('%r is not callable') + raise ArgumentError(action, msg % type_func) + + # convert the value to the appropriate type + try: + result = type_func(arg_string) + + # TypeErrors or ValueErrors 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): + 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): + self._print_message(self.format_usage(), file) + + def print_help(self, file=None): + self._print_message(self.format_help(), file) + + def print_version(self, file=None): + 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: + _sys.stderr.write(message) + _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/doc/source/conf.py b/doc/source/conf.py index 054e6af..43ef9a2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -1,198 +1,198 @@ -# -*- coding: utf-8 -*- -# -# argparse documentation build configuration file, created by -# sphinx-quickstart on Thu Mar 26 10:47:44 2009. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.doctest', 'sphinx.ext.coverage'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'argparse' -copyright = u'2006-2009, Steven Bethard' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '1.0.1' -# The full version, including alpha/beta/rc tags. -release = '1.0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'argparsedoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'argparse.tex', u'argparse Documentation', - u'Steven Bethard', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True - -# Python code that is treated like it were put in a testsetup directive for -# every file that is tested, and for every group. +# -*- coding: utf-8 -*- +# +# argparse documentation build configuration file, created by +# sphinx-quickstart on Thu Mar 26 10:47:44 2009. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.doctest', 'sphinx.ext.coverage'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'argparse' +copyright = u'2006-2009, Steven Bethard' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0.1' +# The full version, including alpha/beta/rc tags. +release = '1.0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'argparsedoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'argparse.tex', u'argparse Documentation', + u'Steven Bethard', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True + +# Python code that is treated like it were put in a testsetup directive for +# every file that is tested, and for every group. doctest_global_setup = "import argparse" \ No newline at end of file diff --git a/setup.py b/setup.py index d69f8a7..8105d39 100644 --- a/setup.py +++ b/setup.py @@ -1,52 +1,52 @@ -# -*- coding: utf-8 -*- - -# Copyright © 2006-2009 Steven J. Bethard . -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import argparse -import distutils.core -import os -import re - - -def read_description(): - readme_file = open(os.path.join(os.path.dirname(__file__), 'README.txt')) - readme_text = readme_file.read() - readme_file.close() - main_desc_regexp = r'^argparse\s*[\d.]*\n=======+\n(.*)Requirements &' - main_desc, = re.findall(main_desc_regexp, readme_text, re.DOTALL) - avail_desc_regexp = r'Availability & Documentation\n-----+\n(.*)' - avail_desc, = re.findall(avail_desc_regexp, readme_text, re.DOTALL) - return main_desc + avail_desc - -distutils.core.setup( - name='argparse', - version=argparse.__version__, - author='Steven Bethard', - author_email='steven.bethard@gmail.com', - url='http://code.google.com/p/argparse/', - description='Python command-line parsing library', - long_description = read_description(), - license='Apache 2.0', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Software Development', - ], - py_modules=['argparse'], -) +# -*- coding: utf-8 -*- + +# Copyright © 2006-2009 Steven J. Bethard . +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import argparse +import distutils.core +import os +import re + + +def read_description(): + readme_file = open(os.path.join(os.path.dirname(__file__), 'README.txt')) + readme_text = readme_file.read() + readme_file.close() + main_desc_regexp = r'^argparse\s*[\d.]*\n=======+\n(.*)Requirements &' + main_desc, = re.findall(main_desc_regexp, readme_text, re.DOTALL) + avail_desc_regexp = r'Availability & Documentation\n-----+\n(.*)' + avail_desc, = re.findall(avail_desc_regexp, readme_text, re.DOTALL) + return main_desc + avail_desc + +distutils.core.setup( + name='argparse', + version=argparse.__version__, + author='Steven Bethard', + author_email='steven.bethard@gmail.com', + url='http://code.google.com/p/argparse/', + description='Python command-line parsing library', + long_description = read_description(), + license='Apache 2.0', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + py_modules=['argparse'], +) diff --git a/test/test_argparse.py b/test/test_argparse.py index 452933f..34a9c3f 100644 --- a/test/test_argparse.py +++ b/test/test_argparse.py @@ -1,3674 +1,3674 @@ -# -*- coding: utf-8 -*- - -# Copyright © 2006-2009 Steven J. Bethard . -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import codecs -import os -import shutil -import sys -import textwrap -import tempfile -import unittest -import argparse - -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - -try: - set -except NameError: - from sets import Set as set - -try: - sorted -except NameError: - - def sorted(iterable, reverse=False): - result = list(iterable) - result.sort() - if reverse: - result.reverse() - return result - - -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, error_code): - Exception.__init__(self, message) - self.message = message - self.error_code = error_code - - -def stderr_to_parser_error(func, *args, **kwargs): - # if this is being called recursively and stderr is already being - # redirected, simply call the function and let the enclosing function - # catch the exception - if isinstance(sys.stderr, StringIO): - return func(*args, **kwargs) - - # if this is not being called recursively, redirect stderr and - # use it as the ArgumentParserError message - old_stderr = sys.stderr - sys.stderr = StringIO() - try: - try: - return func(*args, **kwargs) - except SystemExit: - code = sys.exc_info()[1].code - message = sys.stderr.getvalue() - raise ArgumentParserError(message, code) - finally: - 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() - - # --------------------------------------- - # 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 = ErrorRaisingArgumentParser(*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 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 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 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 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'])), - ] - - -# ===================== -# 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', '-x bar'] - 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', '-x bar'] - 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', '-x bar'] - 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', '-x bar'] - 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) - - def get_my_type(value): - return TestTypeUserDefined.MyType(value) - - argument_signatures = [ - Sig('-x', type=get_my_type), - Sig('spam', type=get_my_type), - ] - failures = [] - successes = [ - ('a -x b', NS(x=MyType('b'), spam=MyType('a'))), - ('-xf g', NS(x=MyType('f'), spam=MyType('g'))), - ] - - -class TestTypeRegistration(TestCase): - """Test a user-defined type by registering it""" - - def test(self): - - def get_my_type(string): - return 'my_type{%s}' % string - - parser = argparse.ArgumentParser() - parser.register('type', 'my_type', get_my_type) - parser.add_argument('-x', type='my_type') - parser.add_argument('y', type='my_type') - - self.assertEqual(parser.parse_args('1'.split()), - NS(x=None, y='my_type{1}')) - self.assertEqual(parser.parse_args('-x 1 42'.split()), - NS(x='my_type{1}', y='my_type{42}')) - - -# ============ -# Action tests -# ============ - -class TestActionUserDefined(ParserTestCase): - """Test a user-defined option/argument action""" - - class OptionalAction(argparse.Action): - - def __call__(self, parser, namespace, value, option_string=None): - try: - # check destination and option string - assert self.dest == 'spam', 'dest: %s' % self.dest - assert option_string == '-s', 'flag: %s' % option_string - # when option is before argument, badger=2, and when - # option is after argument, badger= - expected_ns = NS(spam=0.25) - if value in [0.125, 0.625]: - expected_ns.badger = 2 - elif value in [2.0]: - expected_ns.badger = 84 - else: - raise AssertionError('value: %s' % value) - assert expected_ns == namespace, ('expected %s, got %s' % - (expected_ns, namespace)) - except AssertionError: - e = sys.exc_info()[1] - raise ArgumentParserError('opt_action failed: %s' % e) - setattr(namespace, 'spam', value) - - class PositionalAction(argparse.Action): - - def __call__(self, parser, namespace, value, option_string=None): - try: - assert option_string is None, ('option_string: %s' % - option_string) - # check destination - assert self.dest == 'badger', 'dest: %s' % self.dest - # when argument is before option, spam=0.25, and when - # option is after argument, spam= - expected_ns = NS(badger=2) - if value in [42, 84]: - expected_ns.spam = 0.25 - elif value in [1]: - expected_ns.spam = 0.625 - elif value in [2]: - expected_ns.spam = 0.125 - else: - raise AssertionError('value: %s' % value) - assert expected_ns == namespace, ('expected %s, got %s' % - (expected_ns, namespace)) - except AssertionError: - e = sys.exc_info()[1] - raise ArgumentParserError('arg_action failed: %s' % e) - setattr(namespace, 'badger', value) - - argument_signatures = [ - Sig('-s', dest='spam', action=OptionalAction, - type=float, default=0.25), - Sig('badger', action=PositionalAction, - type=int, nargs='?', default=2), - ] - failures = [] - successes = [ - ('-s0.125', NS(spam=0.125, badger=2)), - ('42', NS(spam=0.25, badger=42)), - ('-s 0.625 1', NS(spam=0.625, badger=1)), - ('84 -s2', NS(spam=2.0, badger=84)), - ] - - -class TestActionRegistration(TestCase): - """Test a user-defined action supplied by registering it""" - - class MyAction(argparse.Action): - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, 'foo[%s]' % values) - - def test(self): - - parser = argparse.ArgumentParser() - parser.register('action', 'my_action', self.MyAction) - parser.add_argument('badger', action='my_action') - - self.assertEqual(parser.parse_args(['1']), NS(badger='foo[1]')) - self.assertEqual(parser.parse_args(['42']), NS(badger='foo[42]')) - - -# ================ -# Subparsers tests -# ================ - -class TestAddSubparsers(TestCase): - """Test the add_subparsers method""" - - def assertArgumentParserError(self, *args, **kwargs): - self.assertRaises(ArgumentParserError, *args, **kwargs) - - def _get_parser(self, subparser_help=False): - # create a parser with a subparsers argument - parser = ErrorRaisingArgumentParser( - prog='PROG', description='main description') - parser.add_argument( - '--foo', action='store_true', help='foo help') - parser.add_argument( - 'bar', type=float, help='bar help') - - # check that only one subparsers argument can be added - subparsers = parser.add_subparsers(help='command help') - self.assertArgumentParserError(parser.add_subparsers) - - # add first sub-parser - parser1_kwargs = dict(description='1 description') - if subparser_help: - parser1_kwargs['help'] = '1 help' - parser1 = subparsers.add_parser('1', **parser1_kwargs) - parser1.add_argument('-w', type=int, help='w help') - parser1.add_argument('x', choices='abc', help='x help') - - # add second sub-parser - parser2_kwargs = dict(description='2 description') - if subparser_help: - parser2_kwargs['help'] = '2 help' - parser2 = subparsers.add_parser('2', **parser2_kwargs) - parser2.add_argument('-y', choices='123', help='y help') - parser2.add_argument('z', type=complex, nargs='*', help='z help') - - # return the main parser - return parser - - def setUp(self): - self.parser = self._get_parser() - self.command_help_parser = self._get_parser(subparser_help=True) - - def test_parse_args_failures(self): - # check some failure cases: - for args_str in ['', 'a', 'a a', '0.5 a', '0.5 1', - '0.5 1 -y', '0.5 2 -w']: - args = args_str.split() - self.assertArgumentParserError(self.parser.parse_args, args) - - def test_parse_args(self): - # check some non-failure cases: - self.assertEqual( - self.parser.parse_args('0.5 1 b -w 7'.split()), - NS(foo=False, bar=0.5, w=7, x='b'), - ) - self.assertEqual( - self.parser.parse_args('0.25 --foo 2 -y 2 3j -- -1j'.split()), - NS(foo=True, bar=0.25, y='2', z=[3j, -1j]), - ) - self.assertEqual( - self.parser.parse_args('--foo 0.125 1 c'.split()), - NS(foo=True, bar=0.125, w=None, x='c'), - ) - - def test_dest(self): - parser = ErrorRaisingArgumentParser() - parser.add_argument('--foo', action='store_true') - subparsers = parser.add_subparsers(dest='bar') - parser1 = subparsers.add_parser('1') - parser1.add_argument('baz') - self.assertEqual(NS(foo=False, bar='1', baz='2'), - parser.parse_args('1 2'.split())) - - def test_help(self): - self.assertEqual(self.parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') - self.assertEqual(self.parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... - - main description - - positional arguments: - bar bar help - {1,2} command help - - optional arguments: - -h, --help show this help message and exit - --foo foo help - ''')) - - def test_parser_command_help(self): - self.assertEqual(self.command_help_parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') - self.assertEqual(self.command_help_parser.format_help(), - textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... - - main description - - positional arguments: - bar bar help - {1,2} command help - 1 1 help - 2 2 help - - optional arguments: - -h, --help show this help message and exit - --foo foo help - ''')) - - def test_subparser_title_help(self): - parser = ErrorRaisingArgumentParser(prog='PROG', - description='main description') - parser.add_argument('--foo', action='store_true', help='foo help') - parser.add_argument('bar', help='bar help') - subparsers = parser.add_subparsers(title='subcommands', - description='command help', - help='additional text') - parser1 = subparsers.add_parser('1') - parser2 = subparsers.add_parser('2') - self.assertEqual(parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') - self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... - - main description - - positional arguments: - bar bar help - - optional arguments: - -h, --help show this help message and exit - --foo foo help - - subcommands: - command help - - {1,2} additional text - ''')) - - def _test_subparser_help(self, args_str, expected_help): - try: - self.parser.parse_args(args_str.split()) - except ArgumentParserError: - err = sys.exc_info()[1] - if err.message != expected_help: - print(repr(expected_help)) - print(repr(err.message)) - self.assertEqual(err.message, 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.failUnlessEqual(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.failUnlessEqual(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.failUnlessEqual(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') - - 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_multiple_parents(self): - parser = ErrorRaisingArgumentParser( - parents=[self.abcd_parent, self.wxyz_parent]) - 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_conflicting_parents(self): - self.assertRaises( - argparse.ArgumentError, - argparse.ArgumentParser, - parents=[self.w_parent, self.wxyz_parent]) - - def test_same_argument_name_parents(self): - parser = ErrorRaisingArgumentParser( - parents=[self.wxyz_parent, self.z_parent]) - 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_parent_help(self): - parser = ErrorRaisingArgumentParser( - parents=[self.abcd_parent, self.wxyz_parent]) - 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 - ''')) - -# ============================== -# 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 - ''' - - -# ================= -# 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([])) - -# ===================== -# 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): - self.func_suffix = func_suffix - - 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) - oldstderr = sys.stderr - sys.stderr = StringIO() - try: - print_() - parser_text = sys.stderr.getvalue() - finally: - sys.stderr = oldstderr - 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 in ['usage', 'help', 'version']: - AddTests(cls, func_suffix) - -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 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 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_action(self): - self.assertTypeError('-x', action='foo') - self.assertTypeError('foo', action='baz') - - 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_message(self, func, *args, **kwargs): - try: - func(*args, **kwargs) - except ArgumentParserError: - err = sys.exc_info()[1] - return err.message - else: - self.assertRaises(ArgumentParserError, func, *args, **kwargs) - - def assertPrintHelpExit(self, parser, args_str): - self.assertEqual( - parser.format_help(), - self._get_error_message(parser.parse_args, args_str.split())) - - def assertPrintVersionExit(self, parser, args_str): - self.assertEqual( - parser.format_version(), - self._get_error_message(parser.parse_args, args_str.split())) - - 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_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_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.failUnless(ns1 != ns3) - self.failUnless(ns1 != ns4) - self.failUnless(ns2 != ns3) - self.failUnless(ns2 != ns4) - - -# =================== -# File encoding tests -# =================== - -class TestEncoding(TestCase): - - def test_argparse_module_encoding(self): - text = codecs.open(argparse.__file__, 'r', 'utf8').read() - - def test_test_argparse_module_encoding(self): - text = codecs.open(__file__, 'r', 'utf8').read() - -# =================== -# ArgumentError tests -# =================== - -class TestArgumentError(TestCase): - - def test_argument_error(self): - msg = "my error here" - error = argparse.ArgumentError(None, msg) - self.failUnlessEqual(str(error), msg) - -# ====================== -# 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.failUnlessEqual(NS(foo='F'), args) - self.failUnlessEqual(['--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.failUnlessEqual(NS(v=3, spam=True, badger="B"), args) - self.failUnlessEqual(["C", "--foo", "4"], extras) - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +# Copyright © 2006-2009 Steven J. Bethard . +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy +# of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import codecs +import os +import shutil +import sys +import textwrap +import tempfile +import unittest +import argparse + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +try: + set +except NameError: + from sets import Set as set + +try: + sorted +except NameError: + + def sorted(iterable, reverse=False): + result = list(iterable) + result.sort() + if reverse: + result.reverse() + return result + + +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, error_code): + Exception.__init__(self, message) + self.message = message + self.error_code = error_code + + +def stderr_to_parser_error(func, *args, **kwargs): + # if this is being called recursively and stderr is already being + # redirected, simply call the function and let the enclosing function + # catch the exception + if isinstance(sys.stderr, StringIO): + return func(*args, **kwargs) + + # if this is not being called recursively, redirect stderr and + # use it as the ArgumentParserError message + old_stderr = sys.stderr + sys.stderr = StringIO() + try: + try: + return func(*args, **kwargs) + except SystemExit: + code = sys.exc_info()[1].code + message = sys.stderr.getvalue() + raise ArgumentParserError(message, code) + finally: + 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() + + # --------------------------------------- + # 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 = ErrorRaisingArgumentParser(*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 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 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 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 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'])), + ] + + +# ===================== +# 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', '-x bar'] + 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', '-x bar'] + 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', '-x bar'] + 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', '-x bar'] + 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) + + def get_my_type(value): + return TestTypeUserDefined.MyType(value) + + argument_signatures = [ + Sig('-x', type=get_my_type), + Sig('spam', type=get_my_type), + ] + failures = [] + successes = [ + ('a -x b', NS(x=MyType('b'), spam=MyType('a'))), + ('-xf g', NS(x=MyType('f'), spam=MyType('g'))), + ] + + +class TestTypeRegistration(TestCase): + """Test a user-defined type by registering it""" + + def test(self): + + def get_my_type(string): + return 'my_type{%s}' % string + + parser = argparse.ArgumentParser() + parser.register('type', 'my_type', get_my_type) + parser.add_argument('-x', type='my_type') + parser.add_argument('y', type='my_type') + + self.assertEqual(parser.parse_args('1'.split()), + NS(x=None, y='my_type{1}')) + self.assertEqual(parser.parse_args('-x 1 42'.split()), + NS(x='my_type{1}', y='my_type{42}')) + + +# ============ +# Action tests +# ============ + +class TestActionUserDefined(ParserTestCase): + """Test a user-defined option/argument action""" + + class OptionalAction(argparse.Action): + + def __call__(self, parser, namespace, value, option_string=None): + try: + # check destination and option string + assert self.dest == 'spam', 'dest: %s' % self.dest + assert option_string == '-s', 'flag: %s' % option_string + # when option is before argument, badger=2, and when + # option is after argument, badger= + expected_ns = NS(spam=0.25) + if value in [0.125, 0.625]: + expected_ns.badger = 2 + elif value in [2.0]: + expected_ns.badger = 84 + else: + raise AssertionError('value: %s' % value) + assert expected_ns == namespace, ('expected %s, got %s' % + (expected_ns, namespace)) + except AssertionError: + e = sys.exc_info()[1] + raise ArgumentParserError('opt_action failed: %s' % e) + setattr(namespace, 'spam', value) + + class PositionalAction(argparse.Action): + + def __call__(self, parser, namespace, value, option_string=None): + try: + assert option_string is None, ('option_string: %s' % + option_string) + # check destination + assert self.dest == 'badger', 'dest: %s' % self.dest + # when argument is before option, spam=0.25, and when + # option is after argument, spam= + expected_ns = NS(badger=2) + if value in [42, 84]: + expected_ns.spam = 0.25 + elif value in [1]: + expected_ns.spam = 0.625 + elif value in [2]: + expected_ns.spam = 0.125 + else: + raise AssertionError('value: %s' % value) + assert expected_ns == namespace, ('expected %s, got %s' % + (expected_ns, namespace)) + except AssertionError: + e = sys.exc_info()[1] + raise ArgumentParserError('arg_action failed: %s' % e) + setattr(namespace, 'badger', value) + + argument_signatures = [ + Sig('-s', dest='spam', action=OptionalAction, + type=float, default=0.25), + Sig('badger', action=PositionalAction, + type=int, nargs='?', default=2), + ] + failures = [] + successes = [ + ('-s0.125', NS(spam=0.125, badger=2)), + ('42', NS(spam=0.25, badger=42)), + ('-s 0.625 1', NS(spam=0.625, badger=1)), + ('84 -s2', NS(spam=2.0, badger=84)), + ] + + +class TestActionRegistration(TestCase): + """Test a user-defined action supplied by registering it""" + + class MyAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, 'foo[%s]' % values) + + def test(self): + + parser = argparse.ArgumentParser() + parser.register('action', 'my_action', self.MyAction) + parser.add_argument('badger', action='my_action') + + self.assertEqual(parser.parse_args(['1']), NS(badger='foo[1]')) + self.assertEqual(parser.parse_args(['42']), NS(badger='foo[42]')) + + +# ================ +# Subparsers tests +# ================ + +class TestAddSubparsers(TestCase): + """Test the add_subparsers method""" + + def assertArgumentParserError(self, *args, **kwargs): + self.assertRaises(ArgumentParserError, *args, **kwargs) + + def _get_parser(self, subparser_help=False): + # create a parser with a subparsers argument + parser = ErrorRaisingArgumentParser( + prog='PROG', description='main description') + parser.add_argument( + '--foo', action='store_true', help='foo help') + parser.add_argument( + 'bar', type=float, help='bar help') + + # check that only one subparsers argument can be added + subparsers = parser.add_subparsers(help='command help') + self.assertArgumentParserError(parser.add_subparsers) + + # add first sub-parser + parser1_kwargs = dict(description='1 description') + if subparser_help: + parser1_kwargs['help'] = '1 help' + parser1 = subparsers.add_parser('1', **parser1_kwargs) + parser1.add_argument('-w', type=int, help='w help') + parser1.add_argument('x', choices='abc', help='x help') + + # add second sub-parser + parser2_kwargs = dict(description='2 description') + if subparser_help: + parser2_kwargs['help'] = '2 help' + parser2 = subparsers.add_parser('2', **parser2_kwargs) + parser2.add_argument('-y', choices='123', help='y help') + parser2.add_argument('z', type=complex, nargs='*', help='z help') + + # return the main parser + return parser + + def setUp(self): + self.parser = self._get_parser() + self.command_help_parser = self._get_parser(subparser_help=True) + + def test_parse_args_failures(self): + # check some failure cases: + for args_str in ['', 'a', 'a a', '0.5 a', '0.5 1', + '0.5 1 -y', '0.5 2 -w']: + args = args_str.split() + self.assertArgumentParserError(self.parser.parse_args, args) + + def test_parse_args(self): + # check some non-failure cases: + self.assertEqual( + self.parser.parse_args('0.5 1 b -w 7'.split()), + NS(foo=False, bar=0.5, w=7, x='b'), + ) + self.assertEqual( + self.parser.parse_args('0.25 --foo 2 -y 2 3j -- -1j'.split()), + NS(foo=True, bar=0.25, y='2', z=[3j, -1j]), + ) + self.assertEqual( + self.parser.parse_args('--foo 0.125 1 c'.split()), + NS(foo=True, bar=0.125, w=None, x='c'), + ) + + def test_dest(self): + parser = ErrorRaisingArgumentParser() + parser.add_argument('--foo', action='store_true') + subparsers = parser.add_subparsers(dest='bar') + parser1 = subparsers.add_parser('1') + parser1.add_argument('baz') + self.assertEqual(NS(foo=False, bar='1', baz='2'), + parser.parse_args('1 2'.split())) + + def test_help(self): + self.assertEqual(self.parser.format_usage(), + 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + self.assertEqual(self.parser.format_help(), textwrap.dedent('''\ + usage: PROG [-h] [--foo] bar {1,2} ... + + main description + + positional arguments: + bar bar help + {1,2} command help + + optional arguments: + -h, --help show this help message and exit + --foo foo help + ''')) + + def test_parser_command_help(self): + self.assertEqual(self.command_help_parser.format_usage(), + 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + self.assertEqual(self.command_help_parser.format_help(), + textwrap.dedent('''\ + usage: PROG [-h] [--foo] bar {1,2} ... + + main description + + positional arguments: + bar bar help + {1,2} command help + 1 1 help + 2 2 help + + optional arguments: + -h, --help show this help message and exit + --foo foo help + ''')) + + def test_subparser_title_help(self): + parser = ErrorRaisingArgumentParser(prog='PROG', + description='main description') + parser.add_argument('--foo', action='store_true', help='foo help') + parser.add_argument('bar', help='bar help') + subparsers = parser.add_subparsers(title='subcommands', + description='command help', + help='additional text') + parser1 = subparsers.add_parser('1') + parser2 = subparsers.add_parser('2') + self.assertEqual(parser.format_usage(), + 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + self.assertEqual(parser.format_help(), textwrap.dedent('''\ + usage: PROG [-h] [--foo] bar {1,2} ... + + main description + + positional arguments: + bar bar help + + optional arguments: + -h, --help show this help message and exit + --foo foo help + + subcommands: + command help + + {1,2} additional text + ''')) + + def _test_subparser_help(self, args_str, expected_help): + try: + self.parser.parse_args(args_str.split()) + except ArgumentParserError: + err = sys.exc_info()[1] + if err.message != expected_help: + print(repr(expected_help)) + print(repr(err.message)) + self.assertEqual(err.message, 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.failUnlessEqual(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.failUnlessEqual(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.failUnlessEqual(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') + + 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_multiple_parents(self): + parser = ErrorRaisingArgumentParser( + parents=[self.abcd_parent, self.wxyz_parent]) + 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_conflicting_parents(self): + self.assertRaises( + argparse.ArgumentError, + argparse.ArgumentParser, + parents=[self.w_parent, self.wxyz_parent]) + + def test_same_argument_name_parents(self): + parser = ErrorRaisingArgumentParser( + parents=[self.wxyz_parent, self.z_parent]) + 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_parent_help(self): + parser = ErrorRaisingArgumentParser( + parents=[self.abcd_parent, self.wxyz_parent]) + 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 + ''')) + +# ============================== +# 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 + ''' + + +# ================= +# 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([])) + +# ===================== +# 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): + self.func_suffix = func_suffix + + 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) + oldstderr = sys.stderr + sys.stderr = StringIO() + try: + print_() + parser_text = sys.stderr.getvalue() + finally: + sys.stderr = oldstderr + 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 in ['usage', 'help', 'version']: + AddTests(cls, func_suffix) + +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 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 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_action(self): + self.assertTypeError('-x', action='foo') + self.assertTypeError('foo', action='baz') + + 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_message(self, func, *args, **kwargs): + try: + func(*args, **kwargs) + except ArgumentParserError: + err = sys.exc_info()[1] + return err.message + else: + self.assertRaises(ArgumentParserError, func, *args, **kwargs) + + def assertPrintHelpExit(self, parser, args_str): + self.assertEqual( + parser.format_help(), + self._get_error_message(parser.parse_args, args_str.split())) + + def assertPrintVersionExit(self, parser, args_str): + self.assertEqual( + parser.format_version(), + self._get_error_message(parser.parse_args, args_str.split())) + + 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_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_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.failUnless(ns1 != ns3) + self.failUnless(ns1 != ns4) + self.failUnless(ns2 != ns3) + self.failUnless(ns2 != ns4) + + +# =================== +# File encoding tests +# =================== + +class TestEncoding(TestCase): + + def test_argparse_module_encoding(self): + text = codecs.open(argparse.__file__, 'r', 'utf8').read() + + def test_test_argparse_module_encoding(self): + text = codecs.open(__file__, 'r', 'utf8').read() + +# =================== +# ArgumentError tests +# =================== + +class TestArgumentError(TestCase): + + def test_argument_error(self): + msg = "my error here" + error = argparse.ArgumentError(None, msg) + self.failUnlessEqual(str(error), msg) + +# ====================== +# 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.failUnlessEqual(NS(foo='F'), args) + self.failUnlessEqual(['--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.failUnlessEqual(NS(v=3, spam=True, badger="B"), args) + self.failUnlessEqual(["C", "--foo", "4"], extras) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.1