@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 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, _i, results_1, result;
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 (_i = 0, results_1 = results; _i < results_1.length; _i++) {
106
- result = results_1[_i];
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.errors.length > 0) {
113
- console.error("".concat(result.value.filePath, ":"));
114
- console.table(result.value.errors);
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
  }
@@ -4,4 +4,4 @@ export declare enum ResourceDiffType {
4
4
  Modified = "~",
5
5
  Unchanged = "="
6
6
  }
7
- export declare function createDiff(a: string, b: string, tokenize?: ResourceDiffTokenizer): ResourceDiff;
7
+ export declare function createDiff(a: string, b: string): ResourceDiff;
@@ -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 = Array(m + 1).fill(0).map(function () { return Array(n + 1).fill(0); });
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[i_1 + 1][j_1 + 1] = C[i_1][j_1] + 1;
43
+ C.setValue(i_1 + 1, j_1 + 1, C.getValue(i_1, j_1) + 1);
30
44
  }
31
45
  else {
32
- C[i_1 + 1][j_1 + 1] = Math.max(C[i_1][j_1 + 1], C[i_1 + 1][j_1]);
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[i - 1][j] > C[i][j - 1])) {
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 gitTokenize(text) {
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 createDiff(a, b, tokenize) {
73
- var _a, _b;
74
- if (tokenize === void 0) { tokenize = gitTokenize; }
75
- var X = tokenize(a.trimEnd());
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 word from remains, combine with prefix and suffix, add trailing
98
- // newline
99
- var changes = __spreadArray(__spreadArray(__spreadArray(__spreadArray([], prefix, true), LCS(X, Y), true), suffix, true), [
100
- { text: '\n', type: ResourceDiffType.Unchanged }
101
- ], false);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "SDK and tools for data from the Library of Identification Resources",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -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<{ filePath: string, errors: WorkError[]}> {
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 (const result of results) {
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.errors.length > 0) {
27
- console.error(`${result.value.filePath}:`)
28
- console.table(result.value.errors)
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
@@ -100,8 +100,6 @@ interface ResourceDiffPart {
100
100
  type: ResourceDiffType
101
101
  }
102
102
 
103
- type ResourceDiffTokenizer = (text: string) => string[]
104
-
105
103
  declare enum ResourceDiffType {
106
104
  Added = '+',
107
105
  Deleted = '-',
@@ -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 = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0))
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[i + 1][j + 1] = C[i][j] + 1
42
+ C.setValue(i + 1, j + 1, C.getValue(i, j) + 1)
23
43
  } else {
24
- C[i + 1][j + 1] = Math.max(C[i][j + 1], C[i + 1][j])
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[i - 1][j] > C[i][j - 1])) {
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 gitTokenize (text: string): string[] {
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
- export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenizer = gitTokenize): ResourceDiff {
67
- const X = tokenize(a.trimEnd())
68
- const Y = tokenize(b.trimEnd())
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 word from remains, combine with prefix and suffix, add trailing
93
- // newline
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
- // Convert word diff to line diff
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
+ }