@larsgw/formica 0.7.2 → 0.8.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/eslint.config.js +34 -0
  3. package/lib/bin/SHEETS.js +0 -0
  4. package/lib/bin/generate-linked-data.d.ts +2 -0
  5. package/lib/bin/generate-linked-data.js +740 -0
  6. package/lib/bin/process-resources-index.js +36 -3
  7. package/lib/bin/process-resources.js +40 -7
  8. package/lib/bin/util.js +40 -8
  9. package/lib/bin/validate-catalog.js +36 -3
  10. package/lib/bin/validate-resources-text.js +36 -3
  11. package/lib/catalog/entities.js +2 -2
  12. package/lib/catalog/entity.js +0 -2
  13. package/lib/catalog/index.js +3 -3
  14. package/lib/catalog/tables/author.js +5 -3
  15. package/lib/catalog/tables/index.js +4 -4
  16. package/lib/catalog/tables/place.js +3 -1
  17. package/lib/catalog/tables/publisher.js +5 -3
  18. package/lib/catalog/tables/taxon.js +3 -1
  19. package/lib/catalog/tables/work.js +5 -5
  20. package/lib/catalog/value.d.ts +6 -2
  21. package/lib/catalog/value.js +14 -9
  22. package/lib/csv.js +2 -3
  23. package/lib/index.js +37 -4
  24. package/lib/resources/diff-resource.js +5 -4
  25. package/lib/resources/parse-text.js +45 -12
  26. package/lib/taxon-names/index.js +2 -3
  27. package/package.json +13 -7
  28. package/src/bin/generate-linked-data.ts +709 -0
  29. package/src/bin/process-resources-index.ts +1 -1
  30. package/src/catalog/entity.ts +0 -2
  31. package/src/catalog/tables/author.ts +5 -3
  32. package/src/catalog/tables/index.ts +4 -4
  33. package/src/catalog/tables/place.ts +3 -1
  34. package/src/catalog/tables/publisher.ts +5 -3
  35. package/src/catalog/tables/taxon.ts +3 -1
  36. package/src/catalog/tables/work.ts +5 -5
  37. package/src/catalog/value.ts +7 -5
  38. package/src/module.d.ts +5 -0
  39. package/src/resources/diff-resource.ts +2 -1
  40. package/src/resources/parse-text.ts +1 -0
  41. package/tsconfig.json +2 -1
  42. package/.eslintrc.js +0 -16
  43. package/src/bin/clean-links.ts +0 -96
@@ -39,7 +39,7 @@ async function main (args: string[]): Promise<void> {
39
39
 
40
40
  const gbifIndex: Record<string, TaxonId[]> = {}
41
41
  const colIndex: Record<string, TaxonId[]> = {}
42
- const resourceIndex: Record<TaxonId, AmendedResourceMetadata> = {}
42
+ const resourceIndex: Record<ResourceId, AmendedResourceMetadata> = {}
43
43
 
44
44
  await Promise.all(files.map(async function (fileName) {
45
45
  if (!fileName.endsWith('.txt')) { return }
@@ -18,10 +18,8 @@ export class Entity {
18
18
  this.derivedFields = {}
19
19
  }
20
20
 
21
- /* eslint-disable @typescript-eslint/no-empty-function */
22
21
  deriveFields () {
23
22
  }
24
- /* eslint-enable @typescript-eslint/no-empty-function */
25
23
 
26
24
  get (key: string): Value | undefined {
27
25
  if (key in this.fields) {
@@ -4,10 +4,12 @@ import { Entity } from '../entity'
4
4
  export class Author extends Entity {
5
5
  constructor (values: Record<string, string>) {
6
6
  super(values, {
7
- name: { required: true, multiple: false },
7
+ name: { required: true, multiple: true },
8
+ id: { required: true, multiple: false, format: FORMATS.AUTHOR_ID },
8
9
  qid: { required: false, multiple: false, format: FORMATS.QID },
9
- main_full_name: { required: false, multiple: false },
10
- full_names: { required: false, multiple: true }
10
+ display_name: { required: false, multiple: false },
11
+ full_names: { required: false, multiple: true },
12
+ duplicate_of: { required: false, multiple: false, format: FORMATS.AUTHOR_ID }
11
13
  })
12
14
  }
13
15
  }
@@ -6,9 +6,9 @@ import { Taxon } from './taxon'
6
6
  import { Work } from './work'
7
7
 
8
8
  export const TYPE_INFO: Record<string, [typeof Entity, string]> = {
9
- authors: [Author, 'name'],
10
- publishers: [Publisher, 'name'],
11
- places: [Place, 'name'],
12
- taxa: [Taxon, 'name'],
9
+ authors: [Author, 'id'],
10
+ publishers: [Publisher, 'id'],
11
+ places: [Place, 'id'],
12
+ taxa: [Taxon, 'id'],
13
13
  catalog: [Work, 'id']
14
14
  }
@@ -5,8 +5,10 @@ export class Place extends Entity {
5
5
  constructor (values: Record<string, string>) {
6
6
  super(values, {
7
7
  name: { required: true, multiple: false },
8
+ id: { required: true, multiple: false, format: FORMATS.PLACE_ID },
8
9
  qid: { required: false, multiple: false, format: FORMATS.QID },
9
- display_name: { required: false, multiple: false }
10
+ display_name: { required: false, multiple: false },
11
+ duplicate_of: { required: false, multiple: false, format: FORMATS.PLACE_ID }
10
12
  })
11
13
  }
12
14
  }
@@ -4,10 +4,12 @@ import { Entity } from '../entity'
4
4
  export class Publisher extends Entity {
5
5
  constructor (values: Record<string, string>) {
6
6
  super(values, {
7
- name: { required: true, multiple: false },
7
+ name: { required: true, multiple: true },
8
+ id: { required: true, multiple: false, format: FORMATS.PUBLISHER_ID },
8
9
  qid: { required: false, multiple: false, format: FORMATS.QID },
9
- full_name: { required: false, multiple: false },
10
- long_name: { required: false, multiple: true }
10
+ display_name: { required: false, multiple: false },
11
+ full_names: { required: false, multiple: true },
12
+ duplicate_of: { required: false, multiple: false, format: FORMATS.PUBLISHER_ID }
11
13
  })
12
14
  }
13
15
  }
@@ -5,11 +5,13 @@ export class Taxon extends Entity {
5
5
  constructor (values: Record<string, string>) {
6
6
  super(values, {
7
7
  name: { required: true, multiple: false },
8
+ id: { required: true, multiple: false, format: FORMATS.TAXON_ID },
8
9
  qid: { required: false, multiple: false, format: FORMATS.QID },
9
10
  rank: { required: false, multiple: false },
10
11
  gbif: { required: false, multiple: false, format: FORMATS.INTEGER },
11
12
  children_gbif: { required: false, multiple: true, format: FORMATS.INTEGER },
12
- ancestors_gbif: { required: false, multiple: true, format: FORMATS.INTEGER }
13
+ ancestors_gbif: { required: false, multiple: true, format: FORMATS.INTEGER },
14
+ duplicate_of: { required: false, multiple: false, format: FORMATS.TAXON_ID }
13
15
  })
14
16
  }
15
17
  }
@@ -4,7 +4,7 @@ import { Entity } from '../entity'
4
4
  export class Work extends Entity {
5
5
  constructor (values: Record<string, string>) {
6
6
  super(values, {
7
- id: { required: true, multiple: false, format: FORMATS.ID },
7
+ id: { required: true, multiple: false, format: FORMATS.WORK_ID },
8
8
  title: { required: true, multiple: CHECK.MULTILANG },
9
9
  author: { required: false, multiple: true },
10
10
  url: { required: false, multiple: true, format: FORMATS.URL },
@@ -31,10 +31,10 @@ export class Work extends Entity {
31
31
  region: { required: true, multiple: true },
32
32
  complete: { required: false, multiple: false, format: FORMATS.COMPLETE },
33
33
  target_taxa: { required: false, multiple: true },
34
- listed_in: { required: false, multiple: true, format: FORMATS.ID },
35
- part_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 }
34
+ listed_in: { required: false, multiple: true, format: FORMATS.WORK_ID },
35
+ part_of: { required: false, multiple: true, format: FORMATS.WORK_ID },
36
+ version_of: { required: false, multiple: true, format: FORMATS.WORK_ID },
37
+ duplicate_of: { required: false, multiple: false, format: FORMATS.WORK_ID }
38
38
  })
39
39
  }
40
40
 
@@ -1,7 +1,5 @@
1
- import spdxLicenseList = require('spdx-license-list')
2
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3
- // @ts-ignore
4
- import ietfTagListFactory = require('ietf-language-tag-regex')
1
+ import spdxLicenseList from 'spdx-license-list'
2
+ import ietfTagListFactory from 'ietf-language-tag-regex'
5
3
 
6
4
  const ietfTagList = ietfTagListFactory()
7
5
 
@@ -10,7 +8,11 @@ export const FORMATS = {
10
8
  KEY_TYPE: ['key', 'matrix', 'reference', 'gallery', 'checklist', 'supplement', 'collection', 'algorithm'],
11
9
  COMPLETE: ['TRUE', 'FALSE'],
12
10
 
13
- ID: /^B\d+$/,
11
+ WORK_ID: /^B[1-9]\d*$/,
12
+ AUTHOR_ID: /^A[1-9]\d*$/,
13
+ PLACE_ID: /^G[1-9]\d*$/,
14
+ PUBLISHER_ID: /^P[1-9]\d*$/,
15
+ TAXON_ID: /^T[1-9]\d*$/,
14
16
  EDTF_0: /^(\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}:\d{2}(Z|[-+]\d{2}(:\d{2})?))?)?)?|\d{4}(-\d{2}(-\d{2})?)?\/(\d{4}(-\d{2}(-\d{2})?)?|\.\.))$/,
15
17
  ISSN_L: /^[0-9]{4}-[0-9]{3}[0-9X]$/,
16
18
  ISBN: /^(\d{13}|\d{9}[0-9X])$/,
package/src/module.d.ts CHANGED
@@ -139,3 +139,8 @@ interface ResourceProcessorConfig {
139
139
  update: boolean,
140
140
  updateMappings: boolean
141
141
  }
142
+
143
+ declare module 'ietf-language-tag-regex' {
144
+ function createLanguageTagRegex(): RegExp
145
+ export = createLanguageTagRegex
146
+ }
@@ -36,7 +36,8 @@ function LCS (X: string[], Y: string[]): DiffPart[] {
36
36
  text: X[i - 1],
37
37
  type: ResourceDiffType.Unchanged
38
38
  })
39
- i--, j--
39
+ i--
40
+ j--
40
41
  } else if (i !== 0 && (j === 0 || C[i - 1][j] > C[i][j - 1])) {
41
42
  diff.unshift({
42
43
  text: X[i - 1],
@@ -294,6 +294,7 @@ function parseName (name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxo
294
294
  item.taxonRemarks = notes
295
295
  item.taxonRank = rank
296
296
 
297
+ // @ts-expect-error TS1501: This regular expression flag is only available when targeting 'es6' or later.
297
298
  if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon)) {
298
299
  throw new Error(`Taxon name contains unexpected characters: "${taxon}"`)
299
300
  }
package/tsconfig.json CHANGED
@@ -5,7 +5,8 @@
5
5
 
6
6
  "noImplicitAny": true,
7
7
  "strictNullChecks": true,
8
- "noImplicitThis": true
8
+ "noImplicitThis": true,
9
+ "esModuleInterop": true
9
10
  },
10
11
  "include": ["./src/**/*"]
11
12
  }
package/.eslintrc.js DELETED
@@ -1,16 +0,0 @@
1
- module.exports = {
2
- extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
3
- parser: '@typescript-eslint/parser',
4
- plugins: ['@typescript-eslint'],
5
- root: true,
6
-
7
- rules: {
8
- indent: ['error', 4, { SwitchCase: 1 }],
9
- 'no-unused-vars': 'off',
10
- '@typescript-eslint/no-unused-vars': ['warn', {
11
- argsIgnorePattern: '^_',
12
- varsIgnorePattern: '^_',
13
- caughtErrorsIgnorePattern: '^_'
14
- }]
15
- }
16
- }
@@ -1,96 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { promises as fs } from 'fs'
4
- import * as path from 'path'
5
- import * as util from 'util'
6
-
7
- import { catalog, csv } from '../index'
8
-
9
- const LINKS: Record<string, { sheet: string, cleanField: string }> = {
10
- author: { sheet: 'authors', cleanField: 'main_full_name' },
11
- publisher: { sheet: 'publishers', cleanField: 'full_name' },
12
- }
13
-
14
- function replaceLink (value: string, data: catalog.Entities, cleanField: string): string {
15
- const entity = data.get(value)
16
- return entity.get(cleanField) as string ?? value
17
- }
18
-
19
- function save (sheet: catalog.Entities, path: string): Promise<void> {
20
- return fs.writeFile(path, csv.formatCsv(sheet.toTable()))
21
- }
22
-
23
- async function main (): Promise<void> {
24
- const args = util.parseArgs({ allowPositionals: true })
25
-
26
- const files: Record<string, { path: string, sheet: catalog.Entities }> = {}
27
- for (const arg of args.positionals) {
28
- const filePath = path.resolve(arg)
29
- const file = await fs.readFile(filePath, 'utf8')
30
- const sheet = path.basename(filePath, '.csv')
31
- files[sheet] = {
32
- path: filePath,
33
- sheet: catalog.loadData(file, sheet),
34
- }
35
- }
36
-
37
- if (!files.catalog) {
38
- throw new Error('Catalog file must be provided')
39
- }
40
-
41
- for (const field in LINKS) {
42
- const { sheet, cleanField } = LINKS[field]
43
- const indexField = catalog.getTypeInfo(sheet)[1]
44
- const used = new Set()
45
-
46
- if (!files[sheet]) {
47
- continue
48
- }
49
-
50
- // Update works
51
- for (const work of files.catalog.sheet.entities) {
52
- const value = work.get(field)
53
-
54
- if (value === undefined) {
55
- continue
56
- } else if (Array.isArray(value)) {
57
- work.fields[field] = value.map(value => {
58
- value = replaceLink(value, files[sheet].sheet, cleanField)
59
- used.add(value)
60
- return value
61
- })
62
- } else {
63
- work.fields[field] = replaceLink(value, files[sheet].sheet, cleanField)
64
- used.add(work.fields[field])
65
- }
66
- }
67
-
68
- // Filter unused values
69
- const entities = []
70
- for (const entity of files[sheet].sheet.entities) {
71
- const oldKey = entity.get(indexField) as string
72
-
73
- if (entity.has(cleanField)) {
74
- entity.fields[indexField] = entity.get(cleanField) as Value
75
- delete entity.fields[cleanField]
76
- }
77
-
78
- const newKey = entity.get(indexField) as string
79
-
80
- if (used.has(newKey) && (newKey === oldKey || !files[sheet].sheet.index[newKey])) {
81
- entities.push(entity)
82
- }
83
- }
84
-
85
- // Save new file
86
- await save(new catalog.Entities(entities, indexField), files[sheet].path)
87
- }
88
-
89
- // Save catalog
90
- await save(files.catalog.sheet, files.catalog.path)
91
- }
92
-
93
- main().catch((error: Error) => {
94
- console.error(error)
95
- process.exit(1)
96
- })