@larsgw/formica 0.6.0 → 0.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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,8 @@
8
8
  "loir-validate-catalog": "./lib/bin/validate-catalog.js",
9
9
  "loir-validate-resources": "./lib/bin/validate-resources-text.js",
10
10
  "loir-resources-process": "./lib/bin/process-resources.js",
11
- "loir-resources-index": "./lib/bin/process-resources-index.js"
11
+ "loir-resources-index": "./lib/bin/process-resources-index.js",
12
+ "loir-catalog-clean-links": "./lib/bin/clean-links.js"
12
13
  },
13
14
  "scripts": {
14
15
  "test": "node --test --test-reporter spec",
@@ -0,0 +1,96 @@
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
+ })
@@ -50,7 +50,7 @@ export class Entities {
50
50
  if (this.entities.length === 0) {
51
51
  return [[]]
52
52
  }
53
- const header = Object.keys(this.entities[0].fields)
53
+ const header = Object.keys(this.entities[0].schema)
54
54
  const table = this.entities.map((entity: Entity) => {
55
55
  return header.map((field: string): string => {
56
56
  const value = entity.get(field) || ''
@@ -13,7 +13,7 @@ function getTypeInfo (type: string): [typeof Entity, string] {
13
13
  }
14
14
  }
15
15
 
16
- export { Entities, Entity }
16
+ export { Entities, Entity, getTypeInfo }
17
17
 
18
18
  export function loadData (file: string, type: string): Entities {
19
19
  const [subClass, indexField] = getTypeInfo(type)
@@ -245,8 +245,8 @@ function parseName (name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxo
245
245
  }
246
246
 
247
247
  // Hybrids
248
- if (rank === 'species' && /^x /.test(name)) {
249
- name = '\u00D7' + name.slice(2)
248
+ if (rank === 'species' && /(^| )x /.test(name)) {
249
+ name = name.replace(/(^| )x /, '\u00D7')
250
250
  }
251
251
 
252
252
  // Divide the name into the main scientific name (only the epithet for taxa
package/test/resources.js CHANGED
@@ -124,4 +124,16 @@ Microdynerus Thomson, 1874
124
124
  `, 'T1')
125
125
  assert.strictEqual(resource.taxa['T1:1:3'].scientificName, 'Microdynerus microdynerus (Dalla Torre, 1889)')
126
126
  })
127
+
128
+ await t.test('parses hybrids', (t) => {
129
+ const [resource] = resources.parseTextFile(`---
130
+ levels: [species]
131
+ ---
132
+
133
+ Rumex conglomeratus x maritimus
134
+ Tilia x vulgaris
135
+ `, 'T1')
136
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Rumex conglomeratus×maritimus')
137
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Tilia ×vulgaris')
138
+ })
127
139
  })