@larsgw/formica 0.1.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 (65) hide show
  1. package/.eslintrc.js +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +19 -0
  4. package/lib/bin/process-resources-index.d.ts +1 -0
  5. package/lib/bin/process-resources-index.js +142 -0
  6. package/lib/bin/process-resources.d.ts +1 -0
  7. package/lib/bin/process-resources.js +594 -0
  8. package/lib/bin/util.d.ts +16 -0
  9. package/lib/bin/util.js +125 -0
  10. package/lib/bin/validate-catalog.d.ts +2 -0
  11. package/lib/bin/validate-catalog.js +92 -0
  12. package/lib/bin/validate-resources-text.d.ts +2 -0
  13. package/lib/bin/validate-resources-text.js +78 -0
  14. package/lib/catalog/entities.d.ts +12 -0
  15. package/lib/catalog/entities.js +111 -0
  16. package/lib/catalog/entity.d.ts +13 -0
  17. package/lib/catalog/entity.js +103 -0
  18. package/lib/catalog/index.d.ts +4 -0
  19. package/lib/catalog/index.js +33 -0
  20. package/lib/catalog/tables/author.d.ts +4 -0
  21. package/lib/catalog/tables/author.js +33 -0
  22. package/lib/catalog/tables/index.d.ts +2 -0
  23. package/lib/catalog/tables/index.js +13 -0
  24. package/lib/catalog/tables/place.d.ts +4 -0
  25. package/lib/catalog/tables/place.js +32 -0
  26. package/lib/catalog/tables/publisher.d.ts +4 -0
  27. package/lib/catalog/tables/publisher.js +33 -0
  28. package/lib/catalog/tables/work.d.ts +5 -0
  29. package/lib/catalog/tables/work.js +82 -0
  30. package/lib/catalog/value.d.ts +19 -0
  31. package/lib/catalog/value.js +49 -0
  32. package/lib/csv.d.ts +2 -0
  33. package/lib/csv.js +37 -0
  34. package/lib/index.d.ts +3 -0
  35. package/lib/index.js +6 -0
  36. package/lib/resources/diff-resource.d.ts +7 -0
  37. package/lib/resources/diff-resource.js +152 -0
  38. package/lib/resources/index.d.ts +1 -0
  39. package/lib/resources/index.js +6 -0
  40. package/lib/resources/parse-text.d.ts +2 -0
  41. package/lib/resources/parse-text.js +499 -0
  42. package/lib/types.d.ts +62 -0
  43. package/lib/types.js +0 -0
  44. package/package.json +42 -0
  45. package/src/bin/process-resources-index.ts +73 -0
  46. package/src/bin/process-resources.ts +406 -0
  47. package/src/bin/util.ts +74 -0
  48. package/src/bin/validate-catalog.ts +37 -0
  49. package/src/bin/validate-resources-text.ts +25 -0
  50. package/src/catalog/entities.ts +62 -0
  51. package/src/catalog/entity.ts +113 -0
  52. package/src/catalog/index.ts +32 -0
  53. package/src/catalog/tables/author.ts +13 -0
  54. package/src/catalog/tables/index.ts +12 -0
  55. package/src/catalog/tables/place.ts +12 -0
  56. package/src/catalog/tables/publisher.ts +13 -0
  57. package/src/catalog/tables/work.ts +62 -0
  58. package/src/catalog/value.ts +48 -0
  59. package/src/csv.ts +33 -0
  60. package/src/index.ts +3 -0
  61. package/src/module.d.ts +105 -0
  62. package/src/resources/diff-resource.ts +155 -0
  63. package/src/resources/index.ts +4 -0
  64. package/src/resources/parse-text.ts +519 -0
  65. package/tsconfig.json +11 -0
@@ -0,0 +1,519 @@
1
+ import * as yaml from 'js-yaml'
2
+ import { createDiff, ResourceDiffType } from './diff-resource'
3
+
4
+ const RANKS: Rank[] = [
5
+ 'class',
6
+ 'infraclass',
7
+ 'superorder',
8
+ 'order',
9
+ 'suborder',
10
+ 'infraorder',
11
+ 'superfamily',
12
+ 'family',
13
+ 'subfamily',
14
+ 'tribe',
15
+ 'subtribe',
16
+ 'genus',
17
+ 'subgenus',
18
+ 'section', // not ICZN
19
+ 'subsection', // not ICZN
20
+ 'series', // not ICZN
21
+ 'group',
22
+ 'subgroup', // ...
23
+ 'aggregate', // not ICZN
24
+ 'complex', // not ICZN
25
+ 'species',
26
+ 'subspecies',
27
+ 'variety',
28
+ 'form',
29
+ 'aberration', // not ICZN
30
+ 'race', // not ICZN
31
+ 'stirps' // not ICZN
32
+ ]
33
+
34
+ const DWC_RANKS: DwcRank[] = [
35
+ 'kingdom',
36
+ 'phylum',
37
+ 'class',
38
+ 'order',
39
+ 'family',
40
+ 'subfamily',
41
+ 'genus',
42
+ 'subgenus'
43
+ ]
44
+
45
+ const TAXONOMIC_STATUS: Record<string, TaxonStatus> = {
46
+ '>': 'incorrect',
47
+ '+': 'heterotypic synonym',
48
+ '=': 'synonym'
49
+ }
50
+
51
+ const INDET_SUFFIXES = new Set([
52
+ 'sp.',
53
+ 'spec.',
54
+ 'indet.',
55
+ 'sp. indet.',
56
+ 'spec. indet.'
57
+ ])
58
+
59
+ const RANK_LABELS: Record<Rank, string> = {
60
+ 'subspecies': 'subsp.',
61
+ 'variety': 'var.',
62
+ 'form': 'f.',
63
+ 'aberration': 'ab.',
64
+ 'race': 'r.',
65
+ 'stirps': 'st.'
66
+ }
67
+
68
+ const RANK_LABELS_REVERSE: Record<string, Rank> = {
69
+ 'st': 'stirps',
70
+ 'r': 'race',
71
+ 'ab': 'aberration',
72
+ 'f': 'form',
73
+ 'var': 'variety',
74
+ 'ssp': 'subspecies',
75
+ 'subsp': 'subspecies'
76
+ }
77
+
78
+ const NAME_PATTERN = new RegExp(
79
+ '^' +
80
+ // $1 main name part
81
+ '(\\S+)' +
82
+ // $2 optional author citation
83
+ '(?: ' +
84
+ // but not auct(t)., etc.
85
+ '(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
86
+ '(' +
87
+ // $2.1 anything in parentheses
88
+ '\\(.+?\\)' +
89
+ '|' +
90
+ // $2.2 anything followed by a year
91
+ '.+?\\d{4}\\)?' +
92
+ '|' +
93
+ // $2.3 name(, name)* & name
94
+ '.+(?:, .+)* & \\S+' +
95
+ '|' +
96
+ // $2.4 name y name
97
+ '\\S+ [yY] \\S+' +
98
+ '|' +
99
+ // $2.5 name( in name)
100
+ '\\p{Lu}\\S*(?: in \\S+)?' +
101
+ '))?' +
102
+ // $3 optional notes
103
+ '(?:,? (.+))?' +
104
+ '$',
105
+ 'u'
106
+ )
107
+
108
+ /**
109
+ * Structure
110
+ * $1 genus+subgenus (+ trailing space): (?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?
111
+ * $1.1 genus: ([A-Z]\S+)
112
+ * $1.2 subgenus: (?:\(([A-Z]\S+?)\) )?
113
+ * $2 species: ([a-z]\S+)
114
+ */
115
+ const BINAME_PATTERN = /^(?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?([a-z]\S+) ?/
116
+
117
+ function compareRanks (a: Rank, b: Rank): number {
118
+ return RANKS.indexOf(a) - RANKS.indexOf(b)
119
+ }
120
+
121
+ function capitalize (name: string): string {
122
+ return name[0].toUpperCase() + name.slice(1).toLowerCase()
123
+ }
124
+
125
+ function isUpperCase (name: string): boolean {
126
+ return name === name.toUpperCase()
127
+ }
128
+
129
+ function getSynonymRank (name: string, rank: Rank): Rank {
130
+ const BINAME_PATTERN = /^([A-Z]\S+ (\([A-Z]\S+\) )?)?(x )?[a-z0-9-]+(?= |$)/
131
+ if (!BINAME_PATTERN.test(name)) {
132
+ return rank
133
+ }
134
+ const rest = name.replace(BINAME_PATTERN, '')
135
+ const rankPrefix = rest.match(/^ (st|r|ab|f|var|ssp|subsp)\. /)
136
+ if (rankPrefix) {
137
+ return RANK_LABELS_REVERSE[rankPrefix[1]] as string
138
+ } else if (/^ (?!sensu)[a-z0-9-]+($| )/.test(rest)) {
139
+ return 'subspecies'
140
+ } else {
141
+ return 'species'
142
+ }
143
+ }
144
+
145
+ function capitalizeAuthors (authors: string): string {
146
+ return authors
147
+ .replace(
148
+ /[^\x00-\x40\x5B-\x60\x7B-\x7F]+/g, // eslint-disable-line no-control-regex
149
+ name => isUpperCase(name) ? capitalize(name) : name
150
+ )
151
+ .replace(/ Y /g, ' y ')
152
+ }
153
+
154
+ function parseName (name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxon {
155
+ const item = {} as WorkingTaxon
156
+
157
+ // Synonyms have the accepted name usage as 'parent'.
158
+ const isSynonym = /^[+=>] /.test(name)
159
+ if (isSynonym) {
160
+ item.taxonomicStatus = TAXONOMIC_STATUS[name[0]]
161
+ name = name.replace(/^[+=>] (\? ?)?/, '')
162
+ rank = getSynonymRank(name, parent.taxonRank as Rank)
163
+ } else {
164
+ item.taxonomicStatus = 'accepted'
165
+ }
166
+
167
+ // Clusters
168
+ if (/^\[(_|\d+)\] /.test(name)) {
169
+ name = name.replace(/^\[(_|\d+)\] /, '')
170
+ }
171
+
172
+ // Parent context is used for parsing and formatting binomial names.
173
+ const parentContext = { ...parent }
174
+ if (parent.incorrect) { parentContext.incorrect = { ...parent.incorrect } }
175
+
176
+ // The parent context should be amended in the two cases where binomial names
177
+ // are truly accepted: synonyms and species (and below) without parents (resp.
178
+ // genera and genera and species) to provide parts of the name.
179
+ if (isSynonym || !parentContext.genus || (compareRanks('species', rank) < 0 && !parentContext.specificEpithet)) {
180
+ const [, genus, subgenus, species] = name.match(BINAME_PATTERN) || []
181
+ if (genus) {
182
+ parentContext.genus = capitalize(genus)
183
+ if (parentContext.incorrect) parentContext.incorrect.genus = capitalize(genus)
184
+ }
185
+ if (subgenus) {
186
+ parentContext.subgenus = capitalize(subgenus)
187
+ if (parentContext.incorrect) parentContext.incorrect.subgenus = capitalize(subgenus)
188
+ } else if (genus) {
189
+ // If a genus is given but no subgenus, remove it from the parent context
190
+ delete parentContext.subgenus
191
+ if (parentContext.incorrect) delete parentContext.incorrect.subgenus
192
+ }
193
+ if (species) {
194
+ parentContext.specificEpithet = species
195
+ if (parentContext.incorrect) parentContext.incorrect.specificEpithet = species
196
+ }
197
+ }
198
+
199
+ // In taxa of group, species or lower, the name should just contain the
200
+ // (inter)specific epithet and the author information & remarks when processing
201
+ // further.
202
+ if (compareRanks('group', rank) <= 0) {
203
+ const parseContext = parentContext.incorrect || parentContext
204
+ if (!parseContext.genus) { parseContext.genus = name.split(' ', 1)[0] }
205
+ const genusPrefix = new RegExp(`^${parentContext.genus} (\\(.*?\\) )?`, 'i')
206
+ if (name[0] === (parentContext.genus as string)[0]) {
207
+ name = name.replace(genusPrefix, '')
208
+ }
209
+
210
+ if (compareRanks('species', rank) < 0) {
211
+ const speciesPrefix = parseContext.specificEpithet + ' '
212
+ if (name.startsWith(speciesPrefix)) {
213
+ name = name.slice(speciesPrefix.length).replace(/^(st|r|ab|f|var|ssp|subsp)\. /, '')
214
+ }
215
+ }
216
+ }
217
+
218
+ // Hybrids
219
+ if (rank === 'species' && /^x /.test(name)) {
220
+ name = '\u00D7' + name.slice(2)
221
+ }
222
+
223
+ // Divide the name into the main scientific name (only the epithet for taxa
224
+ // lower than genus), the authorship information, and optionally remarks
225
+ const nameParts = name.match(NAME_PATTERN)
226
+ if (!nameParts) {
227
+ throw new Error(`Taxon "${name}" could not be parsed`)
228
+ }
229
+ const [_, taxon, citation = '', notes] = nameParts
230
+ item.scientificNameAuthorship = capitalizeAuthors(citation)
231
+ item.taxonRemarks = notes
232
+ item.taxonRank = rank
233
+
234
+ if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon) && !INDET_SUFFIXES.has(taxon)) {
235
+ throw new Error(`Taxon name contains unexpected characters: "${taxon}"`)
236
+ }
237
+
238
+ // Validate names and recompose binomial and trinomial names
239
+ if (compareRanks('group', rank) > 0) {
240
+ item.scientificName = capitalize(taxon)
241
+ if (item.scientificName[0] !== taxon[0]) {
242
+ throw new Error(`Taxon name (${rank}) should be capitalized: "${taxon}"`)
243
+ }
244
+ } else if (rank === 'group') {
245
+ item.genericName = parentContext.genus
246
+ item.infragenericEpithet = parentContext.subgenus
247
+ const specificEpithet = taxon.toLowerCase().replace(/(-group)?$/, '')
248
+ item.scientificName = `${item.genericName} ${specificEpithet}-group`
249
+ if (taxon.toLowerCase() !== taxon) {
250
+ console.log(item, taxon)
251
+ throw new Error(`Group name should be lowercase: "${taxon}"`)
252
+ }
253
+ } else if (rank === 'subgroup') {
254
+ item.genericName = parentContext.genus
255
+ item.infragenericEpithet = parentContext.subgenus
256
+ const specificEpithet = taxon.toLowerCase().replace(/(-subgroup)?$/, '')
257
+ item.scientificName = `${item.genericName} ${specificEpithet}-subgroup`
258
+ if (taxon.toLowerCase() !== taxon) {
259
+ console.log(item, taxon)
260
+ throw new Error(`Subgroup name should be lowercase: "${taxon}"`)
261
+ }
262
+ } else if (rank === 'species') {
263
+ item.genericName = parentContext.genus
264
+ item.infragenericEpithet = parentContext.subgenus
265
+ item.specificEpithet = taxon.toLowerCase()
266
+ item.scientificName = `${item.genericName} ${item.specificEpithet}`
267
+ if (item.specificEpithet !== taxon) {
268
+ console.log(item, taxon)
269
+ throw new Error(`Specific epithet should be lowercase: "${taxon}"`)
270
+ }
271
+ } else if (compareRanks('species', rank) < 0) {
272
+ item.genericName = parentContext.genus
273
+ item.infragenericEpithet = parentContext.subgenus
274
+ item.specificEpithet = parentContext.specificEpithet
275
+ item.intraspecificEpithet = taxon.toLowerCase()
276
+
277
+ // If possible, names below species should have abbreviations for ranks,
278
+ // like "subsp."
279
+ const nameParts = [
280
+ item.genericName,
281
+ item.specificEpithet,
282
+ item.intraspecificEpithet
283
+ ]
284
+ if (item.taxonRank in RANK_LABELS) {
285
+ nameParts.splice(2, 0, RANK_LABELS[item.taxonRank])
286
+ }
287
+ item.scientificName = nameParts.join(' ')
288
+
289
+ if (item.intraspecificEpithet !== taxon) {
290
+ console.log(item, taxon)
291
+ throw new Error(`Intraspecific epithet should be lowercase: "${taxon}"`)
292
+ }
293
+ }
294
+
295
+ // Re-add authorship information
296
+ item.scientificNameOnly = item.scientificName
297
+ if (item.scientificNameAuthorship) {
298
+ item.scientificName += ` ${item.scientificNameAuthorship}`
299
+ }
300
+
301
+ // Amend "parent" with corrections
302
+ if (item.taxonomicStatus === 'incorrect') {
303
+ const itemAsObject = item as { [index: string]: unknown }
304
+ const parentAsObject = parent as { [index: string]: unknown }
305
+
306
+ parent.incorrect = { ...parent }
307
+ for (const key in item) {
308
+ if (key !== 'taxonomicStatus') {
309
+ parentAsObject[key] = itemAsObject[key]
310
+ }
311
+ }
312
+ }
313
+
314
+ return item
315
+ }
316
+
317
+ function parseHeader (header: string): ResourceMetadata {
318
+ const config = yaml.load(header)
319
+
320
+ if (typeof config !== 'object' || Array.isArray(config) || config === null) {
321
+ throw new SyntaxError('yaml header should be an object')
322
+ }
323
+
324
+ // Invalid configuration
325
+ let levels
326
+ if (!('levels' in config)) {
327
+ levels = []
328
+ } else if (!Array.isArray(config.levels)) {
329
+ throw new SyntaxError('"levels" should be an array')
330
+ } else {
331
+ levels = config.levels
332
+ }
333
+
334
+ let scope
335
+ if (!('scope' in config)) {
336
+ scope = []
337
+ } else if (!Array.isArray(config.scope)) {
338
+ throw new SyntaxError('"scope" should be an array')
339
+ } else {
340
+ scope = config.scope
341
+ }
342
+
343
+ // No taxon ranks
344
+ if (levels.length === 0) {
345
+ throw new SyntaxError('Resource contains no taxa')
346
+ }
347
+
348
+ // Invalid taxon ranks
349
+ const invalidTaxonRanks = levels.filter(rank => !RANKS.includes(rank))
350
+ if (invalidTaxonRanks.length) {
351
+ throw new SyntaxError(`"levels" contains invalid values: ${invalidTaxonRanks.join(', ')}`)
352
+ }
353
+
354
+ const metadata: ResourceMetadata = { levels, scope }
355
+
356
+ if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
357
+ metadata.catalog = config.catalog
358
+ }
359
+
360
+ return metadata
361
+ }
362
+
363
+ function parseResource (resource: string): [ResourceMetadata, string] {
364
+ const [header, _, ...rest] = resource.split(/(\n---\n+)/)
365
+ const config = parseHeader(header)
366
+ const content = rest.join('')
367
+
368
+ // Check for too much indentation
369
+ const longerIndent = new RegExp(`^( ){${config.levels.length - 1}}(?! [+=>] ) `, 'm')
370
+ const longerIndentMatch = content.match(longerIndent)
371
+ if (longerIndentMatch !== null) {
372
+ const offset = longerIndentMatch.index
373
+ const line = (content.slice(0, offset).match(/\n/g) || []).length + 1
374
+ throw new SyntaxError(`Too much indentation at ${line}:0
375
+ ${content.slice(offset).split('\n', 1)}
376
+ ^`)
377
+ }
378
+
379
+ return [config, content]
380
+ }
381
+
382
+ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds: number[]): Resource {
383
+ const idBase = `${resource.id}:`
384
+
385
+ const data = resource.taxa as Record<TaxonId, WorkingTaxon>
386
+ let id = 0
387
+ let parents: Array<TaxonId | null> = []
388
+ let groupIndent = 0
389
+ let previousId = ''
390
+ let newIdOffset = Math.max(...oldIds)
391
+
392
+ for (const { text: line, type } of content) {
393
+ if (type === ResourceDiffType.Deleted) {
394
+ id++
395
+ continue
396
+ }
397
+
398
+ const lineIndent = (line.match(/^ */) as string[])[0].length
399
+
400
+ if (lineIndent > groupIndent) {
401
+ // Do not count synonyms as parents
402
+ if (data[previousId] && data[previousId].taxonomicStatus === 'accepted') {
403
+ parents.push(previousId)
404
+ } else {
405
+ parents.push(null)
406
+ }
407
+ // Handle skips in indentation levels,
408
+ // e.g. if a certain genus has only species
409
+ // whereas other genera in the same key also
410
+ // have subgenera
411
+ if ((lineIndent - groupIndent) > 2) {
412
+ const gap = (lineIndent - groupIndent - 2) / 2
413
+ for (let i = 0; i < gap; i++) { parents.push(null) }
414
+ }
415
+ groupIndent = lineIndent
416
+ } else if (lineIndent < groupIndent) {
417
+ parents = parents.slice(0, lineIndent / 2)
418
+ groupIndent = lineIndent
419
+ }
420
+
421
+ const parentId = parents.reduce((grandparent, parent) => parent || grandparent, null)
422
+ const parent = parentId === null ? {} as WorkingTaxon : data[parentId]
423
+
424
+ const name = line.slice(groupIndent)
425
+ const rank = resource.metadata.levels[groupIndent / 2]
426
+ const item = parseName(name, rank, parent)
427
+ const isSynonym = item.taxonomicStatus !== 'accepted'
428
+ const isIndet = Array.from(INDET_SUFFIXES).some(suffix => name.endsWith(' ' + suffix))
429
+
430
+ if (item.taxonomicStatus === 'incorrect' || isIndet) {
431
+ continue
432
+ }
433
+
434
+ if (type === ResourceDiffType.Added) {
435
+ newIdOffset++
436
+ item.scientificNameID = idBase + newIdOffset.toString()
437
+ } else {
438
+ id++
439
+ item.scientificNameID = idBase + (oldIds[id - 1] || id).toString()
440
+ }
441
+ previousId = item.scientificNameID
442
+
443
+ item.parentNameUsageID = isSynonym ? undefined : parent.scientificNameID
444
+ item.parentNameUsage = isSynonym ? undefined : parent.scientificName
445
+ item.acceptedNameUsageID = isSynonym ? parent.scientificNameID : undefined
446
+ item.acceptedNameUsage = isSynonym ? parent.scientificName : undefined
447
+ item.collectionCode = idBase.slice(0, -1)
448
+
449
+ for (const rank of DWC_RANKS) {
450
+ const itemAsObject = item as { [index: string]: unknown }
451
+ const parentAsObject = parent as { [index: string]: unknown }
452
+
453
+ itemAsObject[rank] = undefined
454
+ if (parentAsObject[rank]) {
455
+ itemAsObject[rank] = parentAsObject[rank]
456
+ }
457
+ if (item.taxonRank === rank) {
458
+ itemAsObject[rank] = item.scientificNameOnly
459
+ }
460
+ }
461
+
462
+ if (item.genericName && !item.genus) {
463
+ item.genus = item.genericName
464
+ }
465
+ if (item.infragenericEpithet && !item.subgenus) {
466
+ item.subgenus = item.infragenericEpithet
467
+ }
468
+
469
+ if (isSynonym) {
470
+ item.higherClassification = parent.higherClassification
471
+ } else if (parent.higherClassification) {
472
+ item.higherClassification = parent.higherClassification + ` | ${parent.scientificNameOnly}`
473
+ } else if (parentId) {
474
+ item.higherClassification = parent.scientificNameOnly
475
+ }
476
+
477
+ data[item.scientificNameID] = item
478
+ }
479
+
480
+ return resource
481
+ }
482
+
483
+ function splitResources (file: string): string[] {
484
+ return file.split('\n\n===\n\n')
485
+ }
486
+
487
+ export function parseFile (file: string, id: WorkId, old?: ResourceHistory): Resource[] {
488
+ const oldResources = old ? splitResources(old.txt) : []
489
+ return splitResources(file).map((resource, index) => {
490
+ const [config, content] = parseResource(resource)
491
+ const template: Resource = {
492
+ id: `${id}:${index + 1}`,
493
+ file: `${id}-${index + 1}`,
494
+ workId: id,
495
+ metadata: config,
496
+ taxa: {}
497
+ }
498
+
499
+ let diff
500
+ if (oldResources[index]) {
501
+ diff = createDiff(content, parseResource(oldResources[index])[1])
502
+ } else {
503
+ diff = createDiff(content, content)
504
+ }
505
+
506
+ const oldIds = []
507
+ if (old) {
508
+ for (const row of old.dwc[index].slice(1)) {
509
+ oldIds.push(parseInt(row[0].split(':')[2]))
510
+ }
511
+ }
512
+
513
+ return parseResourceContent(diff, template, oldIds)
514
+ })
515
+ }
516
+
517
+ export function parseFileHeader (file: string): ResourceMetadata[] {
518
+ return splitResources(file).map(resource => parseResource(resource)[0])
519
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./lib",
4
+ "target": "es5",
5
+
6
+ "noImplicitAny": true,
7
+ "strictNullChecks": true,
8
+ "noImplicitThis": true
9
+ },
10
+ "include": ["./src/**/*"]
11
+ }