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.
- package/LICENSE +21 -21
- package/README.md +75 -499
- package/dist/CmpStr.esm.js +4863 -0
- package/dist/CmpStr.esm.js.map +1 -0
- package/dist/CmpStr.esm.min.js +8 -0
- package/dist/CmpStr.esm.min.js.map +1 -0
- package/dist/CmpStr.umd.js +4875 -0
- package/dist/CmpStr.umd.js.map +1 -0
- package/dist/CmpStr.umd.min.js +8 -0
- package/dist/CmpStr.umd.min.js.map +1 -0
- package/dist/cjs/CmpStr.js +663 -0
- package/dist/cjs/CmpStr.js.map +1 -0
- package/dist/cjs/CmpStrAsync.js +336 -0
- package/dist/cjs/CmpStrAsync.js.map +1 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/metric/Cosine.js +101 -0
- package/dist/cjs/metric/Cosine.js.map +1 -0
- package/dist/cjs/metric/DamerauLevenshtein.js +110 -0
- package/dist/cjs/metric/DamerauLevenshtein.js.map +1 -0
- package/dist/cjs/metric/DiceSorensen.js +91 -0
- package/dist/cjs/metric/DiceSorensen.js.map +1 -0
- package/dist/cjs/metric/Hamming.js +82 -0
- package/dist/cjs/metric/Hamming.js.map +1 -0
- package/dist/cjs/metric/Jaccard.js +76 -0
- package/dist/cjs/metric/Jaccard.js.map +1 -0
- package/dist/cjs/metric/JaroWinkler.js +114 -0
- package/dist/cjs/metric/JaroWinkler.js.map +1 -0
- package/dist/cjs/metric/LCS.js +89 -0
- package/dist/cjs/metric/LCS.js.map +1 -0
- package/dist/cjs/metric/Levenshtein.js +94 -0
- package/dist/cjs/metric/Levenshtein.js.map +1 -0
- package/dist/cjs/metric/Metric.js +445 -0
- package/dist/cjs/metric/Metric.js.map +1 -0
- package/dist/cjs/metric/NeedlemanWunsch.js +95 -0
- package/dist/cjs/metric/NeedlemanWunsch.js.map +1 -0
- package/dist/cjs/metric/SmithWaterman.js +98 -0
- package/dist/cjs/metric/SmithWaterman.js.map +1 -0
- package/dist/cjs/metric/qGram.js +91 -0
- package/dist/cjs/metric/qGram.js.map +1 -0
- package/dist/cjs/phonetic/Cologne.js +112 -0
- package/dist/cjs/phonetic/Cologne.js.map +1 -0
- package/dist/cjs/phonetic/Metaphone.js +172 -0
- package/dist/cjs/phonetic/Metaphone.js.map +1 -0
- package/dist/cjs/phonetic/Phonetic.js +413 -0
- package/dist/cjs/phonetic/Phonetic.js.map +1 -0
- package/dist/cjs/phonetic/Soundex.js +135 -0
- package/dist/cjs/phonetic/Soundex.js.map +1 -0
- package/dist/cjs/utils/DeepMerge.js +144 -0
- package/dist/cjs/utils/DeepMerge.js.map +1 -0
- package/dist/cjs/utils/DiffChecker.js +500 -0
- package/dist/cjs/utils/DiffChecker.js.map +1 -0
- package/dist/cjs/utils/Filter.js +189 -0
- package/dist/cjs/utils/Filter.js.map +1 -0
- package/dist/cjs/utils/HashTable.js +175 -0
- package/dist/cjs/utils/HashTable.js.map +1 -0
- package/dist/cjs/utils/Normalizer.js +144 -0
- package/dist/cjs/utils/Normalizer.js.map +1 -0
- package/dist/cjs/utils/Pool.js +196 -0
- package/dist/cjs/utils/Pool.js.map +1 -0
- package/dist/cjs/utils/Profiler.js +229 -0
- package/dist/cjs/utils/Profiler.js.map +1 -0
- package/dist/cjs/utils/Registry.js +148 -0
- package/dist/cjs/utils/Registry.js.map +1 -0
- package/dist/cjs/utils/TextAnalyzer.js +358 -0
- package/dist/cjs/utils/TextAnalyzer.js.map +1 -0
- package/dist/esm/CmpStr.js +662 -0
- package/dist/esm/CmpStr.js.map +1 -0
- package/dist/esm/CmpStrAsync.js +331 -0
- package/dist/esm/CmpStrAsync.js.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/metric/Cosine.js +99 -0
- package/dist/esm/metric/Cosine.js.map +1 -0
- package/dist/esm/metric/DamerauLevenshtein.js +108 -0
- package/dist/esm/metric/DamerauLevenshtein.js.map +1 -0
- package/dist/esm/metric/DiceSorensen.js +89 -0
- package/dist/esm/metric/DiceSorensen.js.map +1 -0
- package/dist/esm/metric/Hamming.js +77 -0
- package/dist/esm/metric/Hamming.js.map +1 -0
- package/dist/esm/metric/Jaccard.js +74 -0
- package/dist/esm/metric/Jaccard.js.map +1 -0
- package/dist/esm/metric/JaroWinkler.js +112 -0
- package/dist/esm/metric/JaroWinkler.js.map +1 -0
- package/dist/esm/metric/LCS.js +87 -0
- package/dist/esm/metric/LCS.js.map +1 -0
- package/dist/esm/metric/Levenshtein.js +92 -0
- package/dist/esm/metric/Levenshtein.js.map +1 -0
- package/dist/esm/metric/Metric.js +442 -0
- package/dist/esm/metric/Metric.js.map +1 -0
- package/dist/esm/metric/NeedlemanWunsch.js +93 -0
- package/dist/esm/metric/NeedlemanWunsch.js.map +1 -0
- package/dist/esm/metric/SmithWaterman.js +96 -0
- package/dist/esm/metric/SmithWaterman.js.map +1 -0
- package/dist/esm/metric/qGram.js +89 -0
- package/dist/esm/metric/qGram.js.map +1 -0
- package/dist/esm/phonetic/Cologne.js +114 -0
- package/dist/esm/phonetic/Cologne.js.map +1 -0
- package/dist/esm/phonetic/Metaphone.js +174 -0
- package/dist/esm/phonetic/Metaphone.js.map +1 -0
- package/dist/esm/phonetic/Phonetic.js +409 -0
- package/dist/esm/phonetic/Phonetic.js.map +1 -0
- package/dist/esm/phonetic/Soundex.js +137 -0
- package/dist/esm/phonetic/Soundex.js.map +1 -0
- package/dist/esm/utils/DeepMerge.js +139 -0
- package/dist/esm/utils/DeepMerge.js.map +1 -0
- package/dist/esm/utils/DiffChecker.js +498 -0
- package/dist/esm/utils/DiffChecker.js.map +1 -0
- package/dist/esm/utils/Filter.js +187 -0
- package/dist/esm/utils/Filter.js.map +1 -0
- package/dist/esm/utils/HashTable.js +173 -0
- package/dist/esm/utils/HashTable.js.map +1 -0
- package/dist/esm/utils/Normalizer.js +142 -0
- package/dist/esm/utils/Normalizer.js.map +1 -0
- package/dist/esm/utils/Pool.js +194 -0
- package/dist/esm/utils/Pool.js.map +1 -0
- package/dist/esm/utils/Profiler.js +227 -0
- package/dist/esm/utils/Profiler.js.map +1 -0
- package/dist/esm/utils/Registry.js +142 -0
- package/dist/esm/utils/Registry.js.map +1 -0
- package/dist/esm/utils/TextAnalyzer.js +356 -0
- package/dist/esm/utils/TextAnalyzer.js.map +1 -0
- package/dist/types/CmpStr.d.ts +472 -0
- package/dist/types/CmpStrAsync.d.ts +233 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/metric/Cosine.d.ts +57 -0
- package/dist/types/metric/DamerauLevenshtein.d.ts +50 -0
- package/dist/types/metric/DiceSorensen.d.ts +57 -0
- package/dist/types/metric/Hamming.d.ts +49 -0
- package/dist/types/metric/Jaccard.d.ts +48 -0
- package/dist/types/metric/JaroWinkler.d.ts +50 -0
- package/dist/types/metric/LCS.d.ts +50 -0
- package/dist/types/metric/Levenshtein.d.ts +50 -0
- package/dist/types/metric/Metric.d.ts +261 -0
- package/dist/types/metric/NeedlemanWunsch.d.ts +47 -0
- package/dist/types/metric/SmithWaterman.d.ts +48 -0
- package/dist/types/metric/index.d.ts +41 -0
- package/dist/types/metric/qGram.d.ts +56 -0
- package/dist/types/phonetic/Cologne.d.ts +46 -0
- package/dist/types/phonetic/Metaphone.d.ts +50 -0
- package/dist/types/phonetic/Phonetic.d.ts +189 -0
- package/dist/types/phonetic/Soundex.d.ts +49 -0
- package/dist/types/phonetic/index.d.ts +30 -0
- package/dist/types/utils/DeepMerge.d.ts +70 -0
- package/dist/types/utils/DiffChecker.d.ts +137 -0
- package/dist/types/utils/Filter.d.ts +97 -0
- package/dist/types/utils/HashTable.d.ts +86 -0
- package/dist/types/utils/Normalizer.d.ts +76 -0
- package/dist/types/utils/Pool.d.ts +63 -0
- package/dist/types/utils/Profiler.d.ts +129 -0
- package/dist/types/utils/Registry.d.ts +57 -0
- package/dist/types/utils/TextAnalyzer.d.ts +199 -0
- package/dist/types/utils/Types.d.ts +313 -0
- package/package.json +62 -49
- package/src/CmpStr.d.ts +0 -70
- package/src/CmpStr.js +0 -912
- package/src/CmpStrAsync.d.ts +0 -19
- package/src/CmpStrAsync.js +0 -204
- package/src/algorithms/cosine.js +0 -86
- package/src/algorithms/damerau.js +0 -78
- package/src/algorithms/dice.js +0 -65
- package/src/algorithms/hamming.js +0 -44
- package/src/algorithms/jaccard.js +0 -34
- package/src/algorithms/jaroWinkler.js +0 -106
- package/src/algorithms/lcs.js +0 -58
- package/src/algorithms/levenshtein.js +0 -70
- package/src/algorithms/needlemanWunsch.js +0 -72
- package/src/algorithms/qGram.js +0 -63
- package/src/algorithms/smithWaterman.js +0 -78
- package/src/algorithms/soundex.js +0 -152
- package/src/index.d.ts +0 -3
- 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
|