Skip to content

Commit

Permalink
refactor: reduce complexity in parser function
Browse files Browse the repository at this point in the history
  • Loading branch information
jnoortheen committed Apr 16, 2020
1 parent fbdaedd commit 2c2d586
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 342 deletions.
32 changes: 13 additions & 19 deletions arger/parser/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,22 @@ def __init__(
* If the default value is a list, the action is append
(multiple instances of that option are permitted).
* Strings or None imply a store action.
:param type_: The type to which the command-line argument should be converted.
:param help_: A brief description of what the argument does.
:param type: The type to which the command-line argument should be converted.
:param help: A brief description of what the argument does.
:param metavar: A name for the argument in usage messages.
:param required: Whether or not the command-line option may be omitted (optionals only).
:param kwargs: will be passed onto parser.add_argument
"""

# :param nargs: The number of command-line arguments that should be consumed.
# nargs: to be generated from the type

# action: Union[str, Type[argparse.Action]]
# :param action: The basic type of action to be taken when this argument is encountered at the command line.
# :param dest: The name of the attribute to be added to the object returned by parse_args().
:param nargs: The number of command-line arguments that should be consumed. nargs: to be generated from the type
:param dest: The name of the attribute to be added to the object returned by parse_args().
:param const: covered by type-hint and default value given
:param choices: covered by enum type
# :param const: A constant value required by some action and nargs selections.
# will be covered by type-hint and default value given

# :param choices: A container of the allowable values for the argument.
# will covered by enum type

self.flags = flags or []
:param action: The basic type of action to be taken when this argument is encountered at the command line.
:type action: Union[str, Type[argparse.Action]]
"""
name = kwargs.pop('name', None)
self.flags = flags or ([name] if name else [])

type_ = kwargs.pop('type', UNDEFINED)
if default is not UNDEFINED:
Expand All @@ -86,10 +80,10 @@ def add(self, parser: argparse.ArgumentParser):
def __repr__(self):
return f'{self.__class__.__name__}: {self.flags}, {repr(self.kwargs)}'

def set_flags(self, option_generator):
def set_flags(self, option_generator, name: str):
hlp = self.kwargs.pop('help').split()
# generate flags
self.flags = generate_flags(self.flags[0], hlp, option_generator)
self.flags = generate_flags(name, hlp, option_generator)
self.kwargs['help'] = " ".join(hlp)


Expand Down
64 changes: 40 additions & 24 deletions arger/parser/parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from collections import OrderedDict
from collections import OrderedDict, namedtuple
from typing import Dict, Tuple

from arger.parser.docstring import parse_docstring
Expand All @@ -9,7 +9,40 @@
from .utils import generate_options


def prepare_arguments(func, param_docs,) -> Dict[str, Option]:
Param = namedtuple('Param', ['name', 'type', 'help'])


def to_dict(p: Param):
return p._asdict()


def prepare_params(func, docs: Dict[str, str]):
(args, kwargs, annotations) = portable_argspec(func)

def get_param(param):
return Param(param, annotations.get(param, UNDEFINED), docs.get(param, ""))

return (
[get_param(param) for param in args],
[(get_param(param), default) for param, default in kwargs.items()],
)


def create_option(param: Param, default, option_generator):
if isinstance(default, Argument):
default.flags = [param.name]
elif isinstance(default, Option):
if 'dest' not in default.kwargs:
default.kwargs['dest'] = param.name
if not default.flags:
default.set_flags(option_generator, param.name)
else:
default = Option(dest=param, default=default, **param._asdict())
default.set_flags(option_generator, param.name)
return default


def prepare_arguments(func, param_docs) -> Dict[str, Option]:
"""Parse 'func' and adds parser arguments from function signature.
:param func: Function's signature is used to create parser
Expand Down Expand Up @@ -38,33 +71,16 @@ def prepare_arguments(func, param_docs,) -> Dict[str, Option]:
must refer to a keyword argument. All options must have a :param: line like
this.
"""
(positional_params, kw_params, annotations) = portable_argspec(func)
positional_params, kw_params = prepare_params(func, param_docs)
option_generator = generate_options()
next(option_generator)

def get_args(param):
return dict(
flags=[param],
help=param_docs.get(param, ""),
type=annotations.get(param, UNDEFINED),
)

arguments: Dict[str, Option] = OrderedDict()
for param in positional_params:
arguments[param] = Argument(**get_args(param))

for param, default in kw_params.items():
if isinstance(default, Argument):
default.flags = [param]
elif isinstance(default, Option):
if 'dest' not in default.kwargs:
default.kwargs['dest'] = param
if not default.flags:
default.set_flags(option_generator)
else:
default = Option(dest=param, default=default, **get_args(param))
default.set_flags(option_generator)
arguments[param] = default
arguments[param.name] = Argument(**param._asdict())

for param, default in kw_params:
arguments[param.name] = create_option(param, default, option_generator)
return arguments


Expand Down
5 changes: 4 additions & 1 deletion arger/types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from typing import Any, Callable, TypeVar
from typing import Any, Callable, Dict, NewType, Tuple, TypeVar


F = TypeVar('F', bound=Callable[..., Any])

UNDEFINED = object()
"""sometimes the value could be None. we need this to distinguish such values."""

VarArg = NewType('VarArg', Tuple)
KwArg = NewType('KwArg', Dict)
8 changes: 6 additions & 2 deletions arger/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def portable_argspec(func) -> Tuple[List[str], Dict[str, Any], Dict[str, Any]]:
This function is portable between Python 2 and Python 3, and does some
extra processing of the output from inspect.
"""
(argnames, _, _, defaults, _, _, annotations,) = inspect.getfullargspec(func)
(argnames, varargs, _, defaults, _, _, annotations) = inspect.getfullargspec(func)

kw_params = {}
if defaults:
Expand All @@ -26,7 +26,11 @@ def portable_argspec(func) -> Tuple[List[str], Dict[str, Any], Dict[str, Any]]:
argnames[kw_boundary + idx]: val for idx, val in enumerate(defaults)
}
argnames = argnames[:kw_boundary]

if varargs:
argnames.append(varargs)
annotations[varargs] = Tuple[annotations.get(varargs, str)]
# if varkw:
# kw_params['']
return (
argnames,
kw_params,
Expand Down

0 comments on commit 2c2d586

Please sign in to comment.