/breezy/trunk

To get this branch, use:
bzr branch https://code.breezy-vcs.org/breezy/trunk

« back to all changes in this revision

Viewing changes to breezy/commands.py

  • Committer: Jelmer Vernooij
  • Date: 2017-07-23 22:06:41 UTC
  • mfrom: (6738 trunk)
  • mto: This revision was merged to the branch mainline in revision 6739.
  • Revision ID: jelmer@jelmer.uk-20170723220641-69eczax9bmv8d6kk
Merge trunk, address review comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
37
37
    cleanup,
38
38
    cmdline,
39
39
    debug,
40
 
    errors,
41
40
    i18n,
42
41
    option,
43
42
    osutils,
51
50
# Compatibility - Option used to be in commands.
52
51
from .option import Option
53
52
from .plugin import disable_plugins, load_plugins, plugin_name
54
 
from . import registry
 
53
from . import errors, registry
55
54
from .sixish import (
56
55
    string_types,
57
56
    )
58
57
 
59
58
 
 
59
class BzrOptionError(errors.BzrCommandError):
 
60
 
 
61
    _fmt = "Error in command line options"
 
62
 
 
63
 
60
64
class CommandInfo(object):
61
65
    """Information about a command."""
62
66
 
218
222
    return plugin_cmds.keys()
219
223
 
220
224
 
 
225
# Overrides for common mispellings that heuristics get wrong
 
226
_GUESS_OVERRIDES = {
 
227
    'ic': {'ci': 0}, # heuristic finds nick
 
228
    }
 
229
 
 
230
 
 
231
def guess_command(cmd_name):
 
232
    """Guess what command a user typoed.
 
233
 
 
234
    :param cmd_name: Command to search for
 
235
    :return: None if no command was found, name of a command otherwise
 
236
    """
 
237
    names = set()
 
238
    for name in all_command_names():
 
239
        names.add(name)
 
240
        cmd = get_cmd_object(name)
 
241
        names.update(cmd.aliases)
 
242
    # candidate: modified levenshtein distance against cmd_name.
 
243
    costs = {}
 
244
    from . import patiencediff
 
245
    for name in sorted(names):
 
246
        matcher = patiencediff.PatienceSequenceMatcher(None, cmd_name, name)
 
247
        distance = 0.0
 
248
        opcodes = matcher.get_opcodes()
 
249
        for opcode, l1, l2, r1, r2 in opcodes:
 
250
            if opcode == 'delete':
 
251
                distance += l2-l1
 
252
            elif opcode == 'replace':
 
253
                distance += max(l2-l1, r2-l1)
 
254
            elif opcode == 'insert':
 
255
                distance += r2-r1
 
256
            elif opcode == 'equal':
 
257
                # Score equal ranges lower, making similar commands of equal
 
258
                # length closer than arbitrary same length commands.
 
259
                distance -= 0.1 * (l2-l1)
 
260
        costs[name] = distance
 
261
    costs.update(_GUESS_OVERRIDES.get(cmd_name, {}))
 
262
    costs = sorted((value, key) for key, value in costs.iteritems())
 
263
    if not costs:
 
264
        return
 
265
    if costs[0][0] > 4:
 
266
        return
 
267
    candidate = costs[0][1]
 
268
    return candidate
 
269
 
 
270
 
221
271
def get_cmd_object(cmd_name, plugins_override=True):
222
272
    """Return the command object for a command.
223
273
 
227
277
    try:
228
278
        return _get_cmd_object(cmd_name, plugins_override)
229
279
    except KeyError:
230
 
        raise errors.BzrCommandError(gettext('unknown command "%s"') % cmd_name)
 
280
        # No command found, see if this was a typo
 
281
        candidate = guess_command(cmd_name)
 
282
        if candidate is not None:
 
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"')
 
287
                % cmd_name)
231
288
 
232
289
 
233
290
def _get_cmd_object(cmd_name, plugins_override=True, check_missing=True):