diff --git a/commandr/commandr.py b/commandr/commandr.py index ced7fa7..4c4d9b4 100644 --- a/commandr/commandr.py +++ b/commandr/commandr.py @@ -140,9 +140,9 @@ def iteritems(d): class CommandInfo( namedtuple('BaseCommandInfo', - ['name', 'callable', 'category', 'ignore_self'])): + ['name', 'callable', 'category', 'ignore_self', 'options'])): """Class to contain information about a spepcific supported command.""" - def __new__(cls, name=None, callable=None, category=None, ignore_self=None): + def __new__(cls, name=None, callable=None, category=None, ignore_self=None, options=None): """Creates a new CommandInfo allowing for default values. Args: @@ -151,11 +151,12 @@ def __new__(cls, name=None, callable=None, category=None, ignore_self=None): category - Category classification of the command. ignore_self - Whether the arg list should ignore the first value if it is self. + options - A dict of argname to command switch to use as a short option. Returns: info - A CommandInfo. """ return super(CommandInfo, cls).__new__(cls, name, callable, category, - ignore_self) + ignore_self, options) class Commandr(object): """Class for managing commandr context.""" @@ -184,7 +185,7 @@ def __init__(self): self.command('help', ignore_self=True)(self._HelpExitNoCommand) def command(self, command_name=None, category=None, main=False, - ignore_self=None): + ignore_self=None, options=None): """Decorator that marks a function as a 'command' which can be invoked with arguments from the command line. e.g.: @@ -205,12 +206,13 @@ def Hi(name, title='Mr.'): main. ignore_self - If True or False, it will apply the ignore_self option for this command, while others use the global default. + options - A dictionary of argname to letter to use as the short switch. Returns: decorator/function to register the command. """ def command_decorator(cmd_fn, cmd_fn_name=None): info = self.AddCommand(cmd_fn, cmd_fn_name or command_name, category, - ignore_self) + ignore_self, options) if main: if not self.main: self.main = info.name @@ -226,7 +228,7 @@ def command_decorator(cmd_fn, cmd_fn_name=None): return command_decorator - def AddCommand(self, cmd_fn, cmd_fn_name, category, ignore_self): + def AddCommand(self, cmd_fn, cmd_fn_name, category, ignore_self, options): """Adds a command to the commandr list. Args: @@ -234,12 +236,13 @@ def AddCommand(self, cmd_fn, cmd_fn_name, category, ignore_self): cmd_fn_name - The name of the command being added or the __name__. category - The category of the command. ignore_self - Whether to ignore self in the arg list. + options - A dict of argname to command switch. Returns: info - The CommandInfo created. """ final_name = (cmd_fn_name if cmd_fn_name is not None else cmd_fn.__name__) - info = CommandInfo(final_name, cmd_fn, category, ignore_self) + info = CommandInfo(final_name, cmd_fn, category, ignore_self, options) self._all_commands[info.name] = info self._command_list.append(info) return info @@ -352,7 +355,6 @@ def RunFunction(self, argspec, defaults_dict = self._BuildOptParse(info) (options, args) = self.parser.parse_args() - options_dict = vars(options) # If help, print our message, else remove it so it doesn't confuse the @@ -425,6 +427,17 @@ def RunFunction(self, elif defaults_dict[key] is not None: options_dict[key] = defaults_dict[key] + for arg in argspec.args: + if arg in defaults_dict and hasattr(defaults_dict[arg], 'commandr_args'): + sub_options = {} + for argname, default in defaults_dict[arg].commandr_args: + sub_options[argname] = options_dict[argname] + del options_dict[argname] + try: + options_dict[arg] = defaults_dict[arg].commandr_init(**sub_options) + except CommandrUsageError as e: + self.Usage(str(e) or None) + self.current_command = info try: result = info.callable(**options_dict) @@ -458,7 +471,8 @@ def _BuildOptParse(self, info): # Parse the command function's arguments into the OptionsParser. letters = set(['h']) # -h is for help - + if info.options: + letters.update(k for k in info.options.keys() if len(k) == 1) # Check if the command function is wrapped with other decorators, and if so, # find the original function signature. cmd_fn_root = info.callable @@ -475,58 +489,75 @@ def _BuildOptParse(self, info): defaults_dict[argspec.args[-i]] = argspec.defaults[-i] for arg in argspec.args: - argname = arg - - if argname == 'self': - ignore = (info.ignore_self - if info.ignore_self is not None - else self.ignore_self) - if ignore: - continue - - args = ['--%s' % argname] + defaults_dict, letters = self._ProcessArg(arg, argspec, defaults_dict, info, letters) + return argspec, defaults_dict - switch_options = (argname[0], argname[0].upper()) + def _ProcessArg(self, arg, argspec, defaults_dict, info, letters): + if arg == 'self': + ignore = (info.ignore_self + if info.ignore_self is not None + else self.ignore_self) + if ignore: + return defaults_dict, letters + if arg in defaults_dict and hasattr(defaults_dict[arg], 'commandr_args'): + for argname, default in defaults_dict[arg].commandr_args: + defaults_dict[argname] = default + self._ProcessArg(argname, argspec, defaults_dict, info, letters) + return defaults_dict, letters + + args = ['--%s' % arg] + short = False + if info.options: + for switch, switch_arg in info.options.items(): + if switch_arg == arg: + if not switch: + return defaults_dict, letters + if len(switch) == 1: + short = True + args.insert(0, '-%s' % switch) + else: + args.append('--%s' % switch) + if not short: + switch_options = (arg[0], arg[0].upper()) for switch in switch_options: if switch not in letters and switch not in argspec.args: args.insert(0, '-%s' % switch) letters.add(switch) break - if arg in defaults_dict: - if repr(defaults_dict[arg]) == 'False': - self._AddOption( - ['--%s' % argname], dest=arg, action='store_true', default=False) - self._AddOption( - ['--no_%s' % argname], dest=arg, action='store_false', - default=False) - elif repr(defaults_dict[arg]) == 'True': - self._AddOption( - ['--%s' % argname], dest=arg, action='store_true', default=True) - self._AddOption( - ['--no_%s' % argname], dest=arg, action='store_false', - default=True) - elif isinstance(defaults_dict[arg], list): - self._AddOption(args, dest=arg, action='append', type='string') - else: - if isinstance(defaults_dict[arg], int): - arg_type = 'int' - help_str = '%default' - elif isinstance(defaults_dict[arg], float): - arg_type = 'float' - help_str = '%default' - elif defaults_dict[arg] == None: - arg_type = 'string' - help_str = 'None' - else: - arg_type = 'string' - help_str = '"%default"' - self._AddOption(args, dest=arg, default=defaults_dict[arg], - type=arg_type, help='[default: %s]' % (help_str)) + if arg in defaults_dict: + if repr(defaults_dict[arg]) == 'False': + self._AddOption( + ['--%s' % arg], dest=arg, action='store_true', default=False) + self._AddOption( + ['--no_%s' % arg], dest=arg, action='store_false', + default=False) + elif repr(defaults_dict[arg]) == 'True': + self._AddOption( + ['--%s' % arg], dest=arg, action='store_true', default=True) + self._AddOption( + ['--no_%s' % arg], dest=arg, action='store_false', + default=True) + elif isinstance(defaults_dict[arg], list): + self._AddOption(args, dest=arg, action='append', type='string') else: - self._AddOption(args, dest=arg) - - return argspec, defaults_dict + if isinstance(defaults_dict[arg], int): + arg_type = 'int' + help_str = '%default' + elif isinstance(defaults_dict[arg], float): + arg_type = 'float' + help_str = '%default' + elif defaults_dict[arg] == None: + arg_type = 'string' + help_str = 'None' + else: + arg_type = 'string' + help_str = '"%default"' + self._AddOption(args, dest=arg, default=defaults_dict[arg], + type=arg_type, help='[default: %s]' % (help_str)) + else: + self._AddOption(args, dest=arg) + return defaults_dict, letters def _AddOption(self, args, **kwargs): """Adds an option to the parser.