blob: 8bf349888e49446abbd4f441039038e2a59dd5e3 [file] [log] [blame]
# 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
"""
Hierarchy of action classes which support multiple destinations, similar to the
default actions provided by the standard argparse.
"""
import argparse
import copy
from .types import BoolType, PathType
__all__ = [
'Action',
'Nargs',
'AppendAction',
'CustomCallAction',
'StoreAction',
'StoreIntAction',
'StoreTrueAction',
'StoreFalseAction',
'StorePathAction',
'ToggleTrueAction',
'ToggleFalseAction',
'UnsupportedAction',
]
# -----------------------------------------------------------------------------
class Nargs(object):
"""Container class holding valid values for the number of arguments
actions can accept. Other possible values include integer literals.
"""
ZERO = 0
SINGLE = None
OPTIONAL = argparse.OPTIONAL
ZERO_OR_MORE = argparse.ZERO_OR_MORE
ONE_OR_MORE = argparse.ONE_OR_MORE
# -----------------------------------------------------------------------------
class Action(argparse.Action):
"""Slightly modified version of the Action class from argparse which has
support for multiple destinations available rather than just a single one.
"""
def __init__(self,
option_strings,
dests=None,
nargs=Nargs.SINGLE,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar=None,
dest=None): # exists for compatibility purposes
if dests is None and dest is None:
raise TypeError('action must supply either dests or dest')
dests = dests or dest
if dests == argparse.SUPPRESS:
dests = []
metavar = metavar or ''
elif isinstance(dests, str):
dests = [dests]
metavar = metavar or dests[0].upper()
super(Action, self).__init__(
option_strings=option_strings,
dest=argparse.SUPPRESS,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
self.dests = dests
def _get_kwargs(self):
"""Unofficial method used for pretty-printing out actions.
"""
names = [
'option_strings',
'dests',
'nargs',
'const',
'default',
'type',
'choices',
'required',
'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 AppendAction(Action):
"""Action that appends
"""
def __init__(self, option_strings, join=None, **kwargs):
kwargs['nargs'] = Nargs.SINGLE
kwargs.setdefault('default', [])
super(AppendAction, self).__init__(
option_strings=option_strings,
**kwargs)
def __call__(self, parser, namespace, values, option_string=None):
if isinstance(values, str):
values = [values]
for dest in self.dests:
if getattr(namespace, dest) is None:
setattr(namespace, dest, [])
items = copy.copy(getattr(namespace, dest))
items += values
setattr(namespace, dest, items)
# -----------------------------------------------------------------------------
class CustomCallAction(Action):
"""Action that allows for instances to implement custom call functionality.
The call_func must follow the same calling convention as implementing the
__call__ method for an Action.
"""
def __init__(self, option_strings, call_func, **kwargs):
if not callable(call_func):
raise TypeError('call_func must be callable')
super(CustomCallAction, self).__init__(
option_strings=option_strings,
**kwargs)
self.call_func = call_func
def __call__(self, parser, namespace, values, option_string=None):
self.call_func(self, parser, namespace, values, option_string)
# -----------------------------------------------------------------------------
class StoreAction(Action):
"""Action that stores a string.
"""
def __init__(self, option_strings, **kwargs):
if 'choices' in kwargs:
kwargs['nargs'] = Nargs.OPTIONAL
elif 'const' in kwargs:
kwargs['nargs'] = Nargs.ZERO
super(StoreAction, self).__init__(
option_strings=option_strings,
**kwargs)
def __call__(self, parser, namespace, values, option_string=None):
for dest in self.dests:
if self.nargs == Nargs.ZERO and self.const is not None:
values = self.const
setattr(namespace, dest, values)
class StoreIntAction(StoreAction):
"""Action that stores an int.
"""
def __init__(self, option_strings, **kwargs):
kwargs['nargs'] = Nargs.SINGLE
kwargs['type'] = int
kwargs.setdefault('metavar', 'N')
super(StoreAction, self).__init__(
option_strings=option_strings,
**kwargs)
class StoreTrueAction(StoreAction):
"""Action that stores True when called and False by default.
"""
def __init__(self, option_strings, **kwargs):
kwargs['nargs'] = Nargs.ZERO
kwargs['const'] = True
kwargs['default'] = False
super(StoreTrueAction, self).__init__(
option_strings=option_strings,
**kwargs)
class StoreFalseAction(StoreAction):
"""Action that stores False when called and True by default.
"""
def __init__(self, option_strings, **kwargs):
kwargs['nargs'] = Nargs.ZERO
kwargs['const'] = False
kwargs['default'] = True
super(StoreFalseAction, self).__init__(
option_strings=option_strings,
**kwargs)
class StorePathAction(StoreAction):
"""Action that stores a path, which it will attempt to expand.
"""
def __init__(self, option_strings, **kwargs):
assert_exists = kwargs.pop('exists', False)
assert_executable = kwargs.pop('executable', False)
kwargs['nargs'] = Nargs.SINGLE
kwargs['type'] = PathType(assert_exists, assert_executable)
kwargs.setdefault('metavar', 'PATH')
super(StorePathAction, self).__init__(
option_strings=option_strings,
**kwargs)
# -----------------------------------------------------------------------------
class _ToggleAction(Action):
"""Action that can be toggled on or off, either implicitly when called or
explicitly when an optional boolean value is parsed.
"""
def __init__(self, option_strings, on_value, off_value, **kwargs):
kwargs['nargs'] = Nargs.OPTIONAL
kwargs['type'] = BoolType()
kwargs.setdefault('default', off_value)
kwargs.setdefault('metavar', 'BOOL')
super(_ToggleAction, self).__init__(
option_strings=option_strings,
**kwargs)
self.on_value = on_value
self.off_value = off_value
def __call__(self, parser, namespace, values, option_string=None):
if values is None:
values = self.on_value
elif values is True:
values = self.on_value
elif values is False:
values = self.off_value
else:
raise argparse.ArgumentTypeError(
'{} is not a boolean value'.format(values))
for dest in self.dests:
setattr(namespace, dest, values)
class ToggleTrueAction(_ToggleAction):
"""Action that toggles True when called or False otherwise, with the
option to explicitly override the value.
"""
def __init__(self, option_strings, **kwargs):
super(ToggleTrueAction, self).__init__(
option_strings=option_strings,
on_value=True,
off_value=False,
**kwargs)
class ToggleFalseAction(_ToggleAction):
"""Action that toggles False when called or True otherwise, with the
option to explicitly override the value.
"""
def __init__(self, option_strings, **kwargs):
super(ToggleFalseAction, self).__init__(
option_strings=option_strings,
on_value=False,
off_value=True,
**kwargs)
# -----------------------------------------------------------------------------
class UnsupportedAction(Action):
"""Action that denotes an unsupported argument or opiton and subsequently
raises an ArgumentError.
"""
def __init__(self, option_strings, message=None, **kwargs):
kwargs['nargs'] = Nargs.ZERO
kwargs['default'] = argparse.SUPPRESS
super(UnsupportedAction, self).__init__(
option_strings=option_strings,
dests=argparse.SUPPRESS,
**kwargs)
self.message = message
def __call__(self, parser, namespace, values, option_string=None):
if self.message is not None:
parser.error(self.message)
arg = option_string or str(values)
parser.error('unsupported argument: {}'.format(arg))