Skip to content

Commit

Permalink
refactor: add arger structs
Browse files Browse the repository at this point in the history
  • Loading branch information
jnoortheen committed Apr 11, 2020
1 parent 053c153 commit 6722d7a
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 76 deletions.
74 changes: 20 additions & 54 deletions arger/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,74 +8,40 @@
F = TypeVar('F', bound=Callable[..., Any])


class Arger:
"""A decorator to ease up the process of creating parser
class Arger(ArgumentParser):
"""Contains one function (parser) or more functions (subparsers).
Also a decorator to ease up the process of creating parser with its own options.
Examples
in a file called main.py
``
from arger import Arger
arger = Arger("cli tool's title")
@arger
def build(remove=False):
'''
Args:
remove: --rm -r you can specify the short option in the docstring
'''
print(remove)
@arger
def install(code:int):
assert type(code) == int
build()
if __name__ == "__main__":
arger.dispatch()
``
-- run this with
python main.py build --rm
python main.py install 10
Usage: see `tests/examples`_.
"""

def __init__(self, desc: Optional[str] = None, add_help=True):
self.desc = desc
self.add_help = add_help
self.funcs = {} # type: Dict[str, Any]

@property
def first_func(self):
return self.funcs[list(self.funcs)[0]]

def get_parser(self) -> ArgumentParser:
if len(self.funcs) == 1:
parser = opterate(self.first_func)
else:
parser = ArgumentParser(description=self.desc, add_help=self.add_help)
commands = parser.add_subparsers(help=CMD)
for func in self.funcs:
opterate(func, commands)

return parser
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._funcs: Dict[str, Any] = {} # registry

def dispatch(self, *args):
if not self.funcs:
if not self._funcs:
raise NotImplementedError("No function added.")

parser = self.get_parser()
kwargs = vars(parser.parse_args(args)) # type: Dict[str, Any]
if len(self.funcs) == 1:
kwargs = vars(self.parse_args(args)) # type: Dict[str, Any]
if len(self._funcs) == 1:
return self.first_func(**kwargs)

func = self.funcs[kwargs[CMD]]
func = self._funcs[kwargs[CMD]]
return func(**kwargs)

def __call__(self, func: F) -> F:
"""Decorator.
called as a decorator to add any function as a command to the parser
"""
self.funcs[func.__name__] = func
self._funcs[func.__name__] = func
return func

def add_command(self, func: F) -> F:
"""Creates subparser and adds the function as one of the subcommand
:param func:
:return:
"""
self._funcs[func.__name__] = func
return func
35 changes: 35 additions & 0 deletions arger/docstring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Optional

from docstring_parser import parse


#
# def parse_docstring(doc: Optional[str]):
# param_docs = {}
# desc = ""
# if doc:
# param_doc = doc.split(':param')
# desc = param_doc.pop(0).strip()
# for param in param_doc:
# param_args = param.split()
# variable_name = param_args.pop(0)[:-1]
# param_docs[variable_name] = param_args
# return desc, param_docs


def parse_docstring(doc: Optional[str]):
if doc:
parsed = parse(doc)
params = {arg.arg_name: arg.description for arg in parsed.params}
params = {k: val.replace("\n", " ") for k, val in params.items()}
return (
"\n".join(
(
desc
for desc in (parsed.short_description, parsed.long_description)
if desc
)
),
params,
)
return "", {}
26 changes: 4 additions & 22 deletions arger/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,9 @@
import inspect
from argparse import ArgumentParser
from enum import Enum
from typing import Any, Iterable, Optional, Set

from docstring_parser import parse


def parse_docstring(doc: Optional[str]):
if doc:
parsed = parse(doc)
params = {arg.arg_name: arg.description for arg in parsed.params}
params = {k: val.replace("\n", " ") for k, val in params.items()}
return (
"\n".join(
(
desc
for desc in (parsed.short_description, parsed.long_description)
if desc
)
),
params,
)
return "", {}
from typing import Any, List, Set, Tuple

from arger.docstring import parse_docstring


def generate_options():
Expand Down Expand Up @@ -87,7 +69,7 @@ def get_action(
return "store_true"
if default is True:
return "store_false"
if issubclass(_type, Iterable):
if (_type is not UNDEFINED) and issubclass(_type, (List, Tuple)):
return "append"
return "store"

Expand Down
56 changes: 56 additions & 0 deletions arger/structs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import argparse
from typing import Callable


class Option:
def __init__(
self,
type: Callable,
default=None,
flags: str = None,
help: str = None,
metavar: str = None,
required=False,
):
"""represents optional arguments to the command.
Tries to be compatible to `ArgumentParser.add_argument
https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument`_.
:param flags: Either a name or a list of option strings, e.g. -f, --foo.
:param default: The value produced if the argument is absent from the command line.
: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 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 const: A constant value required by some action and nargs selections.
# will be covered by typehint and default value given

# :param choices: A container of the allowable values for the argument.
# will covered by enum type
self.type = type
self.default = default
self.flags = flags
self.help = help
self.metavar = metavar
self.required = required

# tmp
p = argparse.ArgumentParser()
p.add_argument()


class Argument(Option):
"""represents positional argument and are required."""

def __init__(self, type: Callable, help: str = None, metavar: str = None):
super().__init__(type, help=help, metavar=metavar, required=True)

0 comments on commit 6722d7a

Please sign in to comment.