From dff6bd9bf89ca80e2265696a478e540476718c9c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sat, 12 Dec 2009 20:57:59 -0500 Subject: Moved be to libbe.ui.command_line and transitioned to Command format. --- libbe/ui/command_line.py | 270 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100755 libbe/ui/command_line.py (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py new file mode 100755 index 0000000..e84d32a --- /dev/null +++ b/libbe/ui/command_line.py @@ -0,0 +1,270 @@ +# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Chris Ball +# Gianluca Montecchi +# Oleg Romanyshyn +# W. Trevor King +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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. + +""" +A command line interface to Bugs Everywhere. +""" + +import optparse +import os +import sys + +import libbe +import libbe.command +import libbe.command.util +import libbe.version +import libbe.ui.util.pager + +if libbe.TESTING == True: + import doctest + +class CallbackExit (Exception): + pass + +class CmdOptionParser(optparse.OptionParser): + def __init__(self, command): + self.command = command + optparse.OptionParser.__init__(self) + self.disable_interspersed_args() + self.remove_option('-h') + self._option_by_name = {} + for option in self.command.options: + self._add_option(option) + + def _add_option(self, option): + option.validate() + self._option_by_name[option.name] = option + opt_strings = ['--'+option.name] + if option.short_name != None: + opt_strings.append('-'+option.short_name) + if option.arg == None: # a callback option + opt = optparse.Option( + *opt_strings, action='callback', + callback=self.callback, help=option.help) + else: + kwargs = {} + if option.arg.type == 'bool': + action = 'store_true' + else: + kwargs['type'] = option.arg.type + action = 'store' + opt = optparse.Option( + *opt_strings, metavar=option.arg.metavar, + default=option.arg.default, action=action, + help=option.help, **kwargs) + opt._option = option + self.add_option(opt) + + def parse_args(self, args=None, values=None): + args = self._get_args(args) + options,parsed_args = optparse.OptionParser.parse_args( + self, args=args, values=values) + for name,value in options.__dict__.items(): + if value == '--complete': + argument = None + option = self._option_by_name[name] + if option.arg != None: + argument = option.arg + fragment = None + indices = [i for i,arg in enumerate(args) + if arg == '--complete'] + for i in indices: + assert i > 0 # this --complete is an option value + if args[i-1] in ['--%s' % o.name + for o in self.command.options]: + name = args[i-1][2:] + if name == option.name: + break + elif option.short_name != None \ + and args[i-1].startswith('-') \ + and args[i-1].endswith(option.short_name): + break + if i+1 < len(args): + fragment = args[i+1] + self.complete(argument, fragment) + for i,arg in enumerate(parsed_args): + if arg == '--complete': + if i < len(self.command.args): + argument = self.command.args[i] + else: + argument = self.command.args[-1] + if argument.repeatable == False: + raise libbe.command.UserError('Too many arguments') + fragment = None + if i < len(args) - 1: + fragment = args[i+1] + self.complete(argument, fragment) + if len(parsed_args) > len(self.command.args) \ + and self.command.args[-1] == False: + raise libbe.command.UserError('Too many arguments') + for arg in self.command.args[len(parsed_args):]: + if arg.optional == False: + raise libbe.command.UserError( + 'Missing required argument %s' % arg.metavar) + return (options, parsed_args) + + def callback(self, option, opt, value, parser): + command_option = option._option + if command_option.name == 'complete': + argument = None + fragment = None + if len(parser.rargs) > 0: + fragment = parser.rargs[0] + self.complete(argument, fragment) + else: + print command_option.callback( + self.command, command_option, value) + raise CallbackExit + + def complete(self, argument=None, fragment=None): + print argument, fragment + comps = self.command.complete(argument, fragment) + if fragment != None: + comps = [c for c in comps if c.startswith(fragment)] + print '\n'.join(comps) + raise CallbackExit + + +class BE (libbe.command.Command): + """Class for parsing the command line arguments for `be`. + This class does not contain a useful _run() method. Call this + module's main() function instead. + + >>> be = BE() + >>> p = CmdOptionParser(be) + >>> p.exit_after_callback = False + >>> try: + ... options,args = p.parse_args(['--help']) # doctest: +ELLIPSIS + ... except CallbackExit: + ... pass + usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]] + + Options: + -h HELP, --help=HELP Print a help message. + + --complete=STRING Print a list of possible completions. + + --version=VERSION Print version string. + ... + >>> options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS + """ + name = 'be' + + def __init__(self, *args, **kwargs): + libbe.command.Command.__init__(self, *args, **kwargs) + self.options.extend([ + libbe.command.Option(name='version', + help='Print version string.', + callback=self.version), + libbe.command.Option(name='full-version', + help='Print full version information.', + callback=self.full_version), + libbe.command.Option(name='repo', short_name='r', + help='Select BE repository (see `be help repo`) rather ' + 'than the current directory.', + arg=libbe.command.Argument( + name='repo', metavar='REPO', default='.', + completion_callback=libbe.command.util.complete_path)), + libbe.command.Option(name='paginate', + help='Pipe all output into less (or if set, $PAGER).'), + libbe.command.Option(name='no-pager', + help='Do not pipe git output into a pager.'), + ]) + self.args.extend([ + libbe.command.Argument( + name='command', optional=False, + completion_callback=libbe.command.util.complete_command), + libbe.command.Argument( + name='args', optional=True, repeatable=True) + ]) + + def _usage(self): + return 'usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]]' + + def _long_help(self): + cmdlist = [] + for name in libbe.command.commands(): + module = libbe.command.get_command(name) + Class = getattr(module, name.capitalize()) + cmdlist.append((name, Class.__doc__.splitlines()[0])) + 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.']) + return '\n'.join(ret) + + def version(self, *args): + return libbe.version.version(verbose=False) + + def full_version(self, *args): + return libbe.version.version(verbose=True) + +def main(): + parser = CmdOptionParser(BE()) + try: + options,args = parser.parse_args() + except CallbackExit: + return 0 + except libbe.command.UserError, e: + print 'ERROR:\n', e + return 1 + + paginate = 'auto' + if options.paginate == True: + paginate = 'always' + if options.no_pager== True: + paginate = 'never' + libbe.ui.util.pager.run_pager(paginate) + + command_name = args[0] + try: + module = libbe.command.get_command(command_name) + except libbe.command.UnknownCommand, e: + print e + return 1 + Class = getattr(module, command_name.capitalize()) + command = Class() + parser = CmdOptionParser(command) + if command.requires_bugdir == True: + storage = libbe.storage.get_storage(options['repo']) + storage.connect() + bugdir = BugDir(storage) + else: + storage = None + bugdir = None + try: + options,args = parser.parse_args(args[1:]) + command.run(bugdir, options, args) + except CallbackExit: + if storage != None: storage.disconnect() + return 0 + except libbe.command.UserError, e: + if storage != None: storage.disconnect() + print 'ERROR:\n', e + return 1 + if storage != None: storage.disconnect() + return 0 + +if __name__ == '__main__': + sys.exit(main()) -- cgit From 4d057dab603f42ec40b911dbee6792dcf107bd14 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 06:19:23 -0500 Subject: Converted libbe.storage.vcs.base to new Storage format. --- libbe/ui/command_line.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index e84d32a..b0b52af 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -27,6 +27,7 @@ import os import sys import libbe +import libbe.bugdir import libbe.command import libbe.command.util import libbe.version @@ -52,11 +53,14 @@ class CmdOptionParser(optparse.OptionParser): option.validate() self._option_by_name[option.name] = option opt_strings = ['--'+option.name] + dest = option.name.replace('-', '_') + assert '_' not in option.name, \ + 'Non-reconstructable option name %s' % option.name if option.short_name != None: opt_strings.append('-'+option.short_name) if option.arg == None: # a callback option opt = optparse.Option( - *opt_strings, action='callback', + *opt_strings, action='callback', dest=dest, callback=self.callback, help=option.help) else: kwargs = {} @@ -68,7 +72,7 @@ class CmdOptionParser(optparse.OptionParser): opt = optparse.Option( *opt_strings, metavar=option.arg.metavar, default=option.arg.default, action=action, - help=option.help, **kwargs) + dest=dest, help=option.help, **kwargs) opt._option = option self.add_option(opt) @@ -76,7 +80,11 @@ class CmdOptionParser(optparse.OptionParser): args = self._get_args(args) options,parsed_args = optparse.OptionParser.parse_args( self, args=args, values=values) - for name,value in options.__dict__.items(): + options = options.__dict__ + for name,value in options.items(): + if '_' in name: # reconstruct original option name + options[name.replace('_', '-')] = options.pop(name) + for name,value in options.items(): if value == '--complete': argument = None option = self._option_by_name[name] @@ -231,9 +239,9 @@ def main(): return 1 paginate = 'auto' - if options.paginate == True: + if options['paginate'] == True: paginate = 'always' - if options.no_pager== True: + if options['no-pager'] == True: paginate = 'never' libbe.ui.util.pager.run_pager(paginate) @@ -249,7 +257,7 @@ def main(): if command.requires_bugdir == True: storage = libbe.storage.get_storage(options['repo']) storage.connect() - bugdir = BugDir(storage) + bugdir = libbe.bugdir.BugDir(storage, from_storage=True) else: storage = None bugdir = None -- cgit From c2a50865f1ea73f43f2d347b2e7595a484f43e78 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 13 Dec 2009 06:33:50 -0500 Subject: Rearrange libbe.ui.command_line.CmdOptionParser._add_option() for Python 2.5 Python 2.6 doesn't mind, but 2.5 doesn't like kwargs after a * expansion: $ ./be list Traceback (most recent call last): File "./be", line 5, in import libbe.ui.command_line File "/home/wking/src/fun/be/be.restructure/libbe/ui/command_line.py", line 63 *opt_strings, action='callback', dest=dest, ^ SyntaxError: invalid syntax --- libbe/ui/command_line.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index b0b52af..60741f5 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -52,27 +52,28 @@ class CmdOptionParser(optparse.OptionParser): def _add_option(self, option): option.validate() self._option_by_name[option.name] = option - opt_strings = ['--'+option.name] - dest = option.name.replace('-', '_') + long_opt = '--%s' % option.name + if option.short_name != None: + short_opt = '-%s' % option.short_name assert '_' not in option.name, \ 'Non-reconstructable option name %s' % option.name - if option.short_name != None: - opt_strings.append('-'+option.short_name) + kwargs = {'dest':option.name.replace('-', '_'), + 'help':option.help} if option.arg == None: # a callback option - opt = optparse.Option( - *opt_strings, action='callback', dest=dest, - callback=self.callback, help=option.help) + kwargs['action'] = 'callback' + kwargs['callback'] = self.callback else: - kwargs = {} if option.arg.type == 'bool': - action = 'store_true' + kwargs['action'] = 'store_true' else: kwargs['type'] = option.arg.type - action = 'store' - opt = optparse.Option( - *opt_strings, metavar=option.arg.metavar, - default=option.arg.default, action=action, - dest=dest, help=option.help, **kwargs) + kwargs['action'] = 'store' + kwargs['metavar'] = option.arg.metavar + kwargs['default'] = option.arg.default + if option.short_name != None: + opt = optparse.Option(short_opt, long_opt, **kwargs) + else: + opt = optparse.Option(long_opt, **kwargs) opt._option = option self.add_option(opt) -- cgit From 2f0ceedba5b6619faf476cd1aa67e826e91d5c7c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 03:29:20 -0500 Subject: Transitioned init to Command format --- libbe/ui/command_line.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 60741f5..c59a302 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -255,16 +255,18 @@ def main(): Class = getattr(module, command_name.capitalize()) command = Class() parser = CmdOptionParser(command) + storage = None + bugdir = None if command.requires_bugdir == True: + assert command.requires_unconnected_storage == False storage = libbe.storage.get_storage(options['repo']) storage.connect() bugdir = libbe.bugdir.BugDir(storage, from_storage=True) - else: - storage = None - bugdir = None + elif: command.requires_unconnected_storage == True: + storage = libbe.storage.get_storage(options['repo']) try: options,args = parser.parse_args(args[1:]) - command.run(bugdir, options, args) + command.run(storage, bugdir, options, args) except CallbackExit: if storage != None: storage.disconnect() return 0 -- cgit From 19fe0817ba7c2cd04caea3adfa82d4490288a548 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 07:37:51 -0500 Subject: Transitioned comment to Command format --- libbe/ui/command_line.py | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index c59a302..4042123 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -43,8 +43,8 @@ class CmdOptionParser(optparse.OptionParser): def __init__(self, command): self.command = command optparse.OptionParser.__init__(self) - self.disable_interspersed_args() self.remove_option('-h') + self.disable_interspersed_args() self._option_by_name = {} for option in self.command.options: self._add_option(option) @@ -62,12 +62,13 @@ class CmdOptionParser(optparse.OptionParser): if option.arg == None: # a callback option kwargs['action'] = 'callback' kwargs['callback'] = self.callback + elif option.arg.type == 'bool': + kwargs['action'] = 'store_true' + kwargs['metavar'] = None + kwargs['default'] = False else: - if option.arg.type == 'bool': - kwargs['action'] = 'store_true' - else: - kwargs['type'] = option.arg.type - kwargs['action'] = 'store' + kwargs['type'] = option.arg.type + kwargs['action'] = 'store' kwargs['metavar'] = option.arg.metavar kwargs['default'] = option.arg.default if option.short_name != None: @@ -143,7 +144,6 @@ class CmdOptionParser(optparse.OptionParser): raise CallbackExit def complete(self, argument=None, fragment=None): - print argument, fragment comps = self.command.complete(argument, fragment) if fragment != None: comps = [c for c in comps if c.startswith(fragment)] @@ -166,13 +166,24 @@ class BE (libbe.command.Command): usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]] Options: - -h HELP, --help=HELP Print a help message. + -h, --help Print a help message. - --complete=STRING Print a list of possible completions. + --complete Print a list of possible completions. - --version=VERSION Print version string. + --version Print version string. + ... + >>> try: + ... options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS + ... except CallbackExit: + ... print ' got callback' + --help + --complete + --version ... - >>> options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS + subscribe + tag + target + got callback """ name = 'be' @@ -262,8 +273,11 @@ def main(): storage = libbe.storage.get_storage(options['repo']) storage.connect() bugdir = libbe.bugdir.BugDir(storage, from_storage=True) - elif: command.requires_unconnected_storage == True: + elif command.requires_storage == True \ + or command.requires_unconnected_storage == True: storage = libbe.storage.get_storage(options['repo']) + if command.requires_unconnected_storage == False: + storage.connect() try: options,args = parser.parse_args(args[1:]) command.run(storage, bugdir, options, args) -- cgit From 3e5823d0985a54dec37f103dc72fda604d12a948 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 18:25:28 -0500 Subject: Transitioned import_xml to Command-format --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 4042123..feeccd4 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -222,7 +222,7 @@ class BE (libbe.command.Command): cmdlist = [] for name in libbe.command.commands(): module = libbe.command.get_command(name) - Class = getattr(module, name.capitalize()) + Class = libbe.command.get_command_class(module, name) cmdlist.append((name, Class.__doc__.splitlines()[0])) cmdlist.sort() longest_cmd_len = max([len(name) for name,desc in cmdlist]) -- cgit From f9ee7a537561be80b9c232dd4fc848ddb564f6b0 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 20:33:35 -0500 Subject: Transitioned help to Command-format --- libbe/ui/command_line.py | 1 + 1 file changed, 1 insertion(+) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index feeccd4..84f9450 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -265,6 +265,7 @@ def main(): return 1 Class = getattr(module, command_name.capitalize()) command = Class() + command.ui = self parser = CmdOptionParser(command) storage = None bugdir = None -- cgit From 1b9c628529848af370adbc67b5ba298236a1b86d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 23:15:58 -0500 Subject: Transitioned severity to Command-format, also added Command._get_*() The old .requires_* thing was rediculous. The new ._get_*() callbacks allow the caller to provide a means for getting the expensive structures, which the command can use, or not, as required. This will also make it easier to implement the completion callbacks. The callbacks should probably have matching .set_*() methods, to avoid the current cache tweaking cmd._storage = ... etc. But that can wait for now... --- libbe/ui/command_line.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 84f9450..0aa34f7 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -264,29 +264,18 @@ def main(): print e return 1 Class = getattr(module, command_name.capitalize()) - command = Class() - command.ui = self + def gucs(): + return libbe.storage.get_storage(options['repo']) + command = Class(get_unconnected_storage=gucs, ui=ui) parser = CmdOptionParser(command) - storage = None - bugdir = None - if command.requires_bugdir == True: - assert command.requires_unconnected_storage == False - storage = libbe.storage.get_storage(options['repo']) - storage.connect() - bugdir = libbe.bugdir.BugDir(storage, from_storage=True) - elif command.requires_storage == True \ - or command.requires_unconnected_storage == True: - storage = libbe.storage.get_storage(options['repo']) - if command.requires_unconnected_storage == False: - storage.connect() try: options,args = parser.parse_args(args[1:]) - command.run(storage, bugdir, options, args) + command.run(options, args) except CallbackExit: - if storage != None: storage.disconnect() + command.cleanup() return 0 except libbe.command.UserError, e: - if storage != None: storage.disconnect() + command.cleanup() print 'ERROR:\n', e return 1 if storage != None: storage.disconnect() -- cgit From 6e474b0dc04efa60aaeb82c09d4fd4f4b10678da Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 14 Dec 2009 23:31:57 -0500 Subject: Transitioned status to Command-format --- libbe/ui/command_line.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 0aa34f7..bb38be8 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -241,7 +241,8 @@ class BE (libbe.command.Command): return libbe.version.version(verbose=True) def main(): - parser = CmdOptionParser(BE()) + be = BE() + parser = CmdOptionParser(be) try: options,args = parser.parse_args() except CallbackExit: @@ -264,9 +265,12 @@ def main(): print e return 1 Class = getattr(module, command_name.capitalize()) - def gucs(): - return libbe.storage.get_storage(options['repo']) - command = Class(get_unconnected_storage=gucs, ui=ui) + class GUCS (object): + def __init__(self, repo): + self.repo = repo + def __call__(self): + return libbe.storage.get_storage(self.repo) + command = Class(get_unconnected_storage=GUCS(options['repo']), ui=be) parser = CmdOptionParser(command) try: options,args = parser.parse_args(args[1:]) @@ -278,7 +282,7 @@ def main(): command.cleanup() print 'ERROR:\n', e return 1 - if storage != None: storage.disconnect() + command.cleanup() return 0 if __name__ == '__main__': -- cgit From a1bd5432ffbf28bf2fadfed8a5b2db917f243344 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Dec 2009 01:52:17 -0500 Subject: Transitioned tag to Command-format --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index bb38be8..2dff930 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -122,7 +122,7 @@ class CmdOptionParser(optparse.OptionParser): fragment = args[i+1] self.complete(argument, fragment) if len(parsed_args) > len(self.command.args) \ - and self.command.args[-1] == False: + and self.command.args[-1].repeatable == False: raise libbe.command.UserError('Too many arguments') for arg in self.command.args[len(parsed_args):]: if arg.optional == False: -- cgit From 4370270929db62a32d168ae221ecc70a2d80269e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Dec 2009 02:38:51 -0500 Subject: Transitioned target to Command-format --- libbe/ui/command_line.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 2dff930..ce0e55e 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -223,6 +223,8 @@ class BE (libbe.command.Command): for name in libbe.command.commands(): module = libbe.command.get_command(name) Class = libbe.command.get_command_class(module, name) + assert hasattr(Class, '__doc__') and Class.__doc__ != None, \ + 'Command class %s missing docstring' % Class cmdlist.append((name, Class.__doc__.splitlines()[0])) cmdlist.sort() longest_cmd_len = max([len(name) for name,desc in cmdlist]) -- cgit From 89b7a1411e4658e831f5d635534b24355dbb941d Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 15 Dec 2009 06:44:20 -0500 Subject: Fixed libbe.command.diff + ugly BugDir.duplicate_bugdir implementation duplicate_bugdir() works, but for the vcs backends, it could require shelling out for _every_ file read. This could, and probably will, be horribly slow. Still it works ;). I'm not sure what a better implementation would be. The old implementation checked out the entire earlier state into a temporary directory pros: single shell out, simple upgrade implementation cons: wouldn't work well for HTTP backens I think a good solution would run along the lines of the currently commented out code in duplicate_bugdir(), where a VersionedStorage.changed_since(revision) call would give you a list of changed files. diff could work off of that directly, without the need to generate a whole duplicate bugdir. I'm stuck on how to handle upgrades though... Also removed trailing whitespace from all python files. --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index ce0e55e..6eead67 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -259,7 +259,7 @@ def main(): if options['no-pager'] == True: paginate = 'never' libbe.ui.util.pager.run_pager(paginate) - + command_name = args[0] try: module = libbe.command.get_command(command_name) -- cgit From cfebc238cbda9b6338ec57d5c215c4cbf0246f8b Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 27 Dec 2009 16:50:36 -0500 Subject: Moved InvalidStorageVersion from libbe.command to libbe.storage Also added ConnectionError pretty-print to ui.command_line, storage version checking to BugDir.duplicate_bugdir(), and optional revision argument to Storage.storage_version(). --- libbe/ui/command_line.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 6eead67..3812789 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -284,6 +284,10 @@ def main(): command.cleanup() print 'ERROR:\n', e return 1 + except libbe.storage.ConnectionError, e: + command.cleanup() + print 'Connection Error:\n', e + return 1 command.cleanup() return 0 -- cgit From 83d7624d1deeb73b7f0baddef88069ff27a128ab Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 07:09:18 -0500 Subject: Don't run pager for the 'comment' command. It may need access to the tty for the spawned editor. --- libbe/ui/command_line.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 3812789..856f810 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -253,13 +253,6 @@ def main(): print 'ERROR:\n', e return 1 - paginate = 'auto' - if options['paginate'] == True: - paginate = 'always' - if options['no-pager'] == True: - paginate = 'never' - libbe.ui.util.pager.run_pager(paginate) - command_name = args[0] try: module = libbe.command.get_command(command_name) @@ -274,6 +267,17 @@ def main(): return libbe.storage.get_storage(self.repo) command = Class(get_unconnected_storage=GUCS(options['repo']), ui=be) parser = CmdOptionParser(command) + + if command.name in ['comment']: + paginate = 'never' + else: + paginate = 'auto' + if options['paginate'] == True: + paginate = 'always' + if options['no-pager'] == True: + paginate = 'never' + libbe.ui.util.pager.run_pager(paginate) + try: options,args = parser.parse_args(args[1:]) command.run(options, args) -- cgit From e2b648f2148d7b6550fb3a4bcfde4eff714a7ec6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 28 Dec 2009 10:56:04 -0500 Subject: Allow external use of Command.usage() and use CmdOptionParser.set_usage() This fixes $ python be diff -2 Usage: be [options] be: error: no such option: -2 and we now get the correct output $ python be diff -2 Usage: be diff [options] [REVISION] be: error: no such option: -2 --- libbe/ui/command_line.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 856f810..b5a3991 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -48,6 +48,8 @@ class CmdOptionParser(optparse.OptionParser): self._option_by_name = {} for option in self.command.options: self._add_option(option) + self.set_usage(command.usage()) + def _add_option(self, option): option.validate() @@ -215,7 +217,7 @@ class BE (libbe.command.Command): name='args', optional=True, repeatable=True) ]) - def _usage(self): + def usage(self): return 'usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]]' def _long_help(self): -- cgit From 4372a17b4215df25b3da0b68daf4d6b490a8955c Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 19:00:40 -0500 Subject: Fixed up the completion helpers in libbe.command.util This entailed a fairly thorough cleanup of libbe.util.id. Remaining unimplemented completion helpers: * complete_assigned() * complete_extra_strings() Since these would require scanning all (active?) bugs to compile lists, and I was feeling lazy... --- libbe/ui/command_line.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index b5a3991..b99f812 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -113,15 +113,19 @@ class CmdOptionParser(optparse.OptionParser): self.complete(argument, fragment) for i,arg in enumerate(parsed_args): if arg == '--complete': - if i < len(self.command.args): + if i > 0 and self.command.name == 'be': + break # let this pass through for the command parser to handle + elif i < len(self.command.args): argument = self.command.args[i] + elif len(self.command.args) == 0: + break # command doesn't take arguments else: argument = self.command.args[-1] if argument.repeatable == False: raise libbe.command.UserError('Too many arguments') fragment = None - if i < len(args) - 1: - fragment = args[i+1] + if i < len(parsed_args) - 1: + fragment = parsed_args[i+1] self.complete(argument, fragment) if len(parsed_args) > len(self.command.args) \ and self.command.args[-1].repeatable == False: @@ -149,7 +153,8 @@ class CmdOptionParser(optparse.OptionParser): comps = self.command.complete(argument, fragment) if fragment != None: comps = [c for c in comps if c.startswith(fragment)] - print '\n'.join(comps) + if len(comps) > 0: + print '\n'.join(comps) raise CallbackExit -- cgit From 182a44a4a284e118da03c1182f2b9a8b76dd0d93 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 29 Dec 2009 19:53:13 -0500 Subject: Don't worry about whitespace in `be --help` --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index b99f812..9df474e 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -167,7 +167,7 @@ class BE (libbe.command.Command): >>> p = CmdOptionParser(be) >>> p.exit_after_callback = False >>> try: - ... options,args = p.parse_args(['--help']) # doctest: +ELLIPSIS + ... options,args = p.parse_args(['--help']) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE ... except CallbackExit: ... pass usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]] -- cgit From cfae8a8302f06a84196700138d7ddbb25e91ea31 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 31 Dec 2009 14:32:39 -0500 Subject: Added UserInterface and other improved abstractions for command handling --- libbe/ui/command_line.py | 78 ++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 35 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 9df474e..b36d251 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -145,7 +145,7 @@ class CmdOptionParser(optparse.OptionParser): fragment = parser.rargs[0] self.complete(argument, fragment) else: - print command_option.callback( + print >> self.command.stdout, command_option.callback( self.command, command_option, value) raise CallbackExit @@ -154,16 +154,18 @@ class CmdOptionParser(optparse.OptionParser): if fragment != None: comps = [c for c in comps if c.startswith(fragment)] if len(comps) > 0: - print '\n'.join(comps) + print >> self.command.stdout, '\n'.join(comps) raise CallbackExit - class BE (libbe.command.Command): """Class for parsing the command line arguments for `be`. This class does not contain a useful _run() method. Call this module's main() function instead. - >>> be = BE() + >>> ui = libbe.command.UserInterface() + >>> ui.io.stdout = sys.stdout + >>> be = BE(ui=ui) + >>> ui.io.setup_command(be) >>> p = CmdOptionParser(be) >>> p.exit_after_callback = False >>> try: @@ -228,8 +230,7 @@ class BE (libbe.command.Command): def _long_help(self): cmdlist = [] for name in libbe.command.commands(): - module = libbe.command.get_command(name) - Class = libbe.command.get_command_class(module, name) + Class = libbe.command.get_command_class(command_name=name) assert hasattr(Class, '__doc__') and Class.__doc__ != None, \ 'Command class %s missing docstring' % Class cmdlist.append((name, Class.__doc__.splitlines()[0])) @@ -249,31 +250,51 @@ class BE (libbe.command.Command): def full_version(self, *args): return libbe.version.version(verbose=True) +def dispatch(ui, command, args): + parser = CmdOptionParser(command) + try: + options,args = parser.parse_args(args) + ret = ui.run(command, options, args) + except CallbackExit: + return 0 + except libbe.command.UserError, e: + print >> ui.io.stdout, 'ERROR:\n', e + return 1 + except libbe.storage.ConnectionError, e: + print >> ui.io.stdout, 'Connection Error:\n', e + return 1 + except (libbe.util.id.MultipleIDMatches, libbe.util.id.NoIDMatches, + libbe.util.id.InvalidIDStructure), e: + print >> ui.io.stdout, 'Invalid id:\n', e + return 1 + finally: + command.cleanup() + return ret + def main(): - be = BE() + io = libbe.command.StdInputOutput() + ui = libbe.command.UserInterface(io) + ui.restrict_file_access = False + ui.storage_callbacks = None + be = BE(ui=ui) parser = CmdOptionParser(be) try: options,args = parser.parse_args() except CallbackExit: return 0 except libbe.command.UserError, e: - print 'ERROR:\n', e + print >> be.stdout, 'ERROR:\n', e return 1 - command_name = args[0] + command_name = args.pop(0) try: - module = libbe.command.get_command(command_name) + Class = libbe.command.get_command_class(command_name=command_name) except libbe.command.UnknownCommand, e: - print e + print >> be.stdout, e return 1 - Class = getattr(module, command_name.capitalize()) - class GUCS (object): - def __init__(self, repo): - self.repo = repo - def __call__(self): - return libbe.storage.get_storage(self.repo) - command = Class(get_unconnected_storage=GUCS(options['repo']), ui=be) - parser = CmdOptionParser(command) + + ui.storage_callbacks = libbe.command.StorageCallbacks(options['repo']) + command = Class(ui=ui) if command.name in ['comment']: paginate = 'never' @@ -285,22 +306,9 @@ def main(): paginate = 'never' libbe.ui.util.pager.run_pager(paginate) - try: - options,args = parser.parse_args(args[1:]) - command.run(options, args) - except CallbackExit: - command.cleanup() - return 0 - except libbe.command.UserError, e: - command.cleanup() - print 'ERROR:\n', e - return 1 - except libbe.storage.ConnectionError, e: - command.cleanup() - print 'Connection Error:\n', e - return 1 - command.cleanup() - return 0 + ret = dispatch(ui, command, args) + ui.cleanup() + return ret if __name__ == '__main__': sys.exit(main()) -- cgit From 2d1562d951e763fed71fe60e77cc9921be9abdc9 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Thu, 31 Dec 2009 15:33:39 -0500 Subject: Use fragment in base command completion + command io fixups. --- libbe/ui/command_line.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index b36d251..1c7399d 100755 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -277,24 +277,27 @@ def main(): ui.restrict_file_access = False ui.storage_callbacks = None be = BE(ui=ui) + ui.setup_command(be) + parser = CmdOptionParser(be) try: options,args = parser.parse_args() except CallbackExit: return 0 except libbe.command.UserError, e: - print >> be.stdout, 'ERROR:\n', e + print >> ui.io.stdout, 'ERROR:\n', e return 1 command_name = args.pop(0) try: Class = libbe.command.get_command_class(command_name=command_name) except libbe.command.UnknownCommand, e: - print >> be.stdout, e + print >> ui.io.stdout, e return 1 ui.storage_callbacks = libbe.command.StorageCallbacks(options['repo']) command = Class(ui=ui) + ui.setup_command(command) if command.name in ['comment']: paginate = 'never' -- cgit From 4d4283ecd654f1efb058cd7f7dba6be88b70ee92 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jan 2010 08:11:08 -0500 Subject: Updated copyright information --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 7f74782..f57983f 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -1,4 +1,4 @@ -# Copyright (C) 2005-2009 Aaron Bentley and Panometrics, Inc. +# Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc. # Chris Ball # Gianluca Montecchi # Oleg Romanyshyn -- cgit From 5f26c407789a2f8fc51d89b6d0c590253b50c754 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Fri, 1 Jan 2010 14:43:03 -0500 Subject: Added libbe.command.serve and libbe.storage.http for HTTP backend. Now the following works: some-BE-dir$ ./be serve $ ./be --repo http://localhost:8000 list I haven't come up with a clean idea for testing this yet, so other commands may be broken, but once we get the testing working, it shouldn't be too hard to get everything working over HTTP :). --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index f57983f..7ba6cf5 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -299,7 +299,7 @@ def main(): command = Class(ui=ui) ui.setup_command(command) - if command.name in ['comment', 'commit']: + if command.name in ['comment', 'commit', 'serve']: paginate = 'never' else: paginate = 'auto' -- cgit From 34b5bbd98e420672ecce64ba085bbf1a4205267e Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 20 Jan 2010 07:19:56 -0500 Subject: Add better help message on COMMAND-less be call --- libbe/ui/command_line.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 7ba6cf5..17f7b35 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -285,7 +285,14 @@ def main(): except CallbackExit: return 0 except libbe.command.UserError, e: - print >> ui.io.stdout, 'ERROR:\n', e + if str(e).endswith('COMMAND'): + # no command given, print usage string + print >> ui.io.stdout, 'ERROR:' + print >> ui.io.stdout, be.usage(), '\n', e + print >> ui.io.stdout, 'For example, try' + print >> ui.io.stdout, ' be help' + else: + print >> ui.io.stdout, 'ERROR:\n', e return 1 command_name = args.pop(0) -- cgit From 116c6eb9e54dd223d715923dc6fb32e87a89aad6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 20 Jan 2010 07:32:56 -0500 Subject: Fixed `be help` (used to raise NotImplementedError) --- libbe/ui/command_line.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 17f7b35..9c97eec 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -250,6 +250,16 @@ class BE (libbe.command.Command): def full_version(self, *args): return libbe.version.version(verbose=True) +class CommandLine (libbe.command.UserInterface): + def __init__(self, *args, **kwargs): + libbe.command.UserInterface.__init__(self, *args, **kwargs) + self.restrict_file_access = False + self.storage_callbacks = None + def help(self): + be = BE(ui=self) + self.setup_command(be) + return be.help() + def dispatch(ui, command, args): parser = CmdOptionParser(command) try: @@ -273,9 +283,7 @@ def dispatch(ui, command, args): def main(): io = libbe.command.StdInputOutput() - ui = libbe.command.UserInterface(io) - ui.restrict_file_access = False - ui.storage_callbacks = None + ui = CommandLine(io) be = BE(ui=ui) ui.setup_command(be) -- cgit From 55f1e8588edc35410dd16b883e7cddd06ebc4ed6 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Wed, 20 Jan 2010 15:44:39 -0500 Subject: Strip footers (signatures) in be-mail-to-xml --- libbe/ui/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 9c97eec..89d791d 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -314,7 +314,7 @@ def main(): command = Class(ui=ui) ui.setup_command(command) - if command.name in ['comment', 'commit', 'serve']: + if command.name in ['comment', 'commit', 'import-xml', 'serve']: paginate = 'never' else: paginate = 'auto' -- cgit From 148379492d314c0eb98ee8559aea7a2fd0baaeb2 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Mon, 1 Feb 2010 10:59:52 -0500 Subject: Clearer UnicodeDecodeError message in command_line.dispatch(). See #bea/e30# ("Where should the vcs-name and encoding configuration options live?") for details. --- libbe/ui/command_line.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'libbe/ui/command_line.py') diff --git a/libbe/ui/command_line.py b/libbe/ui/command_line.py index 89d791d..dd10954 100644 --- a/libbe/ui/command_line.py +++ b/libbe/ui/command_line.py @@ -267,6 +267,14 @@ def dispatch(ui, command, args): ret = ui.run(command, options, args) except CallbackExit: return 0 + except UnicodeDecodeError, e: + print >> ui.io.stdout, '\n'.join([ + 'ERROR:', str(e), + 'You should set a locale that supports unicode, e.g.', + ' export LANG=en_US.utf8', + 'See http://docs.python.org/library/locale.html for details', + ]) + return 1 except libbe.command.UserError, e: print >> ui.io.stdout, 'ERROR:\n', e return 1 -- cgit