1 # Copyright (C) 2005-2010 Aaron Bentley and Panometrics, Inc.
2 # Chris Ball <cjb@laptop.org>
3 # Gianluca Montecchi <gian@grys.it>
4 # Oleg Romanyshyn <oromanyshyn@panoramicfeedback.com>
5 # W. Trevor King <wking@drexel.edu>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 A command line interface to Bugs Everywhere.
32 import libbe.command.util
34 import libbe.ui.util.pager
36 if libbe.TESTING == True:
39 class CallbackExit (Exception):
42 class CmdOptionParser(optparse.OptionParser):
43 def __init__(self, command):
44 self.command = command
45 optparse.OptionParser.__init__(self)
46 self.remove_option('-h')
47 self.disable_interspersed_args()
48 self._option_by_name = {}
49 for option in self.command.options:
50 self._add_option(option)
51 self.set_usage(command.usage())
54 def _add_option(self, option):
56 self._option_by_name[option.name] = option
57 long_opt = '--%s' % option.name
58 if option.short_name != None:
59 short_opt = '-%s' % option.short_name
60 assert '_' not in option.name, \
61 'Non-reconstructable option name %s' % option.name
62 kwargs = {'dest':option.name.replace('-', '_'),
64 if option.arg == None: # a callback option
65 kwargs['action'] = 'callback'
66 kwargs['callback'] = self.callback
67 elif option.arg.type == 'bool':
68 kwargs['action'] = 'store_true'
69 kwargs['metavar'] = None
70 kwargs['default'] = False
72 kwargs['type'] = option.arg.type
73 kwargs['action'] = 'store'
74 kwargs['metavar'] = option.arg.metavar
75 kwargs['default'] = option.arg.default
76 if option.short_name != None:
77 opt = optparse.Option(short_opt, long_opt, **kwargs)
79 opt = optparse.Option(long_opt, **kwargs)
83 def parse_args(self, args=None, values=None):
84 args = self._get_args(args)
85 options,parsed_args = optparse.OptionParser.parse_args(
86 self, args=args, values=values)
87 options = options.__dict__
88 for name,value in options.items():
89 if '_' in name: # reconstruct original option name
90 options[name.replace('_', '-')] = options.pop(name)
91 for name,value in options.items():
92 if value == '--complete':
94 option = self._option_by_name[name]
95 if option.arg != None:
98 indices = [i for i,arg in enumerate(args)
99 if arg == '--complete']
101 assert i > 0 # this --complete is an option value
102 if args[i-1] in ['--%s' % o.name
103 for o in self.command.options]:
105 if name == option.name:
107 elif option.short_name != None \
108 and args[i-1].startswith('-') \
109 and args[i-1].endswith(option.short_name):
113 self.complete(argument, fragment)
114 for i,arg in enumerate(parsed_args):
115 if arg == '--complete':
116 if i > 0 and self.command.name == 'be':
117 break # let this pass through for the command parser to handle
118 elif i < len(self.command.args):
119 argument = self.command.args[i]
120 elif len(self.command.args) == 0:
121 break # command doesn't take arguments
123 argument = self.command.args[-1]
124 if argument.repeatable == False:
125 raise libbe.command.UserError('Too many arguments')
127 if i < len(parsed_args) - 1:
128 fragment = parsed_args[i+1]
129 self.complete(argument, fragment)
130 if len(parsed_args) > len(self.command.args) \
131 and self.command.args[-1].repeatable == False:
132 raise libbe.command.UserError('Too many arguments')
133 for arg in self.command.args[len(parsed_args):]:
134 if arg.optional == False:
135 raise libbe.command.UserError(
136 'Missing required argument %s' % arg.metavar)
137 return (options, parsed_args)
139 def callback(self, option, opt, value, parser):
140 command_option = option._option
141 if command_option.name == 'complete':
144 if len(parser.rargs) > 0:
145 fragment = parser.rargs[0]
146 self.complete(argument, fragment)
148 print >> self.command.stdout, command_option.callback(
149 self.command, command_option, value)
152 def complete(self, argument=None, fragment=None):
153 comps = self.command.complete(argument, fragment)
155 comps = [c for c in comps if c.startswith(fragment)]
157 print >> self.command.stdout, '\n'.join(comps)
160 class BE (libbe.command.Command):
161 """Class for parsing the command line arguments for `be`.
162 This class does not contain a useful _run() method. Call this
163 module's main() function instead.
165 >>> ui = libbe.command.UserInterface()
166 >>> ui.io.stdout = sys.stdout
168 >>> ui.io.setup_command(be)
169 >>> p = CmdOptionParser(be)
170 >>> p.exit_after_callback = False
172 ... options,args = p.parse_args(['--help']) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
173 ... except CallbackExit:
175 usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]]
178 -h, --help Print a help message.
180 --complete Print a list of possible completions.
182 --version Print version string.
185 ... options,args = p.parse_args(['--complete']) # doctest: +ELLIPSIS
186 ... except CallbackExit:
187 ... print ' got callback'
199 def __init__(self, *args, **kwargs):
200 libbe.command.Command.__init__(self, *args, **kwargs)
201 self.options.extend([
202 libbe.command.Option(name='version',
203 help='Print version string.',
204 callback=self.version),
205 libbe.command.Option(name='full-version',
206 help='Print full version information.',
207 callback=self.full_version),
208 libbe.command.Option(name='repo', short_name='r',
209 help='Select BE repository (see `be help repo`) rather '
210 'than the current directory.',
211 arg=libbe.command.Argument(
212 name='repo', metavar='REPO', default='.',
213 completion_callback=libbe.command.util.complete_path)),
214 libbe.command.Option(name='paginate',
215 help='Pipe all output into less (or if set, $PAGER).'),
216 libbe.command.Option(name='no-pager',
217 help='Do not pipe git output into a pager.'),
220 libbe.command.Argument(
221 name='command', optional=False,
222 completion_callback=libbe.command.util.complete_command),
223 libbe.command.Argument(
224 name='args', optional=True, repeatable=True)
228 return 'usage: be [options] [COMMAND [command-options] [COMMAND-ARGS ...]]'
230 def _long_help(self):
232 for name in libbe.command.commands():
233 Class = libbe.command.get_command_class(command_name=name)
234 assert hasattr(Class, '__doc__') and Class.__doc__ != None, \
235 'Command class %s missing docstring' % Class
236 cmdlist.append((name, Class.__doc__.splitlines()[0]))
238 longest_cmd_len = max([len(name) for name,desc in cmdlist])
239 ret = ['Bugs Everywhere - Distributed bug tracking',
240 '', 'Supported commands']
241 for name, desc in cmdlist:
242 numExtraSpaces = longest_cmd_len-len(name)
243 ret.append('be %s%*s %s' % (name, numExtraSpaces, '', desc))
244 ret.extend(['', 'Run', ' be help [command]', 'for more information.'])
245 return '\n'.join(ret)
247 def version(self, *args):
248 return libbe.version.version(verbose=False)
250 def full_version(self, *args):
251 return libbe.version.version(verbose=True)
253 class CommandLine (libbe.command.UserInterface):
254 def __init__(self, *args, **kwargs):
255 libbe.command.UserInterface.__init__(self, *args, **kwargs)
256 self.restrict_file_access = False
257 self.storage_callbacks = None
260 self.setup_command(be)
263 def dispatch(ui, command, args):
264 parser = CmdOptionParser(command)
266 options,args = parser.parse_args(args)
267 ret = ui.run(command, options, args)
270 except libbe.command.UserError, e:
271 print >> ui.io.stdout, 'ERROR:\n', e
273 except libbe.storage.ConnectionError, e:
274 print >> ui.io.stdout, 'Connection Error:\n', e
276 except (libbe.util.id.MultipleIDMatches, libbe.util.id.NoIDMatches,
277 libbe.util.id.InvalidIDStructure), e:
278 print >> ui.io.stdout, 'Invalid id:\n', e
285 io = libbe.command.StdInputOutput()
290 parser = CmdOptionParser(be)
292 options,args = parser.parse_args()
295 except libbe.command.UserError, e:
296 if str(e).endswith('COMMAND'):
297 # no command given, print usage string
298 print >> ui.io.stdout, 'ERROR:'
299 print >> ui.io.stdout, be.usage(), '\n', e
300 print >> ui.io.stdout, 'For example, try'
301 print >> ui.io.stdout, ' be help'
303 print >> ui.io.stdout, 'ERROR:\n', e
306 command_name = args.pop(0)
308 Class = libbe.command.get_command_class(command_name=command_name)
309 except libbe.command.UnknownCommand, e:
310 print >> ui.io.stdout, e
313 ui.storage_callbacks = libbe.command.StorageCallbacks(options['repo'])
314 command = Class(ui=ui)
315 ui.setup_command(command)
317 if command.name in ['comment', 'commit', 'import-xml', 'serve']:
321 if options['paginate'] == True:
323 if options['no-pager'] == True:
325 libbe.ui.util.pager.run_pager(paginate)
327 ret = dispatch(ui, command, args)
331 if __name__ == '__main__':