summaryrefslogtreecommitdiff
path: root/node_modules/commander/lib/suggestSimilar.js
diff options
context:
space:
mode:
authorShipwreckt <me@shipwreckt.co.uk>2025-10-31 20:02:14 +0000
committerShipwreckt <me@shipwreckt.co.uk>2025-10-31 20:02:14 +0000
commit7a52ddeba2a68388b544f529d2d92104420f77b0 (patch)
tree15ddd47457a2cb4a96060747437d36474e4f6b4e /node_modules/commander/lib/suggestSimilar.js
parent53d6ae2b5568437afa5e4995580a3fb679b7b91b (diff)
Changed from static to 11ty!
Diffstat (limited to 'node_modules/commander/lib/suggestSimilar.js')
-rw-r--r--node_modules/commander/lib/suggestSimilar.js100
1 files changed, 100 insertions, 0 deletions
diff --git a/node_modules/commander/lib/suggestSimilar.js b/node_modules/commander/lib/suggestSimilar.js
new file mode 100644
index 0000000..9a4066c
--- /dev/null
+++ b/node_modules/commander/lib/suggestSimilar.js
@@ -0,0 +1,100 @@
+const maxDistance = 3;
+
+function editDistance(a, b) {
+ // https://en.wikipedia.org/wiki/Damerau–Levenshtein_distance
+ // Calculating optimal string alignment distance, no substring is edited more than once.
+ // (Simple implementation.)
+
+ // Quick early exit, return worst case.
+ if (Math.abs(a.length - b.length) > maxDistance) return Math.max(a.length, b.length);
+
+ // distance between prefix substrings of a and b
+ const d = [];
+
+ // pure deletions turn a into empty string
+ for (let i = 0; i <= a.length; i++) {
+ d[i] = [i];
+ }
+ // pure insertions turn empty string into b
+ for (let j = 0; j <= b.length; j++) {
+ d[0][j] = j;
+ }
+
+ // fill matrix
+ for (let j = 1; j <= b.length; j++) {
+ for (let i = 1; i <= a.length; i++) {
+ let cost = 1;
+ if (a[i - 1] === b[j - 1]) {
+ cost = 0;
+ } else {
+ cost = 1;
+ }
+ d[i][j] = Math.min(
+ d[i - 1][j] + 1, // deletion
+ d[i][j - 1] + 1, // insertion
+ d[i - 1][j - 1] + cost // substitution
+ );
+ // transposition
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
+ }
+ }
+ }
+
+ return d[a.length][b.length];
+}
+
+/**
+ * Find close matches, restricted to same number of edits.
+ *
+ * @param {string} word
+ * @param {string[]} candidates
+ * @returns {string}
+ */
+
+function suggestSimilar(word, candidates) {
+ if (!candidates || candidates.length === 0) return '';
+ // remove possible duplicates
+ candidates = Array.from(new Set(candidates));
+
+ const searchingOptions = word.startsWith('--');
+ if (searchingOptions) {
+ word = word.slice(2);
+ candidates = candidates.map(candidate => candidate.slice(2));
+ }
+
+ let similar = [];
+ let bestDistance = maxDistance;
+ const minSimilarity = 0.4;
+ candidates.forEach((candidate) => {
+ if (candidate.length <= 1) return; // no one character guesses
+
+ const distance = editDistance(word, candidate);
+ const length = Math.max(word.length, candidate.length);
+ const similarity = (length - distance) / length;
+ if (similarity > minSimilarity) {
+ if (distance < bestDistance) {
+ // better edit distance, throw away previous worse matches
+ bestDistance = distance;
+ similar = [candidate];
+ } else if (distance === bestDistance) {
+ similar.push(candidate);
+ }
+ }
+ });
+
+ similar.sort((a, b) => a.localeCompare(b));
+ if (searchingOptions) {
+ similar = similar.map(candidate => `--${candidate}`);
+ }
+
+ if (similar.length > 1) {
+ return `\n(Did you mean one of ${similar.join(', ')}?)`;
+ }
+ if (similar.length === 1) {
+ return `\n(Did you mean ${similar[0]}?)`;
+ }
+ return '';
+}
+
+exports.suggestSimilar = suggestSimilar;