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