Skip to content

Commit 96bab1a

Browse files
committed
Merge pull request tellapart#23 from chuegle/master
Fixes issue when RunFunction tries to run a non-registerd command.
2 parents a497187 + c9d254e commit 96bab1a

File tree

1 file changed

+55
-26
lines changed

1 file changed

+55
-26
lines changed

commandr/commandr.py

+55-26
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,25 @@
129129
from optparse import OptionParser, SUPPRESS_HELP
130130
import sys
131131

132+
class CommandInfo(
133+
namedtuple('BaseCommandInfo',
134+
['name', 'callable', 'category', 'ignore_self'])):
135+
"""Class to contain information about a spepcific supported command."""
136+
def __new__(cls, name=None, callable=None, category=None, ignore_self=None):
137+
"""Creates a new CommandInfo allowing for default values.
138+
139+
Args:
140+
name - Name of the command.
141+
callable - Callable function of the command.
142+
category - Category classification of the command.
143+
ignore_self - Whether the arg list should ignore the first value if it is
144+
self.
145+
Returns:
146+
info - A CommandInfo.
147+
"""
148+
return super(CommandInfo, cls).__new__(cls, name, callable, category,
149+
ignore_self)
150+
132151
class Commandr(object):
133152
"""Class for managing commandr context."""
134153

@@ -152,8 +171,6 @@ def __init__(self):
152171
# List of commands in the order they appeared, of the format:
153172
# [(name, callable, category)]
154173
self._command_list = []
155-
self._command_info = namedtuple(
156-
'_COMMAND_INFO', ['name', 'callable', 'category', 'ignore_self'])
157174

158175
self.command('help', ignore_self=True)(self._HelpExitNoCommand)
159176

@@ -183,18 +200,14 @@ def Hi(name, title='Mr.'):
183200
decorator/function to register the command.
184201
"""
185202
def command_decorator(cmd_fn, cmd_fn_name=None):
186-
final_name = (cmd_fn_name if cmd_fn_name is not None
187-
else command_name if command_name is not None
188-
else cmd_fn.func_name)
189-
info = self._command_info(final_name, cmd_fn, category, ignore_self)
190-
self._all_commands[final_name] = info
203+
info = self.AddCommand(cmd_fn, cmd_fn_name or command_name, category,
204+
ignore_self)
191205
if main:
192206
if not self.main:
193-
self.main = final_name
207+
self.main = info.name
194208
else:
195209
raise CommandrDuplicateMainError("'%s' tried to override '%s'" % (
196-
final_name, self._main_command))
197-
self._command_list.append(info)
210+
info.name, self._main_command))
198211
return cmd_fn
199212

200213
# Handle no command_name case.
@@ -204,6 +217,24 @@ def command_decorator(cmd_fn, cmd_fn_name=None):
204217

205218
return command_decorator
206219

220+
def AddCommand(self, cmd_fn, cmd_fn_name, category, ignore_self):
221+
"""Adds a command to the commandr list.
222+
223+
Args:
224+
cmd_fn - The function to add.
225+
cmd_fn_name - The name of the command being added or the func_name.
226+
category - The category of the command.
227+
ignore_self - Whether to ignore self in the arg list.
228+
Returns:
229+
info - The CommandInfo created.
230+
"""
231+
final_name = (cmd_fn_name if cmd_fn_name is not None
232+
else cmd_fn.func_name)
233+
info = CommandInfo(final_name, cmd_fn, category, ignore_self)
234+
self._all_commands[info.name] = info
235+
self._command_list.append(info)
236+
return info
237+
207238
def SetOptions(self,
208239
hyphenate=None,
209240
show_all_help_variants=None,
@@ -302,12 +333,14 @@ def RunFunction(self,
302333
main - If set, it will use the supplied value as the command name to run
303334
if no command name is supplied. It will override any previous values.
304335
"""
336+
info = self._all_commands.get(cmd_name)
337+
if not info:
338+
info = self.AddCommand(cmd_fn, cmd_name, None, ignore_self)
339+
305340
self.SetOptions(hyphenate, show_all_help_variants, ignore_self, main_doc,
306341
main)
307342

308-
cmd_name = cmd_name or ""
309-
310-
argspec, defaults_dict = self._BuildOptParse(cmd_name)
343+
argspec, defaults_dict = self._BuildOptParse(info)
311344

312345
(options, args) = self.parser.parse_args()
313346

@@ -316,12 +349,10 @@ def RunFunction(self,
316349
# If help, print our message, else remove it so it doesn't confuse the
317350
# execution
318351
if options_dict['help']:
319-
self._HelpExitCommand(None, cmd_name, cmd_fn)
352+
self._HelpExitCommand(None, info.name, info.callable)
320353
elif 'help' in options_dict:
321354
del options_dict['help']
322355

323-
info = (self._all_commands.get(cmd_name)
324-
or self._command_info(cmd_name, cmd_fn))
325356
ignore = (info.ignore_self
326357
if info.ignore_self is not None
327358
else self.ignore_self)
@@ -335,7 +366,7 @@ def RunFunction(self,
335366
while True:
336367
if i + skipped >= len(argspec.args):
337368
self._HelpExitCommand("Too many arguments",
338-
cmd_name, cmd_fn, options_dict, argspec.args)
369+
info.name, info.callable, options_dict, argspec.args)
339370
key = argspec.args[i + skipped]
340371
if ignore and key == 'self':
341372
skipped += 1
@@ -348,7 +379,7 @@ def RunFunction(self,
348379
if i + skipped >= len(argspec.args):
349380
self._HelpExitCommand(
350381
"Too many arguments: True/False must be specified via switches",
351-
cmd_name, cmd_fn, options_dict, argspec.args)
382+
info.name, info.callable, options_dict, argspec.args)
352383

353384
key = argspec.args[i + skipped]
354385

@@ -360,7 +391,7 @@ def RunFunction(self,
360391
self._HelpExitCommand(
361392
"Repeated option: %s\nOption: %s\nArgument: %s" % (
362393
key, options_dict[key], value),
363-
cmd_name, cmd_fn, options_dict, argspec.args)
394+
info.name, info.callable, options_dict, argspec.args)
364395

365396
# cast specific types
366397
if key in defaults_dict:
@@ -381,24 +412,24 @@ def RunFunction(self,
381412
if key not in defaults_dict:
382413
self._HelpExitCommand(
383414
"All options without default values must be specified",
384-
cmd_name, cmd_fn, options_dict, argspec.args)
415+
info.name, info.callable, options_dict, argspec.args)
385416
elif defaults_dict[key] is not None:
386417
options_dict[key] = defaults_dict[key]
387418

388419
self.current_command = info
389420
try:
390-
result = cmd_fn(**options_dict)
421+
result = info.callable(**options_dict)
391422
except CommandrUsageError as e:
392423
self.Usage(str(e) or None)
393424

394425
if result:
395426
print result
396427

397-
def _BuildOptParse(self, cmd_name):
428+
def _BuildOptParse(self, info):
398429
"""Sets the current command parser to reflect the provided command.
399430
400431
Args:
401-
cmd_name - Name of the command being built.
432+
info - CommandInfo of the command being built.
402433
Returns:
403434
argspec - ArgSpec object returned from inspect.getargspec() on the chosen
404435
command function.
@@ -407,7 +438,7 @@ def _BuildOptParse(self, cmd_name):
407438
Returns:
408439
A populated OptionParser object.
409440
"""
410-
usage = 'Usage: %%prog %s [options]\n' % (cmd_name) + \
441+
usage = 'Usage: %%prog %s [options]\n' % (info.name) + \
411442
'Options without default values MUST be specified\n\n' + \
412443
'Use: %prog help [command]\n to see other commands available.'
413444

@@ -419,8 +450,6 @@ def _BuildOptParse(self, cmd_name):
419450
# Parse the command function's arguments into the OptionsParser.
420451
letters = set(['h']) # -h is for help
421452

422-
info = self._all_commands.get(cmd_name) or self._command_info()
423-
424453
# Check if the command function is wrapped with other decorators, and if so,
425454
# find the original function signature.
426455
cmd_fn_root = info.callable

0 commit comments

Comments
 (0)