PK!A0Dcliar/__init__.pyfrom .cliar import Cliar from .utils import set_help, set_metavars, set_arg_map, set_name, add_aliases, ignore __all__ = [ 'Cliar', 'set_help', 'set_metavars', 'set_arg_map', 'set_name', 'add_aliases', 'ignore' ] PK!cliar/cliar.pyfrom argparse import ArgumentParser, RawTextHelpFormatter from inspect import signature, getmembers, ismethod, isclass from collections import OrderedDict from typing import List, Tuple, Iterable, Callable from .utils import ignore # pylint: disable=too-few-public-methods,protected-access class _Arg: '''CLI command argument. Its attributes correspond to the homonymous params of the ``add_argument`` function. ''' def __init__(self): self.type = None self.default = None self.action = None self.nargs = None self.metavar = None self.help = None class _Command: '''CLI command corresponding to a handler. Command args correspond to its handler args. ''' def __init__(self, handler: Callable): self.handler = handler if hasattr(handler, '_arg_map'): self.arg_map = handler._arg_map else: self.arg_map = {} if hasattr(handler, '_metavar_map'): self.metavar_map = handler._metavar_map else: self.metavar_map = {} if hasattr(handler, '_command_name'): self.name = handler._command_name else: self.name = handler.__name__.replace('_', '-') if hasattr(handler, '_command_aliases'): self.aliases = handler._command_aliases else: self.aliases = [] if hasattr(handler, '_help_map'): self.help_map = handler._help_map else: self.help_map = {} self.args = self._get_args() def _get_args(self) -> OrderedDict: '''Get command arguments from the parsed signature of its handler.''' args = OrderedDict() handler_signature = signature(self.handler) for param_name, param_data in handler_signature.parameters.items(): arg = _Arg() arg.help = self.help_map.get(param_name, '') if param_data.annotation is not param_data.empty: arg.type = param_data.annotation if param_data.default is not param_data.empty: arg.default = param_data.default if not arg.type: arg.type = type(arg.default) if arg.type == bool: arg.action = 'store_true' elif isclass(arg.type) and (issubclass(arg.type, List) or issubclass(arg.type, Tuple)): if arg.default: arg.nargs = '*' else: arg.nargs = '+' if arg.type.__args__: arg.type = arg.type.__args__[0] if not arg.action and param_name in self.metavar_map: arg.metavar = self.metavar_map[param_name] if param_name not in self.arg_map: self.arg_map[param_name] = param_name.replace('_', '-') args[self.arg_map[param_name]] = arg return args class Cliar: '''Base CLI class. Subclass from it to create your own CLIs. A few rules apply: - Regular methods are converted to commands. Such methods are called *handlers*. - Command args are generated from the corresponding method args. - Methods that start with an underscore are ignored. - ``self._root`` corresponds to the class itself. Use it to define global args. ''' def __init__(self): self._parser = ArgumentParser( description=self.__doc__, formatter_class=RawTextHelpFormatter ) self._register_root_args() self._commands = {} handlers = self._get_handlers() if handlers: self._command_parsers = self._parser.add_subparsers( dest='command', title='commands', help='Available commands:' ) self._register_commands(handlers) def _register_root_args(self): '''Register root args, i.e. params of ``self._root``, in the global argparser.''' self.root_command = _Command(self._root) for arg_name, arg_data in self.root_command.args.items(): self._register_arg(self._parser, arg_name, arg_data) @staticmethod def _register_arg(command_parser: ArgumentParser, arg_name: str, arg_data: _Arg): '''Register an arg in the specified argparser. :param command_parser: global argparser or a subparser corresponding to a CLI command :param str arg_name: handler param name without prefixing dashes :param arg_data: arg type, default value, and action ''' if arg_data.default is None: arg_prefixed_names = [arg_name] else: arg_prefixed_names = ['-'+arg_name[0], '--'+arg_name] if arg_data.action: command_parser.add_argument( *arg_prefixed_names, default=arg_data.default, action=arg_data.action, help=arg_data.help ) elif arg_data.nargs: command_parser.add_argument( *arg_prefixed_names, type=arg_data.type, default=arg_data.default, nargs=arg_data.nargs, metavar=arg_data.metavar, help=arg_data.help ) else: command_parser.add_argument( *arg_prefixed_names, type=arg_data.type, default=arg_data.default, metavar=arg_data.metavar, help=arg_data.help ) def _get_handlers(self) -> List[Callable]: '''Get all handlers except ``self._root``.''' return ( method for method_name, method in getmembers(self, predicate=ismethod) if not method_name.startswith('_') and not hasattr(method, '_ignore') ) def _register_commands(self, handlers: Iterable[Callable]): '''Create parsers for commands from handlers (except for ``self._root``).''' for handler in handlers: command = _Command(handler) command_parser = self._command_parsers.add_parser( command.name, help=handler.__doc__.splitlines()[0] if handler.__doc__ else '', description=handler.__doc__, formatter_class=RawTextHelpFormatter, aliases=command.aliases ) for arg_name, arg_data in command.args.items(): self._register_arg(command_parser, arg_name, arg_data) self._commands[command.name] = command for alias in command.aliases: self._commands[alias] = command @ignore def parse(self): '''Parse commandline input, i.e. launch the CLI.''' args = self._parser.parse_args() if self._commands: command = self._commands.get(args.command) if command: command_args = {arg: vars(args)[arg.replace('-', '_')] for arg in command.args} inverse_arg_map = {arg: param for param, arg in command.arg_map.items()} command.handler( **{inverse_arg_map[arg]: value for arg, value in command_args.items()} ) else: root_args = {arg: vars(args)[arg] for arg in self.root_command.args} inverse_root_arg_map = { arg: param for param, arg in self.root_command.arg_map.items() } if self.root_command.handler( **{inverse_root_arg_map[arg]: value for arg, value in root_args.items()} ) == NotImplemented: self._parser.print_help() def _root(self): '''The root command, which corresponds to the script being called without any command.''' # pylint: disable=no-self-use return NotImplemented PK!D<47 7 cliar/utils.pyfrom typing import Callable, List, Dict # pylint: disable=too-few-public-methods,protected-access def set_help(help_map: Dict[str, str]) -> Callable: '''Set help messages for arguments. :param help_map: mapping from handler param names to help messages ''' def decorator(handler: Callable) -> Callable: '''Decorator returning command handler with a help message map.''' handler._help_map = help_map return handler return decorator def set_metavars(metavar_map: Dict[str, str]) -> Callable: '''Override default metavars for arguments. By default, metavars are generated from arg names: ``foo`` → ``FOO``, `--bar`` → ``BAR``. :param metavar_map: mapping from handler param names to metavars ''' def decorator(handler: Callable) -> Callable: '''Decorator returning command handler with a custom metavar map.''' handler._metavar_map = metavar_map return handler return decorator def set_arg_map(arg_map: Dict[str, str]) -> Callable: '''Override mapping from handler params to commandline args. Be default, param names are used as arg names with underscores replaced with dashes. :param arg_map: mapping from handler param names to arg names ''' def decorator(handler: Callable) -> Callable: '''Decorator returning command handler with a custom arg map.''' handler._arg_map = arg_map return handler return decorator def set_name(name: str) -> Callable: '''Override the name of the CLI command. By default, commands are called the same as their corresponding handlers. :param name: new command name ''' if name == '': raise NameError('Command name cannot be empty') def decorator(handler: Callable) -> Callable: '''Decorator returning command handler with a custom command name.''' handler._command_name = name return handler return decorator def add_aliases(aliases: List[str]) -> Callable: '''Add command aliases. :param aliases: list of aliases ''' def decorator(handler: Callable) -> Callable: '''Decorator returning command handler with a list of aliases set for its command.''' handler._command_aliases = aliases return handler return decorator def ignore(handler: Callable) -> Callable: '''Exclude a method from being converted into a command. :param method: method to ignore ''' handler._ignore = True return handler PK!H=BTTcliar-1.2.2.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]n0H*J>mlcAPK!Hl> cliar-1.2.2.dist-info/METADATAWo6 pr`,۲l@&]f+iJ&R }HQd؀b_サSrjfFɑx#+5Բ89ڪM7FIFץsʑ4VTh]"^JMjc}fieu.sk2vMťNqzuq-^nc̅TƩEd(q63zmxmʖi!]UI]hRU'g&o;\Deߞ$/߼Hs.B>wtV׍]#=YUt)Mޢl#z?y-?*6 nrnWOɌ)ٍtv*O\U9j=]GznQb| \Pn;F~lILUigXf*k8t)\=쟼 %iGнjvL2xKil LKʦ-윮RwcW^2| `&Ą(6"Ar3r6E/?c.CE>H8VXZJF@l>5,9rTiÆ[ T9JkE69*mS`꼒Ntk1wPd΢[)G^Wj*gF6(GKI ύ?_ UJ62`+П1 gVYJN!+TYZסG;5l//̅g{mnqdmȕL8~L WTZq Մ:eQ"MA:`T~v:mL[2^q+k1Ѻ?LN H5 tm xf-KE[x艠an)Ix׀6lIP I1De+L j cs\"A1- jymOӏUox7m$To0 nF. +)d:c~vV)= Fam!:<ِ댗[~ .'rf9wY4:=iqjh%:QVȑ~`<5_UyoH킠'PSm%NlmhC7fY]8Fyl \GA;bĕ#M.Ul`̴pD:)Sob?У/ J 6ma9TMa]j$0HFg ].x0}(կ{= bT,x1S8[Ӧ*9ͽOf~ȷz.jbaokg~KFnmhۢdG~xO6'jT% ϕNIZ;gnXVbްVL 0PK!A0Dcliar/__init__.pyPK!/cliar/cliar.pyPK!D<47 7 w cliar/utils.pyPK!H=BTT*cliar-1.2.2.dist-info/WHEELPK!Hl> g+cliar-1.2.2.dist-info/METADATAPK!Hʒ;#%1cliar-1.2.2.dist-info/RECORDPK3