Added libbe.command.base (with Command class) and moved list command to new format.
authorW. Trevor King <wking@drexel.edu>
Sat, 12 Dec 2009 05:31:55 +0000 (00:31 -0500)
committerW. Trevor King <wking@drexel.edu>
Sat, 12 Dec 2009 05:31:55 +0000 (00:31 -0500)
12 files changed:
be
libbe/command/__init__.py
libbe/command/base.py [new file with mode: 0644]
libbe/command/list.py
libbe/ui/__init__.py [new file with mode: 0644]
libbe/ui/base.py [new file with mode: 0644]
libbe/ui/util/__init__.py [new file with mode: 0644]
libbe/ui/util/cmdutil.py
libbe/ui/util/repo.py [new file with mode: 0644]
libbe/util/plugin.py
libbe/util/utility.py
test.py

diff --git a/be b/be
index f026c05a37ca83b648b1f1fe3965ad89c1caeae4..5e3088e8208b58c95bb85bf34941d02aa742a08d 100755 (executable)
--- a/be
+++ b/be
@@ -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()
index 794013cd2fa1ab3a0ef4217988fe7d86a29bf429..344a8a27bd9c2c556b61486a58148879673fce85 100644 (file)
 # 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 (file)
index 0000000..973b840
--- /dev/null
@@ -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."
index 1c3e78d751e711e5b38d04b26495957a856fbf0c..508cc70583d6f5264bb679971c1e0c008a05ea30 100644 (file)
 # 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 (file)
index 0000000..b98f164
--- /dev/null
@@ -0,0 +1 @@
+# Copyright
diff --git a/libbe/ui/base.py b/libbe/ui/base.py
new file mode 100644 (file)
index 0000000..d26115f
--- /dev/null
@@ -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 (file)
index 0000000..a650d33
--- /dev/null
@@ -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
index c56798480482d092cbfa12711fd6bb136aadeb36..b2d8a99108dd7a4695e238bc560c73bcfc17856b 100644 (file)
@@ -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 (file)
index 0000000..174c5b1
--- /dev/null
@@ -0,0 +1,4 @@
+# Copyright
+
+def complete(string):
+    pass
index edb492223e99d800771ac34c3126614d58d69d6f..982c5ca6c767e6dc564f9d2157222b50600e2bd4 100644 (file)
@@ -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]
index f954422ba60c3ffb40885f54f33c632857eea93d..779eaa5185a27283cce43818480c2180b8189724 100644 (file)
@@ -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()
diff --git a/test.py b/test.py
index 2b93bcfa78dd8e5664fcb6093cf281d1b2a2ed3d..fecff8cd9cbd1ad85ccf95864b314f33765cd315 100644 (file)
--- a/test.py
+++ b/test.py
@@ -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()