cmpstr 3.0.2 → 3.0.4
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/dist/CmpStr.esm.js +2228 -4930
- package/dist/CmpStr.esm.js.map +1 -1
- package/dist/CmpStr.esm.min.js +2 -2
- package/dist/CmpStr.esm.min.js.map +1 -1
- package/dist/CmpStr.umd.js +2348 -5026
- package/dist/CmpStr.umd.js.map +1 -1
- package/dist/CmpStr.umd.min.js +2 -2
- package/dist/CmpStr.umd.min.js.map +1 -1
- package/dist/cjs/CmpStr.cjs +10 -404
- package/dist/cjs/CmpStr.cjs.map +1 -1
- package/dist/cjs/CmpStrAsync.cjs +10 -220
- package/dist/cjs/CmpStrAsync.cjs.map +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/metric/Cosine.cjs +1 -56
- package/dist/cjs/metric/Cosine.cjs.map +1 -1
- package/dist/cjs/metric/DamerauLevenshtein.cjs +2 -64
- package/dist/cjs/metric/DamerauLevenshtein.cjs.map +1 -1
- package/dist/cjs/metric/DiceSorensen.cjs +1 -56
- package/dist/cjs/metric/DiceSorensen.cjs.map +1 -1
- package/dist/cjs/metric/Hamming.cjs +2 -51
- package/dist/cjs/metric/Hamming.cjs.map +1 -1
- package/dist/cjs/metric/Jaccard.cjs +1 -48
- package/dist/cjs/metric/Jaccard.cjs.map +1 -1
- package/dist/cjs/metric/JaroWinkler.cjs +1 -53
- package/dist/cjs/metric/JaroWinkler.cjs.map +1 -1
- package/dist/cjs/metric/LCS.cjs +1 -54
- package/dist/cjs/metric/LCS.cjs.map +1 -1
- package/dist/cjs/metric/Levenshtein.cjs +2 -60
- package/dist/cjs/metric/Levenshtein.cjs.map +1 -1
- package/dist/cjs/metric/Metric.cjs +2 -262
- package/dist/cjs/metric/Metric.cjs.map +1 -1
- package/dist/cjs/metric/NeedlemanWunsch.cjs +4 -56
- package/dist/cjs/metric/NeedlemanWunsch.cjs.map +1 -1
- package/dist/cjs/metric/SmithWaterman.cjs +4 -58
- package/dist/cjs/metric/SmithWaterman.cjs.map +1 -1
- package/dist/cjs/metric/qGram.cjs +1 -55
- package/dist/cjs/metric/qGram.cjs.map +1 -1
- package/dist/cjs/phonetic/Caverphone.cjs +1 -78
- package/dist/cjs/phonetic/Caverphone.cjs.map +1 -1
- package/dist/cjs/phonetic/Cologne.cjs +1 -43
- package/dist/cjs/phonetic/Cologne.cjs.map +1 -1
- package/dist/cjs/phonetic/Metaphone.cjs +1 -76
- package/dist/cjs/phonetic/Metaphone.cjs.map +1 -1
- package/dist/cjs/phonetic/Phonetic.cjs +1 -261
- package/dist/cjs/phonetic/Phonetic.cjs.map +1 -1
- package/dist/cjs/phonetic/Soundex.cjs +1 -47
- package/dist/cjs/phonetic/Soundex.cjs.map +1 -1
- package/dist/cjs/root.cjs +37 -0
- package/dist/cjs/root.cjs.map +1 -0
- package/dist/cjs/utils/DeepMerge.cjs +8 -75
- package/dist/cjs/utils/DeepMerge.cjs.map +1 -1
- package/dist/cjs/utils/DiffChecker.cjs +2 -190
- package/dist/cjs/utils/DiffChecker.cjs.map +1 -1
- package/dist/cjs/utils/Filter.cjs +1 -112
- package/dist/cjs/utils/Filter.cjs.map +1 -1
- package/dist/cjs/utils/HashTable.cjs +1 -99
- package/dist/cjs/utils/HashTable.cjs.map +1 -1
- package/dist/cjs/utils/Normalizer.cjs +3 -94
- package/dist/cjs/utils/Normalizer.cjs.map +1 -1
- package/dist/cjs/utils/Pool.cjs +1 -105
- package/dist/cjs/utils/Pool.cjs.map +1 -1
- package/dist/cjs/utils/Profiler.cjs +1 -133
- package/dist/cjs/utils/Profiler.cjs.map +1 -1
- package/dist/cjs/utils/Registry.cjs +2 -90
- package/dist/cjs/utils/Registry.cjs.map +1 -1
- package/dist/cjs/utils/TextAnalyzer.cjs +1 -180
- package/dist/cjs/utils/TextAnalyzer.cjs.map +1 -1
- package/dist/esm/CmpStr.mjs +10 -404
- package/dist/esm/CmpStr.mjs.map +1 -1
- package/dist/esm/CmpStrAsync.mjs +10 -220
- package/dist/esm/CmpStrAsync.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/metric/Cosine.mjs +1 -56
- package/dist/esm/metric/Cosine.mjs.map +1 -1
- package/dist/esm/metric/DamerauLevenshtein.mjs +2 -64
- package/dist/esm/metric/DamerauLevenshtein.mjs.map +1 -1
- package/dist/esm/metric/DiceSorensen.mjs +1 -56
- package/dist/esm/metric/DiceSorensen.mjs.map +1 -1
- package/dist/esm/metric/Hamming.mjs +2 -51
- package/dist/esm/metric/Hamming.mjs.map +1 -1
- package/dist/esm/metric/Jaccard.mjs +1 -48
- package/dist/esm/metric/Jaccard.mjs.map +1 -1
- package/dist/esm/metric/JaroWinkler.mjs +1 -53
- package/dist/esm/metric/JaroWinkler.mjs.map +1 -1
- package/dist/esm/metric/LCS.mjs +1 -54
- package/dist/esm/metric/LCS.mjs.map +1 -1
- package/dist/esm/metric/Levenshtein.mjs +2 -60
- package/dist/esm/metric/Levenshtein.mjs.map +1 -1
- package/dist/esm/metric/Metric.mjs +2 -262
- package/dist/esm/metric/Metric.mjs.map +1 -1
- package/dist/esm/metric/NeedlemanWunsch.mjs +4 -56
- package/dist/esm/metric/NeedlemanWunsch.mjs.map +1 -1
- package/dist/esm/metric/SmithWaterman.mjs +4 -58
- package/dist/esm/metric/SmithWaterman.mjs.map +1 -1
- package/dist/esm/metric/qGram.mjs +1 -55
- package/dist/esm/metric/qGram.mjs.map +1 -1
- package/dist/esm/phonetic/Caverphone.mjs +1 -78
- package/dist/esm/phonetic/Caverphone.mjs.map +1 -1
- package/dist/esm/phonetic/Cologne.mjs +1 -43
- package/dist/esm/phonetic/Cologne.mjs.map +1 -1
- package/dist/esm/phonetic/Metaphone.mjs +1 -76
- package/dist/esm/phonetic/Metaphone.mjs.map +1 -1
- package/dist/esm/phonetic/Phonetic.mjs +1 -261
- package/dist/esm/phonetic/Phonetic.mjs.map +1 -1
- package/dist/esm/phonetic/Soundex.mjs +1 -47
- package/dist/esm/phonetic/Soundex.mjs.map +1 -1
- package/dist/esm/root.mjs +29 -0
- package/dist/esm/root.mjs.map +1 -0
- package/dist/esm/utils/DeepMerge.mjs +8 -76
- package/dist/esm/utils/DeepMerge.mjs.map +1 -1
- package/dist/esm/utils/DiffChecker.mjs +2 -190
- package/dist/esm/utils/DiffChecker.mjs.map +1 -1
- package/dist/esm/utils/Filter.mjs +1 -112
- package/dist/esm/utils/Filter.mjs.map +1 -1
- package/dist/esm/utils/HashTable.mjs +1 -99
- package/dist/esm/utils/HashTable.mjs.map +1 -1
- package/dist/esm/utils/Normalizer.mjs +3 -94
- package/dist/esm/utils/Normalizer.mjs.map +1 -1
- package/dist/esm/utils/Pool.mjs +1 -105
- package/dist/esm/utils/Pool.mjs.map +1 -1
- package/dist/esm/utils/Profiler.mjs +1 -133
- package/dist/esm/utils/Profiler.mjs.map +1 -1
- package/dist/esm/utils/Registry.mjs +2 -90
- package/dist/esm/utils/Registry.mjs.map +1 -1
- package/dist/esm/utils/TextAnalyzer.mjs +1 -180
- package/dist/esm/utils/TextAnalyzer.mjs.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/root.d.ts +38 -0
- package/dist/types/utils/Types.d.ts +1 -0
- package/package.json +15 -9
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// CmpStr v3.0.4 build-74e65a5-250915 by Paul Köhler @komed3 / MIT License
|
|
2
|
+
import './metric/Cosine.mjs';
|
|
3
|
+
import './metric/DamerauLevenshtein.mjs';
|
|
4
|
+
import './metric/DiceSorensen.mjs';
|
|
5
|
+
import './metric/Hamming.mjs';
|
|
6
|
+
import './metric/Jaccard.mjs';
|
|
7
|
+
import './metric/JaroWinkler.mjs';
|
|
8
|
+
import './metric/LCS.mjs';
|
|
9
|
+
import './metric/Levenshtein.mjs';
|
|
10
|
+
import './metric/NeedlemanWunsch.mjs';
|
|
11
|
+
import './metric/qGram.mjs';
|
|
12
|
+
import './metric/SmithWaterman.mjs';
|
|
13
|
+
export { Metric, MetricRegistry } from './metric/Metric.mjs';
|
|
14
|
+
import './phonetic/Caverphone.mjs';
|
|
15
|
+
import './phonetic/Cologne.mjs';
|
|
16
|
+
import './phonetic/Metaphone.mjs';
|
|
17
|
+
import './phonetic/Soundex.mjs';
|
|
18
|
+
export {
|
|
19
|
+
Phonetic,
|
|
20
|
+
PhoneticMappingRegistry,
|
|
21
|
+
PhoneticRegistry
|
|
22
|
+
} from './phonetic/Phonetic.mjs';
|
|
23
|
+
import * as DeepMerge from './utils/DeepMerge.mjs';
|
|
24
|
+
export { DeepMerge };
|
|
25
|
+
export { Filter } from './utils/Filter.mjs';
|
|
26
|
+
export { HashTable } from './utils/HashTable.mjs';
|
|
27
|
+
export { Pool } from './utils/Pool.mjs';
|
|
28
|
+
export { Profiler } from './utils/Profiler.mjs';
|
|
29
|
+
//# sourceMappingURL=root.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,94 +1,38 @@
|
|
|
1
|
-
// CmpStr v3.0.
|
|
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
|
-
*/
|
|
1
|
+
// CmpStr v3.0.4 build-74e65a5-250915 by Paul Köhler @komed3 / MIT License
|
|
28
2
|
const parse = (p) =>
|
|
29
3
|
p
|
|
30
4
|
.replace(/\[(\d+)]/g, '.$1')
|
|
31
5
|
.split('.')
|
|
32
6
|
.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
7
|
function get(t, path, fallback) {
|
|
43
8
|
return parse(path).reduce((o, k) => o?.[k] ?? fallback, t);
|
|
44
9
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
*/
|
|
10
|
+
function has(t, path) {
|
|
11
|
+
return (
|
|
12
|
+
parse(path).reduce((o, k) => (o && k in o ? o[k] : undefined), t) !==
|
|
13
|
+
undefined
|
|
14
|
+
);
|
|
15
|
+
}
|
|
55
16
|
function set(t, path, value) {
|
|
56
|
-
// If the path is empty, return the value
|
|
57
17
|
if (path === '') return value;
|
|
58
|
-
// Split the path into the first key and the rest of the path
|
|
59
18
|
const [k, ...r] = parse(path);
|
|
60
|
-
// Throw an error if the key is not a valid identifier
|
|
61
19
|
if (t !== undefined && (typeof t !== 'object' || t === null))
|
|
62
20
|
throw Error(`Cannot set property <${k}> of <${JSON.stringify(t)}>`);
|
|
63
|
-
// Assign the value to the specified key in the object
|
|
64
21
|
return Object.assign(
|
|
65
22
|
t ?? (typeof k === 'number' ? [] : Object.create(null)),
|
|
66
23
|
{ [k]: set(t?.[k], r.join('.'), value) }
|
|
67
24
|
);
|
|
68
25
|
}
|
|
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
26
|
function merge(
|
|
79
27
|
t = Object.create(null),
|
|
80
28
|
o = Object.create(null),
|
|
81
29
|
mergeUndefined = false
|
|
82
30
|
) {
|
|
83
|
-
// Iterate over the keys of the source object and merge them into the target object
|
|
84
31
|
return (
|
|
85
32
|
Object.keys(o).forEach((k) => {
|
|
86
33
|
const val = o[k];
|
|
87
|
-
// If the value is undefined and mergeUndefined is false, skip it
|
|
88
34
|
if (!mergeUndefined && val === undefined) return;
|
|
89
|
-
// Skip dangerous property names to prevent prototype pollution
|
|
90
35
|
if (k === '__proto__' || k === 'constructor') return;
|
|
91
|
-
// If the value is an object and not an array, recursively merge it
|
|
92
36
|
t[k] =
|
|
93
37
|
typeof val === 'object' && !Array.isArray(val)
|
|
94
38
|
? merge(
|
|
@@ -102,26 +46,14 @@ function merge(
|
|
|
102
46
|
t
|
|
103
47
|
);
|
|
104
48
|
}
|
|
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
49
|
function rmv(t, path, preserveEmpty = false) {
|
|
115
50
|
const r = (o, k, i = 0) => {
|
|
116
51
|
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
52
|
if (!o || typeof o !== 'object') return false;
|
|
119
53
|
if (i === k.length - 1) return delete o[key];
|
|
120
54
|
if (!r(o[key], k, i + 1)) return false;
|
|
121
|
-
// If preserveEmpty is false, check if the object or array is empty
|
|
122
55
|
if (!preserveEmpty) {
|
|
123
56
|
const val = o[key];
|
|
124
|
-
// If the value is an empty array or object, delete the key
|
|
125
57
|
if (
|
|
126
58
|
typeof val === 'object' &&
|
|
127
59
|
((Array.isArray(val) && val.every((v) => v == null)) ||
|
|
@@ -135,5 +67,5 @@ function rmv(t, path, preserveEmpty = false) {
|
|
|
135
67
|
return t;
|
|
136
68
|
}
|
|
137
69
|
|
|
138
|
-
export { get, merge, rmv, set };
|
|
70
|
+
export { get, has, merge, rmv, set };
|
|
139
71
|
//# sourceMappingURL=DeepMerge.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeepMerge.mjs","sources":["../../../src/utils/DeepMerge.ts"],"sourcesContent":[null],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"DeepMerge.mjs","sources":["../../../src/utils/DeepMerge.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -1,55 +1,13 @@
|
|
|
1
|
-
// CmpStr v3.0.
|
|
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
|
-
*/
|
|
1
|
+
// CmpStr v3.0.4 build-74e65a5-250915 by Paul Köhler @komed3 / MIT License
|
|
32
2
|
class DiffChecker {
|
|
33
|
-
// Original input texts and options
|
|
34
3
|
a;
|
|
35
4
|
b;
|
|
36
5
|
options;
|
|
37
|
-
// Computed diff entries and groups
|
|
38
6
|
entries = [];
|
|
39
7
|
grouped = [];
|
|
40
|
-
// Flag to indicate if the diff has already been computed
|
|
41
8
|
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
9
|
constructor(a, b, opt = {}) {
|
|
50
|
-
// Set the two texts to compare
|
|
51
10
|
((this.a = a), (this.b = b));
|
|
52
|
-
// Merge default with user-provided options
|
|
53
11
|
this.options = {
|
|
54
12
|
...{
|
|
55
13
|
mode: 'word',
|
|
@@ -63,88 +21,48 @@ class DiffChecker {
|
|
|
63
21
|
},
|
|
64
22
|
...opt
|
|
65
23
|
};
|
|
66
|
-
// Run the diff computation immediately
|
|
67
24
|
this.computeDiff();
|
|
68
25
|
}
|
|
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
26
|
text2lines() {
|
|
76
|
-
// Trim and split the input texts into lines
|
|
77
27
|
const linesA = this.a.trim().split(/\r?\n/);
|
|
78
28
|
const linesB = this.b.trim().split(/\r?\n/);
|
|
79
29
|
return { linesA, linesB, maxLen: Math.max(linesA.length, linesB.length) };
|
|
80
30
|
}
|
|
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
31
|
tokenize(input) {
|
|
88
32
|
const { mode } = this.options;
|
|
89
33
|
switch (mode) {
|
|
90
|
-
// Tokenize by lines
|
|
91
34
|
case 'line':
|
|
92
35
|
return [input];
|
|
93
|
-
// Tokenize by words
|
|
94
36
|
case 'word':
|
|
95
37
|
return input.split(/\s+/);
|
|
96
38
|
}
|
|
97
39
|
}
|
|
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
40
|
concat(input) {
|
|
105
41
|
const { mode } = this.options;
|
|
106
42
|
return input.join(mode === 'word' ? ' ' : '');
|
|
107
43
|
}
|
|
108
|
-
/**
|
|
109
|
-
* Computes the diff between the two input texts and populates the
|
|
110
|
-
* entries and grouped arrays.
|
|
111
|
-
*/
|
|
112
44
|
computeDiff() {
|
|
113
45
|
if (!this.diffRun) {
|
|
114
|
-
// Get the lines from both texts
|
|
115
46
|
const { linesA, linesB, maxLen } = this.text2lines();
|
|
116
|
-
// Loop through each line and compare them
|
|
117
47
|
for (let i = 0; i < maxLen; i++) {
|
|
118
48
|
const a = linesA[i] || '';
|
|
119
49
|
const b = linesB[i] || '';
|
|
120
|
-
// Perform line diffing
|
|
121
50
|
this.lineDiff(a, b, i);
|
|
122
51
|
}
|
|
123
|
-
// Find groups of adjacent changes
|
|
124
52
|
this.findGroups();
|
|
125
|
-
// Set the diff run flag to true
|
|
126
53
|
this.diffRun = true;
|
|
127
54
|
}
|
|
128
55
|
}
|
|
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
56
|
lineDiff(a, b, line) {
|
|
137
57
|
const { mode, caseInsensitive } = this.options;
|
|
138
58
|
const baseLen = Math.max(a.length, b.length);
|
|
139
59
|
let A = a,
|
|
140
60
|
B = b;
|
|
141
|
-
// If case-insensitive mode is enabled, convert both lines to lowercase
|
|
142
61
|
if (caseInsensitive) ((A = a.toLowerCase()), (B = b.toLowerCase()));
|
|
143
62
|
let diffs = [];
|
|
144
63
|
let delSize = 0,
|
|
145
64
|
insSize = 0;
|
|
146
65
|
if (mode === 'line') {
|
|
147
|
-
// For line mode, compare the entire lines directly
|
|
148
66
|
if (A !== B) {
|
|
149
67
|
diffs.push({
|
|
150
68
|
posA: 0,
|
|
@@ -157,14 +75,11 @@ class DiffChecker {
|
|
|
157
75
|
insSize = b.length;
|
|
158
76
|
}
|
|
159
77
|
} else {
|
|
160
|
-
// For word mode, find precise diffs between tokenized lines
|
|
161
78
|
diffs = this.preciseDiff(a, A, b, B);
|
|
162
|
-
// Calculate total sizes of deletions and insertions
|
|
163
79
|
for (const d of diffs)
|
|
164
80
|
((delSize += d.del.length), (insSize += d.ins.length));
|
|
165
81
|
}
|
|
166
82
|
if (diffs.length) {
|
|
167
|
-
// Add the diff entry for this line
|
|
168
83
|
this.entries.push({
|
|
169
84
|
line,
|
|
170
85
|
diffs,
|
|
@@ -176,24 +91,12 @@ class DiffChecker {
|
|
|
176
91
|
});
|
|
177
92
|
}
|
|
178
93
|
}
|
|
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
94
|
preciseDiff(a, A, b, B) {
|
|
190
|
-
// Helper function to calculate positions of tokens in the original text
|
|
191
95
|
const posIndex = (t) =>
|
|
192
96
|
t.reduce(
|
|
193
97
|
(p, _, i) => (p.push(i ? p[i - 1] + t[i - 1].length + 1 : 0), p),
|
|
194
98
|
[]
|
|
195
99
|
);
|
|
196
|
-
// Original and tokenized arrays, their lengths and position arrays
|
|
197
100
|
const origA = this.tokenize(a);
|
|
198
101
|
const origB = this.tokenize(b);
|
|
199
102
|
const tokenA = this.tokenize(A);
|
|
@@ -202,15 +105,12 @@ class DiffChecker {
|
|
|
202
105
|
const lenB = tokenB.length;
|
|
203
106
|
const posArrA = posIndex(origA);
|
|
204
107
|
const posArrB = posIndex(origB);
|
|
205
|
-
// Find all matching blocks (LCS)
|
|
206
108
|
const matches = [];
|
|
207
109
|
let ai = 0,
|
|
208
110
|
bi = 0;
|
|
209
111
|
while (ai < lenA && bi < lenB) {
|
|
210
|
-
// If tokens match, find the length of the match
|
|
211
112
|
if (tokenA[ai] === tokenB[bi]) {
|
|
212
113
|
let len = 1;
|
|
213
|
-
// Extend the match as long as tokens continue to match
|
|
214
114
|
while (
|
|
215
115
|
ai + len < lenA &&
|
|
216
116
|
bi + len < lenB &&
|
|
@@ -221,34 +121,25 @@ class DiffChecker {
|
|
|
221
121
|
((ai += len), (bi += len));
|
|
222
122
|
} else {
|
|
223
123
|
let found = false;
|
|
224
|
-
// Look ahead for next sync point (greedy, but avoids long tails)
|
|
225
124
|
for (let offset = 1; offset <= 3 && !found; offset++) {
|
|
226
|
-
// Check if the next token in A matches the current token in B
|
|
227
125
|
if (ai + offset < lenA && tokenA[ai + offset] === tokenB[bi]) {
|
|
228
126
|
matches.push({ ai: ai + offset, bi, len: 1 });
|
|
229
127
|
((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]) {
|
|
128
|
+
} else if (bi + offset < lenB && tokenA[ai] === tokenB[bi + offset]) {
|
|
233
129
|
matches.push({ ai, bi: bi + offset, len: 1 });
|
|
234
130
|
((ai += 1), (bi += offset + 1), (found = true));
|
|
235
131
|
}
|
|
236
132
|
}
|
|
237
|
-
// If no match was found, advance both pointers by one
|
|
238
133
|
if (!found) (ai++, bi++);
|
|
239
134
|
}
|
|
240
135
|
}
|
|
241
|
-
// Walk through tokens and emit diffs between matches
|
|
242
136
|
const diffs = [];
|
|
243
137
|
let i = 0,
|
|
244
138
|
j = 0;
|
|
245
139
|
for (const m of matches) {
|
|
246
|
-
// If there are unmatched tokens before the match, record them
|
|
247
140
|
if (i < m.ai || j < m.bi) {
|
|
248
|
-
// Slice the original arrays to get the unmatched tokens
|
|
249
141
|
const delArr = origA.slice(i, m.ai);
|
|
250
142
|
const insArr = origB.slice(j, m.bi);
|
|
251
|
-
// Push the diff entry for unmatched tokens
|
|
252
143
|
diffs.push({
|
|
253
144
|
posA: posArrA[i] ?? 0,
|
|
254
145
|
posB: posArrB[j] ?? 0,
|
|
@@ -257,15 +148,11 @@ class DiffChecker {
|
|
|
257
148
|
size: insArr.join('').length - delArr.join('').length
|
|
258
149
|
});
|
|
259
150
|
}
|
|
260
|
-
// Advance to after the match
|
|
261
151
|
((i = m.ai + m.len), (j = m.bi + m.len));
|
|
262
152
|
}
|
|
263
|
-
// Tail diffs after the last match
|
|
264
153
|
if (i < lenA || j < lenB) {
|
|
265
|
-
// Slice the original arrays to get the unmatched tokens
|
|
266
154
|
const delArr = origA.slice(i);
|
|
267
155
|
const insArr = origB.slice(j);
|
|
268
|
-
// Push the diff entry for unmatched tokens at the end
|
|
269
156
|
diffs.push({
|
|
270
157
|
posA: posArrA[i] ?? 0,
|
|
271
158
|
posB: posArrB[j] ?? 0,
|
|
@@ -274,25 +161,17 @@ class DiffChecker {
|
|
|
274
161
|
size: insArr.join('').length - delArr.join('').length
|
|
275
162
|
});
|
|
276
163
|
}
|
|
277
|
-
// Remove empty diffs
|
|
278
164
|
return diffs.filter((d) => d.del.length > 0 || d.ins.length > 0);
|
|
279
165
|
}
|
|
280
|
-
/**
|
|
281
|
-
* Groups adjacent changed lines together, including context lines,
|
|
282
|
-
* and calculates group metrics.
|
|
283
|
-
*/
|
|
284
166
|
findGroups() {
|
|
285
167
|
const { contextLines } = this.options;
|
|
286
|
-
// Helper function to add a group to the grouped array
|
|
287
168
|
const addGroup = (group, start, end) => {
|
|
288
|
-
// Calculate total sizes and base length for the group
|
|
289
169
|
const [delSize, insSize, totalSize, baseLen] = [
|
|
290
170
|
'delSize',
|
|
291
171
|
'insSize',
|
|
292
172
|
'totalSize',
|
|
293
173
|
'baseLen'
|
|
294
174
|
].map((k) => group.reduce((sum, e) => sum + e[k], 0));
|
|
295
|
-
// Push the group to the grouped array
|
|
296
175
|
this.grouped.push({
|
|
297
176
|
start,
|
|
298
177
|
end,
|
|
@@ -307,55 +186,32 @@ class DiffChecker {
|
|
|
307
186
|
let group = [];
|
|
308
187
|
let start = 0,
|
|
309
188
|
end = 0;
|
|
310
|
-
// Iterate through each diff entry to find groups
|
|
311
189
|
for (const entry of this.entries) {
|
|
312
190
|
const s = Math.max(0, entry.line - contextLines);
|
|
313
191
|
const e = entry.line + contextLines;
|
|
314
|
-
// If the group is empty or the current entry is adjacent to the last one
|
|
315
192
|
if (!group.length || s <= end + 1) {
|
|
316
|
-
// If this is the first entry, set the start position
|
|
317
193
|
if (!group.length) start = s;
|
|
318
194
|
end = Math.max(end, e);
|
|
319
195
|
group.push(entry);
|
|
320
196
|
} else {
|
|
321
|
-
// If the group is not empty, finalize it and start a new one
|
|
322
197
|
addGroup(group, start, end);
|
|
323
198
|
((group = [entry]), (start = s), (end = e));
|
|
324
199
|
}
|
|
325
200
|
}
|
|
326
|
-
// If there is a remaining group, finalize it
|
|
327
201
|
if (group.length) addGroup(group, start, end);
|
|
328
202
|
}
|
|
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
203
|
magnitude(del, ins, baseLen) {
|
|
338
204
|
const { maxMagnitudeSymbols } = this.options;
|
|
339
205
|
const total = del + ins;
|
|
340
|
-
// If there are no changes or base length is zero, return empty string
|
|
341
206
|
if (total === 0 || baseLen === 0) return '';
|
|
342
|
-
// Calculate the length of the magnitude string based on the full length
|
|
343
207
|
const magLen = Math.min(
|
|
344
208
|
maxMagnitudeSymbols,
|
|
345
209
|
Math.max(Math.round((total / baseLen) * maxMagnitudeSymbols), 1)
|
|
346
210
|
);
|
|
347
|
-
// Calculate the number of plus and minus symbols
|
|
348
211
|
const plus = Math.round((ins / total) * magLen);
|
|
349
212
|
const minus = magLen - plus;
|
|
350
|
-
// Return the magnitude string with plus and minus symbols
|
|
351
213
|
return '+'.repeat(plus) + '-'.repeat(minus);
|
|
352
214
|
}
|
|
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
215
|
output(cli) {
|
|
360
216
|
const {
|
|
361
217
|
mode,
|
|
@@ -365,10 +221,8 @@ class DiffChecker {
|
|
|
365
221
|
showChangeMagnitude,
|
|
366
222
|
lineBreak
|
|
367
223
|
} = this.options;
|
|
368
|
-
// Get the lines and maximum length from the input texts
|
|
369
224
|
const { linesA, linesB, maxLen } = this.text2lines();
|
|
370
225
|
const linePad = Math.max(4, maxLen.toString().length);
|
|
371
|
-
// Helper functions for coloring and formatting (ASCII or CLI colored)
|
|
372
226
|
const highlight = (s, ansi) => (cli ? `\x1b[${ansi}m${s}\x1b[0m` : s);
|
|
373
227
|
const cy = (s) => highlight(s, '36');
|
|
374
228
|
const gy = (s) => highlight(s, '90');
|
|
@@ -377,30 +231,21 @@ class DiffChecker {
|
|
|
377
231
|
const ye = (s) => highlight(s, '33');
|
|
378
232
|
const del = (s) => (cli ? `\x1b[37;41m${s}\x1b[31;49m` : `-[${s}]`);
|
|
379
233
|
const ins = (s) => (cli ? `\x1b[37;42m${s}\x1b[32;49m` : `+[${s}]`);
|
|
380
|
-
// Function to output a block of lines with optional header
|
|
381
234
|
const block = (start, end, forced, headerEntry) => {
|
|
382
|
-
// If there is a header entry, output the header
|
|
383
235
|
if (headerEntry) header(headerEntry);
|
|
384
|
-
// Loop through the range and output lines
|
|
385
236
|
for (let i = start; i <= end; i++) line(i, forced ?? i);
|
|
386
237
|
out.push('');
|
|
387
238
|
};
|
|
388
|
-
// Function to output a header for a group or line
|
|
389
239
|
const header = (e) => {
|
|
390
240
|
out.push(
|
|
391
241
|
`${' '.repeat(linePad)} ${cy(`@@ -${e.line + 1},${e.delSize} +${e.line + 1},${e.insSize} @@`)} ${showChangeMagnitude ? ye(e.magnitude) : ''}`
|
|
392
242
|
);
|
|
393
243
|
};
|
|
394
|
-
// Function to output a single line with optional diff highlighting
|
|
395
244
|
const line = (i, forced) => {
|
|
396
|
-
// If the line exists in either text, output it
|
|
397
245
|
if (linesA[i] || linesB[i]) {
|
|
398
|
-
// Find the diff entry for this line, if it exists
|
|
399
246
|
const entry = this.entries.find((e) => e.line === i);
|
|
400
|
-
// Format the line number with padding
|
|
401
247
|
const lineNo = (i + 1).toString().padStart(linePad, ' ');
|
|
402
248
|
if (entry && forced === i) {
|
|
403
|
-
// If there is an entry, output the line with diff highlighting
|
|
404
249
|
out.push(
|
|
405
250
|
`${lineNo} ${rd(`- ${mark(linesA[i], entry.diffs, 'del')}`)}`
|
|
406
251
|
);
|
|
@@ -408,45 +253,33 @@ class DiffChecker {
|
|
|
408
253
|
`${' '.repeat(linePad)} ${gn(`+ ${mark(linesB[i], entry.diffs, 'ins')}`)}`
|
|
409
254
|
);
|
|
410
255
|
} else {
|
|
411
|
-
// If no entry, just output the line without diff (context lines)
|
|
412
256
|
out.push(`${lineNo} ${gy(linesA[i])}`);
|
|
413
257
|
}
|
|
414
258
|
}
|
|
415
259
|
};
|
|
416
|
-
// Function to mark changes in a line based on the diffs
|
|
417
260
|
const mark = (line, diffs, type) => {
|
|
418
|
-
// If there are no diffs or the mode is line, return the line as is
|
|
419
261
|
if (!diffs.length || mode === 'line') return line;
|
|
420
262
|
let res = '',
|
|
421
263
|
idx = 0;
|
|
422
|
-
// Loop through each diff entry and apply the changes
|
|
423
264
|
for (const d of diffs) {
|
|
424
|
-
// Get the position and value based on the type
|
|
425
265
|
const pos = type === 'del' ? d.posA : d.posB;
|
|
426
266
|
const val = type === 'del' ? d.del : d.ins;
|
|
427
|
-
// If the value is empty, skip it
|
|
428
267
|
if (!val) continue;
|
|
429
|
-
// Add the unchanged part of the line before the change
|
|
430
268
|
if (pos > idx) res += line.slice(idx, pos);
|
|
431
|
-
// Add the changed part of the line with appropriate formatting
|
|
432
269
|
res += type === 'del' ? del(val) : ins(val);
|
|
433
270
|
idx = pos + val.length;
|
|
434
271
|
}
|
|
435
|
-
// Return the marked line with any remaining unchanged part
|
|
436
272
|
return res + line.slice(idx);
|
|
437
273
|
};
|
|
438
274
|
let out = [''];
|
|
439
275
|
switch (true) {
|
|
440
|
-
// For expandLines, output the entire file context
|
|
441
276
|
case expandLines:
|
|
442
277
|
block(0, maxLen);
|
|
443
278
|
break;
|
|
444
|
-
// For groupedLines, output each group with its start and end
|
|
445
279
|
case groupedLines:
|
|
446
280
|
for (const group of this.grouped)
|
|
447
281
|
block(group.start, group.end, undefined, group);
|
|
448
282
|
break;
|
|
449
|
-
// For individual lines, output each entry with context lines
|
|
450
283
|
default:
|
|
451
284
|
for (const entry of this.entries)
|
|
452
285
|
block(
|
|
@@ -457,38 +290,17 @@ class DiffChecker {
|
|
|
457
290
|
);
|
|
458
291
|
break;
|
|
459
292
|
}
|
|
460
|
-
// Output the final diff as a string (ASCII or CLI colored)
|
|
461
293
|
return out.join(lineBreak);
|
|
462
294
|
}
|
|
463
|
-
/**
|
|
464
|
-
* Returns the structured diff as an array of DiffLine objects.
|
|
465
|
-
*
|
|
466
|
-
* @returns {DiffLine[]} - Array of line-level diffs
|
|
467
|
-
*/
|
|
468
295
|
getStructuredDiff() {
|
|
469
296
|
return this.entries;
|
|
470
297
|
}
|
|
471
|
-
/**
|
|
472
|
-
* Returns the grouped diff as an array of DiffGroup objects.
|
|
473
|
-
*
|
|
474
|
-
* @returns {DiffGroup[]} - Array of grouped diffs
|
|
475
|
-
*/
|
|
476
298
|
getGroupedDiff() {
|
|
477
299
|
return this.grouped;
|
|
478
300
|
}
|
|
479
|
-
/**
|
|
480
|
-
* Returns the unified diff as a plain ASCII string.
|
|
481
|
-
*
|
|
482
|
-
* @returns {string} - Unified diff (ASCII)
|
|
483
|
-
*/
|
|
484
301
|
getASCIIDiff() {
|
|
485
302
|
return this.output(false);
|
|
486
303
|
}
|
|
487
|
-
/**
|
|
488
|
-
* Returns the unified diff as a CLI-colored string.
|
|
489
|
-
*
|
|
490
|
-
* @returns {string} - Unified diff (CLI colors)
|
|
491
|
-
*/
|
|
492
304
|
getCLIDiff() {
|
|
493
305
|
return this.output(true);
|
|
494
306
|
}
|