Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Hrabal committed Dec 15, 2018
1 parent 4e8c98a commit c005272
Showing 1 changed file with 55 additions and 46 deletions.
101 changes: 55 additions & 46 deletions classcli/clibuilder.py
Expand Up @@ -23,10 +23,21 @@ def error(self, message):


class CliBuilder:

def __init__(self, module_or_obj_collection):
""" Creates a hierarchical parser from a module, a list or a dict.
Filters the input looking for classes, and only uses those who have bool(callable_cls) == True
"""
# Main argparse instance
self.parser = ClassParser()
# subparser collector
self.subparsers = self.parser.add_subparsers()

# For every controller class defined in the input module we add a subparser
for _, cls in self._load_object(module_or_obj_collection):
self._make_subparser(cls)

def _load_object(self, module_or_obj_collection):
# Input cleanup, get an iterable of classes out of the argument
if isinstance(module_or_obj_collection, dict):
iterator = ((cname, obj) for cname, obj in module_or_obj_collection.items() if inspect.isclass(obj))
Expand All @@ -36,58 +47,56 @@ def __init__(self, module_or_obj_collection):
iterator = inspect.getmembers(module_or_obj_collection, inspect.isclass)
else:
raise TypeError('Unsupported type for CLI init: list/dist with classes in it or module supported.')
return iterator

# Main argparse instance
self.parser = ClassParser()
# subparser colleector
subparsers = self.parser.add_subparsers()
def _make_subparser(self, cls):
# Only classes with callable_cls will be added as subparser (so to exclude utility classes)
if getattr(cls, 'callable_cls', False):
# Help for this command from the class docstring and the _base method docstring
help_str = '\n'.join(doc for doc in (inspect.getdoc(cls), inspect.getdoc(cls._base)) if doc)

# For every controller class defined in the input module we add a subparser
for _, cls in iterator:
# Only classes with callable_cls will be added as subparser (so to exclude utility classes)
if getattr(cls, 'callable_cls', False):
# Help for this command from the class docstring and the _base method docstring
help_str = '\n'.join(doc for doc in (inspect.getdoc(cls), inspect.getdoc(cls._base)) if doc)
# Instance of the controller class that will be called
controller = cls()

# Instance of the controller class that will be called
controller = cls()
# Bind the subparser to the command defined in the class, to run the _base method
cls_parser = self.subparsers.add_parser(controller.command, help=help_str)
cls_parser.set_defaults(func=controller._base)

# Bind the subparser to the command defined in the class, to run the _base method
cls_parser = subparsers.add_parser(controller.command, help=help_str)
cls_parser.set_defaults(func=controller._base)
# Every method in the controller class will have it's own subparser
method_parser = cls_parser.add_subparsers()
for _, fnc in inspect.getmembers(controller, inspect.ismethod):
# Exclude the 'private' methods only methods with no leading underscore are used
if not fnc.__name__.startswith('_'):
# Make a new subparser in the class one
method_args_parser = method_parser.add_parser(fnc.__name__, help=inspect.getdoc(fnc) or '')
# Bind the class method to the subparser argument
method_args_parser.set_defaults(func=fnc)
self._read_arguments(method_args_parser, fnc)

# Every method in the controller class will have it's own subparser
method_parser = cls_parser.add_subparsers()
for _, fnc in inspect.getmembers(controller, inspect.ismethod):
# Exclude the 'private' methods only methods with no leading underscore are used
if not fnc.__name__.startswith('_'):
# Make a new subparser in the class one
method = method_parser.add_parser(fnc.__name__, help=inspect.getdoc(fnc) or '')
# Bind the class method to the subparser argument
method.set_defaults(func=fnc)
opt_args = set()
for arg in inspect.signature(fnc).parameters.values():
arg_kwargs = {}
if arg.annotation is bool:
name = '-%s' % arg.name
opt_letter = None
arg_kwargs['action'] = 'store_false' if arg.default is False else 'store_true'
else:
arg_kwargs['type'] = arg.annotation if arg.annotation is not arg.empty else str
opt_letter = None
if arg.default is not arg.empty:
arg_kwargs['default'] = arg.default
name = '--%s' % arg.name
for letter in arg.name:
if letter not in opt_args:
opt_letter = '-%s' % letter
opt_args.add(letter)
break
else:
name = arg.name
def _read_arguments(self, method_args_parser, fnc):
opt_args = set()
for arg in inspect.signature(fnc).parameters.values():
arg_kwargs = {}
if arg.annotation is bool:
name = '-%s' % arg.name
opt_letter = None
arg_kwargs['action'] = 'store_false' if arg.default is False else 'store_true'
else:
arg_kwargs['type'] = arg.annotation if arg.annotation is not arg.empty else str
opt_letter = None
if arg.default is not arg.empty:
arg_kwargs['default'] = arg.default
name = '--%s' % arg.name
for letter in arg.name:
if letter not in opt_args:
opt_letter = '-%s' % letter
opt_args.add(letter)
break
else:
name = arg.name

names = [n for n in (opt_letter, name) if n]
method.add_argument(*names, **arg_kwargs)
names = [n for n in (opt_letter, name) if n]
method_args_parser.add_argument(*names, **arg_kwargs)

def run_cli(self, args=None):
if args:
Expand Down

0 comments on commit c005272

Please sign in to comment.