forked from rogpeppe/go-internal
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
testscript: suggest misspelled commands
If a command is not found, we go through the list of defined commands and check if any of them are sufficiently close to the one used. "Sufficiently close" is defined by having a Damerau-Levenshtein distance of 1, which feels like it hits the sweet spot between usefulness and ease of implementation. The negation case is still special-cased, as negation is not in the set of defined commands. Fixes rogpeppe#190
- Loading branch information
Showing
3 changed files
with
128 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package textutil | ||
|
||
import "unicode/utf8" | ||
|
||
// AlmostEqual reports whether a and b have Damerau-Levenshtein distance of at | ||
// most 1. That is, it reports whether a can be transformed into b by adding, | ||
// removing or substituting a single rune, or by swapping two adjacent runes. | ||
// | ||
// It runs in O(len(a)+len(b)) time. | ||
func AlmostEqual(a, b string) bool { | ||
for len(a) > 0 && len(b) > 0 { | ||
ra, tailA := shiftRune(a) | ||
rb, tailB := shiftRune(b) | ||
if ra == rb { | ||
a, b = tailA, tailB | ||
continue | ||
} | ||
// check for addition/deletion/substitution | ||
if a == tailB || tailA == b || tailA == tailB { | ||
return true | ||
} | ||
// check for swap | ||
a, b = tailA, tailB | ||
if len(a) == 0 || len(b) == 0 { | ||
return false | ||
} | ||
Ra, tailA := shiftRune(tailA) | ||
Rb, tailB := shiftRune(tailB) | ||
return ra == Rb && Ra == rb && tailA == tailB | ||
} | ||
if len(a) == 0 { | ||
return len(b) == 0 || singleRune(b) | ||
} | ||
return singleRune(a) | ||
} | ||
|
||
// singleRune reports whether s consists of a single UTF-8 codepoint. | ||
func singleRune(s string) bool { | ||
_, n := utf8.DecodeRuneInString(s) | ||
return n == len(s) | ||
} | ||
|
||
// shiftRune splits off the first UTF-8 codepoint from s and returns it and the | ||
// rest of the string. It panics if s is empty. | ||
func shiftRune(s string) (rune, string) { | ||
if len(s) == 0 { | ||
panic(s) | ||
} | ||
r, n := utf8.DecodeRuneInString(s) | ||
return r, s[n:] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package textutil | ||
|
||
import "testing" | ||
|
||
func FuzzAlmostEqual(f *testing.F) { | ||
f.Fuzz(func(t *testing.T, a, b string) { | ||
AlmostEqual(a, b) | ||
}) | ||
} | ||
|
||
func TestAlmostEqual(t *testing.T) { | ||
t.Parallel() | ||
|
||
tcs := []struct { | ||
inA string | ||
inB string | ||
want bool | ||
}{ | ||
{"", "", true}, | ||
{"", "a", true}, | ||
{"a", "a", true}, | ||
{"a", "b", true}, | ||
{"hello", "hell", true}, | ||
{"hello", "jello", true}, | ||
{"hello", "helol", true}, | ||
{"hello", "jelol", false}, | ||
} | ||
for _, tc := range tcs { | ||
got := AlmostEqual(tc.inA, tc.inB) | ||
if got != tc.want { | ||
t.Errorf("AlmostEqual(%q, %q) = %v, want %v", tc.inA, tc.inB, got, tc.want) | ||
} | ||
// two tests for the price of one \o/ | ||
if got != AlmostEqual(tc.inB, tc.inA) { | ||
t.Errorf("AlmostEqual(%q, %q) == %v != AlmostEqual(%q, %q)", tc.inA, tc.inB, got, tc.inB, tc.inA) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters