@larsgw/formica 0.8.3 → 0.8.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/CHANGELOG.md +15 -0
- package/lib/bin/generate-linked-data.js +1 -0
- package/lib/bin/validate-catalog.js +8 -10
- package/lib/resources/diff-resource.d.ts +1 -1
- package/lib/resources/diff-resource.js +61 -16
- package/package.json +1 -1
- package/src/bin/generate-linked-data.ts +1 -0
- package/src/bin/validate-catalog.ts +9 -9
- package/src/module.d.ts +0 -2
- package/src/resources/diff-resource.ts +70 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [0.8.4](https://github.com/identification-resources/formica/compare/v0.8.3...v0.8.4) (2025-09-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **catalog:** clearer error message when validating ([b1c34f0](https://github.com/identification-resources/formica/commit/b1c34f00ed9a262251d2d24b3e6c46fd17730405))
|
|
7
|
+
* **resources:** optimize diffing algorithm ([0c86c49](https://github.com/identification-resources/formica/commit/0c86c49474754dced74c8423f3e6b7ed2fd2a78f)), closes [#6](https://github.com/identification-resources/formica/issues/6)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* **catalog:** map additional scope value ([80c6d26](https://github.com/identification-resources/formica/commit/80c6d269a7d5b03600991fb353280e81afcefb0e))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
## [0.8.3](https://github.com/identification-resources/formica/compare/v0.8.2...v0.8.3) (2025-07-15)
|
|
2
17
|
|
|
3
18
|
|
|
@@ -115,6 +115,7 @@ var SCOPES = {
|
|
|
115
115
|
'nymphs': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
116
116
|
'nypmhs': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
117
117
|
'nymphs (instar V)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
118
|
+
'nymphs (instar IV)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
118
119
|
'eggs': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/embryo'],
|
|
119
120
|
// plant life stage
|
|
120
121
|
'flowering plants': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/adult'],
|
|
@@ -84,17 +84,14 @@ function validateFile(arg) {
|
|
|
84
84
|
case 1:
|
|
85
85
|
file = _a.sent();
|
|
86
86
|
sheet = path.basename(filePath, '.csv');
|
|
87
|
-
return [2 /*return*/,
|
|
88
|
-
filePath: filePath,
|
|
89
|
-
errors: index_1.catalog.loadData(file, sheet).validate()
|
|
90
|
-
}];
|
|
87
|
+
return [2 /*return*/, index_1.catalog.loadData(file, sheet).validate()];
|
|
91
88
|
}
|
|
92
89
|
});
|
|
93
90
|
});
|
|
94
91
|
}
|
|
95
92
|
function main(args) {
|
|
96
93
|
return __awaiter(this, void 0, void 0, function () {
|
|
97
|
-
var exitStatus, results,
|
|
94
|
+
var exitStatus, results, i, result;
|
|
98
95
|
return __generator(this, function (_a) {
|
|
99
96
|
switch (_a.label) {
|
|
100
97
|
case 0:
|
|
@@ -102,16 +99,17 @@ function main(args) {
|
|
|
102
99
|
return [4 /*yield*/, Promise.allSettled(args.map(validateFile))];
|
|
103
100
|
case 1:
|
|
104
101
|
results = _a.sent();
|
|
105
|
-
for (
|
|
106
|
-
result =
|
|
102
|
+
for (i = 0; i < results.length; i++) {
|
|
103
|
+
result = results[i];
|
|
107
104
|
if (result.status === 'rejected') {
|
|
105
|
+
console.error("".concat(args[i], ":"));
|
|
108
106
|
console.error(result.reason);
|
|
109
107
|
console.error();
|
|
110
108
|
exitStatus = 1;
|
|
111
109
|
}
|
|
112
|
-
else if (result.value.
|
|
113
|
-
console.error("".concat(
|
|
114
|
-
console.table(result.value
|
|
110
|
+
else if (result.value.length > 0) {
|
|
111
|
+
console.error("".concat(args[i], ":"));
|
|
112
|
+
console.table(result.value);
|
|
115
113
|
console.error();
|
|
116
114
|
exitStatus = 1;
|
|
117
115
|
}
|
|
@@ -18,18 +18,32 @@ var ResourceDiffType;
|
|
|
18
18
|
ResourceDiffType["Modified"] = "~";
|
|
19
19
|
ResourceDiffType["Unchanged"] = "=";
|
|
20
20
|
})(ResourceDiffType || (exports.ResourceDiffType = ResourceDiffType = {}));
|
|
21
|
+
var Matrix = /** @class */ (function () {
|
|
22
|
+
function Matrix(m, n) {
|
|
23
|
+
this.values = Array(m * n).fill(0);
|
|
24
|
+
this.m = m;
|
|
25
|
+
this.n = n;
|
|
26
|
+
}
|
|
27
|
+
Matrix.prototype.getValue = function (i, j) {
|
|
28
|
+
return this.values[(i * this.n) + j];
|
|
29
|
+
};
|
|
30
|
+
Matrix.prototype.setValue = function (i, j, value) {
|
|
31
|
+
this.values[(i * this.n) + j] = value;
|
|
32
|
+
};
|
|
33
|
+
return Matrix;
|
|
34
|
+
}());
|
|
21
35
|
function LCS(X, Y) {
|
|
22
36
|
var m = X.length;
|
|
23
37
|
var n = Y.length;
|
|
24
38
|
// Build matrix
|
|
25
|
-
var C =
|
|
39
|
+
var C = new Matrix(m + 1, n + 1);
|
|
26
40
|
for (var i_1 = 0; i_1 < m; i_1++) {
|
|
27
41
|
for (var j_1 = 0; j_1 < n; j_1++) {
|
|
28
42
|
if (X[i_1] === Y[j_1]) {
|
|
29
|
-
C
|
|
43
|
+
C.setValue(i_1 + 1, j_1 + 1, C.getValue(i_1, j_1) + 1);
|
|
30
44
|
}
|
|
31
45
|
else {
|
|
32
|
-
C
|
|
46
|
+
C.setValue(i_1 + 1, j_1 + 1, Math.max(C.getValue(i_1, j_1 + 1), C.getValue(i_1 + 1, j_1)));
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
49
|
}
|
|
@@ -46,7 +60,7 @@ function LCS(X, Y) {
|
|
|
46
60
|
i--;
|
|
47
61
|
j--;
|
|
48
62
|
}
|
|
49
|
-
else if (i !== 0 && (j === 0 || C
|
|
63
|
+
else if (i !== 0 && (j === 0 || C.getValue(i - 1, j) > C.getValue(i, j - 1))) {
|
|
50
64
|
diff.unshift({
|
|
51
65
|
text: X[i - 1],
|
|
52
66
|
type: ResourceDiffType.Added
|
|
@@ -63,17 +77,16 @@ function LCS(X, Y) {
|
|
|
63
77
|
}
|
|
64
78
|
return diff;
|
|
65
79
|
}
|
|
66
|
-
function
|
|
80
|
+
function tokenizeWords(text) {
|
|
67
81
|
if (text.length === 0) {
|
|
68
82
|
return [];
|
|
69
83
|
}
|
|
70
84
|
return text.match(/\S+|\n|[\r\t\f\v \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/g);
|
|
71
85
|
}
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
var Y = tokenize(b.trimEnd());
|
|
86
|
+
function tokenizeLines(text) {
|
|
87
|
+
return text.split('\n');
|
|
88
|
+
}
|
|
89
|
+
function diffTokens(X, Y) {
|
|
77
90
|
// Remove common prefix
|
|
78
91
|
var prefix = [];
|
|
79
92
|
while (X.length && X[0] === Y[0]) {
|
|
@@ -94,12 +107,11 @@ function createDiff(a, b, tokenize) {
|
|
|
94
107
|
X.pop();
|
|
95
108
|
Y.pop();
|
|
96
109
|
}
|
|
97
|
-
// Generate
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Convert word diff to line diff
|
|
110
|
+
// Generate diff from remains, combine with prefix and suffix
|
|
111
|
+
return __spreadArray(__spreadArray(__spreadArray([], prefix, true), LCS(X, Y), true), suffix, true);
|
|
112
|
+
}
|
|
113
|
+
function convertWordDiff(changes) {
|
|
114
|
+
var _a, _b;
|
|
103
115
|
var lines = [];
|
|
104
116
|
var line = null;
|
|
105
117
|
var deletedNewlines = 0;
|
|
@@ -156,3 +168,36 @@ function createDiff(a, b, tokenize) {
|
|
|
156
168
|
}
|
|
157
169
|
return lines;
|
|
158
170
|
}
|
|
171
|
+
function getWordTokensFromLines(lines) {
|
|
172
|
+
return lines.flatMap(function (change) { return tokenizeWords(change.text).concat('\n'); });
|
|
173
|
+
}
|
|
174
|
+
function createDiff(a, b) {
|
|
175
|
+
var lines = diffTokens(tokenizeLines(a.trimEnd()), tokenizeLines(b.trimEnd()));
|
|
176
|
+
var changes = [];
|
|
177
|
+
var diffPart = { added: [], deleted: [] };
|
|
178
|
+
for (var i = 0; i < lines.length; i++) {
|
|
179
|
+
if (lines[i].type === ResourceDiffType.Added) {
|
|
180
|
+
diffPart.added.push(lines[i]);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
else if (lines[i].type === ResourceDiffType.Deleted) {
|
|
184
|
+
diffPart.deleted.push(lines[i]);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (diffPart.added.length && diffPart.deleted.length) {
|
|
188
|
+
changes.push.apply(changes, convertWordDiff(diffTokens(getWordTokensFromLines(diffPart.added), getWordTokensFromLines(diffPart.deleted))));
|
|
189
|
+
diffPart.added.length = 0;
|
|
190
|
+
diffPart.deleted.length = 0;
|
|
191
|
+
}
|
|
192
|
+
else if (diffPart.added.length) {
|
|
193
|
+
changes.push.apply(changes, diffPart.added);
|
|
194
|
+
diffPart.added.length = 0;
|
|
195
|
+
}
|
|
196
|
+
else if (diffPart.deleted.length) {
|
|
197
|
+
changes.push.apply(changes, diffPart.deleted.map(function (change) { return ({ text: undefined, original: change.text, type: change.type }); }));
|
|
198
|
+
diffPart.deleted.length = 0;
|
|
199
|
+
}
|
|
200
|
+
changes.push(lines[i]);
|
|
201
|
+
}
|
|
202
|
+
return changes;
|
|
203
|
+
}
|
package/package.json
CHANGED
|
@@ -45,6 +45,7 @@ const SCOPES: Record<string, [string, string]> = {
|
|
|
45
45
|
'nymphs': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
46
46
|
'nypmhs': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
47
47
|
'nymphs (instar V)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
48
|
+
'nymphs (instar IV)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
48
49
|
'eggs': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/embryo'],
|
|
49
50
|
|
|
50
51
|
// plant life stage
|
|
@@ -4,28 +4,28 @@ import { promises as fs } from 'fs'
|
|
|
4
4
|
import * as path from 'path'
|
|
5
5
|
import { catalog } from '../index'
|
|
6
6
|
|
|
7
|
-
async function validateFile (arg: string): Promise<
|
|
7
|
+
async function validateFile (arg: string): Promise<WorkError[]> {
|
|
8
8
|
const filePath = path.resolve(arg)
|
|
9
9
|
const file = await fs.readFile(filePath, 'utf8')
|
|
10
10
|
const sheet = path.basename(filePath, '.csv')
|
|
11
|
-
return
|
|
12
|
-
filePath,
|
|
13
|
-
errors: catalog.loadData(file, sheet).validate()
|
|
14
|
-
}
|
|
11
|
+
return catalog.loadData(file, sheet).validate()
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
async function main (args: string[]): Promise<void> {
|
|
18
15
|
let exitStatus = 0
|
|
19
16
|
|
|
20
17
|
const results = await Promise.allSettled(args.map(validateFile))
|
|
21
|
-
for (
|
|
18
|
+
for (let i = 0; i < results.length; i++) {
|
|
19
|
+
const result = results[i]
|
|
20
|
+
|
|
22
21
|
if (result.status === 'rejected') {
|
|
22
|
+
console.error(`${args[i]}:`)
|
|
23
23
|
console.error(result.reason)
|
|
24
24
|
console.error()
|
|
25
25
|
exitStatus = 1
|
|
26
|
-
} else if (result.value.
|
|
27
|
-
console.error(`${
|
|
28
|
-
console.table(result.value
|
|
26
|
+
} else if (result.value.length > 0) {
|
|
27
|
+
console.error(`${args[i]}:`)
|
|
28
|
+
console.table(result.value)
|
|
29
29
|
console.error()
|
|
30
30
|
exitStatus = 1
|
|
31
31
|
}
|
package/src/module.d.ts
CHANGED
|
@@ -10,18 +10,38 @@ interface DiffPart {
|
|
|
10
10
|
type: ResourceDiffType
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
class Matrix {
|
|
14
|
+
values: Array<number>
|
|
15
|
+
m: number
|
|
16
|
+
n: number
|
|
17
|
+
|
|
18
|
+
constructor (m: number, n: number) {
|
|
19
|
+
this.values = Array(m * n).fill(0)
|
|
20
|
+
this.m = m
|
|
21
|
+
this.n = n
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getValue (i: number, j: number) {
|
|
25
|
+
return this.values[(i * this.n) + j]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setValue (i: number, j: number, value: number) {
|
|
29
|
+
this.values[(i * this.n) + j] = value
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
13
33
|
function LCS (X: string[], Y: string[]): DiffPart[] {
|
|
14
34
|
const m = X.length
|
|
15
35
|
const n = Y.length
|
|
16
36
|
|
|
17
37
|
// Build matrix
|
|
18
|
-
const C =
|
|
38
|
+
const C = new Matrix(m + 1, n + 1)
|
|
19
39
|
for (let i = 0; i < m; i++) {
|
|
20
40
|
for (let j = 0; j < n; j++) {
|
|
21
41
|
if (X[i] === Y[j]) {
|
|
22
|
-
C
|
|
42
|
+
C.setValue(i + 1, j + 1, C.getValue(i, j) + 1)
|
|
23
43
|
} else {
|
|
24
|
-
C
|
|
44
|
+
C.setValue(i + 1, j + 1, Math.max(C.getValue(i, j + 1), C.getValue(i + 1, j)))
|
|
25
45
|
}
|
|
26
46
|
}
|
|
27
47
|
}
|
|
@@ -38,7 +58,7 @@ function LCS (X: string[], Y: string[]): DiffPart[] {
|
|
|
38
58
|
})
|
|
39
59
|
i--
|
|
40
60
|
j--
|
|
41
|
-
} else if (i !== 0 && (j === 0 || C
|
|
61
|
+
} else if (i !== 0 && (j === 0 || C.getValue(i - 1, j) > C.getValue(i, j - 1))) {
|
|
42
62
|
diff.unshift({
|
|
43
63
|
text: X[i - 1],
|
|
44
64
|
type: ResourceDiffType.Added
|
|
@@ -56,17 +76,18 @@ function LCS (X: string[], Y: string[]): DiffPart[] {
|
|
|
56
76
|
return diff
|
|
57
77
|
}
|
|
58
78
|
|
|
59
|
-
function
|
|
79
|
+
function tokenizeWords (text: string): string[] {
|
|
60
80
|
if (text.length === 0) {
|
|
61
81
|
return []
|
|
62
82
|
}
|
|
63
83
|
return text.match(/\S+|\n|[\r\t\f\v \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/g) as string[]
|
|
64
84
|
}
|
|
65
85
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
86
|
+
function tokenizeLines (text: string): string[] {
|
|
87
|
+
return text.split('\n')
|
|
88
|
+
}
|
|
69
89
|
|
|
90
|
+
function diffTokens (X: string[], Y: string[]): ResourceDiff {
|
|
70
91
|
// Remove common prefix
|
|
71
92
|
const prefix = []
|
|
72
93
|
while (X.length && X[0] === Y[0]) {
|
|
@@ -89,16 +110,15 @@ export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenize
|
|
|
89
110
|
Y.pop()
|
|
90
111
|
}
|
|
91
112
|
|
|
92
|
-
// Generate
|
|
93
|
-
|
|
94
|
-
const changes = [
|
|
113
|
+
// Generate diff from remains, combine with prefix and suffix
|
|
114
|
+
return [
|
|
95
115
|
...prefix,
|
|
96
116
|
...LCS(X, Y),
|
|
97
|
-
...suffix
|
|
98
|
-
{ text: '\n', type: ResourceDiffType.Unchanged }
|
|
117
|
+
...suffix
|
|
99
118
|
]
|
|
119
|
+
}
|
|
100
120
|
|
|
101
|
-
|
|
121
|
+
function convertWordDiff (changes: ResourceDiff): ResourceDiff {
|
|
102
122
|
const lines: ResourceDiff = []
|
|
103
123
|
let line: ResourceDiffPart|null = null
|
|
104
124
|
let deletedNewlines = 0
|
|
@@ -164,3 +184,39 @@ export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenize
|
|
|
164
184
|
|
|
165
185
|
return lines
|
|
166
186
|
}
|
|
187
|
+
|
|
188
|
+
function getWordTokensFromLines (lines: ResourceDiff): string[] {
|
|
189
|
+
return lines.flatMap(change => tokenizeWords(change.text as string).concat('\n'))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function createDiff (a: string, b: string): ResourceDiff {
|
|
193
|
+
const lines: ResourceDiff = diffTokens(tokenizeLines(a.trimEnd()), tokenizeLines(b.trimEnd()))
|
|
194
|
+
|
|
195
|
+
const changes: ResourceDiff = []
|
|
196
|
+
const diffPart: Record<string, ResourceDiff> = { added: [], deleted: [] }
|
|
197
|
+
for (let i = 0; i < lines.length; i++) {
|
|
198
|
+
if (lines[i].type === ResourceDiffType.Added) {
|
|
199
|
+
diffPart.added.push(lines[i])
|
|
200
|
+
continue
|
|
201
|
+
} else if (lines[i].type === ResourceDiffType.Deleted) {
|
|
202
|
+
diffPart.deleted.push(lines[i])
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (diffPart.added.length && diffPart.deleted.length) {
|
|
207
|
+
changes.push(...convertWordDiff(diffTokens(getWordTokensFromLines(diffPart.added), getWordTokensFromLines(diffPart.deleted))))
|
|
208
|
+
diffPart.added.length = 0
|
|
209
|
+
diffPart.deleted.length = 0
|
|
210
|
+
} else if (diffPart.added.length) {
|
|
211
|
+
changes.push(...diffPart.added)
|
|
212
|
+
diffPart.added.length = 0
|
|
213
|
+
} else if (diffPart.deleted.length) {
|
|
214
|
+
changes.push(...diffPart.deleted.map(change => ({ text: undefined, original: change.text, type: change.type })))
|
|
215
|
+
diffPart.deleted.length = 0
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
changes.push(lines[i])
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return changes
|
|
222
|
+
}
|