#!/usr/bin/env python3 # -*- encoding: utf-8 -*- import re from inspect import _empty, getfullargspec, signature from typing import Callable, Dict, List, Optional, Set, Type, TypeVar import colored as clrlib from .table_fmt import table_fmt SPLITTER_RGX = re.compile(r'(? str: return txt.replace('~:', ':') T = TypeVar('T') def parse_cmdline(func: Callable[..., T], encoded_args: str) -> Optional[T]: ''' Transforms a colon-separated key-pairs into callable arguments Type-annotated fields will be converted. ''' split_args = SPLITTER_RGX.split(encoded_args) split_args = list(map(unescape_after_splitter, split_args)) brute_dict = dict(zip(split_args[0::2], split_args[1::2])) full_arg_spec = getfullargspec(func) sig = signature(func) func_args = full_arg_spec.args func_annotations = full_arg_spec.annotations str_args = {k: v for k, v in brute_dict.items() if k in func_args} unknown_args = {k: v for k, v in brute_dict.items() if k not in func_args} if encoded_args == 'help' or len(unknown_args) > 0: if len(unknown_args) > 0: print(clrlib.stylize('Unknown arguments found:', [ clrlib.fg('light_red'), clrlib.attr('bold'), ])) for k, v in unknown_args.items(): print(clrlib.stylize(f' {k}: {repr(v)}', [ clrlib.fg('light_red'), ])) print() print(clrlib.stylize(f'Usage help for: {func.__module__}.{func.__name__}', [ clrlib.fg('light_cyan'), clrlib.attr('bold'), ])) tbl = list() if len(sig.parameters) <= 0: print(clrlib.stylize(' ' * 4 + 'No arguments accepted', [ clrlib.fg('light_cyan'), ])) else: for name, parameter in sig.parameters.items(): annotation = parameter.annotation if parameter.annotation != _empty else str tbl.append(( str(name), repr(annotation), repr(parameter.default) if parameter.default != _empty else '-unset-', )) print(clrlib.stylize(space_left_pad_text(4, table_fmt( 'name,type,default'.split(','), tbl, alignment='^'*3, )), [ clrlib.fg('light_cyan'), ])) return None kwargs = dict() for key in str_args: kwargs[key] = convert_type( func_annotations.get(key, str), str_args[key] ) print(clrlib.stylize(f'Calling {func.__module__}.{func.__name__} with arguments:', [ clrlib.fg('light_gray'), clrlib.attr('dim'), ])) if len(kwargs) <= 0: print(clrlib.stylize(' --- no arguments given ---', [ clrlib.fg('light_gray'), clrlib.attr('dim'), ])) else: for k, v in kwargs.items(): print(clrlib.stylize(f' {k}: {repr(v)}', [ clrlib.fg('light_gray'), clrlib.attr('dim'), ])) return func(**kwargs) K = TypeVar('K') def convert_type(cls: Type[K], data: str) -> K: if cls not in (str, int, float): cls = eval return cls(data) def space_left_pad_text(qty: int, multiline_text: str) -> str: return '\n'.join([' ' * qty + line for line in multiline_text.splitlines()])