From 9ef346468c58411f1e2334f60cf05047d54b950a Mon Sep 17 00:00:00 2001 From: mmussomele Date: Sat, 16 May 2015 21:42:21 -0700 Subject: [PATCH 1/5] added cd_correction.py --- thefuck/rules/cd_correction.py | 85 ++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 thefuck/rules/cd_correction.py diff --git a/thefuck/rules/cd_correction.py b/thefuck/rules/cd_correction.py new file mode 100644 index 0000000..9521993 --- /dev/null +++ b/thefuck/rules/cd_correction.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +__author__ = "mmussomele" + +"""Attempts to spellcheck and correct failed cd commands""" + +import os +import cd_mkdir +from thefuck.utils import sudo_support + +MAX_ALLOWED_STR_DIST = 5 + +def _get_sub_dirs(parent): + """Returns a list of the child directories of the given parent directory""" + return [child for child in os.listdir(parent) if os.path.isdir(os.path.join(parent, child))] + +def _dam_lev_dist(): + """Returns a Damerau-Levenshtein distance calculator.""" + cache = {} + def _calculator(first, second): + """ + Calculates the Damerau-Levenshtein distance of two strings. + See: http://en.wikipedia.org/wiki/Damerau-Levenshtein_distance#Algorithm + """ + if (first, second) in cache: + return cache[(first, second)] + else: + l_first = len(first) + l_second = len(second) + distances = [[0 for _ in range(l_second + 1)] for _ in range(l_first + 1)] + for i in range(l_first + 1): + distances[i][0] = i + for j in range(1, l_second + 1): + distances[0][j] = j + for i in range(l_first): + for j in range(l_second): + if first[i] == second[j]: + cost = 0 + else: + cost = 1 + distances[i+1][j+1] = min(distances[i][j+1] + 1, + distances[i+1][j] + 1, + distances[i][j] + cost) + if i and j and first[i] == second[j-1] and first[i-1] == second[j]: + distances[i][j] = min(distances[i+1][j+1], + distances[i-1][j-1] + cost) + cache[(first, second)] = distances[l_first][l_second] + return distances[l_first][l_second] + return _calculator + +_dam_lev_dist = _dam_lev_dist() + +@sudo_support +def match(command, settings): + """Match function copied from cd_mkdir.py""" + return (command.script.startswith('cd ') + and ('no such file or directory' in command.stderr.lower() + or 'cd: can\'t cd to' in command.stderr.lower())) + +@sudo_support +def get_new_command(command, settings): + """ + Attempt to rebuild the path string by spellchecking the directories. + If it fails (i.e. no directories are a close enough match), then it + defaults to the rules of cd_mkdir. + Change sensitivity to matching by changing MAX_ALLOWED_STR_DIST. + Higher values allow for larger discrepancies in path names. + """ + dest = command.script.split()[1].split(os.sep) + if dest[-1] == '': + dest = dest[:-1] + cwd = os.getcwd() + for directory in dest: + if directory == ".": + continue + elif directory == "..": + cwd = os.path.split(cwd)[0] + continue + best_match = min(_get_sub_dirs(cwd), key=lambda x: _dam_lev_dist(directory, x)) + if _dam_lev_dist(directory, best_match) > MAX_ALLOWED_STR_DIST: + return cd_mkdir.get_new_command(command, settings) + else: + cwd = os.path.join(cwd, best_match) + return "cd {0}".format(cwd) + +enabled_by_default = True \ No newline at end of file From a54c97f624548161123e954ad3e9a01cb84b92a6 Mon Sep 17 00:00:00 2001 From: mmussomele Date: Sat, 16 May 2015 21:47:15 -0700 Subject: [PATCH 2/5] added newline to end of cd_correction.py --- thefuck/rules/cd_correction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thefuck/rules/cd_correction.py b/thefuck/rules/cd_correction.py index 9521993..96bee90 100644 --- a/thefuck/rules/cd_correction.py +++ b/thefuck/rules/cd_correction.py @@ -82,4 +82,4 @@ def get_new_command(command, settings): cwd = os.path.join(cwd, best_match) return "cd {0}".format(cwd) -enabled_by_default = True \ No newline at end of file +enabled_by_default = True From 252859e63a1f41d79899d289da763b973df6a395 Mon Sep 17 00:00:00 2001 From: mmussomele Date: Sat, 16 May 2015 23:53:08 -0700 Subject: [PATCH 3/5] fixed accidentally correcting to some directories with short name length --- thefuck/rules/cd_correction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/thefuck/rules/cd_correction.py b/thefuck/rules/cd_correction.py index 96bee90..98a2655 100644 --- a/thefuck/rules/cd_correction.py +++ b/thefuck/rules/cd_correction.py @@ -76,7 +76,8 @@ def get_new_command(command, settings): cwd = os.path.split(cwd)[0] continue best_match = min(_get_sub_dirs(cwd), key=lambda x: _dam_lev_dist(directory, x)) - if _dam_lev_dist(directory, best_match) > MAX_ALLOWED_STR_DIST: + best_dist = _dam_lev_dist(directory, best_match) + if best_dist > MAX_ALLOWED_STR_DIST or best_dist >= len(best_match): return cd_mkdir.get_new_command(command, settings) else: cwd = os.path.join(cwd, best_match) From 8fdcff776a37579c55281c2107a178ea77bf4317 Mon Sep 17 00:00:00 2001 From: mmussomele Date: Sun, 17 May 2015 09:03:19 -0700 Subject: [PATCH 4/5] reimplemented using native string matching --- thefuck/rules/cd_correction.py | 51 +++++----------------------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/thefuck/rules/cd_correction.py b/thefuck/rules/cd_correction.py index 98a2655..770eff5 100644 --- a/thefuck/rules/cd_correction.py +++ b/thefuck/rules/cd_correction.py @@ -5,50 +5,15 @@ __author__ = "mmussomele" import os import cd_mkdir +from difflib import get_close_matches from thefuck.utils import sudo_support -MAX_ALLOWED_STR_DIST = 5 +MAX_ALLOWED_DIFF = 0.6 def _get_sub_dirs(parent): """Returns a list of the child directories of the given parent directory""" return [child for child in os.listdir(parent) if os.path.isdir(os.path.join(parent, child))] -def _dam_lev_dist(): - """Returns a Damerau-Levenshtein distance calculator.""" - cache = {} - def _calculator(first, second): - """ - Calculates the Damerau-Levenshtein distance of two strings. - See: http://en.wikipedia.org/wiki/Damerau-Levenshtein_distance#Algorithm - """ - if (first, second) in cache: - return cache[(first, second)] - else: - l_first = len(first) - l_second = len(second) - distances = [[0 for _ in range(l_second + 1)] for _ in range(l_first + 1)] - for i in range(l_first + 1): - distances[i][0] = i - for j in range(1, l_second + 1): - distances[0][j] = j - for i in range(l_first): - for j in range(l_second): - if first[i] == second[j]: - cost = 0 - else: - cost = 1 - distances[i+1][j+1] = min(distances[i][j+1] + 1, - distances[i+1][j] + 1, - distances[i][j] + cost) - if i and j and first[i] == second[j-1] and first[i-1] == second[j]: - distances[i][j] = min(distances[i+1][j+1], - distances[i-1][j-1] + cost) - cache[(first, second)] = distances[l_first][l_second] - return distances[l_first][l_second] - return _calculator - -_dam_lev_dist = _dam_lev_dist() - @sudo_support def match(command, settings): """Match function copied from cd_mkdir.py""" @@ -62,8 +27,7 @@ def get_new_command(command, settings): Attempt to rebuild the path string by spellchecking the directories. If it fails (i.e. no directories are a close enough match), then it defaults to the rules of cd_mkdir. - Change sensitivity to matching by changing MAX_ALLOWED_STR_DIST. - Higher values allow for larger discrepancies in path names. + Change sensitivity by changing MAX_ALLOWED_DIFF. Default value is 0.6 """ dest = command.script.split()[1].split(os.sep) if dest[-1] == '': @@ -75,12 +39,11 @@ def get_new_command(command, settings): elif directory == "..": cwd = os.path.split(cwd)[0] continue - best_match = min(_get_sub_dirs(cwd), key=lambda x: _dam_lev_dist(directory, x)) - best_dist = _dam_lev_dist(directory, best_match) - if best_dist > MAX_ALLOWED_STR_DIST or best_dist >= len(best_match): - return cd_mkdir.get_new_command(command, settings) + best_matches = get_close_matches(directory, _get_sub_dirs(cwd), cutoff=MAX_ALLOWED_DIFF) + if len(best_matches): + cwd = os.path.join(cwd, best_matches[0]) else: - cwd = os.path.join(cwd, best_match) + return cd_mkdir.get_new_command(command, settings) return "cd {0}".format(cwd) enabled_by_default = True From 3c673e097238fae27ba5f4786485d58f4c9c5af5 Mon Sep 17 00:00:00 2001 From: mmussomele Date: Sun, 17 May 2015 09:52:42 -0700 Subject: [PATCH 5/5] fixed extra check --- thefuck/rules/cd_correction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thefuck/rules/cd_correction.py b/thefuck/rules/cd_correction.py index 770eff5..442ccf2 100644 --- a/thefuck/rules/cd_correction.py +++ b/thefuck/rules/cd_correction.py @@ -40,7 +40,7 @@ def get_new_command(command, settings): cwd = os.path.split(cwd)[0] continue best_matches = get_close_matches(directory, _get_sub_dirs(cwd), cutoff=MAX_ALLOWED_DIFF) - if len(best_matches): + if best_matches: cwd = os.path.join(cwd, best_matches[0]) else: return cd_mkdir.get_new_command(command, settings)