Remove src layout

This commit is contained in:
Patrick Elmer 2023-08-16 13:17:41 +09:00
parent 15304e1c72
commit 7cb4202a64

View File

@ -1,143 +0,0 @@
import inspect
import sys
import re
from pargv import parse_args
def magicli(exclude=['main'], glbls=None, argv=None):
"""
Get all functions from calling file and interprets them as CLI commands.
Parses command line arguments for function to call
and calls it with all specified arguments.
Displays a help message and exits if the --help flag is set
or if no callable function is found.
Errors out with a TypeError if the specified arguments are invalid.
"""
glbls = glbls if glbls else inspect.currentframe().f_back.f_globals
argv = argv if argv else sys.argv
app_name, args, kwargs = format_args(argv)
if 'version' in kwargs:
print(glbls['__version__']) if '__version__' in glbls else print('Unknown version.')
exit()
if re.search('\.pyc?$', app_name):
app_name = sys.orig_argv[0] + ' ' + app_name
function_to_call, *commands = [f for f in filter_functions(glbls) if f.__name__ not in exclude]
command_names = [f.__name__ for f in commands]
# Call command if specified
if args and args[0] in command_names:
function_to_call = glbls.get(args[0])
app_name += f" {args[0]}"
commands = []
args = args[1:]
if 'help' in kwargs:
print(help_message(app_name, function_to_call, commands))
exit()
try:
function_to_call(*args, **kwargs)
except TypeError:
print(help_message(app_name, function_to_call, commands))
exit()
def format_args(argv):
args, kwargs = parse_args(argv)
app_name, *args = args
return app_name, args, kwargs
def filter_functions(glbls):
"""
Gets list of functions from the globals variable of a specific module.
"""
return [v for v in glbls.values() if inspect.isfunction(v) and v.__module__ == glbls['__name__']]
def help_message(app_name, function, commands=[]):
"""
Automatically create a help message.
"""
specs = inspect.getfullargspec(function)
arguments = specs.args
defaults = list(specs.defaults)
options = list(reversed([arguments.pop() for _ in defaults])) if defaults else []
arguments_docs = [extract_annotation(specs.annotations[arg]) if arg in specs.annotations else '' for arg in arguments]
options_docs = [extract_annotation(specs.annotations[opt]) if opt in specs.annotations else '' for opt in options]
commands_docs = [c.__doc__ if c.__doc__ else '' for c in commands]
options += ['help', 'version']
options_docs += ['Show the help message.', 'Show version information.']
defaults += ['', '']
help_text = []
usage = [app_name]
if arguments:
usage.append(' '.join(arguments))
help_text.append({'lines': [[argument, doc] for argument, doc in zip(arguments, arguments_docs)], 'heading': 'Arguments:'})
if options:
usage.append('[options]')
help_text.append({'lines': [[f"--{option}", f"{doc} ".lstrip() + f"(default: {default})"*(default!='')] for option, doc, default in zip(options, options_docs, defaults)], 'heading': 'Options:'})
usage = [' '.join(usage)]
if commands:
usage.append(f"{app_name} command [...]")
help_text.append({'lines': [[c.__name__, doc] for c, doc in zip(commands, commands_docs)], 'heading': 'Commands:'})
if function.__doc__:
help_text = [{'lines': [line.strip() for line in function.__doc__.strip().split('\n')]}] + help_text
return format_help_message([{'lines': usage, 'heading': 'Usage:'}] + help_text)
def extract_annotation(spec):
return '' if isinstance(spec, type) else spec.__metadata__[0]
def type_to_str(text):
found = re.findall("'(.*)'", str(text))
return found[0] if found else ''
def format_help_message(list_of_dicts):
"""Usage example:
format_help_message([
{'lines': ['--all', 'All files.'], 'heading': 'Options:'},
{'lines': ['run', 'Run script.'], 'heading': 'Commands:'},
{'lines': 'Copyright 2023'},
])
"""
return '\n\n'.join(format_block(**kwargs) for kwargs in list_of_dicts)
def format_block(lines, heading='', indent=2):
if isinstance(lines[0], list):
max_length = len(max([l[0] for l in lines], key=len))
lines = [f"{line[0]:{max_length}} {line[1]}" for line in lines]
elif isinstance(lines, str):
lines = [lines]
if heading:
return '\n'.join([heading]+['\n'.join([indent*' '+l for l in lines])])
return '\n'.join(['\n'.join([indent*' '+l for l in lines])])
def first_calling_frame():
for s in reversed(inspect.stack()):
if s.code_context == None:
continue
if s.code_context[0].lstrip().startswith('import magicli'):
return s.frame
return None
frame = first_calling_frame()
if frame != None:
sys.exit(magicli(glbls=frame.f_globals, exclude=[]))