@larsgw/formica 0.8.4 → 0.8.6

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.
@@ -1,16 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __assign = (this && this.__assign) || function () {
4
- __assign = Object.assign || function(t) {
5
- for (var s, i = 1, n = arguments.length; i < n; i++) {
6
- s = arguments[i];
7
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
8
- t[p] = s[p];
9
- }
10
- return t;
11
- };
12
- return __assign.apply(this, arguments);
13
- };
14
3
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
4
  if (k2 === undefined) k2 = k;
16
5
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -53,48 +42,21 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
53
42
  step((generator = generator.apply(thisArg, _arguments || [])).next());
54
43
  });
55
44
  };
56
- var __generator = (this && this.__generator) || function (thisArg, body) {
57
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
58
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
59
- function verb(n) { return function (v) { return step([n, v]); }; }
60
- function step(op) {
61
- if (f) throw new TypeError("Generator is already executing.");
62
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
63
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
64
- if (y = 0, t) op = [op[0] & 2, t.value];
65
- switch (op[0]) {
66
- case 0: case 1: t = op; break;
67
- case 4: _.label++; return { value: op[1], done: false };
68
- case 5: _.label++; y = op[1]; op = [0]; continue;
69
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
70
- default:
71
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
72
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
73
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
74
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
75
- if (t[2]) _.ops.pop();
76
- _.trys.pop(); continue;
77
- }
78
- op = body.call(thisArg, _);
79
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
80
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
81
- }
82
- };
83
45
  Object.defineProperty(exports, "__esModule", { value: true });
84
46
  exports.ResourceProcessorSource = void 0;
85
- var fs_1 = require("fs");
86
- var path = __importStar(require("path"));
87
- var child_process_1 = require("child_process");
88
- var util = __importStar(require("util"));
89
- var index_1 = require("../index");
90
- var util_1 = require("./util");
47
+ const fs_1 = require("fs");
48
+ const path = __importStar(require("path"));
49
+ const child_process_1 = require("child_process");
50
+ const util = __importStar(require("util"));
51
+ const index_1 = require("../index");
52
+ const util_1 = require("./util");
91
53
  var ResourceProcessorSource;
92
54
  (function (ResourceProcessorSource) {
93
55
  ResourceProcessorSource["All"] = "all";
94
56
  ResourceProcessorSource["Unprocessed"] = "unprocessed";
95
57
  ResourceProcessorSource["Modified"] = "modified";
96
58
  })(ResourceProcessorSource || (exports.ResourceProcessorSource = ResourceProcessorSource = {}));
97
- var DWC_FIELDS = [
59
+ const DWC_FIELDS = [
98
60
  'scientificNameID',
99
61
  'scientificName',
100
62
  'scientificNameAuthorship',
@@ -125,7 +87,7 @@ var DWC_FIELDS = [
125
87
  'colAcceptedTaxonID',
126
88
  'gbifAcceptedTaxonID'
127
89
  ];
128
- var DISPLAY_FIELDS = [
90
+ const DISPLAY_FIELDS = [
129
91
  'scientificNameID',
130
92
  'taxonRank',
131
93
  'scientificName',
@@ -134,7 +96,7 @@ var DISPLAY_FIELDS = [
134
96
  'colTaxonID',
135
97
  'gbifTaxonID'
136
98
  ];
137
- var GBIF_RANKS = [
99
+ const GBIF_RANKS = [
138
100
  'kingdom',
139
101
  'phyllum',
140
102
  'class',
@@ -146,12 +108,12 @@ var GBIF_RANKS = [
146
108
  'variety'
147
109
  ];
148
110
  function runGnverifier(names) {
149
- return new Promise(function (resolve, reject) {
150
- var proc = (0, child_process_1.spawn)('gnverifier', ['-s', '1,11', '-f', 'compact', '-M']);
151
- var stdout = '';
152
- proc.stdout.on('data', function (data) { stdout += data; });
111
+ return new Promise((resolve, reject) => {
112
+ const proc = (0, child_process_1.spawn)('gnverifier', ['-s', '1,11', '-f', 'compact', '-M']);
113
+ let stdout = '';
114
+ proc.stdout.on('data', data => { stdout += data; });
153
115
  proc.stderr.pipe(process.stdout);
154
- proc.on('close', function (code) {
116
+ proc.on('close', code => {
155
117
  if (code === 0) {
156
118
  resolve(stdout);
157
119
  }
@@ -164,489 +126,319 @@ function runGnverifier(names) {
164
126
  });
165
127
  }
166
128
  function listFiles(directory) {
167
- return __awaiter(this, void 0, void 0, function () {
168
- var input;
169
- return __generator(this, function (_a) {
170
- switch (_a.label) {
171
- case 0: return [4 /*yield*/, fs_1.promises.readdir(directory)];
172
- case 1:
173
- input = _a.sent();
174
- return [2 /*return*/, input.map(function (file) { return path.basename(file, '.txt'); }).sort(util_1.numericSort)];
175
- }
176
- });
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ const input = yield fs_1.promises.readdir(directory);
131
+ return input.map(file => path.basename(file, '.txt')).sort(util_1.numericSort);
177
132
  });
178
133
  }
179
134
  function listUnprocessedFiles(directory, outputDirectory) {
180
- return __awaiter(this, void 0, void 0, function () {
181
- var input, output, _a;
182
- return __generator(this, function (_b) {
183
- switch (_b.label) {
184
- case 0: return [4 /*yield*/, listFiles(directory)];
185
- case 1:
186
- input = _b.sent();
187
- _a = Set.bind;
188
- return [4 /*yield*/, fs_1.promises.readdir(outputDirectory)];
189
- case 2:
190
- output = new (_a.apply(Set, [void 0, _b.sent()]))();
191
- return [2 /*return*/, input.filter(function (file) { return !output.has(file + '-1.csv'); })];
192
- }
193
- });
135
+ return __awaiter(this, void 0, void 0, function* () {
136
+ const input = yield listFiles(directory);
137
+ const output = new Set(yield fs_1.promises.readdir(outputDirectory));
138
+ return input.filter(file => !output.has(file + '-1.csv'));
194
139
  });
195
140
  }
196
141
  function listChangedFiles(directory) {
197
- return __awaiter(this, void 0, void 0, function () {
198
- var output;
199
- return __generator(this, function (_a) {
200
- switch (_a.label) {
201
- case 0: return [4 /*yield*/, (0, util_1.runCommand)('git', ['diff', '--name-only', 'HEAD', '--', directory], {
202
- cwd: directory
203
- })];
204
- case 1:
205
- output = _a.sent();
206
- return [2 /*return*/, output.trimEnd().split('\n').map(function (file) { return path.basename(file, '.txt'); }).sort(util_1.numericSort)];
207
- }
142
+ return __awaiter(this, void 0, void 0, function* () {
143
+ const output = yield (0, util_1.runCommand)('git', ['diff', '--name-only', 'HEAD', '--', directory], {
144
+ cwd: directory
208
145
  });
146
+ return output.trimEnd().split('\n').map(file => path.basename(file, '.txt')).sort(util_1.numericSort);
209
147
  });
210
148
  }
211
149
  function getOldFile(file) {
212
- return __awaiter(this, void 0, void 0, function () {
213
- var options, gitRoot;
214
- return __generator(this, function (_a) {
215
- switch (_a.label) {
216
- case 0:
217
- options = {
218
- cwd: path.dirname(file)
219
- };
220
- return [4 /*yield*/, (0, util_1.runCommand)('git', ['rev-parse', '--show-toplevel'], options)];
221
- case 1:
222
- gitRoot = (_a.sent()).trim();
223
- return [4 /*yield*/, (0, util_1.runCommand)('git', ['show', 'HEAD:' + path.relative(gitRoot, file)], options)];
224
- case 2: return [2 /*return*/, _a.sent()];
225
- }
226
- });
150
+ return __awaiter(this, void 0, void 0, function* () {
151
+ const options = {
152
+ cwd: path.dirname(file)
153
+ };
154
+ const gitRoot = (yield (0, util_1.runCommand)('git', ['rev-parse', '--show-toplevel'], options)).trim();
155
+ return yield (0, util_1.runCommand)('git', ['show', 'HEAD:' + path.relative(gitRoot, file)], options);
227
156
  });
228
157
  }
229
- var ResourceProcessor = /** @class */ (function () {
230
- function ResourceProcessor(collectionPath) {
158
+ class ResourceProcessor {
159
+ constructor(collectionPath) {
231
160
  this.DIR_ROOT = path.resolve(collectionPath);
232
161
  this.DIR_TXT = path.join(this.DIR_ROOT, 'txt');
233
162
  this.DIR_DWC = path.join(this.DIR_ROOT, 'dwc');
234
163
  this.FILE_PROBLEMS = path.join(this.DIR_ROOT, 'problems.csv');
235
164
  }
236
- ResourceProcessor.prototype.run = function (source, config) {
237
- return __awaiter(this, void 0, void 0, function () {
238
- var ids, _i, ids_1, id;
239
- return __generator(this, function (_a) {
240
- switch (_a.label) {
241
- case 0: return [4 /*yield*/, this.listWorks(source)];
242
- case 1:
243
- ids = _a.sent();
244
- _i = 0, ids_1 = ids;
245
- _a.label = 2;
246
- case 2:
247
- if (!(_i < ids_1.length)) return [3 /*break*/, 5];
248
- id = ids_1[_i];
249
- return [4 /*yield*/, this.processWork(id, config)];
250
- case 3:
251
- _a.sent();
252
- _a.label = 4;
253
- case 4:
254
- _i++;
255
- return [3 /*break*/, 2];
256
- case 5: return [2 /*return*/];
257
- }
258
- });
259
- });
260
- };
261
- ResourceProcessor.prototype.listWorks = function (source) {
262
- return __awaiter(this, void 0, void 0, function () {
263
- return __generator(this, function (_a) {
264
- switch (source) {
265
- case ResourceProcessorSource.All:
266
- return [2 /*return*/, listFiles(this.DIR_TXT)];
267
- case ResourceProcessorSource.Unprocessed:
268
- return [2 /*return*/, listUnprocessedFiles(this.DIR_TXT, this.DIR_DWC)];
269
- case ResourceProcessorSource.Modified:
270
- return [2 /*return*/, listChangedFiles(this.DIR_TXT)];
271
- default:
272
- return [2 /*return*/, []];
273
- }
274
- return [2 /*return*/];
275
- });
165
+ run(source, config) {
166
+ return __awaiter(this, void 0, void 0, function* () {
167
+ const ids = yield this.listWorks(source);
168
+ for (const id of ids) {
169
+ yield this.processWork(id, config);
170
+ }
276
171
  });
277
- };
278
- ResourceProcessor.prototype.processWork = function (id, config) {
279
- return __awaiter(this, void 0, void 0, function () {
280
- var resources;
281
- var _this = this;
282
- return __generator(this, function (_a) {
283
- switch (_a.label) {
284
- case 0: return [4 /*yield*/, this.processResources(id, config)];
285
- case 1:
286
- resources = _a.sent();
287
- return [4 /*yield*/, Promise.all(resources.map(function (resource) {
288
- var header = DWC_FIELDS;
289
- var table = [header];
290
- var _loop_1 = function (id_1) {
291
- var taxon = resource.taxa[id_1];
292
- table.push(header.map(function (column) { return taxon[column] || ''; }));
293
- };
294
- for (var id_1 in resource.taxa) {
295
- _loop_1(id_1);
296
- }
297
- return fs_1.promises.writeFile(path.join(_this.DIR_DWC, "".concat(resource.file, ".csv")), index_1.csv.formatCsv(table, ',').trim());
298
- }))];
299
- case 2:
300
- _a.sent();
301
- return [2 /*return*/];
302
- }
303
- });
172
+ }
173
+ listWorks(source) {
174
+ return __awaiter(this, void 0, void 0, function* () {
175
+ switch (source) {
176
+ case ResourceProcessorSource.All:
177
+ return listFiles(this.DIR_TXT);
178
+ case ResourceProcessorSource.Unprocessed:
179
+ return listUnprocessedFiles(this.DIR_TXT, this.DIR_DWC);
180
+ case ResourceProcessorSource.Modified:
181
+ return listChangedFiles(this.DIR_TXT);
182
+ default:
183
+ return [];
184
+ }
304
185
  });
305
- };
306
- ResourceProcessor.prototype.processResources = function (id, config) {
307
- return __awaiter(this, void 0, void 0, function () {
308
- var resources, amendedResources, _i, resources_1, resource, results, skip, correct, choice, _a, reason;
309
- return __generator(this, function (_b) {
310
- switch (_b.label) {
311
- case 0: return [4 /*yield*/, this.processResourceText(id, config)];
312
- case 1:
313
- resources = _b.sent();
314
- amendedResources = [];
315
- _i = 0, resources_1 = resources;
316
- _b.label = 2;
317
- case 2:
318
- if (!(_i < resources_1.length)) return [3 /*break*/, 11];
319
- resource = resources_1[_i];
320
- return [4 /*yield*/, this.processResourceDwc(resource, config)];
321
- case 3:
322
- results = _b.sent();
323
- return [4 /*yield*/, this.shouldBeSkipped(resource.id)];
324
- case 4:
325
- skip = _b.sent();
326
- if (!!skip) return [3 /*break*/, 9];
327
- correct = this.checkResults(results);
328
- if (!!correct) return [3 /*break*/, 9];
329
- return [4 /*yield*/, (0, util_1.promptForAnswers)("".concat(resource.workId, ": problems found in ").concat(resource.id, ". Skip or retry (s/r)? "), ['s', 'S', 'r', 'R'])];
330
- case 5:
331
- choice = _b.sent();
332
- _a = choice;
333
- switch (_a) {
334
- case 's': return [3 /*break*/, 6];
335
- case 'S': return [3 /*break*/, 6];
336
- case 'r': return [3 /*break*/, 8];
337
- case 'R': return [3 /*break*/, 8];
338
- }
339
- return [3 /*break*/, 9];
340
- case 6: return [4 /*yield*/, (0, util_1.prompt)('Reason for skipping? ')];
341
- case 7:
342
- reason = _b.sent();
343
- fs_1.promises.appendFile(this.FILE_PROBLEMS, index_1.csv.formatCsv([[
344
- resource.workId,
345
- resource.id,
346
- reason
347
- ]]));
348
- console.log("".concat(resource.workId, ": skipping ").concat(resource.id));
349
- return [3 /*break*/, 9];
350
- case 8:
351
- {
352
- console.log("".concat(resource.workId, ": retrying ").concat(resource.id));
353
- return [2 /*return*/, this.processResources(id, config)];
354
- }
355
- _b.label = 9;
356
- case 9:
357
- amendedResources.push(results);
358
- _b.label = 10;
359
- case 10:
360
- _i++;
361
- return [3 /*break*/, 2];
362
- case 11: return [2 /*return*/, amendedResources];
186
+ }
187
+ processWork(id, config) {
188
+ return __awaiter(this, void 0, void 0, function* () {
189
+ const resources = yield this.processResources(id, config);
190
+ yield Promise.all(resources.map(resource => {
191
+ const header = DWC_FIELDS;
192
+ const table = [header];
193
+ for (const id in resource.taxa) {
194
+ const taxon = resource.taxa[id];
195
+ table.push(header.map(column => taxon[column] || ''));
363
196
  }
364
- });
197
+ return fs_1.promises.writeFile(path.join(this.DIR_DWC, `${resource.file}.csv`), index_1.csv.formatCsv(table, ',').trim());
198
+ }));
365
199
  });
366
- };
367
- ResourceProcessor.prototype.processResourceText = function (id, config) {
368
- return __awaiter(this, void 0, void 0, function () {
369
- var filePath, file, old, dwc, _i, _a, file_1, filePath_1, _b, _c, _d, _e, resources, error_1, prefix, file;
370
- var _f;
371
- return __generator(this, function (_g) {
372
- switch (_g.label) {
373
- case 0:
374
- _g.trys.push([0, 10, , 12]);
375
- console.log("".concat(id, ": generating Darwin Core"));
376
- filePath = path.join(this.DIR_TXT, id + '.txt');
377
- return [4 /*yield*/, fs_1.promises.readFile(filePath, 'utf-8')];
378
- case 1:
379
- file = _g.sent();
380
- old = undefined;
381
- if (!config.update) return [3 /*break*/, 8];
382
- dwc = [];
383
- _i = 0;
384
- return [4 /*yield*/, fs_1.promises.readdir(this.DIR_DWC)];
385
- case 2:
386
- _a = _g.sent();
387
- _g.label = 3;
388
- case 3:
389
- if (!(_i < _a.length)) return [3 /*break*/, 6];
390
- file_1 = _a[_i];
391
- if (!file_1.startsWith(id + '-')) return [3 /*break*/, 5];
392
- filePath_1 = path.join(this.DIR_DWC, file_1);
393
- _c = (_b = dwc).push;
394
- _e = (_d = index_1.csv).parseCsv;
395
- return [4 /*yield*/, getOldFile(filePath_1)];
396
- case 4:
397
- _c.apply(_b, [_e.apply(_d, [_g.sent()])]);
398
- _g.label = 5;
399
- case 5:
400
- _i++;
401
- return [3 /*break*/, 3];
402
- case 6:
403
- _f = {};
404
- return [4 /*yield*/, getOldFile(filePath)];
405
- case 7:
406
- old = (_f.txt = _g.sent(), _f.dwc = dwc, _f);
407
- _g.label = 8;
408
- case 8: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require('../index')); })];
409
- case 9:
410
- resources = (_g.sent()).resources;
411
- return [2 /*return*/, resources.parseTextFile(file, id, old)];
412
- case 10:
413
- error_1 = _g.sent();
414
- console.log(error_1);
415
- return [4 /*yield*/, (0, util_1.prompt)("".concat(id, ": generating Darwin Core failed, retry? "))
416
- // Clear cache to re-import
417
- ];
418
- case 11:
419
- _g.sent();
420
- prefix = path.dirname(require.resolve('../index'));
421
- for (file in require.cache) {
422
- if (file.startsWith(prefix)) {
423
- delete require.cache[file];
200
+ }
201
+ processResources(id, config) {
202
+ return __awaiter(this, void 0, void 0, function* () {
203
+ const resources = yield this.processResourceText(id, config);
204
+ const amendedResources = [];
205
+ for (const resource of resources) {
206
+ const results = yield this.processResourceDwc(resource, config);
207
+ const skip = yield this.shouldBeSkipped(resource.id);
208
+ if (!skip) {
209
+ const correct = this.checkResults(results);
210
+ if (!correct) {
211
+ const choice = yield (0, util_1.promptForAnswers)(`${resource.workId}: problems found in ${resource.id}. Skip or retry (s/r)? `, ['s', 'S', 'r', 'R']);
212
+ switch (choice) {
213
+ case 's':
214
+ case 'S': {
215
+ const reason = yield (0, util_1.prompt)('Reason for skipping? ');
216
+ fs_1.promises.appendFile(this.FILE_PROBLEMS, index_1.csv.formatCsv([[
217
+ resource.workId,
218
+ resource.id,
219
+ reason
220
+ ]]));
221
+ console.log(`${resource.workId}: skipping ${resource.id}`);
222
+ break;
223
+ }
224
+ case 'r':
225
+ case 'R': {
226
+ console.log(`${resource.workId}: retrying ${resource.id}`);
227
+ return this.processResources(id, config);
424
228
  }
425
229
  }
426
- return [2 /*return*/, this.processResourceText(id, config)];
427
- case 12: return [2 /*return*/];
230
+ }
428
231
  }
429
- });
232
+ amendedResources.push(results);
233
+ }
234
+ return amendedResources;
430
235
  });
431
- };
432
- ResourceProcessor.prototype.processResourceDwc = function (resource, config) {
433
- return __awaiter(this, void 0, void 0, function () {
434
- var file, _a, header_1, rows, _b, _c, _i, rows_1, row, oldTaxon, taxon, filteredResults, taxonNames, names, id, name_1, result, _loop_2, _d, _e, results, _f, amendResource, groupNameMatches, groupedNameMatches, amendedResource, _g, _h, _j, _k, source, matches;
435
- return __generator(this, function (_l) {
436
- switch (_l.label) {
437
- case 0:
438
- console.log("".concat(resource.workId, ": matching ").concat(resource.id));
439
- if (!!config.updateMappings) return [3 /*break*/, 3];
440
- file = path.join(this.DIR_DWC, resource.file + '.csv');
441
- if (!(0, fs_1.existsSync)(file)) return [3 /*break*/, 2];
442
- _c = (_b = index_1.csv).parseCsv;
443
- return [4 /*yield*/, fs_1.promises.readFile(file, 'utf-8')];
444
- case 1:
445
- _a = _c.apply(_b, [_l.sent()]), header_1 = _a[0], rows = _a.slice(1);
446
- for (_i = 0, rows_1 = rows; _i < rows_1.length; _i++) {
447
- row = rows_1[_i];
448
- oldTaxon = row.reduce(function (taxon, value, index) {
449
- taxon[header_1[index]] = value;
450
- return taxon;
451
- }, {});
452
- taxon = resource.taxa[oldTaxon.scientificNameID];
453
- if (taxon) {
454
- taxon.colTaxonID = oldTaxon.colTaxonID;
455
- taxon.colAcceptedTaxonID = oldTaxon.colAcceptedTaxonID;
456
- taxon.gbifTaxonID = oldTaxon.gbifTaxonID;
457
- taxon.gbifAcceptedTaxonID = oldTaxon.gbifAcceptedTaxonID;
458
- }
459
- }
460
- _l.label = 2;
461
- case 2: return [2 /*return*/, resource];
462
- case 3:
463
- filteredResults = {};
464
- taxonNames = {};
465
- names = new Set();
466
- for (id in resource.taxa) {
467
- name_1 = resource.taxa[id].scientificName;
468
- if (!taxonNames[name_1]) {
469
- taxonNames[name_1] = [];
470
- }
471
- taxonNames[name_1].push(id);
472
- names.add(name_1);
473
- filteredResults[id] = [];
474
- }
475
- return [4 /*yield*/, runGnverifier(Array.from(names).join('\n'))];
476
- case 4:
477
- result = _l.sent();
478
- _loop_2 = function (results) {
479
- var _m = JSON.parse(results), name_2 = _m.name, matches = _m.results;
480
- if (!matches) {
481
- return "continue";
482
- }
483
- // Fix author scoring for some species, see https://github.com/gnames/gnverifier/issues/129
484
- matches.sort(function (a, b) {
485
- if (a.sortScore !== b.sortScore) {
486
- return b.sortScore - a.sortScore;
487
- }
488
- return name_2 === a.matchedName ? -1 : name_2 === b.matchedName ? 1 : 0;
489
- });
490
- for (var _o = 0, _p = matches; _o < _p.length; _o++) {
491
- var match = _p[_o];
492
- var source = match.dataSourceId;
493
- var currentRank = match.classificationRanks.split('|').pop();
494
- if (match.scoreDetails.cardinalityScore === 0) {
495
- // Rank mismatch
496
- continue;
497
- }
498
- else if (source === 11 && currentRank === 'species' && match.classificationPath.endsWith(' spec')) {
499
- // GBIF species like "Nomada spec"
500
- continue;
501
- }
502
- for (var _q = 0, _r = taxonNames[name_2]; _q < _r.length; _q++) {
503
- var loirId = _r[_q];
504
- var taxon = resource.taxa[loirId];
505
- if (source === 11 && !GBIF_RANKS.includes(taxon.taxonRank)) {
506
- // Exclude GBIF matches for ranks that are not in GBIF
507
- continue;
508
- }
509
- else if (source === 11 && !match.isSynonym && currentRank !== taxon.taxonRank) {
510
- // Exclude matches with rank mismatches (only possible
511
- // for non-synonyms).
512
- continue;
513
- }
514
- if (!filteredResults[loirId]) {
515
- filteredResults[loirId] = [];
516
- }
517
- filteredResults[loirId].push({
518
- source: source,
519
- id: match.recordId,
520
- currentId: match.currentRecordId,
521
- classificationPath: match.classificationPath.split('|')
522
- });
523
- }
524
- }
525
- };
526
- for (_d = 0, _e = result.trim().split('\n'); _d < _e.length; _d++) {
527
- results = _e[_d];
528
- _loop_2(results);
236
+ }
237
+ processResourceText(id, config) {
238
+ return __awaiter(this, void 0, void 0, function* () {
239
+ try {
240
+ console.log(`${id}: generating Darwin Core`);
241
+ const filePath = path.join(this.DIR_TXT, id + '.txt');
242
+ const file = yield fs_1.promises.readFile(filePath, 'utf-8');
243
+ let old = undefined;
244
+ if (config.update) {
245
+ const dwc = [];
246
+ for (const file of yield fs_1.promises.readdir(this.DIR_DWC)) {
247
+ if (file.startsWith(id + '-')) {
248
+ const filePath = path.join(this.DIR_DWC, file);
249
+ dwc.push(index_1.csv.parseCsv(yield getOldFile(filePath)));
529
250
  }
530
- return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require('../index')); })];
531
- case 5:
532
- _f = (_l.sent()).taxonNames, amendResource = _f.amendResource, groupNameMatches = _f.groupNameMatches;
533
- groupedNameMatches = groupNameMatches(filteredResults);
534
- amendedResource = __assign(__assign({}, resource), { taxa: __assign({}, resource.taxa) });
535
- _g = groupedNameMatches;
536
- _h = [];
537
- for (_j in _g)
538
- _h.push(_j);
539
- _k = 0;
540
- _l.label = 6;
541
- case 6:
542
- if (!(_k < _h.length)) return [3 /*break*/, 9];
543
- _j = _h[_k];
544
- if (!(_j in _g)) return [3 /*break*/, 8];
545
- source = _j;
546
- return [4 /*yield*/, this.selectPrefixes(resource, groupedNameMatches, source)];
547
- case 7:
548
- matches = _l.sent();
549
- amendResource(amendedResource, source, matches);
550
- _l.label = 8;
551
- case 8:
552
- _k++;
553
- return [3 /*break*/, 6];
554
- case 9: return [2 /*return*/, amendedResource];
251
+ }
252
+ old = { txt: yield getOldFile(filePath), dwc };
555
253
  }
556
- });
254
+ const { resources } = yield Promise.resolve().then(() => __importStar(require('../index')));
255
+ return resources.parseTextFile(file, id, old);
256
+ }
257
+ catch (error) {
258
+ console.log(error.message);
259
+ yield (0, util_1.prompt)(`${id}: generating Darwin Core failed, retry? `);
260
+ // Clear cache to re-import
261
+ const prefix = path.dirname(require.resolve('../index'));
262
+ for (const file in require.cache) {
263
+ if (file.startsWith(prefix)) {
264
+ delete require.cache[file];
265
+ }
266
+ }
267
+ return this.processResourceText(id, config);
268
+ }
557
269
  });
558
- };
559
- ResourceProcessor.prototype.selectPrefixes = function (resource, groupedNameMatches, source) {
560
- return __awaiter(this, void 0, void 0, function () {
561
- var prefixes, mappedTaxa, _i, prefixes_1, prefix, taxon, missedTaxonCount, choice, i, prefix, taxa, taxonIds, j, taxonId, taxon, match, matches, _a, _b, i, prefix, taxa, id;
562
- return __generator(this, function (_c) {
563
- switch (_c.label) {
564
- case 0:
565
- prefixes = Object.keys(groupedNameMatches[source]);
566
- if (prefixes.length === 0) {
567
- return [2 /*return*/, {}];
568
- }
569
- else if (prefixes.length === 1) {
570
- return [2 /*return*/, groupedNameMatches[source][prefixes[0]]];
571
- }
572
- mappedTaxa = {};
573
- for (_i = 0, prefixes_1 = prefixes; _i < prefixes_1.length; _i++) {
574
- prefix = prefixes_1[_i];
575
- for (taxon in groupedNameMatches[source][prefix]) {
576
- mappedTaxa[taxon] = true;
577
- }
578
- }
579
- missedTaxonCount = Object.keys(mappedTaxa).length - Object.keys(groupedNameMatches[source][prefixes[0]]).length;
580
- if (missedTaxonCount === 0) {
581
- // Multiple prefixes but the first one maps all taxa (not counting that are unmapped in all prefixes)
582
- return [2 /*return*/, groupedNameMatches[source][prefixes[0]]];
270
+ }
271
+ processResourceDwc(resource, config) {
272
+ return __awaiter(this, void 0, void 0, function* () {
273
+ console.log(`${resource.workId}: matching ${resource.id}`);
274
+ if (!config.updateMappings) {
275
+ const file = path.join(this.DIR_DWC, resource.file + '.csv');
276
+ if ((0, fs_1.existsSync)(file)) {
277
+ const [header, ...rows] = index_1.csv.parseCsv(yield fs_1.promises.readFile(file, 'utf-8'));
278
+ for (const row of rows) {
279
+ const oldTaxon = row.reduce((taxon, value, index) => {
280
+ taxon[header[index]] = value;
281
+ return taxon;
282
+ }, {});
283
+ const taxon = resource.taxa[oldTaxon.scientificNameID];
284
+ if (taxon) {
285
+ taxon.colTaxonID = oldTaxon.colTaxonID;
286
+ taxon.colAcceptedTaxonID = oldTaxon.colAcceptedTaxonID;
287
+ taxon.gbifTaxonID = oldTaxon.gbifTaxonID;
288
+ taxon.gbifAcceptedTaxonID = oldTaxon.gbifAcceptedTaxonID;
583
289
  }
584
- console.error("".concat(resource.workId, ": source ").concat(source, " results in multiple prefixes"));
585
- if (!(missedTaxonCount <= 5)) return [3 /*break*/, 1];
586
- console.error(" Most common prefix misses ".concat(missedTaxonCount, " taxa: automatically selecting most common prefix..."));
587
- choice = '1';
588
- return [3 /*break*/, 6];
589
- case 1:
590
- if (!(source === '1')) return [3 /*break*/, 2];
591
- console.error(" Catalogue of Life: automatically selecting most common prefix...");
592
- choice = '1';
593
- return [3 /*break*/, 6];
594
- case 2:
595
- for (i = 0; i < prefixes.length; i++) {
596
- prefix = prefixes[i];
597
- taxa = groupedNameMatches[source][prefix];
598
- taxonIds = Object.keys(taxa);
599
- console.error(" [".concat(i + 1, "] ").concat(prefix, " (").concat(taxonIds.length, " taxa)"));
600
- for (j = 0; j < Math.min(9, taxonIds.length); j++) {
601
- taxonId = taxonIds[j];
602
- taxon = resource.taxa[taxonId];
603
- match = taxa[taxonId];
604
- console.error(" taxon: ".concat(taxonId, " \"").concat(taxon.scientificName, "\" - ").concat(match.classificationPath.join('|')));
605
- }
606
- if (taxonIds.length > 9) {
607
- console.error(" ...");
608
- }
290
+ }
291
+ }
292
+ return resource;
293
+ }
294
+ const filteredResults = {};
295
+ const taxonNames = {};
296
+ const names = new Set();
297
+ for (const id in resource.taxa) {
298
+ const name = resource.taxa[id].scientificName;
299
+ if (!taxonNames[name]) {
300
+ taxonNames[name] = [];
301
+ }
302
+ taxonNames[name].push(id);
303
+ names.add(name);
304
+ filteredResults[id] = [];
305
+ }
306
+ const result = yield runGnverifier(Array.from(names).join('\n'));
307
+ for (const results of result.trim().split('\n')) {
308
+ const { name, results: matches } = JSON.parse(results);
309
+ if (!matches) {
310
+ continue;
311
+ }
312
+ // Fix author scoring for some species, see https://github.com/gnames/gnverifier/issues/129
313
+ matches.sort((a, b) => {
314
+ if (a.sortScore !== b.sortScore) {
315
+ return b.sortScore - a.sortScore;
316
+ }
317
+ return name === a.matchedName ? -1 : name === b.matchedName ? 1 : 0;
318
+ });
319
+ for (const match of matches) {
320
+ const source = match.dataSourceId;
321
+ const currentRank = match.classificationRanks.split('|').pop();
322
+ if (match.scoreDetails.cardinalityScore === 0) {
323
+ // Rank mismatch
324
+ continue;
325
+ }
326
+ else if (source === 11 && currentRank === 'species' && match.classificationPath.endsWith(' spec')) {
327
+ // GBIF species like "Nomada spec"
328
+ continue;
329
+ }
330
+ for (const loirId of taxonNames[name]) {
331
+ const taxon = resource.taxa[loirId];
332
+ if (source === 11 && !GBIF_RANKS.includes(taxon.taxonRank)) {
333
+ // Exclude GBIF matches for ranks that are not in GBIF
334
+ continue;
609
335
  }
610
- _c.label = 3;
611
- case 3: return [4 /*yield*/, (0, util_1.prompt)(" Select prefixes (1-".concat(prefixes.length, ")? "))];
612
- case 4:
613
- choice = _c.sent();
614
- _c.label = 5;
615
- case 5:
616
- if (!/^(|\d+(,\d+)*)$/.test(choice)) return [3 /*break*/, 3];
617
- _c.label = 6;
618
- case 6:
619
- console.error(" Applying selection...");
620
- if (choice === '') {
621
- return [2 /*return*/, {}];
336
+ else if (source === 11 && !match.isSynonym && currentRank !== taxon.taxonRank) {
337
+ // Exclude matches with rank mismatches (only possible
338
+ // for non-synonyms).
339
+ continue;
622
340
  }
623
- matches = {};
624
- for (_a = 0, _b = choice.split(','); _a < _b.length; _a++) {
625
- i = _b[_a];
626
- prefix = prefixes[parseInt(i) - 1];
627
- taxa = groupedNameMatches[source][prefix];
628
- for (id in taxa) {
629
- if (id in matches) {
630
- continue;
631
- }
632
- matches[id] = taxa[id];
633
- }
341
+ if (!filteredResults[loirId]) {
342
+ filteredResults[loirId] = [];
634
343
  }
635
- return [2 /*return*/, matches];
344
+ filteredResults[loirId].push({
345
+ source,
346
+ id: match.recordId,
347
+ currentId: match.currentRecordId,
348
+ classificationPath: match.classificationPath.split('|')
349
+ });
350
+ }
636
351
  }
637
- });
352
+ }
353
+ const { taxonNames: { amendResource, groupNameMatches } } = yield Promise.resolve().then(() => __importStar(require('../index')));
354
+ const groupedNameMatches = groupNameMatches(filteredResults);
355
+ const amendedResource = Object.assign(Object.assign({}, resource), { taxa: Object.assign({}, resource.taxa) });
356
+ for (const source in groupedNameMatches) {
357
+ const matches = yield this.selectPrefixes(resource, groupedNameMatches, source);
358
+ amendResource(amendedResource, source, matches);
359
+ }
360
+ return amendedResource;
638
361
  });
639
- };
640
- ResourceProcessor.prototype.checkResults = function (resource) {
641
- var correct = true;
642
- var missing = [];
643
- for (var id in resource.taxa) {
644
- var taxon = resource.taxa[id];
362
+ }
363
+ selectPrefixes(resource, groupedNameMatches, source) {
364
+ return __awaiter(this, void 0, void 0, function* () {
365
+ const prefixes = Object.keys(groupedNameMatches[source]);
366
+ if (prefixes.length === 0) {
367
+ return {};
368
+ }
369
+ else if (prefixes.length === 1) {
370
+ return groupedNameMatches[source][prefixes[0]];
371
+ }
372
+ // Count total mapped taxa
373
+ const mappedTaxa = {};
374
+ for (const prefix of prefixes) {
375
+ for (const taxon in groupedNameMatches[source][prefix]) {
376
+ mappedTaxa[taxon] = true;
377
+ }
378
+ }
379
+ const missedTaxonCount = Object.keys(mappedTaxa).length - Object.keys(groupedNameMatches[source][prefixes[0]]).length;
380
+ if (missedTaxonCount === 0) {
381
+ // Multiple prefixes but the first one maps all taxa (not counting that are unmapped in all prefixes)
382
+ return groupedNameMatches[source][prefixes[0]];
383
+ }
384
+ console.error(`${resource.workId}: source ${source} results in multiple prefixes`);
385
+ let choice;
386
+ if (missedTaxonCount <= 5) {
387
+ console.error(` Most common prefix misses ${missedTaxonCount} taxa: automatically selecting most common prefix...`);
388
+ choice = '1';
389
+ }
390
+ else if (source === '1') {
391
+ console.error(` Catalogue of Life: automatically selecting most common prefix...`);
392
+ choice = '1';
393
+ }
394
+ else {
395
+ for (let i = 0; i < prefixes.length; i++) {
396
+ const prefix = prefixes[i];
397
+ const taxa = groupedNameMatches[source][prefix];
398
+ const taxonIds = Object.keys(taxa);
399
+ console.error(` [${i + 1}] ${prefix} (${taxonIds.length} taxa)`);
400
+ for (let j = 0; j < Math.min(9, taxonIds.length); j++) {
401
+ const taxonId = taxonIds[j];
402
+ const taxon = resource.taxa[taxonId];
403
+ const match = taxa[taxonId];
404
+ console.error(` taxon: ${taxonId} "${taxon.scientificName}" - ${match.classificationPath.join('|')}`);
405
+ }
406
+ if (taxonIds.length > 9) {
407
+ console.error(` ...`);
408
+ }
409
+ }
410
+ do {
411
+ choice = yield (0, util_1.prompt)(` Select prefixes (1-${prefixes.length})? `);
412
+ } while (!/^(|\d+(,\d+)*)$/.test(choice));
413
+ }
414
+ console.error(` Applying selection...`);
415
+ if (choice === '') {
416
+ return {};
417
+ }
418
+ const matches = {};
419
+ for (const i of choice.split(',')) {
420
+ const prefix = prefixes[parseInt(i) - 1];
421
+ const taxa = groupedNameMatches[source][prefix];
422
+ for (const id in taxa) {
423
+ if (id in matches) {
424
+ continue;
425
+ }
426
+ matches[id] = taxa[id];
427
+ }
428
+ }
429
+ return matches;
430
+ });
431
+ }
432
+ checkResults(resource) {
433
+ let correct = true;
434
+ const missing = [];
435
+ for (const id in resource.taxa) {
436
+ const taxon = resource.taxa[id];
645
437
  if (taxon.taxonomicStatus !== 'accepted') {
646
438
  continue;
647
439
  }
648
- var missingCol = false; // !taxon.colTaxonID
649
- var missingGbif = GBIF_RANKS.includes(taxon.taxonRank) && !taxon.gbifTaxonID;
440
+ const missingCol = false; // !taxon.colTaxonID
441
+ const missingGbif = GBIF_RANKS.includes(taxon.taxonRank) && !taxon.gbifTaxonID;
650
442
  if (missingCol || missingGbif) {
651
443
  correct = false;
652
444
  missing.push(taxon);
@@ -656,29 +448,16 @@ var ResourceProcessor = /** @class */ (function () {
656
448
  console.table(missing, DISPLAY_FIELDS);
657
449
  }
658
450
  return correct;
659
- };
660
- ResourceProcessor.prototype.shouldBeSkipped = function (id) {
661
- return __awaiter(this, void 0, void 0, function () {
662
- var problems, _a, _b;
663
- return __generator(this, function (_c) {
664
- switch (_c.label) {
665
- case 0:
666
- _b = (_a = index_1.csv).parseCsv;
667
- return [4 /*yield*/, fs_1.promises.readFile(this.FILE_PROBLEMS, 'utf8')];
668
- case 1:
669
- problems = _b.apply(_a, [_c.sent()]);
670
- return [2 /*return*/, problems.some(function (_a) {
671
- var _work = _a[0], resource = _a[1], _problem = _a[2];
672
- return resource === id;
673
- })];
674
- }
675
- });
451
+ }
452
+ shouldBeSkipped(id) {
453
+ return __awaiter(this, void 0, void 0, function* () {
454
+ const problems = index_1.csv.parseCsv(yield fs_1.promises.readFile(this.FILE_PROBLEMS, 'utf8'));
455
+ return problems.some(([_work, resource, _problem]) => resource === id);
676
456
  });
677
- };
678
- return ResourceProcessor;
679
- }());
457
+ }
458
+ }
680
459
  function main() {
681
- var args = util.parseArgs({
460
+ const args = util.parseArgs({
682
461
  options: {
683
462
  source: {
684
463
  type: 'string',
@@ -692,16 +471,16 @@ function main() {
692
471
  },
693
472
  allowPositionals: true
694
473
  });
695
- var processor = new ResourceProcessor(args.positionals[0]);
696
- process.on('exit', function () {
474
+ const processor = new ResourceProcessor(args.positionals[0]);
475
+ process.on('exit', () => {
697
476
  process.stdout.write('\n');
698
477
  });
699
- var source = args.values.source;
700
- var config = {
478
+ const source = args.values.source;
479
+ const config = {
701
480
  update: source !== 'unprocessed',
702
481
  updateMappings: !args.values['keep-mappings']
703
482
  };
704
- processor.run(source, config).catch(function (error) {
483
+ processor.run(source, config).catch(error => {
705
484
  console.error(error);
706
485
  process.exit(1);
707
486
  });