diff options
author | W. Trevor King <wking@drexel.edu> | 2009-12-12 00:31:55 -0500 |
---|---|---|
committer | W. Trevor King <wking@drexel.edu> | 2009-12-12 00:31:55 -0500 |
commit | bf3d434b244c57556bec979acbc658c30eb58221 (patch) | |
tree | a31746f74a9aaaf1d95c4bd7e1ef1ae30041f2f8 | |
parent | a153347564e4c6baa0388fda05530f5548d16ac5 (diff) | |
download | bugseverywhere-bf3d434b244c57556bec979acbc658c30eb58221.tar.gz |
Added libbe.command.base (with Command class) and moved list command to new format.
-rwxr-xr-x | be | 5 | ||||
-rw-r--r-- | libbe/command/__init__.py | 13 | ||||
-rw-r--r-- | libbe/command/base.py | 224 | ||||
-rw-r--r-- | libbe/command/list.py | 340 | ||||
-rw-r--r-- | libbe/ui/__init__.py | 1 | ||||
-rw-r--r-- | libbe/ui/base.py | 23 | ||||
-rw-r--r-- | libbe/ui/util/__init__.py | 69 | ||||
-rw-r--r-- | libbe/ui/util/cmdutil.py | 141 | ||||
-rw-r--r-- | libbe/ui/util/repo.py | 4 | ||||
-rw-r--r-- | libbe/util/plugin.py | 60 | ||||
-rw-r--r-- | libbe/util/utility.py | 9 | ||||
-rw-r--r-- | test.py | 10 |
12 files changed, 579 insertions, 320 deletions
@@ -43,6 +43,11 @@ parser.add_option("--no-pager", dest="no_pager", default=False, action='store_true', help="Do not pipe git output into a pager.") +# Option(name='repo', short_name='r', +# help='Select BE repository (see `be help repo`) rather than' +# 'the current directory.', +# arg=Argument(name='repo', metavar='REPO', default='.', +# completion_callback=libbe.ui.util.repo.complete)), try: options,args = parser.parse_args() diff --git a/libbe/command/__init__.py b/libbe/command/__init__.py index 794013c..344a8a2 100644 --- a/libbe/command/__init__.py +++ b/libbe/command/__init__.py @@ -14,3 +14,16 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import base + +UserError = base.UserError +UnkownCommand = base.UnknownCommand +get_command = base.get_command +commands = base.commands +Option = base.Option +Argument = base.Argument +Command = base.Command + +__all__ = [UserError, UnkownCommand, get_command, commands, + Option, Argument, Command] diff --git a/libbe/command/base.py b/libbe/command/base.py new file mode 100644 index 0000000..973b840 --- /dev/null +++ b/libbe/command/base.py @@ -0,0 +1,224 @@ +# Copyright + +import optparse +import sys + +import libbe +import libbe.util.plugin +import libbe.ui.util.repo + +class UserError(Exception): + pass + +class UnknownCommand(UserError): + def __init__(self, cmd): + Exception.__init__(self, "Unknown command '%s'" % cmd) + self.cmd = cmd + + +def get_command(command_name): + """Retrieves the module for a user command + + >>> try: + ... get_command('asdf') + ... except UnknownCommand, e: + ... print e + Unknown command 'asdf' + >>> repr(get_command('list')).startswith("<module 'libbe.command.list' from ") + True + """ + try: + cmd = libbe.util.plugin.import_by_name( + 'libbe.command.%s' % command_name.replace("-", "_")) + except ImportError, e: + raise UnknownCommand(command_name) + return cmd + +def commands(): + for modname in libbe.util.plugin.modnames('libbe.command'): + if modname != 'base': + yield modname + +class CommandInput (object): + def __init__(self, name, help=''): + self.name = name + self.help = help + +class Option (CommandInput): + def __init__(self, option_callback=None, short_name=None, arg=None, + type=None, *args, **kwargs): + CommandInput.__init__(self, *args, **kwargs) + self.option_callback = option_callback + self.short_name = short_name + self.arg = arg + self.type = type + if self.arg != None: + assert self.arg.name == self.name, \ + 'Name missmatch: %s != %s' % (self.arg.name, self.name) + +class Argument (CommandInput): + def __init__(self, metavar=None, default=None, + optional=False, repeatable=False, + completion_callback=None, *args, **kwargs): + CommandInput.__init__(self, *args, **kwargs) + self.metavar = metavar + self.default = default + self.optional = optional + self.repeatable = repeatable + self.completion_callback = completion_callback + +class _DummyParser (object): + def __init__(self, options): + self.option_list = options + self.option_groups = [] + for option in self.option_list: # add required methods and attributes + option.dest = option.name + option._short_opts = [] + if option.short_name != None: + option._short_opts.append('-' + option.short_name) + option._long_opts = ['--' + option.name] + option.takes_value = lambda : option.arg != None + if option.takes_value(): + option.metavar = option.arg.metavar + else: + option.metavar = None + +class OptionFormatter (optparse.IndentedHelpFormatter): + def __init__(self, options): + optparse.IndentedHelpFormatter.__init__(self) + self.options = options + def option_help(self): + # based on optparse.OptionParser.format_option_help() + parser = _DummyParser(self.options) + self.store_option_strings(parser) + ret = [] + ret.append(self.format_heading('Options')) + self.indent() + for option in self.options: + ret.append(self.format_option(option)) + ret.append('\n') + self.dedent() + # Drop the last '\n', or the header if no options or option groups: + return ''.join(ret[:-1]) + +class Command (object): + """ + >>> c = Command() + >>> print c.help() + usage: be command [options] + <BLANKLINE> + Options: + -h HELP, --help=HELP Print a help message + <BLANKLINE> + --complete=STRING Print a list of possible completions + <BLANKLINE> + -r REPO, --repo=REPO Select BE repository (see `be help repo`) rather + thanthe current directory. + <BLANKLINE> + A detailed help message. + """ + + name = 'command' + + def __init__(self, input_encoding=None, output_encoding=None): + self.status = None + self.result = None + self.input_encoding = None + self.output_encoding = None + self.options = [ + Option(name='help', short_name='h', + help='Print a help message', + option_callback=self.help), + Option(name='complete', type='string', + help='Print a list of possible completions', + arg=Argument(name='complete', metavar='STRING', optional=True)), + ] + self.args = [] + + def run(self, bugdir, options=None, args=None): + if options == None: + options = {} + if args == None: + args = [] + params = {} + for option in self.options: + if option.name in options: + params[option.name] = options.pop(option.name) + elif option.arg != None: + params[option.name] = option.arg.default + else: # non-arg options are flags, set to default flag value + params[option.name] = False + if len(options) > 0: + raise UserError, 'Invalid options passed to command %s:\n %s' \ + % (self.name, '\n '.join(['%s: %s' % (k,v) + for k,v in options.items()])) + for arg in self.args: + pass + if params['help'] == True: + pass + else: + params.pop('help') + if params['complete'] != None: + pass + else: + params.pop('complete') + self._setup_io(self.input_encoding, self.output_encoding) + self.status = self._run(bugdir, **params) + return self.status + + def _run(self, bugdir, **kwargs): + pass + + def _setup_io(self, input_encoding=None, output_encoding=None): + if input_encoding == None: + input_encoding = get_terminal_encoding() + if output_encoding == None: + output_encoding = get_terminal_encoding() + self.stdin = codecs.getwriter(input_encoding)(sys.stdin) + self.stdin.encoding = input_encoding + self.stdout = codecs.getwriter(output_encoding)(sys.stdout) + self.stdout.encoding = output_encoding + + def help(self, *args): + return '\n\n'.join([self._usage(), + self._option_help(), + self._long_help()]) +# if cmd != None: +# return get_command(cmd).help() +# cmdlist = [] +# for name in commands(): +# module = get_command(name) +# cmdlist.append((name, module.__desc__)) +# cmdlist.sort() +# longest_cmd_len = max([len(name) for name,desc in cmdlist]) +# ret = ["Bugs Everywhere - Distributed bug tracking", +# "", "Supported commands"] +# for name, desc in cmdlist: +# numExtraSpaces = longest_cmd_len-len(name) +# ret.append("be %s%*s %s" % (name, numExtraSpaces, "", desc)) +# ret.extend(["", "Run", " be help [command]", "for more information."]) +# longhelp = "\n".join(ret) +# if parser == None: +# return longhelp +# return parser.help_str() + "\n" + longhelp + + def _usage(self): + usage = 'usage: be %s [options]' % self.name + num_optional = 0 + for arg in self.args: + usage += ' ' + if arg.optional == True: + usage += '[' + num_optional += 1 + usage += arg.metavar + if arg.repeatable == True: + usage += ' ...' + usage += ']'*num_optional + return usage + + def _option_help(self): + o = OptionFormatter(self.options) + return o.option_help().strip('\n') + + def _long_help(self): + return "A detailed help message." diff --git a/libbe/command/list.py b/libbe/command/list.py index 1c3e78d..508cc70 100644 --- a/libbe/command/list.py +++ b/libbe/command/list.py @@ -16,184 +16,208 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""List bugs""" -from libbe import cmdutil, bugdir, bug + import os import re -__desc__ = __doc__ + +import libbe +import libbe.command +import libbe.bug +import libbe.util.utility +import libbe.ui.util # get a list of * for cmp_*() comparing two bugs. -AVAILABLE_CMPS = [fn[4:] for fn in dir(bug) if fn[:4] == 'cmp_'] -AVAILABLE_CMPS.remove("attr") # a cmp_* template. +AVAILABLE_CMPS = [fn[4:] for fn in dir(libbe.bug) if fn[:4] == 'cmp_'] +AVAILABLE_CMPS.remove('attr') # a cmp_* template. -def execute(args, manipulate_encodings=True, restrict_file_access=False, - dir="."): - """ - >>> import os - >>> bd = bugdir.SimpleBugDir() - >>> os.chdir(bd.root) - >>> execute([], manipulate_encodings=False) - a:om: Bug A - >>> execute(["--status", "closed"], manipulate_encodings=False) - b:cm: Bug B - >>> bd.cleanup() - """ - parser = get_parser() - options, args = parser.parse_args(args) - complete(options, args, parser) - if len(args) > 0: - raise cmdutil.UsageError("Too many arguments.") - cmp_list = [] - if options.sort_by != None: - for cmp in options.sort_by.split(','): - if cmp not in AVAILABLE_CMPS: - raise cmdutil.UserError( - "Invalid sort on '%s'.\nValid sorts:\n %s" - % (cmp, '\n '.join(AVAILABLE_CMPS))) - cmp_list.append(eval('bug.cmp_%s' % cmp)) - - bd = bugdir.BugDir(from_disk=True, - manipulate_encodings=manipulate_encodings, - root=dir) - bd.load_all_bugs() - # select status - if options.status != None: - if options.status == "all": - status = bug.status_values - else: - status = cmdutil.select_values(options.status, bug.status_values) - else: - status = [] - if options.active == True: - status.extend(list(bug.active_status_values)) - if options.unconfirmed == True: - status.append("unconfirmed") - if options.open == True: - status.append("opened") - if options.test == True: - status.append("test") - if status == []: # set the default value - status = bug.active_status_values - # select severity - if options.severity != None: - if options.severity == "all": - severity = bug.severity_values - else: - severity = cmdutil.select_values(options.severity, - bug.severity_values) - else: - severity = [] - if options.wishlist == True: - severity.extend("wishlist") - if options.important == True: - serious = bug.severity_values.index("serious") - severity.append(list(bug.severity_values[serious:])) - if severity == []: # set the default value - severity = bug.severity_values - # select assigned - if options.assigned != None: - if options.assigned == "all": - assigned = "all" - else: - possible_assignees = [] - for _bug in bd: - if _bug.assigned != None \ - and not _bug.assigned in possible_assignees: - possible_assignees.append(_bug.assigned) - assigned = cmdutil.select_values(options.assigned, - possible_assignees) - print 'assigned', assigned - else: - assigned = [] - if options.mine == True: - assigned.extend('-') - if assigned == []: # set the default value - assigned = "all" - for i in range(len(assigned)): - if assigned[i] == '-': - assigned[i] = bd.user_id - if options.extra_strings != None: - extra_string_regexps = [re.compile(x) for x in options.extra_strings.split(',')] - - def filter(bug): - if status != "all" and not bug.status in status: +class Filter (object): + def __init__(self, status, severity, assigned, extra_strings_regexps): + self.status = status + self.severity = severity + self.assigned = assigned + self.extra_strings_regexps = extra_strings_regexps + + def __call__(self, bug): + if self.status != "all" and not bug.status in self.status: return False - if severity != "all" and not bug.severity in severity: + if self.severity != "all" and not bug.severity in self.severity: return False - if assigned != "all" and not bug.assigned in assigned: + if self.assigned != "all" and not bug.assigned in self.assigned: return False - if options.extra_strings != None: - if len(bug.extra_strings) == 0 and len(extra_string_regexps) > 0: + if len(bug.extra_strings) == 0: + if len(self.extra_strings_regexps) > 0: return False + else: for string in bug.extra_strings: - for regexp in extra_string_regexps: + for regexp in self.extra_strings_regexps: if not regexp.match(string): return False return True - bugs = [b for b in bd if filter(b) ] - if len(bugs) == 0 and options.xml == False: - print "No matching bugs found" +class List (libbe.command.Command): + """List bugs + + >>> import libbe.bugdir + >>> bd = libbe.bugdir.SimpleBugDir() + >>> bd.uuid = '1234abcd' + >>> cmd = List() + >>> cmd._setup_io = lambda i_enc,o_enc : None + >>> cmd.run(bd) + 123/a:om: Bug A + >>> cmd.run(bd, {'status':'closed'}) + 123/b:cm: Bug B + >>> bd.cleanup() + """ + + name = 'list' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='status', + help='Only show bugs matching the STATUS specifier', + arg=libbe.command.Argument( + name='status', metavar='STATUS', default='active', + completion_callback=libbe.ui.util.complete_status)), + libbe.command.Option(name='severity', + help='Only show bugs matching the SEVERITY specifier', + arg=libbe.command.Argument( + name='severity', metavar='SEVERITY', default='all', + completion_callback=libbe.ui.util.complete_severity)), + libbe.command.Option(name='assigned', short_name='a', + help='Only show bugs matching ASSIGNED', + arg=libbe.command.Argument( + name='assigned', metavar='ASSIGNED', default='all', + completion_callback=libbe.ui.util.complete_assigned)), + libbe.command.Option(name='extra-strings', short_name='e', + help='Only show bugs matching STRINGS, e.g. --extra-strings' + ' TAG:working,TAG:xml', + arg=libbe.command.Argument( + name='extra-strings', metavar='STRINGS', default=None, + completion_callback=libbe.ui.util.complete_extra_strings)), + libbe.command.Option(name='sort', short_name='S', + help='Adjust bug-sort criteria with comma-separated list ' + 'SORT. e.g. "--sort creator,time". ' + 'Available criteria: %s' % ','.join(AVAILABLE_CMPS), + arg=libbe.command.Argument( + name='sort', metavar='SORT', default=None, + completion_callback=libbe.ui.util.Completer(AVAILABLE_CMPS))), + libbe.command.Option(name='uuids', short_name='u', + help='Only print the bug UUIDS'), + libbe.command.Option(name='xml', short_name='x', + help='Dump output in XML format'), + ]) +# parser.add_option("-S", "--sort", metavar="SORT-BY", dest="sort_by", +# help="Adjust bug-sort criteria with comma-separated list SORT-BY. e.g. \"--sort creator,time\". Available criteria: %s" % ','.join(AVAILABLE_CMPS), default=None) +# # boolean options. All but uuids and xml are special cases of long forms +# ("w", "wishlist", "List bugs with 'wishlist' severity"), +# ("i", "important", "List bugs with >= 'serious' severity"), +# ("A", "active", "List all active bugs"), +# ("U", "unconfirmed", "List unconfirmed bugs"), +# ("o", "open", "List open bugs"), +# ("T", "test", "List bugs in testing"), +# ("m", "mine", "List bugs assigned to you")) +# for s in bools: +# attr = s[1].replace('-','_') +# short = "-%c" % s[0] +# long = "--%s" % s[1] +# help = s[2] +# parser.add_option(short, long, action="store_true", +# dest=attr, help=help, default=False) +# return parser +# +# ]) + + def _run(self, bugdir, **params): + cmp_list, status, severity, assigned, extra_strings_regexps = \ + self._parse_params(params) + filter = Filter(status, severity, assigned, extra_strings_regexps) + bugs = [bugdir.bug_from_uuid(uuid) for uuid in bugdir.uuids()] + bugs = [b for b in bugs if filter(b) == True] + self.result = bugs + if len(bugs) == 0 and params['xml'] == False: + print "No matching bugs found" - def list_bugs(cur_bugs, title=None, just_uuids=False, xml=False): + # sort bugs + bugs = self._sort_bugs(bugs, cmp_list) + + # print list of bugs + if params['uuids'] == True: + for bug in bugs: + print bug.uuid + else: + self._list_bugs(bugs, xml=params['xml']) + + def _parse_params(self, params): + cmp_list = [] + if params['sort'] != None: + for cmp in params['sort'].sort_by.split(','): + if cmp not in AVAILABLE_CMPS: + raise libbe.command.UserError( + "Invalid sort on '%s'.\nValid sorts:\n %s" + % (cmp, '\n '.join(AVAILABLE_CMPS))) + cmp_list.append(eval('libbe.bug.cmp_%s' % cmp)) + # select status + if params['status'] == 'all': + status = libbe.bug.status_values + elif params['status'] == 'active': + status = list(libbe.bug.active_status_values) + elif params['status'] == 'inactive': + status = list(libbe.bug.inactive_status_values) + else: + status = libbe.ui.util.select_values( + params['status'], libbe.bug.status_values) + # select severity + if params['severity'] == 'all': + severity = libbe.bug.severity_values + elif params['important'] == True: + serious = libbe.bug.severity_values.index('serious') + severity.append(list(libbe.bug.severity_values[serious:])) + else: + severity = libbe.ui.util.select_values( + params['severity'], bug.severity_values) + # select assigned + if params['assigned'] == "all": + assigned = "all" + else: + possible_assignees = [] + for bug in self.bugdir: + if bug.assigned != None \ + and not bug.assigned in possible_assignees: + possible_assignees.append(bug.assigned) + assigned = libbe.ui.util.select_values( + params['assigned'], possible_assignees) + for i in range(len(assigned)): + if assigned[i] == '-': + assigned[i] = params['user-id'] + if params['extra-strings'] == None: + extra_strings_regexps = [] + else: + extra_strings_regexps = [re.compile(x) + for x in params['extra-strings'].split(',')] + return (cmp_list, status, severity, assigned, extra_strings_regexps) + + def _sort_bugs(self, bugs, cmp_list=[]): + cmp_list.extend(libbe.bug.DEFAULT_CMP_FULL_CMP_LIST) + cmp_fn = libbe.bug.BugCompoundComparator(cmp_list=cmp_list) + bugs.sort(cmp_fn) + return bugs + + def _list_bugs(self, bugs, xml=False): if xml == True: - print '<?xml version="1.0" encoding="%s" ?>' % bd.encoding + print '<?xml version="1.0" encoding="%s" ?>' % self.stdout.encoding print "<bugs>" - if len(cur_bugs) > 0: - if title != None and xml == False: - print cmdutil.underlined(title) - for bg in cur_bugs: + if len(bugs) > 0: + for bug in bugs: if xml == True: - print bg.xml(show_comments=True) - elif just_uuids: - print bg.uuid + print bug.xml(show_comments=True) else: - print bg.string(shortlist=True) + print bug.string(shortlist=True) if xml == True: print "</bugs>" - # sort bugs - cmp_list.extend(bug.DEFAULT_CMP_FULL_CMP_LIST) - cmp_fn = bug.BugCompoundComparator(cmp_list=cmp_list) - bugs.sort(cmp_fn) - - # print list of bugs - list_bugs(bugs, just_uuids=options.uuids, xml=options.xml) - -def get_parser(): - parser = cmdutil.CmdOptionParser("be list [options]") - parser.add_option("--status", dest="status", metavar="STATUS", - help="Only show bugs matching the STATUS specifier") - parser.add_option("--severity", dest="severity", metavar="SEVERITY", - help="Only show bugs matching the SEVERITY specifier") - parser.add_option("-a", "--assigned", metavar="ASSIGNED", dest="assigned", - help="List bugs matching ASSIGNED", default=None) - parser.add_option("-e", "--extra-strings", metavar="STRINGS", dest="extra_strings", - help="List bugs matching _all_ extra strings in comma-seperated list STRINGS. e.g. --extra-strings TAG:working,TAG:xml", default=None) - parser.add_option("-S", "--sort", metavar="SORT-BY", dest="sort_by", - help="Adjust bug-sort criteria with comma-separated list SORT-BY. e.g. \"--sort creator,time\". Available criteria: %s" % ','.join(AVAILABLE_CMPS), default=None) - # boolean options. All but uuids and xml are special cases of long forms - bools = (("u", "uuids", "Only print the bug UUIDS"), - ("x", "xml", "Dump as XML"), - ("w", "wishlist", "List bugs with 'wishlist' severity"), - ("i", "important", "List bugs with >= 'serious' severity"), - ("A", "active", "List all active bugs"), - ("U", "unconfirmed", "List unconfirmed bugs"), - ("o", "open", "List open bugs"), - ("T", "test", "List bugs in testing"), - ("m", "mine", "List bugs assigned to you")) - for s in bools: - attr = s[1].replace('-','_') - short = "-%c" % s[0] - long = "--%s" % s[1] - help = s[2] - parser.add_option(short, long, action="store_true", - dest=attr, help=help, default=False) - return parser - - -def help(): - longhelp=""" + def _long_help(self): + return """ This command lists bugs. Normally it prints a short string like 576:om: Allow attachments Where @@ -224,9 +248,7 @@ assigned In addition, there are some shortcut options that set boolean flags. The boolean options are ignored if the matching string option is used. -""" % (','.join(bug.status_values), - ','.join(bug.severity_values)) - return get_parser().help_str() + longhelp +""" % (','.join(bug.status_values), ','.join(bug.severity_values)) def complete(options, args, parser): for option, value in cmdutil.option_value_pairs(options, parser): diff --git a/libbe/ui/__init__.py b/libbe/ui/__init__.py new file mode 100644 index 0000000..b98f164 --- /dev/null +++ b/libbe/ui/__init__.py @@ -0,0 +1 @@ +# Copyright diff --git a/libbe/ui/base.py b/libbe/ui/base.py new file mode 100644 index 0000000..d26115f --- /dev/null +++ b/libbe/ui/base.py @@ -0,0 +1,23 @@ + def _setup_user_id(self, user_id): + if isinstance(self.storage, storage.vcs.base.VCS): + self.storage.user_id = user_id + def _guess_user_id(self): + if isinstance(self.storage, storage.vcs.base.VCS): + return self.storage.get_user_id() + def _set_user_id(self, old_user_id, new_user_id): + self._setup_user_id(new_user_id) + self._prop_save_settings(old_user_id, new_user_id) + + @_versioned_property(name="user_id", + doc= +"""The user's prefered name, e.g. 'John Doe <jdoe@example.com>'. Note +that the Arch VCS backend *enforces* ids with this format.""", + change_hook=_set_user_id, + generator=_guess_user_id) + def user_id(): return {} + + @_versioned_property(name="default_assignee", + doc= +"""The default assignee for new bugs e.g. 'John Doe <jdoe@example.com>'.""") + def default_assignee(): return {} + diff --git a/libbe/ui/util/__init__.py b/libbe/ui/util/__init__.py new file mode 100644 index 0000000..a650d33 --- /dev/null +++ b/libbe/ui/util/__init__.py @@ -0,0 +1,69 @@ +# Copyright + +class Completer (object): + def __init__(self, options): + self.options = options + def __call__(self, bugdir, fragment=None): + return [fragment] + +def complete_status(bugdir, fragment=None): + return [fragment] +def complete_severity(bugdir, fragment=None): + return [fragment] +def complete_assigned(bugdir, fragment=None): + return [fragment] +def complete_extra_strings(bugdir, fragment=None): + return [fragment] + +def select_values(string, possible_values, name="unkown"): + """ + This function allows the user to select values from a list of + possible values. The default is to select all the values: + + >>> select_values(None, ['abc', 'def', 'hij']) + ['abc', 'def', 'hij'] + + The user selects values with a comma-separated limit_string. + Prepending a minus sign to such a list denotes blacklist mode: + + >>> select_values('-abc,hij', ['abc', 'def', 'hij']) + ['def'] + + Without the leading -, the selection is in whitelist mode: + + >>> select_values('abc,hij', ['abc', 'def', 'hij']) + ['abc', 'hij'] + + In either case, appropriate errors are raised if on of the + user-values is not in the list of possible values. The name + parameter lets you make the error message more clear: + + >>> select_values('-xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + >>> select_values('xyz,hij', ['abc', 'def', 'hij'], name="foobar") + Traceback (most recent call last): + ... + UserError: Invalid foobar xyz + ['abc', 'def', 'hij'] + """ + possible_values = list(possible_values) # don't alter the original + if string == None: + pass + elif string.startswith('-'): + blacklisted_values = set(string[1:].split(',')) + for value in blacklisted_values: + if value not in possible_values: + raise UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values.remove(value) + else: + whitelisted_values = string.split(',') + for value in whitelisted_values: + if value not in possible_values: + raise UserError('Invalid %s %s\n %s' + % (name, value, possible_values)) + possible_values = whitelisted_values + return possible_values diff --git a/libbe/ui/util/cmdutil.py b/libbe/ui/util/cmdutil.py index c567984..b2d8a99 100644 --- a/libbe/ui/util/cmdutil.py +++ b/libbe/ui/util/cmdutil.py @@ -37,48 +37,11 @@ if libbe.TESTING == True: import doctest -class UserError(Exception): - def __init__(self, msg): - Exception.__init__(self, msg) - -class UnknownCommand(UserError): - def __init__(self, cmd): - Exception.__init__(self, "Unknown command '%s'" % cmd) - self.cmd = cmd - -class UsageError(Exception): - pass - -class GetHelp(Exception): - pass - -class GetCompletions(Exception): - def __init__(self, completions=[]): - msg = "Get allowed completions" - Exception.__init__(self, msg) - self.completions = completions def iter_commands(): for name, module in plugin.iter_plugins("becommands"): yield name.replace("_", "-"), module -def get_command(command_name): - """Retrieves the module for a user command - - >>> try: - ... get_command("asdf") - ... except UnknownCommand, e: - ... print e - Unknown command 'asdf' - >>> repr(get_command("list")).startswith("<module 'becommands.list' from ") - True - """ - cmd = plugin.get_plugin("becommands", command_name.replace("-", "_")) - if cmd is None: - raise UnknownCommand(command_name) - return cmd - - def execute(cmd, args, manipulate_encodings=True, restrict_file_access=False, dir="."): @@ -92,37 +55,20 @@ def execute(cmd, args, ret = 0 return ret -def help(cmd=None, parser=None): - if cmd != None: - return get_command(cmd).help() - else: - cmdlist = [] - for name, module in iter_commands(): - cmdlist.append((name, module.__desc__)) - longest_cmd_len = max([len(name) for name,desc in cmdlist]) - ret = ["Bugs Everywhere - Distributed bug tracking", - "", "Supported commands"] - for name, desc in cmdlist: - numExtraSpaces = longest_cmd_len-len(name) - ret.append("be %s%*s %s" % (name, numExtraSpaces, "", desc)) - ret.extend(["", "Run", " be help [command]", "for more information."]) - longhelp = "\n".join(ret) - if parser == None: - return longhelp - return parser.help_str() + "\n" + longhelp +class GetHelp(Exception): + pass -def completions(cmd): - parser = get_command(cmd).get_parser() - longopts = [] - for opt in parser.option_list: - longopts.append(opt.get_opt_string()) - return longopts + +class GetCompletions(Exception): + def __init__(self, completions=[]): + msg = "Get allowed completions" + Exception.__init__(self, msg) + self.completions = completions def raise_get_help(option, opt, value, parser): raise GetHelp def raise_get_completions(option, opt, value, parser): - print "got completion arg" if hasattr(parser, "command") and parser.command == "be": comps = [] for command, module in iter_commands(): @@ -132,6 +78,14 @@ def raise_get_completions(option, opt, value, parser): raise GetCompletions(comps) raise GetCompletions(completions(sys.argv[1])) +def completions(cmd): + parser = get_command(cmd).get_parser() + longopts = [] + for opt in parser.option_list: + longopts.append(opt.get_opt_string()) + return longopts + + class CmdOptionParser(optparse.OptionParser): def __init__(self, usage): optparse.OptionParser.__init__(self, usage) @@ -211,67 +165,6 @@ def complete_path(path): comps.extend(glob.glob(comps[0]+"/*")) return comps -def underlined(instring): - """Produces a version of a string that is underlined with '=' - - >>> underlined("Underlined String") - 'Underlined String\\n=================' - """ - - return "%s\n%s" % (instring, "="*len(instring)) - -def select_values(string, possible_values, name="unkown"): - """ - This function allows the user to select values from a list of - possible values. The default is to select all the values: - - >>> select_values(None, ['abc', 'def', 'hij']) - ['abc', 'def', 'hij'] - - The user selects values with a comma-separated limit_string. - Prepending a minus sign to such a list denotes blacklist mode: - - >>> select_values('-abc,hij', ['abc', 'def', 'hij']) - ['def'] - - Without the leading -, the selection is in whitelist mode: - - >>> select_values('abc,hij', ['abc', 'def', 'hij']) - ['abc', 'hij'] - - In either case, appropriate errors are raised if on of the - user-values is not in the list of possible values. The name - parameter lets you make the error message more clear: - - >>> select_values('-xyz,hij', ['abc', 'def', 'hij'], name="foobar") - Traceback (most recent call last): - ... - UserError: Invalid foobar xyz - ['abc', 'def', 'hij'] - >>> select_values('xyz,hij', ['abc', 'def', 'hij'], name="foobar") - Traceback (most recent call last): - ... - UserError: Invalid foobar xyz - ['abc', 'def', 'hij'] - """ - possible_values = list(possible_values) # don't alter the original - if string == None: - pass - elif string.startswith('-'): - blacklisted_values = set(string[1:].split(',')) - for value in blacklisted_values: - if value not in possible_values: - raise UserError('Invalid %s %s\n %s' - % (name, value, possible_values)) - possible_values.remove(value) - else: - whitelisted_values = string.split(',') - for value in whitelisted_values: - if value not in possible_values: - raise UserError('Invalid %s %s\n %s' - % (name, value, possible_values)) - possible_values = whitelisted_values - return possible_values def restrict_file_access(bugdir, path): """ @@ -352,5 +245,7 @@ def bug_comment_from_id(bdir, id): raise UserError(e.message) return (bug, comm) + + if libbe.TESTING == True: suite = doctest.DocTestSuite() diff --git a/libbe/ui/util/repo.py b/libbe/ui/util/repo.py new file mode 100644 index 0000000..174c5b1 --- /dev/null +++ b/libbe/ui/util/repo.py @@ -0,0 +1,4 @@ +# Copyright + +def complete(string): + pass diff --git a/libbe/util/plugin.py b/libbe/util/plugin.py index edb4922..982c5ca 100644 --- a/libbe/util/plugin.py +++ b/libbe/util/plugin.py @@ -26,50 +26,42 @@ import os import os.path import sys -import libbe -if libbe.TESTING == True: - import doctest -def import_by_name(mod_name): - module = __import__(mod_name) - components = mod_name.split('.') +_PLUGIN_PATH = os.path.realpath( + os.path.dirname( + os.path.dirname( + os.path.dirname(__file__)))) +if _PLUGIN_PATH not in sys.path: + sys.path.append(_PLUGIN_PATH) + +def import_by_name(modname): + """ + >>> mod = import_by_name('libbe.bugdir') + >>> 'BugDir' in dir(mod) + True + >>> import_by_name('libbe.highly_unlikely') + Traceback (most recent call last): + ... + ImportError: No module named highly_unlikely + """ + module = __import__(modname) + components = modname.split('.') for comp in components[1:]: module = getattr(module, comp) return module -def iter_plugins(prefix): +def modnames(prefix): """ - >>> "list" in [n for n,m in iter_plugins("becommands")] + >>> 'list' in [n for n in modnames('libbe.command')] True - >>> "plugin" in [n for n,m in iter_plugins("libbe")] + >>> 'plugin' in [n for n in modnames('libbe.util')] True """ - modfiles = os.listdir(os.path.join(plugin_path, prefix)) + components = prefix.split('.') + modfiles = os.listdir(os.path.join(_PLUGIN_PATH, *components)) modfiles.sort() for modfile in modfiles: if modfile.startswith('.'): continue # the occasional emacs temporary file - if modfile.endswith(".py") and modfile != "__init__.py": - yield modfile[:-3], my_import(prefix+"."+modfile[:-3]) - - -def get_plugin(prefix, name): - """ - >>> get_plugin("becommands", "asdf") is None - True - >>> q = repr(get_plugin("becommands", "list")) - >>> q.startswith("<module 'becommands.list' from ") - True - """ - dirprefix = os.path.join(*prefix.split('.')) - command_path = os.path.join(plugin_path, dirprefix, name+".py") - if os.path.isfile(command_path): - return my_import(prefix + "." + name) - return None - -plugin_path = os.path.realpath(os.path.dirname(os.path.dirname(__file__))) -if plugin_path not in sys.path: - sys.path.append(plugin_path) - -if libbe.TESTING == True: - suite = doctest.DocTestSuite() + if modfile.endswith('.py') and modfile != '__init__.py': + yield modfile[:-3] diff --git a/libbe/util/utility.py b/libbe/util/utility.py index f954422..779eaa5 100644 --- a/libbe/util/utility.py +++ b/libbe/util/utility.py @@ -147,5 +147,14 @@ def iterable_full_of_strings(value, alternative=None): return False return True +def underlined(instring): + """Produces a version of a string that is underlined with '=' + + >>> underlined("Underlined String") + 'Underlined String\\n=================' + """ + + return "%s\n%s" % (instring, "="*len(instring)) + if libbe.TESTING == True: suite = doctest.DocTestSuite() @@ -1,3 +1,5 @@ +# Copyright + """Usage: python test.py [module(s) ...] When called without optional module names, run the test suites for @@ -49,14 +51,14 @@ def python_tree(root_path='libbe', root_modname='libbe'): f.parent.append(f) return tree -def add_module_tests(suite, module_name): - mod = import_by_name(module_name) - if mod == None: - raise KeyError, 'module "%s" not found' % module_name +def add_module_tests(suite, modname): + mod = import_by_name(modname) if hasattr(mod, 'suite'): s = mod.suite else: s = unittest.TestLoader().loadTestsFromModule(mod) + sdoc = doctest.DocTestSuite(mod) + suite.addTest(sdoc) suite.addTest(s) suite = unittest.TestSuite() |