@larsgw/formica 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ # [0.4.0](https://github.com/identification-resources/formica/compare/v0.3.1...v0.4.0) (2023-09-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **catalog:** validation checks for unknown fields ([5fe5cf0](https://github.com/identification-resources/formica/commit/5fe5cf0b8cf4db7c4af50a897821077e410a1e09))
7
+ * **catalog:** verify if required fields are missing ([c327ae4](https://github.com/identification-resources/formica/commit/c327ae458a80567aac34dd03f3970dcf9d575479))
8
+
9
+
10
+ ### Features
11
+
12
+ * **catalog:** add duplicate_of field ([e4a8bbe](https://github.com/identification-resources/formica/commit/e4a8bbecef9c6a40d0f2cc5d4e8bb67685a453ff))
13
+
14
+
15
+
16
+ ## [0.3.1](https://github.com/identification-resources/formica/compare/v0.3.0...v0.3.1) (2023-09-04)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **resources:** allow correction of synonyms of lowest taxa ([c737d5b](https://github.com/identification-resources/formica/commit/c737d5b09d5e5b24f4cc2c8cff88c9ada8d0ab7b))
22
+ * **resources:** fix regression in c737d5b ([d4e52e9](https://github.com/identification-resources/formica/commit/d4e52e95ed1a35dabbd80b9f9aace1a824fdae98))
23
+ * **resources:** handle corrections to synonyms ([21ed9b7](https://github.com/identification-resources/formica/commit/21ed9b79f77e1e6d0639d96ed9822d5212a85a2e)), closes [#4](https://github.com/identification-resources/formica/issues/4)
24
+
25
+
26
+
1
27
  # [0.3.0](https://github.com/identification-resources/formica/compare/v0.2.1...v0.3.0) (2023-08-20)
2
28
 
3
29
 
@@ -1,4 +1,15 @@
1
1
  "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  exports.Entity = void 0;
4
15
  var value_1 = require("./value");
@@ -6,7 +17,7 @@ var Entity = /** @class */ (function () {
6
17
  function Entity(values, schema) {
7
18
  this.fields = {};
8
19
  for (var key in values) {
9
- var value = (0, value_1.parseValue)(values[key], !!schema[key].multiple);
20
+ var value = (0, value_1.parseValue)(values[key], !!schema[key] && !!schema[key].multiple);
10
21
  if (value !== null) {
11
22
  this.fields[key] = value;
12
23
  }
@@ -31,7 +42,7 @@ var Entity = /** @class */ (function () {
31
42
  };
32
43
  Entity.prototype.validate = function () {
33
44
  var errors = [];
34
- for (var field in this.fields) {
45
+ for (var field in __assign(__assign({}, this.fields), this.schema)) {
35
46
  for (var _i = 0, _a = this._validateField(field); _i < _a.length; _i++) {
36
47
  var error = _a[_i];
37
48
  errors.push({ field: field, error: error });
@@ -41,6 +52,10 @@ var Entity = /** @class */ (function () {
41
52
  };
42
53
  Entity.prototype._validateField = function (field) {
43
54
  var errors = [];
55
+ if (!(field in this.schema)) {
56
+ errors.push('Field unknown');
57
+ return errors;
58
+ }
44
59
  if (!(field in this.fields)) {
45
60
  if (this.schema[field].required) {
46
61
  errors.push('Value(s) required but missing');
@@ -51,7 +51,8 @@ var Work = /** @class */ (function (_super) {
51
51
  target_taxa: { required: false, multiple: true },
52
52
  listed_in: { required: false, multiple: true, format: value_1.FORMATS.ID },
53
53
  part_of: { required: false, multiple: true, format: value_1.FORMATS.ID },
54
- version_of: { required: false, multiple: true, format: value_1.FORMATS.ID }
54
+ version_of: { required: false, multiple: true, format: value_1.FORMATS.ID },
55
+ // duplicate_of: { required: false, multiple: false, format: FORMATS.ID }
55
56
  }) || this;
56
57
  }
57
58
  Work.prototype.deriveFields = function () {
@@ -361,7 +361,7 @@ function parseHeader(header) {
361
361
  }
362
362
  function validateResource(config, content) {
363
363
  // Check for too much indentation
364
- var longerIndent = new RegExp("^( ){".concat(config.levels.length - 1, "}(?! [+=>] ) "), 'm');
364
+ var longerIndent = new RegExp("^( ){".concat(config.levels.length - 1, "}(?! [+=>] | > ) "), 'm');
365
365
  var longerIndentMatch = content.match(longerIndent);
366
366
  if (longerIndentMatch !== null) {
367
367
  var offset = longerIndentMatch.index;
@@ -402,8 +402,8 @@ function parseResourceContent(content, resource, oldIds) {
402
402
  }
403
403
  var lineIndent = line.match(/^ */)[0].length;
404
404
  if (lineIndent > groupIndent) {
405
- // Do not count synonyms as parents
406
- if (data[previousId] && data[previousId].taxonomicStatus === 'accepted') {
405
+ // Do not count synonyms as parents (unless this is correcting a typo in the synonym)
406
+ if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line)) {
407
407
  parents.push(previousId);
408
408
  }
409
409
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
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",
@@ -8,7 +8,7 @@ export class Entity {
8
8
  constructor (values: Record<string, string>, schema: Schema) {
9
9
  this.fields = {}
10
10
  for (const key in values) {
11
- const value = parseValue(values[key], !!schema[key].multiple)
11
+ const value = parseValue(values[key], !!schema[key] && !!schema[key].multiple)
12
12
  if (value !== null) {
13
13
  this.fields[key] = value
14
14
  }
@@ -37,7 +37,7 @@ export class Entity {
37
37
 
38
38
  validate (): FieldError[] {
39
39
  const errors = []
40
- for (const field in this.fields) {
40
+ for (const field in { ...this.fields, ...this.schema }) {
41
41
  for (const error of this._validateField(field)) {
42
42
  errors.push({ field, error })
43
43
  }
@@ -48,6 +48,11 @@ export class Entity {
48
48
  _validateField (field: string) {
49
49
  const errors = []
50
50
 
51
+ if (!(field in this.schema)) {
52
+ errors.push('Field unknown')
53
+ return errors
54
+ }
55
+
51
56
  if (!(field in this.fields)) {
52
57
  if (this.schema[field].required) {
53
58
  errors.push('Value(s) required but missing')
@@ -33,7 +33,8 @@ export class Work extends Entity {
33
33
  target_taxa: { required: false, multiple: true },
34
34
  listed_in: { required: false, multiple: true, format: FORMATS.ID },
35
35
  part_of: { required: false, multiple: true, format: FORMATS.ID },
36
- version_of: { required: false, multiple: true, format: FORMATS.ID }
36
+ version_of: { required: false, multiple: true, format: FORMATS.ID },
37
+ // duplicate_of: { required: false, multiple: false, format: FORMATS.ID }
37
38
  })
38
39
  }
39
40
 
@@ -372,7 +372,7 @@ function parseHeader (header: string): ResourceMetadata {
372
372
 
373
373
  function validateResource (config: ResourceMetadata, content: string) {
374
374
  // Check for too much indentation
375
- const longerIndent = new RegExp(`^( ){${config.levels.length - 1}}(?! [+=>] ) `, 'm')
375
+ const longerIndent = new RegExp(`^( ){${config.levels.length - 1}}(?! [+=>] | > ) `, 'm')
376
376
  const longerIndentMatch = content.match(longerIndent)
377
377
  if (longerIndentMatch !== null) {
378
378
  const offset = longerIndentMatch.index
@@ -425,8 +425,8 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
425
425
  const lineIndent = (line.match(/^ */) as string[])[0].length
426
426
 
427
427
  if (lineIndent > groupIndent) {
428
- // Do not count synonyms as parents
429
- if (data[previousId] && data[previousId].taxonomicStatus === 'accepted') {
428
+ // Do not count synonyms as parents (unless this is correcting a typo in the synonym)
429
+ if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line)) {
430
430
  parents.push(previousId)
431
431
  } else {
432
432
  parents.push(null)
package/test/resources.js CHANGED
@@ -1,10 +1,51 @@
1
1
  const test = require('node:test')
2
2
  const assert = require('assert')
3
3
 
4
- const { resources } = require('../lib')
4
+ const { catalog, resources } = require('../lib')
5
+
6
+ test('catalog', async (t) => {
7
+ await t.test('reports missing required fields', (t) => {
8
+ const errors = catalog.loadData(`id
9
+ B1`, 'catalog').validate()
10
+ assert.deepStrictEqual(errors, [
11
+ { entity: 'B1', field: 'title', error: 'Value(s) required but missing' },
12
+ { entity: 'B1', field: 'entry_type', error: 'Value(s) required but missing' },
13
+ { entity: 'B1', field: 'language', error: 'Value(s) required but missing' },
14
+ { entity: 'B1', field: 'key_type', error: 'Value(s) required but missing' },
15
+ { entity: 'B1', field: 'taxon', error: 'Value(s) required but missing' },
16
+ { entity: 'B1', field: 'region', error: 'Value(s) required but missing' }
17
+ ])
18
+ })
19
+ })
5
20
 
6
21
  test('resources', async (t) => {
7
- await t.test('missing leaf taxa', (t) => {
22
+ await t.test('parses author with initials', (t) => {
23
+ const [resource] = resources.parseTextFile(`---
24
+ levels: [family, genus, species]
25
+ ---
26
+
27
+ Dolichurus haemorrhous A. Costa, 1886
28
+ Dolichurus A. Costa, 1886
29
+ Dolichurus indet.
30
+ Sphecidae A. Costa, 1886
31
+ Sphecidae indet.
32
+ `, 'T1')
33
+ assert.deepStrictEqual(Object.values(resource.taxa).map(taxon => taxon.scientificNameAuthorship), Array(3).fill('A. Costa, 1886'))
34
+ })
35
+
36
+ await t.test('does not validate name with correction', (t) => {
37
+ const [resource] = resources.parseTextFile(`---
38
+ levels: [species]
39
+ ---
40
+
41
+ Clytochrysus lapidarius (Panzer, 1804)
42
+ = Crabo chrysostomus Lepeletier & Brullé, 1835
43
+ > Crabro chrysostomus Lepeletier & Brullé, 1835
44
+ `, 'T1')
45
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Clytochrysus lapidarius (Panzer, 1804)')
46
+ })
47
+
48
+ await t.test('errors for missing leaf taxa', (t) => {
8
49
  assert.throws(() => {
9
50
  resources.parseTextFile(`---
10
51
  levels: [family, genus, species]