mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2026-01-13 20:12:26 +00:00
263 lines
9.4 KiB
Python
Executable File
263 lines
9.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import imp
|
|
import os
|
|
|
|
class Argument:
|
|
def __init__(
|
|
self,
|
|
long_or_short_1,
|
|
long_or_short_2=None,
|
|
default=None,
|
|
help=None,
|
|
nargs=None,
|
|
**kwargs
|
|
):
|
|
if long_or_short_2 is None:
|
|
shortname = None
|
|
longname = long_or_short_1
|
|
else:
|
|
shortname = long_or_short_1
|
|
longname = long_or_short_2
|
|
self.args = []
|
|
# argparse is crappy and cannot tell us if arguments were given or not.
|
|
# We need that information to decide if the config file should override argparse or not.
|
|
# So we just use None as a sentinel.
|
|
self.kwargs = {'default': None}
|
|
if shortname is not None:
|
|
self.args.append(shortname)
|
|
if longname[0] == '-':
|
|
self.args.append(longname)
|
|
self.key = longname.lstrip('-').replace('-', '_')
|
|
self.is_option = True
|
|
else:
|
|
self.key = longname.replace('-', '_')
|
|
self.args.append(self.key)
|
|
self.kwargs['metavar'] = longname
|
|
self.is_option = False
|
|
if default is not None and nargs is None:
|
|
self.kwargs['nargs'] = '?'
|
|
if nargs is not None:
|
|
self.kwargs['nargs'] = nargs
|
|
if default is True:
|
|
bool_action = 'store_false'
|
|
self.is_bool = True
|
|
elif default is False:
|
|
bool_action = 'store_true'
|
|
self.is_bool = True
|
|
else:
|
|
self.is_bool = False
|
|
if default is None and nargs in ('*', '+'):
|
|
default = []
|
|
if self.is_bool and not 'action' in kwargs:
|
|
self.kwargs['action'] = bool_action
|
|
if help is not None:
|
|
if not self.is_bool and default:
|
|
help += ' Default: {}'.format(default)
|
|
self.kwargs['help'] = help
|
|
self.optional = (
|
|
default is not None or
|
|
self.is_bool or
|
|
self.is_option or
|
|
nargs in ('?', '*', '+')
|
|
)
|
|
self.kwargs.update(kwargs)
|
|
self.default = default
|
|
self.longname = longname
|
|
|
|
def __str__(self):
|
|
return str(self.args) + ' ' + str(self.kwargs)
|
|
|
|
class CliFunction:
|
|
'''
|
|
Represent a function that can be called either from Python code, or
|
|
from the command line.
|
|
|
|
Features:
|
|
|
|
* single argument description in format very similar to argparse
|
|
* handle default arguments transparently in both cases
|
|
* expose a configuration file mechanism to get default parameters from a file
|
|
* fix some argparse.ArgumentParser() annoyances:
|
|
** allow dashes in positional arguments:
|
|
https://stackoverflow.com/questions/12834785/having-options-in-argparse-with-a-dash
|
|
** boolean defaults automatically use store_true or store_false, and add a --no-* CLI
|
|
option to invert them if set from the config
|
|
|
|
This somewhat duplicates: https://click.palletsprojects.com but:
|
|
|
|
* that decorator API is insane
|
|
* CLI + Python for single functions was wontfixed: https://github.com/pallets/click/issues/40
|
|
'''
|
|
def __call__(self, **args):
|
|
'''
|
|
Python version of the function call.
|
|
|
|
:type arguments: Dict
|
|
'''
|
|
args_with_defaults = args.copy()
|
|
# Add missing args from config file.
|
|
if 'config_file' in args_with_defaults and args_with_defaults['config_file'] is not None:
|
|
config_file = args_with_defaults['config_file']
|
|
else:
|
|
config_file = self._config_file
|
|
if os.path.exists(config_file):
|
|
config_configs = {}
|
|
config = imp.load_source('config', config_file)
|
|
config.set_args(config_configs)
|
|
for key in config_configs:
|
|
if key not in self._all_keys:
|
|
raise Exception('Unknown key in config file: ' + key)
|
|
if (not key in args_with_defaults) or args_with_defaults[key] is None:
|
|
args_with_defaults[key] = config_configs[key]
|
|
# Add missing args from hard-coded defaults.
|
|
for argument in self._arguments:
|
|
key = argument.key
|
|
if (not key in args_with_defaults) or args_with_defaults[key] is None:
|
|
if argument.optional:
|
|
args_with_defaults[key] = argument.default
|
|
else:
|
|
raise Exception('Value not given for mandatory argument: ' + key)
|
|
return self.main(**args_with_defaults)
|
|
|
|
def __init__(self, config_file=None, description=None):
|
|
self._all_keys = set()
|
|
self._arguments = []
|
|
self._config_file = config_file
|
|
self._description = description
|
|
if self._config_file is not None:
|
|
self.add_argument(
|
|
'--config-file',
|
|
default=self._config_file,
|
|
help='Path to the configuration file to use'
|
|
)
|
|
|
|
def __str__(self):
|
|
return '\n'.join(str(arg) for arg in self._arguments)
|
|
|
|
def add_argument(
|
|
self,
|
|
*args,
|
|
**kwargs
|
|
):
|
|
argument = Argument(*args, **kwargs)
|
|
self._arguments.append(argument)
|
|
self._all_keys.add(argument.key)
|
|
|
|
def cli(self, cli_args=None):
|
|
'''
|
|
Call the function from the CLI. Parse command line arguments
|
|
to get all arguments.
|
|
'''
|
|
parser = argparse.ArgumentParser(
|
|
description=self._description,
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
)
|
|
for argument in self._arguments:
|
|
parser.add_argument(*argument.args, **argument.kwargs)
|
|
if argument.is_bool:
|
|
new_longname = '--no' + argument.longname[1:]
|
|
kwargs = argument.kwargs.copy()
|
|
kwargs['default'] = not argument.default
|
|
if kwargs['action'] == 'store_false':
|
|
kwargs['action'] = 'store_true'
|
|
elif kwargs['action'] == 'store_true':
|
|
kwargs['action'] = 'store_false'
|
|
if 'help' in kwargs:
|
|
del kwargs['help']
|
|
parser.add_argument(new_longname, dest=argument.key, **kwargs)
|
|
args = parser.parse_args(args=cli_args)
|
|
return self(**vars(args))
|
|
|
|
def main(self, **kwargs):
|
|
'''
|
|
Do the main function call work.
|
|
|
|
:type arguments: Dict
|
|
'''
|
|
raise NotImplementedError
|
|
|
|
if __name__ == '__main__':
|
|
class OneCliFunction(CliFunction):
|
|
def __init__(self):
|
|
super().__init__(
|
|
config_file='cli_function_test_config.py',
|
|
description = '''\
|
|
Description of this
|
|
amazing function!
|
|
''',
|
|
)
|
|
self.add_argument('-a', '--asdf', default='A', help='Help for asdf'),
|
|
self.add_argument('-q', '--qwer', default='Q', help='Help for qwer'),
|
|
self.add_argument('-b', '--bool', default=True, help='Help for bool'),
|
|
self.add_argument('--bool-cli', default=False, help='Help for bool'),
|
|
self.add_argument('--bool-nargs', default=False, nargs='?', action='store', const='')
|
|
self.add_argument('--no-default', help='Help for no-bool'),
|
|
self.add_argument('pos-mandatory', help='Help for pos-mandatory', type=int),
|
|
self.add_argument('pos-optional', default=0, help='Help for pos-optional', type=int),
|
|
self.add_argument('args-star', help='Help for args-star', nargs='*'),
|
|
def main(self, **kwargs):
|
|
del kwargs['config_file']
|
|
return kwargs
|
|
|
|
one_cli_function = OneCliFunction()
|
|
|
|
# Default code call.
|
|
default = one_cli_function(pos_mandatory=1)
|
|
assert default == {
|
|
'asdf': 'A',
|
|
'qwer': 'Q',
|
|
'bool': True,
|
|
'bool_nargs': False,
|
|
'bool_cli': True,
|
|
'no_default': None,
|
|
'pos_mandatory': 1,
|
|
'pos_optional': 0,
|
|
'args_star': []
|
|
}
|
|
|
|
# Default CLI call.
|
|
out = one_cli_function.cli(['1'])
|
|
assert out == default
|
|
|
|
# asdf
|
|
out = one_cli_function(pos_mandatory=1, asdf='B')
|
|
assert out['asdf'] == 'B'
|
|
out['asdf'] = default['asdf']
|
|
assert(out == default)
|
|
|
|
# asdf and qwer
|
|
out = one_cli_function(pos_mandatory=1, asdf='B', qwer='R')
|
|
assert out['asdf'] == 'B'
|
|
assert out['qwer'] == 'R'
|
|
out['asdf'] = default['asdf']
|
|
out['qwer'] = default['qwer']
|
|
assert(out == default)
|
|
|
|
if '--bool':
|
|
out = one_cli_function(pos_mandatory=1, bool=False)
|
|
cli_out = one_cli_function.cli(['--bool', '1'])
|
|
assert out == cli_out
|
|
assert out['bool'] == False
|
|
out['bool'] = default['bool']
|
|
assert(out == default)
|
|
|
|
if '--bool-nargs':
|
|
|
|
out = one_cli_function(pos_mandatory=1, bool_nargs=True)
|
|
assert out['bool_nargs'] == True
|
|
out['bool_nargs'] = default['bool_nargs']
|
|
assert(out == default)
|
|
|
|
out = one_cli_function(pos_mandatory=1, bool_nargs='asdf')
|
|
assert out['bool_nargs'] == 'asdf'
|
|
out['bool_nargs'] = default['bool_nargs']
|
|
assert(out == default)
|
|
|
|
# Force a boolean value set on the config to be False on CLI.
|
|
assert one_cli_function.cli(['--no-bool-cli', '1'])['bool_cli'] is False
|
|
|
|
# CLI call.
|
|
print(one_cli_function.cli())
|