| # This source file is part of the Swift.org open source project |
| # |
| # Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| # Licensed under Apache License v2.0 with Runtime Library Exception |
| # |
| # See https://swift.org/LICENSE.txt for license information |
| # See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| |
| |
| """ |
| Extensions to the standard argparse ArgumentParer class to support multiple |
| destination actions as well as a new builder DSL for declaratively |
| constructing complex parsers. |
| """ |
| |
| |
| import argparse |
| from contextlib import contextmanager |
| |
| from . import Namespace, SUPPRESS, actions |
| from .actions import Action |
| |
| |
| __all__ = [ |
| 'ArgumentParser', |
| ] |
| |
| |
| # ----------------------------------------------------------------------------- |
| |
| class _ActionContainer(object): |
| """Container object holding partially applied actions used as a part of the |
| builder DSL. |
| """ |
| |
| def __init__(self): |
| self.append = _PartialAction(actions.AppendAction) |
| self.custom_call = _PartialAction(actions.CustomCallAction) |
| self.store = _PartialAction(actions.StoreAction) |
| self.store_int = _PartialAction(actions.StoreIntAction) |
| self.store_true = _PartialAction(actions.StoreTrueAction) |
| self.store_false = _PartialAction(actions.StoreFalseAction) |
| self.store_path = _PartialAction(actions.StorePathAction) |
| self.toggle_true = _PartialAction(actions.ToggleTrueAction) |
| self.toggle_false = _PartialAction(actions.ToggleFalseAction) |
| self.unsupported = _PartialAction(actions.UnsupportedAction) |
| |
| |
| class _CompoundAction(Action): |
| """Action composed of multiple actions. Default attributes are derived |
| from the first action. |
| """ |
| |
| def __init__(self, actions, **kwargs): |
| _actions = [] |
| for action in actions: |
| _actions.append(action(**kwargs)) |
| |
| kwargs.setdefault('nargs', kwargs[0].nargs) |
| kwargs.setdefault('metavar', kwargs[0].metavar) |
| kwargs.setdefault('choices', kwargs[0].choices) |
| |
| super(_CompoundAction, self).__init__(**kwargs) |
| |
| self.actions = _actions |
| |
| def __call__(self, *args): |
| for action in self.actions: |
| action(*args) |
| |
| |
| class _PartialAction(Action): |
| """Action that is partially applied, creating a factory closure used to |
| defer initialization of acitons in the builder DSL. |
| """ |
| |
| def __init__(self, action_class): |
| self.action_class = action_class |
| |
| def __call__(self, dests=None, *call_args, **call_kwargs): |
| def factory(**kwargs): |
| kwargs.update(call_kwargs) |
| if dests is not None: |
| return self.action_class(dests=dests, *call_args, **kwargs) |
| return self.action_class(*call_args, **kwargs) |
| |
| return factory |
| |
| |
| # ----------------------------------------------------------------------------- |
| |
| class _Builder(object): |
| """Builder object for constructing complex ArgumentParser instances with |
| a more friendly and descriptive DSL. |
| """ |
| |
| def __init__(self, parser, **kwargs): |
| assert isinstance(parser, ArgumentParser) |
| |
| self._parser = parser |
| self._current_group = self._parser |
| self._defaults = dict() |
| |
| self.actions = _ActionContainer() |
| |
| def build(self): |
| self._parser.set_defaults(**self._defaults) |
| return self._parser |
| |
| def _add_argument(self, names, *actions, **kwargs): |
| # Unwrap partial actions |
| _actions = [] |
| for action in actions: |
| if isinstance(action, _PartialAction): |
| action = action() |
| _actions.append(action) |
| |
| if len(_actions) == 0: |
| # Default to store action |
| action = actions.StoreAction |
| elif len(_actions) == 1: |
| action = _actions[0] |
| else: |
| def thunk(**kwargs): |
| return _CompoundAction(_actions, **kwargs) |
| action = thunk |
| |
| return self._current_group.add_argument( |
| *names, action=action, **kwargs) |
| |
| def add_positional(self, dests, action=None, **kwargs): |
| if isinstance(dests, str): |
| dests = [dests] |
| |
| if any(dest.startswith('-') for dest in dests): |
| raise ValueError("add_positional can't add optional arguments") |
| |
| if action is None: |
| action = actions.StoreAction |
| |
| return self._add_argument(dests, action, **kwargs) |
| |
| def add_option(self, option_strings, *actions, **kwargs): |
| if isinstance(option_strings, str): |
| option_strings = [option_strings] |
| |
| if not all(opt.startswith('-') for opt in option_strings): |
| raise ValueError("add_option can't add positional arguments") |
| |
| return self._add_argument(option_strings, *actions, **kwargs) |
| |
| def set_defaults(self, *args, **kwargs): |
| if len(args) == 1: |
| raise TypeError('set_defaults takes at least 2 arguments') |
| |
| if len(args) >= 2: |
| dests, value = args[:-1], args[-1] |
| for dest in dests: |
| kwargs[dest] = value |
| |
| self._defaults.update(**kwargs) |
| |
| def in_group(self, description): |
| self._current_group = self._parser.add_argument_group(description) |
| return self._current_group |
| |
| def reset_group(self): |
| self._current_group = self._parser |
| |
| @contextmanager |
| def argument_group(self, description): |
| previous_group = self._current_group |
| self._current_group = self._parser.add_argument_group(description) |
| yield self._current_group |
| self._current_group = previous_group |
| |
| @contextmanager |
| def mutually_exclusive_group(self, **kwargs): |
| previous_group = self._current_group |
| self._current_group = previous_group \ |
| .add_mutually_exclusive_group(**kwargs) |
| yield self._current_group |
| self._current_group = previous_group |
| |
| |
| # ----------------------------------------------------------------------------- |
| |
| class ArgumentParser(argparse.ArgumentParser): |
| """A thin extension class to the standard ArgumentParser which incluldes |
| methods to interact with a builder instance. |
| """ |
| |
| @classmethod |
| def builder(cls, **kwargs): |
| """Create a new builder instance using this parser class. |
| """ |
| |
| return _Builder(parser=cls(**kwargs)) |
| |
| def to_builder(self): |
| """Construct and return a builder instance with this parser. |
| """ |
| |
| return _Builder(parser=self) |
| |
| def parse_known_args(self, args=None, namespace=None): |
| """Thin wrapper around parse_known_args which shims-in support for |
| actions with multiple destinations. |
| """ |
| |
| if namespace is None: |
| namespace = Namespace() |
| |
| # Add action defaults not present in namespace |
| for action in self._actions: |
| if not hasattr(action, 'dests'): |
| continue |
| |
| for dest in action.dests: |
| if hasattr(namespace, dest): |
| continue |
| if action.default is SUPPRESS: |
| continue |
| |
| setattr(namespace, dest, action.default) |
| |
| return super(ArgumentParser, self).parse_known_args(args, namespace) |