cmpstr 2.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +75 -499
  3. package/dist/CmpStr.esm.js +4863 -0
  4. package/dist/CmpStr.esm.js.map +1 -0
  5. package/dist/CmpStr.esm.min.js +8 -0
  6. package/dist/CmpStr.esm.min.js.map +1 -0
  7. package/dist/CmpStr.umd.js +4875 -0
  8. package/dist/CmpStr.umd.js.map +1 -0
  9. package/dist/CmpStr.umd.min.js +8 -0
  10. package/dist/CmpStr.umd.min.js.map +1 -0
  11. package/dist/cjs/CmpStr.js +663 -0
  12. package/dist/cjs/CmpStr.js.map +1 -0
  13. package/dist/cjs/CmpStrAsync.js +336 -0
  14. package/dist/cjs/CmpStrAsync.js.map +1 -0
  15. package/dist/cjs/index.js +15 -0
  16. package/dist/cjs/index.js.map +1 -0
  17. package/dist/cjs/metric/Cosine.js +101 -0
  18. package/dist/cjs/metric/Cosine.js.map +1 -0
  19. package/dist/cjs/metric/DamerauLevenshtein.js +110 -0
  20. package/dist/cjs/metric/DamerauLevenshtein.js.map +1 -0
  21. package/dist/cjs/metric/DiceSorensen.js +91 -0
  22. package/dist/cjs/metric/DiceSorensen.js.map +1 -0
  23. package/dist/cjs/metric/Hamming.js +82 -0
  24. package/dist/cjs/metric/Hamming.js.map +1 -0
  25. package/dist/cjs/metric/Jaccard.js +76 -0
  26. package/dist/cjs/metric/Jaccard.js.map +1 -0
  27. package/dist/cjs/metric/JaroWinkler.js +114 -0
  28. package/dist/cjs/metric/JaroWinkler.js.map +1 -0
  29. package/dist/cjs/metric/LCS.js +89 -0
  30. package/dist/cjs/metric/LCS.js.map +1 -0
  31. package/dist/cjs/metric/Levenshtein.js +94 -0
  32. package/dist/cjs/metric/Levenshtein.js.map +1 -0
  33. package/dist/cjs/metric/Metric.js +445 -0
  34. package/dist/cjs/metric/Metric.js.map +1 -0
  35. package/dist/cjs/metric/NeedlemanWunsch.js +95 -0
  36. package/dist/cjs/metric/NeedlemanWunsch.js.map +1 -0
  37. package/dist/cjs/metric/SmithWaterman.js +98 -0
  38. package/dist/cjs/metric/SmithWaterman.js.map +1 -0
  39. package/dist/cjs/metric/qGram.js +91 -0
  40. package/dist/cjs/metric/qGram.js.map +1 -0
  41. package/dist/cjs/phonetic/Cologne.js +112 -0
  42. package/dist/cjs/phonetic/Cologne.js.map +1 -0
  43. package/dist/cjs/phonetic/Metaphone.js +172 -0
  44. package/dist/cjs/phonetic/Metaphone.js.map +1 -0
  45. package/dist/cjs/phonetic/Phonetic.js +413 -0
  46. package/dist/cjs/phonetic/Phonetic.js.map +1 -0
  47. package/dist/cjs/phonetic/Soundex.js +135 -0
  48. package/dist/cjs/phonetic/Soundex.js.map +1 -0
  49. package/dist/cjs/utils/DeepMerge.js +144 -0
  50. package/dist/cjs/utils/DeepMerge.js.map +1 -0
  51. package/dist/cjs/utils/DiffChecker.js +500 -0
  52. package/dist/cjs/utils/DiffChecker.js.map +1 -0
  53. package/dist/cjs/utils/Filter.js +189 -0
  54. package/dist/cjs/utils/Filter.js.map +1 -0
  55. package/dist/cjs/utils/HashTable.js +175 -0
  56. package/dist/cjs/utils/HashTable.js.map +1 -0
  57. package/dist/cjs/utils/Normalizer.js +144 -0
  58. package/dist/cjs/utils/Normalizer.js.map +1 -0
  59. package/dist/cjs/utils/Pool.js +196 -0
  60. package/dist/cjs/utils/Pool.js.map +1 -0
  61. package/dist/cjs/utils/Profiler.js +229 -0
  62. package/dist/cjs/utils/Profiler.js.map +1 -0
  63. package/dist/cjs/utils/Registry.js +148 -0
  64. package/dist/cjs/utils/Registry.js.map +1 -0
  65. package/dist/cjs/utils/TextAnalyzer.js +358 -0
  66. package/dist/cjs/utils/TextAnalyzer.js.map +1 -0
  67. package/dist/esm/CmpStr.js +662 -0
  68. package/dist/esm/CmpStr.js.map +1 -0
  69. package/dist/esm/CmpStrAsync.js +331 -0
  70. package/dist/esm/CmpStrAsync.js.map +1 -0
  71. package/dist/esm/index.js +7 -0
  72. package/dist/esm/index.js.map +1 -0
  73. package/dist/esm/metric/Cosine.js +99 -0
  74. package/dist/esm/metric/Cosine.js.map +1 -0
  75. package/dist/esm/metric/DamerauLevenshtein.js +108 -0
  76. package/dist/esm/metric/DamerauLevenshtein.js.map +1 -0
  77. package/dist/esm/metric/DiceSorensen.js +89 -0
  78. package/dist/esm/metric/DiceSorensen.js.map +1 -0
  79. package/dist/esm/metric/Hamming.js +77 -0
  80. package/dist/esm/metric/Hamming.js.map +1 -0
  81. package/dist/esm/metric/Jaccard.js +74 -0
  82. package/dist/esm/metric/Jaccard.js.map +1 -0
  83. package/dist/esm/metric/JaroWinkler.js +112 -0
  84. package/dist/esm/metric/JaroWinkler.js.map +1 -0
  85. package/dist/esm/metric/LCS.js +87 -0
  86. package/dist/esm/metric/LCS.js.map +1 -0
  87. package/dist/esm/metric/Levenshtein.js +92 -0
  88. package/dist/esm/metric/Levenshtein.js.map +1 -0
  89. package/dist/esm/metric/Metric.js +442 -0
  90. package/dist/esm/metric/Metric.js.map +1 -0
  91. package/dist/esm/metric/NeedlemanWunsch.js +93 -0
  92. package/dist/esm/metric/NeedlemanWunsch.js.map +1 -0
  93. package/dist/esm/metric/SmithWaterman.js +96 -0
  94. package/dist/esm/metric/SmithWaterman.js.map +1 -0
  95. package/dist/esm/metric/qGram.js +89 -0
  96. package/dist/esm/metric/qGram.js.map +1 -0
  97. package/dist/esm/phonetic/Cologne.js +114 -0
  98. package/dist/esm/phonetic/Cologne.js.map +1 -0
  99. package/dist/esm/phonetic/Metaphone.js +174 -0
  100. package/dist/esm/phonetic/Metaphone.js.map +1 -0
  101. package/dist/esm/phonetic/Phonetic.js +409 -0
  102. package/dist/esm/phonetic/Phonetic.js.map +1 -0
  103. package/dist/esm/phonetic/Soundex.js +137 -0
  104. package/dist/esm/phonetic/Soundex.js.map +1 -0
  105. package/dist/esm/utils/DeepMerge.js +139 -0
  106. package/dist/esm/utils/DeepMerge.js.map +1 -0
  107. package/dist/esm/utils/DiffChecker.js +498 -0
  108. package/dist/esm/utils/DiffChecker.js.map +1 -0
  109. package/dist/esm/utils/Filter.js +187 -0
  110. package/dist/esm/utils/Filter.js.map +1 -0
  111. package/dist/esm/utils/HashTable.js +173 -0
  112. package/dist/esm/utils/HashTable.js.map +1 -0
  113. package/dist/esm/utils/Normalizer.js +142 -0
  114. package/dist/esm/utils/Normalizer.js.map +1 -0
  115. package/dist/esm/utils/Pool.js +194 -0
  116. package/dist/esm/utils/Pool.js.map +1 -0
  117. package/dist/esm/utils/Profiler.js +227 -0
  118. package/dist/esm/utils/Profiler.js.map +1 -0
  119. package/dist/esm/utils/Registry.js +142 -0
  120. package/dist/esm/utils/Registry.js.map +1 -0
  121. package/dist/esm/utils/TextAnalyzer.js +356 -0
  122. package/dist/esm/utils/TextAnalyzer.js.map +1 -0
  123. package/dist/types/CmpStr.d.ts +472 -0
  124. package/dist/types/CmpStrAsync.d.ts +233 -0
  125. package/dist/types/index.d.ts +51 -0
  126. package/dist/types/metric/Cosine.d.ts +57 -0
  127. package/dist/types/metric/DamerauLevenshtein.d.ts +50 -0
  128. package/dist/types/metric/DiceSorensen.d.ts +57 -0
  129. package/dist/types/metric/Hamming.d.ts +49 -0
  130. package/dist/types/metric/Jaccard.d.ts +48 -0
  131. package/dist/types/metric/JaroWinkler.d.ts +50 -0
  132. package/dist/types/metric/LCS.d.ts +50 -0
  133. package/dist/types/metric/Levenshtein.d.ts +50 -0
  134. package/dist/types/metric/Metric.d.ts +261 -0
  135. package/dist/types/metric/NeedlemanWunsch.d.ts +47 -0
  136. package/dist/types/metric/SmithWaterman.d.ts +48 -0
  137. package/dist/types/metric/index.d.ts +41 -0
  138. package/dist/types/metric/qGram.d.ts +56 -0
  139. package/dist/types/phonetic/Cologne.d.ts +46 -0
  140. package/dist/types/phonetic/Metaphone.d.ts +50 -0
  141. package/dist/types/phonetic/Phonetic.d.ts +189 -0
  142. package/dist/types/phonetic/Soundex.d.ts +49 -0
  143. package/dist/types/phonetic/index.d.ts +30 -0
  144. package/dist/types/utils/DeepMerge.d.ts +70 -0
  145. package/dist/types/utils/DiffChecker.d.ts +137 -0
  146. package/dist/types/utils/Filter.d.ts +97 -0
  147. package/dist/types/utils/HashTable.d.ts +86 -0
  148. package/dist/types/utils/Normalizer.d.ts +76 -0
  149. package/dist/types/utils/Pool.d.ts +63 -0
  150. package/dist/types/utils/Profiler.d.ts +129 -0
  151. package/dist/types/utils/Registry.d.ts +57 -0
  152. package/dist/types/utils/TextAnalyzer.d.ts +199 -0
  153. package/dist/types/utils/Types.d.ts +313 -0
  154. package/package.json +62 -49
  155. package/src/CmpStr.d.ts +0 -70
  156. package/src/CmpStr.js +0 -912
  157. package/src/CmpStrAsync.d.ts +0 -19
  158. package/src/CmpStrAsync.js +0 -204
  159. package/src/algorithms/cosine.js +0 -86
  160. package/src/algorithms/damerau.js +0 -78
  161. package/src/algorithms/dice.js +0 -65
  162. package/src/algorithms/hamming.js +0 -44
  163. package/src/algorithms/jaccard.js +0 -34
  164. package/src/algorithms/jaroWinkler.js +0 -106
  165. package/src/algorithms/lcs.js +0 -58
  166. package/src/algorithms/levenshtein.js +0 -70
  167. package/src/algorithms/needlemanWunsch.js +0 -72
  168. package/src/algorithms/qGram.js +0 -63
  169. package/src/algorithms/smithWaterman.js +0 -78
  170. package/src/algorithms/soundex.js +0 -152
  171. package/src/index.d.ts +0 -3
  172. package/src/index.js +0 -47
@@ -0,0 +1,144 @@
1
+ // CmpStr v3.0.0 dev-1a82e20-250612 by Paul Köhler @komed3 / MIT License
2
+ 'use strict';
3
+
4
+ /**
5
+ * Deep Merge Utility
6
+ * src/utils/DeepMerge.ts
7
+ *
8
+ * This module provides utility functions for deep merging objects, getting values by path,
9
+ * and setting values by path in a deeply nested object structure.
10
+ *
11
+ * It supports dot and bracket notation (e.g. `a.b[0].c`) as well as escaped keys.
12
+ *
13
+ * Included functions:
14
+ * - `get`: Retrieve a deeply nested value by path
15
+ * - `set`: Assign a value to a nested path
16
+ * - `merge`: Deeply merge two objects
17
+ * - `has`: Check whether a path exists
18
+ * - `rmv`: Delete a value at a path
19
+ *
20
+ * @module Utils/DeepMerge
21
+ * @author Paul Köhler
22
+ * @license MIT
23
+ */
24
+ /**
25
+ * Parse a path string into an array of keys.
26
+ *
27
+ * @param {string} p - The path string, e.g. `a.b.c` or `a[0].b`
28
+ * @returns {(string|number)[]} - An array of keys, e.g. `['a', 'b', 'c']` or `['a', 0, 'b']`
29
+ */
30
+ const parse = (p) =>
31
+ p
32
+ .replace(/\[(\d+)]/g, '.$1')
33
+ .split('.')
34
+ .map((s) => (/^\d+$/.test(s) ? +s : s));
35
+ /**
36
+ * Deeply get a value from an object by a path string.
37
+ *
38
+ * @template T - The type of the object to get the value from
39
+ * @param {T} t - The object to get the value from
40
+ * @param {string} path - The path string, e.g. `a.b.c`
41
+ * @param {any} fallback - The default value to return if the path does not exist
42
+ * @returns {T|R|undefined} - The value at the specified path, otherwise the default value
43
+ */
44
+ function get(t, path, fallback) {
45
+ return parse(path).reduce((o, k) => o?.[k] ?? fallback, t);
46
+ }
47
+ /**
48
+ * Deeply set a value in an object by a path string.
49
+ *
50
+ * @template T - The type of the object to get the value from
51
+ * @param {T} t - The object to set the value in
52
+ * @param {string} path - The path string, e.g. `a.b.c`
53
+ * @param {any} value - The value to set at the specified path
54
+ * @returns {T} - The modified object with the value set at the specified path
55
+ * @throws {Error} - Throws an error if the key is not a valid identifier
56
+ */
57
+ function set(t, path, value) {
58
+ // If the path is empty, return the value
59
+ if (path === '') return value;
60
+ // Split the path into the first key and the rest of the path
61
+ const [k, ...r] = parse(path);
62
+ // Throw an error if the key is not a valid identifier
63
+ if (t !== undefined && (typeof t !== 'object' || t === null))
64
+ throw Error(`cannot set property <${k}> of <${JSON.stringify(t)}>`);
65
+ // Assign the value to the specified key in the object
66
+ return Object.assign(
67
+ t ?? (typeof k === 'number' ? [] : Object.create(null)),
68
+ { [k]: set(t?.[k], r.join('.'), value) }
69
+ );
70
+ }
71
+ /**
72
+ * Deeply merge two objects, where the second object overrides the first.
73
+ *
74
+ * @template T - The type of the object to get the value from
75
+ * @param {T} t - The target object to merge into
76
+ * @param {T} o - The source object to merge from
77
+ * @param {boolean} [mergeUndefined=false] - Whether to merge undefined values
78
+ * @returns {T} - The merged object
79
+ */
80
+ function merge(
81
+ t = Object.create(null),
82
+ o = Object.create(null),
83
+ mergeUndefined = false
84
+ ) {
85
+ // Iterate over the keys of the source object and merge them into the target object
86
+ return (
87
+ Object.keys(o).forEach((k) => {
88
+ const val = o[k];
89
+ // If the value is undefined and mergeUndefined is false, skip it
90
+ if (!mergeUndefined && val === undefined) return;
91
+ // Skip dangerous property names to prevent prototype pollution
92
+ if (k === '__proto__' || k === 'constructor') return;
93
+ // If the value is an object and not an array, recursively merge it
94
+ t[k] =
95
+ typeof val === 'object' && !Array.isArray(val)
96
+ ? merge(
97
+ typeof t[k] === 'object' && !Array.isArray(t[k])
98
+ ? t[k]
99
+ : Object.create(null),
100
+ val
101
+ )
102
+ : val;
103
+ }),
104
+ t
105
+ );
106
+ }
107
+ /**
108
+ * Delete a value at a specified path in an object.
109
+ *
110
+ * @template T - The type of the object to get the value from
111
+ * @param {T} t - The object to delete the value from
112
+ * @param {string} path - The path string, e.g. `a.b.c`
113
+ * @param {boolean} [preserveEmpty=false] - Whether to preserve empty objects/arrays
114
+ * @returns {T} - The modified object with the value deleted at the specified path
115
+ */
116
+ function rmv(t, path, preserveEmpty = false) {
117
+ const r = (o, k, i = 0) => {
118
+ const key = k[i];
119
+ // Delete the key if it is not an object or if it is the last key in the path
120
+ if (!o || typeof o !== 'object') return false;
121
+ if (i === k.length - 1) return delete o[key];
122
+ if (!r(o[key], k, i + 1)) return false;
123
+ // If preserveEmpty is false, check if the object or array is empty
124
+ if (!preserveEmpty) {
125
+ const val = o[key];
126
+ // If the value is an empty array or object, delete the key
127
+ if (
128
+ typeof val === 'object' &&
129
+ ((Array.isArray(val) && val.every((v) => v == null)) ||
130
+ (!Array.isArray(val) && Object.keys(val).length === 0))
131
+ )
132
+ delete o[key];
133
+ }
134
+ return true;
135
+ };
136
+ r(t, parse(path));
137
+ return t;
138
+ }
139
+
140
+ exports.get = get;
141
+ exports.merge = merge;
142
+ exports.rmv = rmv;
143
+ exports.set = set;
144
+ //# sourceMappingURL=DeepMerge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeepMerge.js","sources":["../../../src/utils/DeepMerge.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,500 @@
1
+ // CmpStr v3.0.0 dev-1a82e20-250612 by Paul Köhler @komed3 / MIT License
2
+ 'use strict';
3
+
4
+ /**
5
+ * DiffChecker Utility
6
+ * src/utils/DiffChecker.ts
7
+ *
8
+ * The DiffChecker class provides a robust and efficient utility for comparing two
9
+ * texts and extracting their differences (full lines or word mode). It supports
10
+ * context-aware grouping of changes, unified diff output (with CLI color or ASCII
11
+ * markup), and detailed change magnitude metrics. The class is highly configurable,
12
+ * allowing users to choose the diff granularity, case sensitivity, context lines,
13
+ * grouping, and output style. It is suitable for text comparison, code review
14
+ * tools, document versioning, and any application requiring precise and human-
15
+ * readable difference reporting.
16
+ *
17
+ * Features:
18
+ * - Line and word-based diffing
19
+ * - Case-insensitive comparison option
20
+ * - Context lines and grouping of adjacent changes
21
+ * - Unified diff output (ASCII or colored CLI)
22
+ * - Highlighting of changed segments within lines
23
+ * - Change magnitude calculation (relative to group or line)
24
+ * - Expand-all mode for full file context
25
+ *
26
+ * @module Utils/DiffChecker
27
+ * @author Paul Köhler (komed3)
28
+ * @license MIT
29
+ */
30
+ /**
31
+ * The DiffChecker class provides methods to compare two texts and generate
32
+ * structured diffs, grouped diffs, and unified diff outputs.
33
+ */
34
+ class DiffChecker {
35
+ // Original input texts and options
36
+ a;
37
+ b;
38
+ options;
39
+ // Computed diff entries and groups
40
+ entries = [];
41
+ grouped = [];
42
+ // Flag to indicate if the diff has already been computed
43
+ diffRun = false;
44
+ /**
45
+ * Constructs a new DiffChecker instance for comparing two texts.
46
+ *
47
+ * @param {string} a - The first (original) text
48
+ * @param {string} b - The second (modified) text
49
+ * @param {DiffOptions} [opt] - Optional diff configuration
50
+ */
51
+ constructor(a, b, opt = {}) {
52
+ // Set the two texts to compare
53
+ (this.a = a), (this.b = b);
54
+ // Merge default with user-provided options
55
+ this.options = {
56
+ ...{
57
+ mode: 'word',
58
+ caseInsensitive: false,
59
+ contextLines: 1,
60
+ groupedLines: true,
61
+ expandLines: false,
62
+ showChangeMagnitude: true,
63
+ maxMagnitudeSymbols: 5,
64
+ lineBreak: '\n'
65
+ },
66
+ ...opt
67
+ };
68
+ // Run the diff computation immediately
69
+ this.computeDiff();
70
+ }
71
+ /**
72
+ * Splits both input texts into arrays of lines and returns them
73
+ * with the maximum line count.
74
+ *
75
+ * @returns { linesA: string[], linesB: string[], maxLen: number }
76
+ */
77
+ text2lines() {
78
+ // Trim and split the input texts into lines
79
+ const linesA = this.a.trim().split(/\r?\n/);
80
+ const linesB = this.b.trim().split(/\r?\n/);
81
+ return { linesA, linesB, maxLen: Math.max(linesA.length, linesB.length) };
82
+ }
83
+ /**
84
+ * Tokenizes a string according to the current diff mode (line or word).
85
+ *
86
+ * @param {string} input - The string to tokenize
87
+ * @returns {string[]} - Array of tokens
88
+ */
89
+ tokenize(input) {
90
+ const { mode } = this.options;
91
+ switch (mode) {
92
+ // Tokenize by lines
93
+ case 'line':
94
+ return [input];
95
+ // Tokenize by words
96
+ case 'word':
97
+ return input.split(/\s+/);
98
+ }
99
+ }
100
+ /**
101
+ * Concatenates an array of tokens back into a string, respecting the diff mode.
102
+ *
103
+ * @param {string[]} input - Array of tokens
104
+ * @returns {string} - Concatenated string
105
+ */
106
+ concat(input) {
107
+ const { mode } = this.options;
108
+ return input.join(mode === 'word' ? ' ' : '');
109
+ }
110
+ /**
111
+ * Computes the diff between the two input texts and populates the
112
+ * entries and grouped arrays.
113
+ */
114
+ computeDiff() {
115
+ if (!this.diffRun) {
116
+ // Get the lines from both texts
117
+ const { linesA, linesB, maxLen } = this.text2lines();
118
+ // Loop through each line and compare them
119
+ for (let i = 0; i < maxLen; i++) {
120
+ const a = linesA[i] || '';
121
+ const b = linesB[i] || '';
122
+ // Perform line diffing
123
+ this.lineDiff(a, b, i);
124
+ }
125
+ // Find groups of adjacent changes
126
+ this.findGroups();
127
+ // Set the diff run flag to true
128
+ this.diffRun = true;
129
+ }
130
+ }
131
+ /**
132
+ * Compares two lines and records their differences at the configured granularity.
133
+ *
134
+ * @param {string} a - Line from the first text
135
+ * @param {string} b - Line from the second text
136
+ * @param {number} line - Line number
137
+ */
138
+ lineDiff(a, b, line) {
139
+ const { mode, caseInsensitive } = this.options;
140
+ const baseLen = Math.max(a.length, b.length);
141
+ let A = a,
142
+ B = b;
143
+ // If case-insensitive mode is enabled, convert both lines to lowercase
144
+ if (caseInsensitive) (A = a.toLowerCase()), (B = b.toLowerCase());
145
+ let diffs = [];
146
+ let delSize = 0,
147
+ insSize = 0;
148
+ if (mode === 'line') {
149
+ // For line mode, compare the entire lines directly
150
+ if (A !== B) {
151
+ diffs.push({
152
+ posA: 0,
153
+ posB: 0,
154
+ del: a,
155
+ ins: b,
156
+ size: b.length - a.length
157
+ });
158
+ delSize = a.length;
159
+ insSize = b.length;
160
+ }
161
+ } else {
162
+ // For word mode, find precise diffs between tokenized lines
163
+ diffs = this.preciseDiff(a, A, b, B);
164
+ // Calculate total sizes of deletions and insertions
165
+ for (const d of diffs)
166
+ (delSize += d.del.length), (insSize += d.ins.length);
167
+ }
168
+ if (diffs.length) {
169
+ // Add the diff entry for this line
170
+ this.entries.push({
171
+ line,
172
+ diffs,
173
+ delSize,
174
+ insSize,
175
+ baseLen,
176
+ totalSize: insSize - delSize,
177
+ magnitude: this.magnitude(delSize, insSize, baseLen)
178
+ });
179
+ }
180
+ }
181
+ /**
182
+ * Finds all minimal diff blocks between two tokenized strings,
183
+ * returning original text and positions.
184
+ *
185
+ * @param {string} a - Original line (case preserved)
186
+ * @param {string} A - Original line (possibly lowercased)
187
+ * @param {string} b - Modified line (case preserved)
188
+ * @param {string} B - Modified line (possibly lowercased)
189
+ * @returns {DiffEntry[]} - Array of diff entries for this line
190
+ */
191
+ preciseDiff(a, A, b, B) {
192
+ // Helper function to calculate positions of tokens in the original text
193
+ const posIndex = (t) =>
194
+ t.reduce(
195
+ (p, _, i) => (p.push(i ? p[i - 1] + t[i - 1].length + 1 : 0), p),
196
+ []
197
+ );
198
+ // Original and tokenized arrays, their lengths and position arrays
199
+ const origA = this.tokenize(a);
200
+ const origB = this.tokenize(b);
201
+ const tokenA = this.tokenize(A);
202
+ const tokenB = this.tokenize(B);
203
+ const lenA = tokenA.length;
204
+ const lenB = tokenB.length;
205
+ const posArrA = posIndex(origA);
206
+ const posArrB = posIndex(origB);
207
+ // Find all matching blocks (LCS)
208
+ const matches = [];
209
+ let ai = 0,
210
+ bi = 0;
211
+ while (ai < lenA && bi < lenB) {
212
+ // If tokens match, find the length of the match
213
+ if (tokenA[ai] === tokenB[bi]) {
214
+ let len = 1;
215
+ // Extend the match as long as tokens continue to match
216
+ while (
217
+ ai + len < lenA &&
218
+ bi + len < lenB &&
219
+ tokenA[ai + len] === tokenB[bi + len]
220
+ )
221
+ len++;
222
+ matches.push({ ai, bi, len });
223
+ (ai += len), (bi += len);
224
+ } else {
225
+ let found = false;
226
+ // Look ahead for next sync point (greedy, but avoids long tails)
227
+ for (let offset = 1; offset <= 3 && !found; offset++) {
228
+ // Check if the next token in A matches the current token in B
229
+ if (ai + offset < lenA && tokenA[ai + offset] === tokenB[bi]) {
230
+ matches.push({ ai: ai + offset, bi, len: 1 });
231
+ (ai += offset + 1), (bi += 1), (found = true);
232
+ }
233
+ // Check if the next token in B matches the current token in A
234
+ else if (bi + offset < lenB && tokenA[ai] === tokenB[bi + offset]) {
235
+ matches.push({ ai, bi: bi + offset, len: 1 });
236
+ (ai += 1), (bi += offset + 1), (found = true);
237
+ }
238
+ }
239
+ // If no match was found, advance both pointers by one
240
+ if (!found) ai++, bi++;
241
+ }
242
+ }
243
+ // Walk through tokens and emit diffs between matches
244
+ const diffs = [];
245
+ let i = 0,
246
+ j = 0;
247
+ for (const m of matches) {
248
+ // If there are unmatched tokens before the match, record them
249
+ if (i < m.ai || j < m.bi) {
250
+ // Slice the original arrays to get the unmatched tokens
251
+ const delArr = origA.slice(i, m.ai);
252
+ const insArr = origB.slice(j, m.bi);
253
+ // Push the diff entry for unmatched tokens
254
+ diffs.push({
255
+ posA: posArrA[i] ?? 0,
256
+ posB: posArrB[j] ?? 0,
257
+ del: this.concat(delArr),
258
+ ins: this.concat(insArr),
259
+ size: insArr.join('').length - delArr.join('').length
260
+ });
261
+ }
262
+ // Advance to after the match
263
+ (i = m.ai + m.len), (j = m.bi + m.len);
264
+ }
265
+ // Tail diffs after the last match
266
+ if (i < lenA || j < lenB) {
267
+ // Slice the original arrays to get the unmatched tokens
268
+ const delArr = origA.slice(i);
269
+ const insArr = origB.slice(j);
270
+ // Push the diff entry for unmatched tokens at the end
271
+ diffs.push({
272
+ posA: posArrA[i] ?? 0,
273
+ posB: posArrB[j] ?? 0,
274
+ del: this.concat(delArr),
275
+ ins: this.concat(insArr),
276
+ size: insArr.join('').length - delArr.join('').length
277
+ });
278
+ }
279
+ // Remove empty diffs
280
+ return diffs.filter((d) => d.del.length > 0 || d.ins.length > 0);
281
+ }
282
+ /**
283
+ * Groups adjacent changed lines together, including context lines,
284
+ * and calculates group metrics.
285
+ */
286
+ findGroups() {
287
+ const { contextLines } = this.options;
288
+ // Helper function to add a group to the grouped array
289
+ const addGroup = (group, start, end) => {
290
+ // Calculate total sizes and base length for the group
291
+ const [delSize, insSize, totalSize, baseLen] = [
292
+ 'delSize',
293
+ 'insSize',
294
+ 'totalSize',
295
+ 'baseLen'
296
+ ].map((k) => group.reduce((sum, e) => sum + e[k], 0));
297
+ // Push the group to the grouped array
298
+ this.grouped.push({
299
+ start,
300
+ end,
301
+ delSize,
302
+ insSize,
303
+ totalSize,
304
+ line: group[0].line,
305
+ entries: group,
306
+ magnitude: this.magnitude(delSize, insSize, baseLen)
307
+ });
308
+ };
309
+ let group = [];
310
+ let start = 0,
311
+ end = 0;
312
+ // Iterate through each diff entry to find groups
313
+ for (const entry of this.entries) {
314
+ const s = Math.max(0, entry.line - contextLines);
315
+ const e = entry.line + contextLines;
316
+ // If the group is empty or the current entry is adjacent to the last one
317
+ if (!group.length || s <= end + 1) {
318
+ // If this is the first entry, set the start position
319
+ if (!group.length) start = s;
320
+ end = Math.max(end, e);
321
+ group.push(entry);
322
+ } else {
323
+ // If the group is not empty, finalize it and start a new one
324
+ addGroup(group, start, end);
325
+ (group = [entry]), (start = s), (end = e);
326
+ }
327
+ }
328
+ // If there is a remaining group, finalize it
329
+ if (group.length) addGroup(group, start, end);
330
+ }
331
+ /**
332
+ * Calculates the change magnitude string for a group or line.
333
+ *
334
+ * @param {number} del - Number of deleted characters
335
+ * @param {number} ins - Number of inserted characters
336
+ * @param {number} baseLen - Base length for normalization
337
+ * @returns {string} - Magnitude string (e.g. "++-")
338
+ */
339
+ magnitude(del, ins, baseLen) {
340
+ const { maxMagnitudeSymbols } = this.options;
341
+ const total = del + ins;
342
+ // If there are no changes or base length is zero, return empty string
343
+ if (total === 0 || baseLen === 0) return '';
344
+ // Calculate the length of the magnitude string based on the full length
345
+ const magLen = Math.min(
346
+ maxMagnitudeSymbols,
347
+ Math.max(Math.round((total / baseLen) * maxMagnitudeSymbols), 1)
348
+ );
349
+ // Calculate the number of plus and minus symbols
350
+ const plus = Math.round((ins / total) * magLen);
351
+ const minus = magLen - plus;
352
+ // Return the magnitude string with plus and minus symbols
353
+ return '+'.repeat(plus) + '-'.repeat(minus);
354
+ }
355
+ /**
356
+ * Generates a unified diff output as a string, with optional CLI coloring.
357
+ *
358
+ * @param {boolean} cli - If true, use CLI colors; otherwise, ASCII markup
359
+ * @returns {string} - Unified diff output
360
+ */
361
+ output(cli) {
362
+ const {
363
+ mode,
364
+ contextLines,
365
+ groupedLines,
366
+ expandLines,
367
+ showChangeMagnitude,
368
+ lineBreak
369
+ } = this.options;
370
+ // Get the lines and maximum length from the input texts
371
+ const { linesA, linesB, maxLen } = this.text2lines();
372
+ const linePad = Math.max(4, maxLen.toString().length);
373
+ // Helper functions for coloring and formatting (ASCII or CLI colored)
374
+ const highlight = (s, ansi) => (cli ? `\x1b[${ansi}m${s}\x1b[0m` : s);
375
+ const cy = (s) => highlight(s, '36');
376
+ const gy = (s) => highlight(s, '90');
377
+ const gn = (s) => highlight(s, '32');
378
+ const rd = (s) => highlight(s, '31');
379
+ const ye = (s) => highlight(s, '33');
380
+ const del = (s) => (cli ? `\x1b[37;41m${s}\x1b[31;49m` : `-[${s}]`);
381
+ const ins = (s) => (cli ? `\x1b[37;42m${s}\x1b[32;49m` : `+[${s}]`);
382
+ // Function to output a block of lines with optional header
383
+ const block = (start, end, forced, headerEntry) => {
384
+ // If there is a header entry, output the header
385
+ if (headerEntry) header(headerEntry);
386
+ // Loop through the range and output lines
387
+ for (let i = start; i <= end; i++) line(i, forced ?? i);
388
+ out.push('');
389
+ };
390
+ // Function to output a header for a group or line
391
+ const header = (e) => {
392
+ out.push(
393
+ `${' '.repeat(linePad)} ${cy(`@@ -${e.line + 1},${e.delSize} +${e.line + 1},${e.insSize} @@`)} ${showChangeMagnitude ? ye(e.magnitude) : ''}`
394
+ );
395
+ };
396
+ // Function to output a single line with optional diff highlighting
397
+ const line = (i, forced) => {
398
+ // If the line exists in either text, output it
399
+ if (linesA[i] || linesB[i]) {
400
+ // Find the diff entry for this line, if it exists
401
+ const entry = this.entries.find((e) => e.line === i);
402
+ // Format the line number with padding
403
+ const lineNo = (i + 1).toString().padStart(linePad, ' ');
404
+ if (entry && forced === i) {
405
+ // If there is an entry, output the line with diff highlighting
406
+ out.push(
407
+ `${lineNo} ${rd(`- ${mark(linesA[i], entry.diffs, 'del')}`)}`
408
+ );
409
+ out.push(
410
+ `${' '.repeat(linePad)} ${gn(`+ ${mark(linesB[i], entry.diffs, 'ins')}`)}`
411
+ );
412
+ } else {
413
+ // If no entry, just output the line without diff (context lines)
414
+ out.push(`${lineNo} ${gy(linesA[i])}`);
415
+ }
416
+ }
417
+ };
418
+ // Function to mark changes in a line based on the diffs
419
+ const mark = (line, diffs, type) => {
420
+ // If there are no diffs or the mode is line, return the line as is
421
+ if (!diffs.length || mode === 'line') return line;
422
+ let res = '',
423
+ idx = 0;
424
+ // Loop through each diff entry and apply the changes
425
+ for (const d of diffs) {
426
+ // Get the position and value based on the type
427
+ const pos = type === 'del' ? d.posA : d.posB;
428
+ const val = type === 'del' ? d.del : d.ins;
429
+ // If the value is empty, skip it
430
+ if (!val) continue;
431
+ // Add the unchanged part of the line before the change
432
+ if (pos > idx) res += line.slice(idx, pos);
433
+ // Add the changed part of the line with appropriate formatting
434
+ res += type === 'del' ? del(val) : ins(val);
435
+ idx = pos + val.length;
436
+ }
437
+ // Return the marked line with any remaining unchanged part
438
+ return res + line.slice(idx);
439
+ };
440
+ let out = [''];
441
+ switch (true) {
442
+ // For expandLines, output the entire file context
443
+ case expandLines:
444
+ block(0, maxLen);
445
+ break;
446
+ // For groupedLines, output each group with its start and end
447
+ case groupedLines:
448
+ for (const group of this.grouped)
449
+ block(group.start, group.end, undefined, group);
450
+ break;
451
+ // For individual lines, output each entry with context lines
452
+ default:
453
+ for (const entry of this.entries)
454
+ block(
455
+ entry.line - contextLines,
456
+ entry.line + contextLines,
457
+ entry.line,
458
+ entry
459
+ );
460
+ break;
461
+ }
462
+ // Output the final diff as a string (ASCII or CLI colored)
463
+ return out.join(lineBreak);
464
+ }
465
+ /**
466
+ * Returns the structured diff as an array of DiffLine objects.
467
+ *
468
+ * @returns {DiffLine[]} - Array of line-level diffs
469
+ */
470
+ getStructuredDiff() {
471
+ return this.entries;
472
+ }
473
+ /**
474
+ * Returns the grouped diff as an array of DiffGroup objects.
475
+ *
476
+ * @returns {DiffGroup[]} - Array of grouped diffs
477
+ */
478
+ getGroupedDiff() {
479
+ return this.grouped;
480
+ }
481
+ /**
482
+ * Returns the unified diff as a plain ASCII string.
483
+ *
484
+ * @returns {string} - Unified diff (ASCII)
485
+ */
486
+ getASCIIDiff() {
487
+ return this.output(false);
488
+ }
489
+ /**
490
+ * Returns the unified diff as a CLI-colored string.
491
+ *
492
+ * @returns {string} - Unified diff (CLI colors)
493
+ */
494
+ getCLIDiff() {
495
+ return this.output(true);
496
+ }
497
+ }
498
+
499
+ exports.DiffChecker = DiffChecker;
500
+ //# sourceMappingURL=DiffChecker.js.map