Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 84 additions & 53 deletions commandr/commandr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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."""
Expand Down Expand Up @@ -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.:

Expand All @@ -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
Expand All @@ -226,20 +228,21 @@ 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:
cmd_fn - The function to add.
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down