14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
17
19
# TODO: Define arguments by objects, rather than just using names.
18
20
# Those objects can specify the expected type of the argument, which
19
21
# would help with validation and shell completion. They could also provide
50
51
from .option import Option
51
52
from .plugin import disable_plugins, load_plugins, plugin_name
52
53
from . import errors, registry
55
class BzrOptionError(errors.CommandError):
59
class BzrOptionError(errors.BzrCommandError):
57
61
_fmt = "Error in command line options"
60
class CommandAvailableInPlugin(Exception):
62
internal_error = False
64
def __init__(self, cmd_name, plugin_metadata, provider):
66
self.plugin_metadata = plugin_metadata
67
self.cmd_name = cmd_name
68
self.provider = provider
72
_fmt = ('"%s" is not a standard brz command. \n'
73
'However, the following official plugin provides this command: %s\n'
74
'You can install it by going to: %s'
75
% (self.cmd_name, self.plugin_metadata['name'],
76
self.plugin_metadata['url']))
81
64
class CommandInfo(object):
82
65
"""Information about a command."""
143
126
trace.warning('Two plugins defined the same command: %r' % k)
144
127
trace.warning('Not loading the one in %r' %
145
sys.modules[cmd.__module__])
128
sys.modules[cmd.__module__])
146
129
trace.warning('Previously this command was registered from %r' %
147
sys.modules[previous.__module__])
130
sys.modules[previous.__module__])
148
131
for a in cmd.aliases:
149
132
self._alias_dict[a] = k_unsquished
171
154
def register_command(cmd, decorate=False):
172
155
"""Register a plugin command.
174
Should generally be avoided in favor of lazy registration.
157
Should generally be avoided in favor of lazy registration.
176
159
global plugin_cmds
177
160
return plugin_cmds.register(cmd, decorate)
205
188
def _list_bzr_commands(names):
206
189
"""Find commands from bzr's core and plugins.
208
This is not the public interface, just the default hook called by
191
This is not the public interface, just the default hook called by all_command_names.
211
193
# to eliminate duplicates
212
194
names.update(builtin_command_names())
259
241
names.update(cmd.aliases)
260
242
# candidate: modified levenshtein distance against cmd_name.
244
from . import patiencediff
263
245
for name in sorted(names):
264
246
matcher = patiencediff.PatienceSequenceMatcher(None, cmd_name, name)
266
248
opcodes = matcher.get_opcodes()
267
249
for opcode, l1, l2, r1, r2 in opcodes:
268
250
if opcode == 'delete':
270
252
elif opcode == 'replace':
271
distance += max(l2 - l1, r2 - l1)
253
distance += max(l2-l1, r2-l1)
272
254
elif opcode == 'insert':
274
256
elif opcode == 'equal':
275
257
# Score equal ranges lower, making similar commands of equal
276
258
# length closer than arbitrary same length commands.
277
distance -= 0.1 * (l2 - l1)
259
distance -= 0.1 * (l2-l1)
278
260
costs[name] = distance
279
261
costs.update(_GUESS_OVERRIDES.get(cmd_name, {}))
280
costs = sorted((costs[key], key) for key in costs)
262
costs = sorted((value, key) for key, value in costs.iteritems())
283
265
if costs[0][0] > 4:
298
280
# No command found, see if this was a typo
299
281
candidate = guess_command(cmd_name)
300
282
if candidate is not None:
301
raise errors.CommandError(
302
gettext('unknown command "%s". Perhaps you meant "%s"')
303
% (cmd_name, candidate))
304
raise errors.CommandError(gettext('unknown command "%s"')
283
raise errors.BzrCommandError(
284
gettext('unknown command "%s". Perhaps you meant "%s"')
285
% (cmd_name, candidate))
286
raise errors.BzrCommandError(gettext('unknown command "%s"')
308
290
def _get_cmd_object(cmd_name, plugins_override=True, check_missing=True):
346
class NoPluginAvailable(errors.BzrError):
350
328
def _try_plugin_provider(cmd_name):
351
329
"""Probe for a plugin provider having cmd_name."""
353
331
plugin_metadata, provider = probe_for_provider(cmd_name)
354
raise CommandAvailableInPlugin(cmd_name, plugin_metadata, provider)
355
except NoPluginAvailable:
332
raise errors.CommandAvailableInPlugin(cmd_name,
333
plugin_metadata, provider)
334
except errors.NoPluginAvailable:
367
346
for provider in command_providers_registry:
369
348
return provider.plugin_for_command(cmd_name), provider
370
except NoPluginAvailable:
349
except errors.NoPluginAvailable:
372
raise NoPluginAvailable(cmd_name)
351
raise errors.NoPluginAvailable(cmd_name)
375
354
def _get_bzr_command(cmd_or_None, cmd_name):
492
471
Functions will be called in LIFO order.
494
self._exit_stack.callback(cleanup_func, *args, **kwargs)
473
self._operation.add_cleanup(cleanup_func, *args, **kwargs)
496
475
def cleanup_now(self):
497
476
"""Execute and empty pending cleanup functions immediately.
506
485
as it releases all resources, this may release locks that the command
507
486
wants to hold, so use should be done with care.
509
self._exit_stack.close()
511
def enter_context(self, cm):
512
return self._exit_stack.enter_context(cm)
488
self._operation.cleanup_now()
514
490
def _usage(self):
515
491
"""Return single-line grammar for this command.
558
534
doc = gettext("No help for this command.")
560
536
# Extract the summary (purpose) and sections out from the text
561
purpose, sections, order = self._get_help_parts(doc)
537
purpose,sections,order = self._get_help_parts(doc)
563
539
# If a custom usage section was provided, use it
564
540
if 'Usage' in sections:
580
556
# XXX: optparse implicitly rewraps the help, and not always perfectly,
581
557
# so we get <https://bugs.launchpad.net/bzr/+bug/249908>. -- mbp
583
parser = option.get_optparser(
584
[v for k, v in sorted(self.options().items())])
559
parser = option.get_optparser(self.options())
585
560
options = parser.format_option_help()
586
561
# FIXME: According to the spec, ReST option lists actually don't
587
562
# support options like --1.14 so that causes syntax errors (in Sphinx
670
645
summary = lines.pop(0)
673
label, section = None, ''
648
label,section = None,''
674
649
for line in lines:
675
650
if line.startswith(':') and line.endswith(':') and len(line) > 2:
676
651
save_section(sections, order, label, section)
677
label, section = line[1:-1], ''
678
elif (label is not None and len(line) > 1 and
679
not line[0].isspace()):
652
label,section = line[1:-1],''
653
elif (label is not None) and len(line) > 1 and not line[0].isspace():
680
654
save_section(sections, order, label, section)
681
label, section = None, line
655
label,section = None,line
683
657
if len(section) > 0:
684
658
section += '\n' + line
773
747
you can override this method.
775
749
class_run = self.run
777
750
def run(*args, **kwargs):
778
751
for hook in Command.hooks['pre_command']:
753
self._operation = cleanup.OperationWithCleanups(class_run)
781
with contextlib.ExitStack() as self._exit_stack:
782
return class_run(*args, **kwargs)
755
return self._operation.run_simple(*args, **kwargs)
784
758
for hook in Command.hooks['post_command']:
795
769
shell error code if not. It's OK for this method to allow
796
770
an exception to raise up.
798
This method is automatically wrapped by Command.__init__ with a
799
ExitStack, stored as self._exit_stack. This can be used
772
This method is automatically wrapped by Command.__init__ with a
773
cleanup operation, stored as self._operation. This can be used
800
774
via self.add_cleanup to perform automatic cleanups at the end of
851
825
Hooks.__init__(self, "breezy.commands", "Command.hooks")
826
self.add_hook('extend_command',
854
827
"Called after creating a command object to allow modifications "
855
828
"such as adding or removing options, docs etc. Called with the "
856
829
"new breezy.commands.Command object.", (1, 13))
830
self.add_hook('get_command',
859
831
"Called when creating a single command. Called with "
860
832
"(cmd_or_None, command_name). get_command should either return "
861
833
"the cmd_or_None parameter, or a replacement Command object that "
863
835
"hooks are core infrastructure. Many users will prefer to use "
864
836
"breezy.commands.register_command or plugin_cmds.register_lazy.",
867
'get_missing_command',
838
self.add_hook('get_missing_command',
868
839
"Called when creating a single command if no command could be "
869
840
"found. Called with (command_name). get_missing_command should "
870
841
"either return None, or a Command object to be used for the "
871
842
"command.", (1, 17))
843
self.add_hook('list_commands',
874
844
"Called when enumerating commands. Called with a set of "
875
845
"cmd_name strings for all the commands found so far. This set "
876
846
" is safe to mutate - e.g. to remove a command. "
877
847
"list_commands should return the updated set of command names.",
849
self.add_hook('pre_command',
881
850
"Called prior to executing a command. Called with the command "
882
851
"object.", (2, 6))
852
self.add_hook('post_command',
885
853
"Called after executing a command. Called with the command "
886
854
"object.", (2, 6))
889
856
Command.hooks = CommandHooks()
898
865
they take, and which commands will accept them.
900
867
# TODO: make it a method of the Command?
901
parser = option.get_optparser(
902
[v for k, v in sorted(command.options().items())])
868
parser = option.get_optparser(command.options())
903
869
if alias_argv is not None:
904
870
args = alias_argv + argv
908
# python 2's optparse raises this exception if a non-ascii
874
# for python 2.5 and later, optparse raises this exception if a non-ascii
909
875
# option name is given. See http://bugs.python.org/issue2931
911
877
options, args = parser.parse_args(args)
912
except UnicodeEncodeError:
913
raise errors.CommandError(
878
except UnicodeEncodeError as e:
879
raise errors.BzrCommandError(
914
880
gettext('Only ASCII permitted in option names'))
916
882
opts = dict((k, v) for k, v in options.__dict__.items() if
935
901
argdict[argname + '_list'] = None
936
902
elif ap[-1] == '+':
938
raise errors.CommandError(gettext(
939
"command {0!r} needs one or more {1}").format(
940
cmd, argname.upper()))
904
raise errors.BzrCommandError(gettext(
905
"command {0!r} needs one or more {1}").format(
906
cmd, argname.upper()))
942
908
argdict[argname + '_list'] = args[:]
944
elif ap[-1] == '$': # all but one
910
elif ap[-1] == '$': # all but one
945
911
if len(args) < 2:
946
raise errors.CommandError(
947
gettext("command {0!r} needs one or more {1}").format(
948
cmd, argname.upper()))
912
raise errors.BzrCommandError(
913
gettext("command {0!r} needs one or more {1}").format(
914
cmd, argname.upper()))
949
915
argdict[argname + '_list'] = args[:-1]
952
918
# just a plain arg
955
raise errors.CommandError(
956
gettext("command {0!r} requires argument {1}").format(
957
cmd, argname.upper()))
921
raise errors.BzrCommandError(
922
gettext("command {0!r} requires argument {1}").format(
923
cmd, argname.upper()))
959
925
argdict[argname] = args.pop(0)
962
raise errors.CommandError(gettext(
963
"extra argument to command {0}: {1}").format(
928
raise errors.BzrCommandError( gettext(
929
"extra argument to command {0}: {1}").format(
969
def apply_coveraged(the_callable, *args, **kwargs):
971
cov = coverage.Coverage()
973
config_file = cov.config.config_file
974
except AttributeError: # older versions of coverage
975
config_file = cov.config_file
976
os.environ['COVERAGE_PROCESS_START'] = config_file
934
def apply_coveraged(dirname, the_callable, *args, **kwargs):
935
# Cannot use "import trace", as that would import breezy.trace instead of
936
# the standard library's trace.
937
trace = __import__('trace')
939
tracer = trace.Trace(count=1, trace=0)
940
sys.settrace(tracer.globaltrace)
941
threading.settrace(tracer.globaltrace)
979
944
return exception_to_return_code(the_callable, *args, **kwargs)
947
results = tracer.results()
948
results.write_results(show_missing=1, summary=False,
985
952
def apply_profiled(the_callable, *args, **kwargs):
991
958
prof = hotshot.Profile(pfname)
993
960
ret = prof.runcall(exception_to_return_code, the_callable, *args,
997
964
stats = hotshot.stats.load(pfname)
998
965
stats.strip_dirs()
999
966
stats.sort_stats('cum') # 'time'
1000
# XXX: Might like to write to stderr or the trace file instead but
1001
# print_stats seems hardcoded to stdout
967
## XXX: Might like to write to stderr or the trace file instead but
968
## print_stats seems hardcoded to stdout
1002
969
stats.print_stats(20)
1017
984
return the_callable(*args, **kwargs)
1018
except (KeyboardInterrupt, Exception):
985
except (KeyboardInterrupt, Exception) as e:
1019
986
# used to handle AssertionError and KeyboardInterrupt
1020
987
# specially here, but hopefully they're handled ok by the logger now
1021
988
exc_info = sys.exc_info()
1095
1062
Run under the Python lsprof profiler.
1098
Generate code coverage report
1065
Generate line coverage report in the specified directory.
1101
Specify the number of processes that can be run concurrently
1068
Specify the number of processes that can be run concurrently (selftest).
1104
1070
trace.mutter("breezy version: " + breezy.__version__)
1105
1071
argv = _specified_or_unicode_argv(argv)
1106
1072
trace.mutter("brz arguments: %r", argv)
1108
1074
opt_lsprof = opt_profile = opt_no_plugins = opt_builtin = \
1109
opt_coverage = opt_no_l10n = opt_no_aliases = False
1110
opt_lsprof_file = None
1075
opt_no_l10n = opt_no_aliases = False
1076
opt_lsprof_file = opt_coverage_dir = None
1112
1078
# --no-plugins is handled specially at a very early stage. We need
1113
1079
# to load plugins before doing other command parsing so that they
1138
1104
os.environ['BRZ_CONCURRENCY'] = argv[i + 1]
1140
1106
elif a == '--coverage':
1107
opt_coverage_dir = argv[i + 1]
1142
1109
elif a == '--profile-imports':
1143
pass # already handled in startup script Bug #588277
1110
pass # already handled in startup script Bug #588277
1144
1111
elif a.startswith('-D'):
1145
1112
debug.debug_flags.add(a[2:])
1146
1113
elif a.startswith('-O'):
1149
1116
argv_copy.append(a)
1152
cmdline_overrides = breezy.get_global_state().cmdline_overrides
1119
if breezy.global_state is None:
1120
# FIXME: Workaround for users that imported breezy but didn't call
1121
# breezy.initialize -- vila 2012-01-19
1122
cmdline_overrides = config.CommandLineStore()
1124
cmdline_overrides = breezy.global_state.cmdline_overrides
1153
1125
cmdline_overrides._from_cmdline(override_config)
1155
1127
debug.set_debug_flags_from_config()
1157
1129
if not opt_no_plugins:
1158
from breezy import config
1159
c = config.GlobalConfig()
1160
warn_load_problems = not c.suppress_warning('plugin_load_failure')
1161
load_plugins(warn_load_problems=warn_load_problems)
1163
1132
disable_plugins()
1191
1160
saved_verbosity_level = option._verbosity_level
1192
1161
option._verbosity_level = 0
1163
if opt_coverage_dir:
1196
1165
'--coverage ignored, because --lsprof is in use.')
1197
1166
ret = apply_lsprofiled(opt_lsprof_file, run, *run_argv)
1198
1167
elif opt_profile:
1168
if opt_coverage_dir:
1201
1170
'--coverage ignored, because --profile is in use.')
1202
1171
ret = apply_profiled(run, *run_argv)
1204
ret = apply_coveraged(run, *run_argv)
1172
elif opt_coverage_dir:
1173
ret = apply_coveraged(opt_coverage_dir, run, *run_argv)
1206
1175
ret = run(*run_argv)
1207
1176
return ret or 0
1241
1210
if _list_bzr_commands in Command.hooks["list_commands"]:
1243
1212
Command.hooks.install_named_hook("list_commands", _list_bzr_commands,
1245
1214
Command.hooks.install_named_hook("get_command", _get_bzr_command,
1247
1216
Command.hooks.install_named_hook("get_command", _get_plugin_command,
1248
"bzr plugin commands")
1217
"bzr plugin commands")
1249
1218
Command.hooks.install_named_hook("get_command", _get_external_command,
1250
"bzr external command lookup")
1219
"bzr external command lookup")
1251
1220
Command.hooks.install_named_hook("get_missing_command",
1252
1221
_try_plugin_provider,
1253
1222
"bzr plugin-provider-db check")
1256
1226
def _specified_or_unicode_argv(argv):
1257
1227
# For internal or testing use, argv can be passed. Otherwise, get it from
1258
# the process arguments.
1228
# the process arguments in a unicode-safe way.
1259
1229
if argv is None:
1263
# ensure all arguments are unicode strings
1265
if not isinstance(a, str):
1266
raise ValueError('not native str or unicode: %r' % (a,))
1268
except (ValueError, UnicodeDecodeError):
1269
raise errors.BzrError("argv should be list of unicode strings.")
1230
return osutils.get_unicode_argv()
1234
# ensure all arguments are unicode strings
1236
if isinstance(a, unicode):
1239
new_argv.append(a.decode('ascii'))
1240
except UnicodeDecodeError:
1241
raise errors.BzrError("argv should be list of unicode strings.")
1273
1245
def main(argv=None):
1294
1266
"""Run a bzr command with parameters as described by argv.
1296
1268
This function assumed that that UI layer is setup, that symbol deprecations
1297
are already applied, and that unicode decoding has already been performed
1269
are already applied, and that unicode decoding has already been performed on argv.
1300
1271
# done here so that they're covered for every test run
1301
1272
install_bzr_command_hooks()
1314
1285
return run_bzr(argv)
1315
1286
except Exception as e:
1316
1287
if (isinstance(e, (OSError, IOError))
1317
or not getattr(e, 'internal_error', True)):
1288
or not getattr(e, 'internal_error', True)):
1318
1289
trace.report_exception(sys.exc_info(), sys.stderr)