@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/CHANGELOG.md +23 -0
- package/lib/bin/clean-links.d.ts +2 -0
- package/lib/bin/clean-links.js +170 -0
- package/lib/catalog/entities.js +1 -1
- package/lib/catalog/index.d.ts +2 -1
- package/lib/catalog/index.js +2 -1
- package/lib/resources/content/clavis.js +10 -0
- package/lib/resources/content/index.js +0 -0
- package/lib/resources/content/sdd.js +151 -0
- package/lib/resources/parse-text.js +2 -2
- package/lib/resources/sdd.js +78 -0
- package/package.json +3 -2
- package/src/bin/clean-links.ts +96 -0
- package/src/catalog/entities.ts +1 -1
- package/src/catalog/index.ts +1 -1
- package/src/resources/parse-text.ts +2 -2
- package/test/resources.js +12 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larsgw/formica",
|
|
3
|
-
"version": "0.6.
|
|
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
|
+
})
|
package/src/catalog/entities.ts
CHANGED
|
@@ -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].
|
|
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) || ''
|
package/src/catalog/index.ts
CHANGED
|
@@ -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' &&
|
|
249
|
-
name = '\u00D7'
|
|
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
|
})
|