@ca-plant-list/ca-plant-list 0.4.17 → 0.4.18

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/data/taxa.csv CHANGED
@@ -227,10 +227,10 @@ Bolboschoenus maritimus subsp. paludosus,saltmarsh bulrush,N,49436,10796,79646,P
227
227
  Bolboschoenus robustus,seacoast bulrush,N,77450,10798,75840,Big Bulrush
228
228
  Bowlesia incana,,N,15999,1133,56822,Hoary Bowlesia
229
229
  Brachypodium distachyon,false-brome,X,16042,1137,57158
230
- Brassica juncea,India mustard,X,16072,1142,64241
231
- Brassica nigra,black mustard,X,16079,1144,1555489,,,yellow
232
- Brassica oleracea,cabbage,X,16080,9040,54516
233
- Brassica rapa,field mustard,X,16081,1145,53271,,,yellow
230
+ Brassica juncea,India mustard,X,16072,1142,64241,,"annual,perennial",yellow,5,9
231
+ Brassica nigra,black mustard,X,16079,1144,1555489,,annual,yellow,4,9
232
+ Brassica oleracea,cabbage,X,16080,9040,54516,,"biennial,perennial",yellow,5,8
233
+ Brassica rapa,field mustard,X,16081,1145,53271,,"annual,biennial",yellow,1,5
234
234
  Brickellia californica,California brickelbush,N,1794,1152,58803,Brickell Bush
235
235
  Briza maxima,quaking grass,X,16135,1165,57157
236
236
  Briza minor,little quaking grass,X,16137,1166,57162
@@ -1075,11 +1075,11 @@ Linanthus dichotomus subsp. meridianus,,N,51287,10625,80001
1075
1075
  Linanthus pungens subsp. pulchriflorus,granite prickly phlox,N,98684,14586,861790,,perennial,"white,pink",5,8
1076
1076
  Linum bienne,,X,31144,4906,55679
1077
1077
  Linum lewisii var. lewisii,blue flax,N,60912,4910,60495,Prairie Flax
1078
- Lithophragma affine,woodland star,N,31236,4922,50803,San Francisco Woodland Star,,white,3,4
1079
- Lithophragma bolanderi,,N,31238,4923,77787,Bolander's Woodland Star
1080
- Lithophragma cymbalaria,,N,31241,4925,58323,Mission Woodland Star
1081
- Lithophragma heterophyllum,hill starflower,N,31244,4927,53124,Hillside Woodland Star,,white
1082
- Lithophragma parviflorum var. parviflorum,,N,60952,4930,81027,Smallflower Woodland-star,,"white,pink",3,7
1078
+ Lithophragma affine,woodland star,N,31236,4922,50803,San Francisco Woodland Star,perennial,white,3,4
1079
+ Lithophragma bolanderi,,N,31238,4923,77787,Bolander's Woodland Star,perennial,white,2,7
1080
+ Lithophragma cymbalaria,,N,31241,4925,58323,Mission Woodland Star,perennial,white,3,5
1081
+ Lithophragma heterophyllum,hill starflower,N,31244,4927,53124,Hillside Woodland Star,perennial,white,2,6
1082
+ Lithophragma parviflorum var. parviflorum,,N,60952,4930,81027,Smallflower Woodland-star,perennial,"white,pink",3,7
1083
1083
  Lobularia maritima,sweet alyssum,X,31347,4939,56992
1084
1084
  Logfia filaginoides,herba impia,N,81847,10960,64254,California Cottonrose,annual,red,2,5
1085
1085
  Logfia gallica,,X,31363,9521,56949,,annual,"brown,yellow",3,7
@@ -1285,9 +1285,10 @@ Osmorhiza berteroi,wood cicely,N,35560,10164,53166,Sweet Cicely
1285
1285
  Osmorhiza brachypoda,California cicely,N,35564,6000,58793,California Sweetcicely
1286
1286
  Oxalis corniculata,creeping wood sorrel,X,35609,6010,53168,,,yellow,1,12
1287
1287
  Oxalis incarnata,crimson wood sorrel,X,35636,6012,61776,,,"white,pink",3,6
1288
- Oxalis oregana,redwood sorrel,N,35641,6015,47757,Redwood Sorrel,,"white,pink",2,8
1288
+ Oxalis oregana,Oregon wood sorrel,N,35641,6015,47757,Redwood Sorrel,perennial,white,2,10
1289
1289
  Oxalis pes-caprae,Bermuda-buttercup,X,35642,6016,53169,,,yellow,1,5
1290
1290
  Oxalis pilosa,hairy wood sorrel,N,35601,11895,78300,Radishroot Woodsorrel,,yellow,2,9
1291
+ Oxalis smalliana,redwood sorrel,N,8615,14752,1464017,,perennial,"white,pink,blue",1,8
1291
1292
  Packera breweri,Brewer groundsel,N,77383,9613,56983,Groundsel
1292
1293
  Panicum acuminatum,Pacific panic grass,N,35923,6044,128949,Western Panicgrass
1293
1294
  Panicum capillare,witchgrass,N,36094,6048,78337,Witch Grass
@@ -1563,17 +1564,17 @@ Rubus pensilvanicus,,X,42114,7202,78894
1563
1564
  Rubus spectabilis,,N,42231,7203,47543,Salmon Berry
1564
1565
  Rubus ulmifolius var. anoplothyrsus,,X,91783,11997,81352
1565
1566
  Rubus ursinus,California blackberry,N,42267,7206,53445,California Blackberry
1566
- Rumex acetosella,sheep sorrel,X,42362,7213,53195
1567
- Rumex californicus,,N,42423,11058,78901,California Dock
1568
- Rumex conglomeratus,green dock,X,42385,7214,57215
1569
- Rumex crassus,,N,42422,11059,61087,Willow Dock
1570
- Rumex crispus,curly dock,X,42386,7215,53197
1571
- Rumex fueginus,golden dock,N,42405,10679,78903,Golden Dock
1572
- Rumex obtusifolius,bitter dock,X,42409,7220,63371
1573
- Rumex occidentalis,western dock,N,42376,7221,61086,Western Dock
1574
- Rumex pulcher,fiddle dock,X,42419,7224,60232
1575
- Rumex salicifolius,willow dock,N,42421,7225,60233,Willow Dock
1576
- Rumex transitorius,,N,42429,11999,78910
1567
+ Rumex acetosella,sheep sorrel,X,42362,7213,53195,,perennial,,4,7
1568
+ Rumex californicus,,N,42423,11058,78901,California Dock,perennial,,5,9
1569
+ Rumex conglomeratus,green dock,X,42385,7214,57215,,perennial,,5,8
1570
+ Rumex crassus,,N,42422,11059,61087,Willow Dock,perennial,,2,7
1571
+ Rumex crispus,curly dock,X,42386,7215,53197,,perennial,,1,12
1572
+ Rumex fueginus,golden dock,N,42405,10679,78903,Golden Dock,annual,,5,8
1573
+ Rumex obtusifolius,bitter dock,X,42409,7220,63371,,perennial,,5,9
1574
+ Rumex occidentalis,western dock,N,42376,7221,61086,Western Dock,perennial,,5,8
1575
+ Rumex pulcher,fiddle dock,X,42419,7224,60232,,perennial,,5,9
1576
+ Rumex salicifolius,willow dock,N,42421,7225,60233,Willow Dock,perennial,,5,7
1577
+ Rumex transitorius,,N,42429,11999,78910,,perennial,,4,6
1577
1578
  Rupertia physodes,California tea,N,42446,7236,53198,Common Rupertia
1578
1579
  Ruppia cirrhosa,ditch-grass,N,42449,7238,61089,Spiral Ditchgrass
1579
1580
  Ruppia maritima,ditch-grass,N,42451,7239,61090,Beaked Tasselweed
@@ -0,0 +1 @@
1
+ Upper leaves not clasping stem. Usually hairy.
@@ -0,0 +1 @@
1
+ Upper leaves clasping stem. Usually not hairy.
@@ -0,0 +1,3 @@
1
+ ## Resources
2
+
3
+ - Brinegar, Chris. (2023). [THE TRUE REDWOOD SORREL: REINSTATEMENT OF OXALIS SMALLIANA (OXALIDACEAE) AS A SPECIES SEPARATE FROM OXALIS OREGANA](https://www.researchgate.net/publication/369602947_THE_TRUE_REDWOOD_SORREL_REINSTATEMENT_OF_OXALIS_SMALLIANA_OXALIDACEAE_AS_A_SPECIES_SEPARATE_FROM_OXALIS_OREGANA). Madroño. 69. 10.3120/0024-9637-69.4.304.
@@ -0,0 +1,3 @@
1
+ ## Resources
2
+
3
+ - Brinegar, Chris. (2023). [THE TRUE REDWOOD SORREL: REINSTATEMENT OF OXALIS SMALLIANA (OXALIDACEAE) AS A SPECIES SEPARATE FROM OXALIS OREGANA](https://www.researchgate.net/publication/369602947_THE_TRUE_REDWOOD_SORREL_REINSTATEMENT_OF_OXALIS_SMALLIANA_OXALIDACEAE_AS_A_SPECIES_SEPARATE_FROM_OXALIS_OREGANA). Madroño. 69. 10.3120/0024-9637-69.4.304.
@@ -0,0 +1 @@
1
+ Leaves with lobes at base. Plants unisexual.
package/lib/csv.js CHANGED
@@ -85,9 +85,10 @@ class CSV {
85
85
  }
86
86
 
87
87
  /**
88
+ * @template T
88
89
  * @param {string} fileName
89
90
  * @param {string} [delimiter]
90
- * @returns {{headers:string[],data:Object<string,any>[]}}
91
+ * @returns {{headers:string[],data:T[]}}
91
92
  */
92
93
  static readFileAndHeaders(fileName, delimiter) {
93
94
  let headers;
package/lib/exceptions.js CHANGED
@@ -30,6 +30,9 @@ class Exceptions {
30
30
  }
31
31
  }
32
32
 
33
+ /**
34
+ * @returns {[string,Object<string,Object<string,string>>][]}
35
+ */
33
36
  getExceptions() {
34
37
  return Object.entries(this.#exceptions);
35
38
  }
package/lib/index.d.ts CHANGED
@@ -24,6 +24,13 @@ export class ErrorLog {
24
24
 
25
25
  export class Exceptions {
26
26
  constructor(dataDir: string);
27
+ getExceptions(): [string, Record<string, Record<string, string>>][];
28
+ getValue(
29
+ name: string,
30
+ cat: string,
31
+ subcat: string,
32
+ defaultValue?: string | undefined,
33
+ ): string | undefined;
27
34
  hasException(name: string, cat: string, subcat: string): boolean;
28
35
  }
29
36
 
@@ -100,7 +107,9 @@ export class Taxon {
100
107
  getGenusName(): string;
101
108
  getINatID(): string;
102
109
  getINatTaxonLink(): string;
110
+ getJepsonID(): string;
103
111
  getName(): string;
112
+ getRPIRankAndThreat(): string;
104
113
  getRPITaxonLink(): string;
105
114
  getSynonyms(): string[];
106
115
  }
package/lib/taxon.js CHANGED
@@ -264,6 +264,9 @@ class Taxon {
264
264
  return this.#iNatSyn ? link + " (" + this.#iNatSyn + ")" : link;
265
265
  }
266
266
 
267
+ /**
268
+ * @returns {string}
269
+ */
267
270
  getJepsonID() {
268
271
  return this.#jepsonID;
269
272
  }
@@ -287,6 +290,9 @@ class Taxon {
287
290
  return this.#rankRPI.split(".")[0];
288
291
  }
289
292
 
293
+ /**
294
+ * @returns {string}
295
+ */
290
296
  getRPIRankAndThreat() {
291
297
  return this.#rankRPI;
292
298
  }
@@ -1,10 +1,17 @@
1
1
  import { scrape } from "@htmltools/scrape";
2
2
  import { Files } from "../files.js";
3
+ import { SynCSV } from "./syncsv.js";
3
4
 
4
5
  /**
5
- * @typedef {{id:string,name:string,common?:string,type:string,under:string}} JepsonInfo
6
+ * @typedef {{
7
+ * id:string,
8
+ * name:string,
9
+ * common?:string,
10
+ * type:string,
11
+ * }} JepsonTaxon
6
12
  */
7
13
 
14
+ /** @type {Object<string,string>} */
8
15
  const TYPES = {
9
16
  EX_ALIEN: "Extirpated alien",
10
17
  HYBRID_SPONT: "Spontaneous hybrid",
@@ -32,44 +39,47 @@ const TYPES = {
32
39
  WEED: "* weed*",
33
40
  };
34
41
 
35
- class JepsonEFlora {
42
+ export class JepsonEFlora {
36
43
  #toolsDataPath;
37
44
  #taxa;
38
45
  #errorLog;
39
- #shouldLogNotes;
40
46
 
41
- /** @type {Map<string,JepsonInfo>} */
47
+ /** @type {Map<string,JepsonTaxon>} */
42
48
  #nameInfo = new Map();
43
- /** @type {Set<string>} */
44
- #loadedLetters = new Set();
49
+ /** @type {Map<string,string[]>} */
50
+ #synInfo = new Map();
51
+ /** @type {import("./syncsv.js").SynData[]} */
52
+ #synonymsToAdd = [];
45
53
 
46
54
  /**
47
55
  * @param {string} toolsDataDir
48
56
  * @param {Taxa} taxa
49
57
  * @param {ErrorLog} errorLog
50
- * @param {boolean} shouldLogNotes
51
58
  */
52
- constructor(toolsDataDir, taxa, errorLog, shouldLogNotes) {
59
+ constructor(toolsDataDir, taxa, errorLog) {
53
60
  this.#toolsDataPath = toolsDataDir + "/jepson-eflora";
54
61
  this.#taxa = taxa;
55
62
  this.#errorLog = errorLog;
56
- this.#shouldLogNotes = shouldLogNotes;
57
63
  }
58
64
 
59
65
  /**
60
66
  * @param {import("../exceptions.js").Exceptions} exceptions
67
+ * @param {boolean} update
61
68
  */
62
- async analyze(exceptions) {
69
+ async analyze(exceptions, update) {
63
70
  // Create data directory if it's not there.
64
71
  Files.mkdir(this.#toolsDataPath);
65
72
 
73
+ // Retrieve all Jepson indexes.
74
+ await this.#loadIndexPages();
75
+
66
76
  for (const taxon of this.#taxa.getTaxonList()) {
67
77
  const name = taxon.getName();
68
78
  if (name.includes(" unknown")) {
69
79
  continue;
70
80
  }
71
81
 
72
- const jepsInfo = await this.#getJepsInfo(name);
82
+ const jepsInfo = this.#getJepsInfo(name);
73
83
  if (jepsInfo === undefined) {
74
84
  // Not found in the index.
75
85
  if (!exceptions.hasException(name, "jepson", "notineflora")) {
@@ -87,12 +97,6 @@ class JepsonEFlora {
87
97
  );
88
98
  }
89
99
 
90
- if (this.#isSynonym(jepsInfo)) {
91
- if (!exceptions.hasException(name, "jepson", "allowsynonym")) {
92
- this.#errorLog.log(name, "is synonym for", jepsInfo.under);
93
- }
94
- }
95
-
96
100
  const efStatus = this.#getStatusCode(jepsInfo);
97
101
  const taxonStatus = taxon.getStatus();
98
102
  if (
@@ -108,8 +112,12 @@ class JepsonEFlora {
108
112
  }
109
113
  }
110
114
 
111
- await this.#checkSynonyms();
115
+ this.#checkSynonyms();
112
116
  this.#checkExceptions(exceptions);
117
+
118
+ if (update) {
119
+ this.#updateSynCSV();
120
+ }
113
121
  }
114
122
 
115
123
  /**
@@ -140,15 +148,6 @@ class JepsonEFlora {
140
148
  for (const [k] of Object.entries(exceptions)) {
141
149
  const jepsonData = this.#nameInfo.get(name);
142
150
  switch (k) {
143
- case "allowsynonym":
144
- // Make sure it really is a synonym.
145
- if (!this.#isSynonym(jepsonData)) {
146
- this.#errorLog.log(
147
- name,
148
- "has Jepson allowsynonym exception but is not a synonym",
149
- );
150
- }
151
- break;
152
151
  case "notineflora":
153
152
  // Make sure it is really not in eFlora.
154
153
  if (jepsonData) {
@@ -169,42 +168,43 @@ class JepsonEFlora {
169
168
  }
170
169
  }
171
170
 
172
- async #checkSynonyms() {
171
+ #checkSynonyms() {
173
172
  // Make sure all synonyms in eFlora are in our list.
174
- for (const jepsonInfo of Object.values(this.#nameInfo)) {
175
- if (!this.#isSynonym(jepsonInfo)) {
176
- continue;
177
- }
173
+ for (const [synName, targetNames] of this.#synInfo.entries()) {
174
+ for (const targetName of targetNames) {
175
+ const taxon = this.#taxa.getTaxon(targetName);
176
+ if (!taxon) {
177
+ // We're not tracking the target.
178
+ continue;
179
+ }
178
180
 
179
- const target = jepsonInfo.under;
180
- const taxon = this.#taxa.getTaxon(target);
181
- if (!taxon) {
182
- // We're not tracking the target.
183
- continue;
184
- }
181
+ if (taxon.getSynonyms().includes(synName)) {
182
+ // Already have it.
183
+ continue;
184
+ }
185
185
 
186
- if (taxon.getSynonyms().includes(jepsonInfo.name)) {
187
- // Already have it.
188
- continue;
186
+ this.#errorLog.log(
187
+ targetName,
188
+ "does not have synonym",
189
+ synName + "," + targetName,
190
+ );
191
+ this.#synonymsToAdd.push({
192
+ Former: synName,
193
+ Current: targetName,
194
+ });
189
195
  }
190
-
191
- this.#errorLog.log(
192
- target,
193
- "does not have synonym",
194
- jepsonInfo.name + "," + target,
195
- );
196
196
  }
197
197
 
198
198
  // Make sure everything in our list is in eFlora.
199
199
  for (const taxon of this.#taxa.getTaxonList()) {
200
200
  for (const synonym of taxon.getSynonyms()) {
201
- const jepsonInfo = await this.#getJepsInfo(synonym);
202
- if (!jepsonInfo || !this.#isSynonym(jepsonInfo)) {
201
+ const synInfo = this.#synInfo.get(synonym);
202
+ if (!synInfo || !synInfo.includes(taxon.getName())) {
203
203
  // Ignore iNat synonyms.
204
204
  if (synonym !== taxon.getINatSyn()) {
205
205
  this.#errorLog.log(
206
206
  synonym,
207
- "is in synonyms.csv but is not a synonym in eFlora",
207
+ `is in synonyms.csv but is not a synonym for ${taxon.getName()} in eFlora`,
208
208
  );
209
209
  }
210
210
  }
@@ -214,31 +214,17 @@ class JepsonEFlora {
214
214
 
215
215
  /**
216
216
  * @param {string} name
217
- * @returns {Promise<JepsonInfo|undefined>}
217
+ * @returns {JepsonTaxon|undefined}
218
218
  */
219
- async #getJepsInfo(name) {
220
- const firstLetter = name[0];
221
- // See if this index has been loaded.
222
- if (!this.#loadedLetters.has(firstLetter)) {
223
- await this.#loadNameIndex(firstLetter);
224
- }
225
-
219
+ #getJepsInfo(name) {
226
220
  return this.#nameInfo.get(name);
227
221
  }
228
222
 
229
223
  /**
230
- * @param {JepsonInfo} jepsInfo
224
+ * @param {JepsonTaxon} jepsInfo
231
225
  * @returns {StatusCode|undefined}
232
226
  */
233
227
  #getStatusCode(jepsInfo) {
234
- // If it's a synonym, return status of the target.
235
- if (this.#isSynonym(jepsInfo)) {
236
- const targetInfo = this.#nameInfo.get(jepsInfo.under);
237
- if (!targetInfo) {
238
- return;
239
- }
240
- return this.#getStatusCode(targetInfo);
241
- }
242
228
  switch (jepsInfo.type) {
243
229
  case TYPES.NATIVE:
244
230
  return "N";
@@ -249,19 +235,12 @@ class JepsonEFlora {
249
235
  }
250
236
  }
251
237
 
252
- /**
253
- * @param {JepsonInfo|undefined} jepsInfo
254
- * @returns {boolean}
255
- */
256
- #isSynonym(jepsInfo) {
257
- if (!jepsInfo) {
258
- return false;
259
- }
260
- switch (jepsInfo.type) {
261
- case TYPES.SYNONYM:
262
- return true;
238
+ async #loadIndexPages() {
239
+ for (let index = 0; index < 26; index++) {
240
+ await this.#loadNameIndex(
241
+ String.fromCharCode("A".charCodeAt(0) + index),
242
+ );
263
243
  }
264
- return false;
265
244
  }
266
245
 
267
246
  /**
@@ -293,32 +272,6 @@ class JepsonEFlora {
293
272
 
294
273
  const document = scrape.parseFile(filePath);
295
274
  this.#parseIndex(document);
296
-
297
- this.#loadedLetters.add(firstLetter);
298
- }
299
-
300
- /**
301
- * @param {JepsonInfo} taxonData
302
- */
303
- #logNotes(taxonData) {
304
- // If we're tracking the source, log it.
305
- if (this.#taxa.getTaxon(taxonData.name)) {
306
- this.#errorLog.log(
307
- taxonData.name,
308
- "has eFlora note (as source)",
309
- taxonData.type + " for",
310
- taxonData.under,
311
- );
312
- }
313
- // If we're tracking the target, log it.
314
- if (this.#taxa.getTaxon(taxonData.under)) {
315
- this.#errorLog.log(
316
- taxonData.under,
317
- "has eFlora note (as target)",
318
- taxonData.type + " for",
319
- taxonData.name,
320
- );
321
- }
322
275
  }
323
276
 
324
277
  /**
@@ -370,71 +323,83 @@ class JepsonEFlora {
370
323
  );
371
324
  }
372
325
 
373
- const under = scrape.getTextContent(cols[0]);
374
326
  const common = scrape.getTextContent(cols[1]);
327
+ const name = linkText;
375
328
 
376
- /** @type {JepsonInfo} */
377
- const taxonData = {};
378
-
379
- taxonData.name = linkText;
380
329
  const href = scrape.getAttr(links[0], "href");
381
330
  if (!href) {
382
331
  throw new Error();
383
332
  }
384
- taxonData.id = href.split("=")[1];
385
- taxonData.type = type;
386
- if (taxonData.common) {
387
- taxonData.common = common;
388
- }
333
+ const id = href.split("=")[1];
389
334
 
390
- if (under) {
391
- const m = under.match(reUnder);
335
+ const sciNameText = scrape.getTextContent(cols[0]);
336
+ let under;
337
+ if (sciNameText) {
338
+ const m = sciNameText.match(reUnder);
392
339
  if (m) {
393
- taxonData.under = m[1];
340
+ under = m[1];
394
341
  }
395
342
  }
396
343
 
397
- // If we're not tracking either the source or target, ignore this entry.
398
- if (
399
- !this.#taxa.getTaxon(taxonData.name) &&
400
- !this.#taxa.getTaxon(taxonData.under) &&
401
- !this.#taxa.hasSynonym(taxonData.name)
402
- ) {
403
- continue;
344
+ switch (type) {
345
+ case TYPES.NATIVE:
346
+ case TYPES.NATIVITY_UNCERTAIN:
347
+ case TYPES.NATURALIZED:
348
+ case TYPES.NATURALIZED_UW:
349
+ case TYPES.SYNONYM:
350
+ case TYPES.WAIF:
351
+ case TYPES.WEED:
352
+ break;
353
+ default:
354
+ continue;
404
355
  }
405
356
 
406
- switch (type) {
407
- case TYPES.ILLEGITIMATE:
408
- case TYPES.INVALID:
409
- case TYPES.INVALID_NOTED:
410
- case TYPES.INVALID_SUPERFLUOUS:
411
- case TYPES.MISAPPLIED:
412
- case TYPES.MISAPP_PART:
413
- case TYPES.MISAPP_UNABRIDGED:
414
- case TYPES.SYN_INED:
415
- case TYPES.SYN_ORTH_VARIANT:
416
- case TYPES.SYN_PART:
417
- case TYPES.SYN_PART_UN:
418
- case TYPES.MENTIONED:
419
- // Not a valid synonym or active taxon. Log it for further investigation.
420
- if (this.#shouldLogNotes) {
421
- this.#logNotes(taxonData);
422
- }
357
+ if (type === TYPES.SYNONYM) {
358
+ // Should have "under".
359
+ if (!under) {
360
+ throw new Error();
361
+ }
362
+ // If we're not tracking the target, ignore this entry.
363
+ if (!this.#taxa.getTaxon(under)) {
423
364
  continue;
365
+ }
366
+
367
+ // Add to synonyms.
368
+ let targetNames = this.#synInfo.get(name);
369
+ if (!targetNames) {
370
+ targetNames = [];
371
+ this.#synInfo.set(name, targetNames);
372
+ }
373
+ targetNames.push(under);
374
+ continue;
424
375
  }
425
376
 
426
- if (this.#nameInfo.get(taxonData.name)) {
427
- this.#errorLog.log(
428
- taxonData.name,
429
- "has multiple entries in eFlora",
430
- );
431
- // Disable the current entry, since we don't know which one is correct.
432
- this.#nameInfo.delete(taxonData.name);
377
+ // Not a synonym. Should not have "under".
378
+ if (under) {
379
+ throw new Error(`under = ${under} for ${name}`);
380
+ }
381
+
382
+ // If we're not tracking either the source, ignore this entry.
383
+ if (!this.#taxa.getTaxon(name)) {
433
384
  continue;
434
385
  }
435
- this.#nameInfo.set(taxonData.name, taxonData);
386
+
387
+ if (this.#nameInfo.get(name)) {
388
+ throw new Error();
389
+ }
390
+ this.#nameInfo.set(name, {
391
+ id: id,
392
+ type: type,
393
+ name: name,
394
+ common: common,
395
+ });
436
396
  }
437
397
  }
438
- }
439
398
 
440
- export { JepsonEFlora };
399
+ #updateSynCSV() {
400
+ const csv = new SynCSV("./data");
401
+ const data = csv.getData();
402
+ data.push(...this.#synonymsToAdd);
403
+ csv.write();
404
+ }
405
+ }
@@ -0,0 +1,41 @@
1
+ import path from "path";
2
+ import { CSV } from "../csv.js";
3
+
4
+ /**
5
+ * @typedef {{Former:string,Current:string,Type?:"INAT"|undefined}} SynData
6
+ */
7
+
8
+ export class SynCSV {
9
+ #filePath;
10
+ #headers;
11
+ /** @type {SynData[]} */
12
+ #data;
13
+
14
+ /**
15
+ * @param {string} dataDir
16
+ */
17
+ constructor(dataDir) {
18
+ this.#filePath = path.join(dataDir, "synonyms.csv");
19
+ const csv = CSV.readFileAndHeaders(this.#filePath);
20
+ this.#data = csv.data;
21
+ this.#headers = csv.headers;
22
+ }
23
+
24
+ /**
25
+ * @returns {SynData[]}
26
+ */
27
+ getData() {
28
+ return this.#data;
29
+ }
30
+
31
+ write() {
32
+ this.#data.sort((a, b) => {
33
+ const former = a.Former.localeCompare(b.Former);
34
+ if (former !== 0) {
35
+ return former;
36
+ }
37
+ return a.Current.localeCompare(b.Current);
38
+ });
39
+ CSV.writeFileObject(this.#filePath, this.#data, this.#headers);
40
+ }
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.4.17",
3
+ "version": "0.4.18",
4
4
  "description": "Tools to create Jekyll files for a website listing plants in an area of California.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,6 +35,7 @@
35
35
  "inatobsphotos": "scripts/inatobsphotos.js"
36
36
  },
37
37
  "dependencies": {
38
+ "@htmltools/scrape": "^0.1.0",
38
39
  "archiver": "^5.3.1",
39
40
  "cli-progress": "^3.12.0",
40
41
  "commander": "^12.1.0",
@@ -48,7 +49,6 @@
48
49
  "unzipper": "^0.12.3"
49
50
  },
50
51
  "devDependencies": {
51
- "@htmltools/scrape": "^0.1.0",
52
52
  "@types/archiver": "^6.0.2",
53
53
  "@types/cli-progress": "^3.11.6",
54
54
  "@types/markdown-it": "^14.1.2",
@@ -85,13 +85,8 @@ async function build(program, options) {
85
85
  );
86
86
  break;
87
87
  case TOOLS.JEPSON_EFLORA: {
88
- const eflora = new JepsonEFlora(
89
- TOOLS_DATA_DIR,
90
- taxa,
91
- errorLog,
92
- options.efLognotes,
93
- );
94
- await eflora.analyze(exceptions);
88
+ const eflora = new JepsonEFlora(TOOLS_DATA_DIR, taxa, errorLog);
89
+ await eflora.analyze(exceptions, !!options.update);
95
90
  break;
96
91
  }
97
92
  case TOOLS.JEPSON_FAM:
@@ -130,10 +125,6 @@ program.option(
130
125
  "The name of the file containing the iNaturalist taxa. Can be used for testing on a smaller subset of the iNaturalist data.",
131
126
  "inat_taxa.csv",
132
127
  );
133
- program.option(
134
- "--ef-lognotes",
135
- "When running the jepson-eflora tool, include eFlora notes, invalid names, etc. in the log file.",
136
- );
137
128
  program.option("--update", "Update taxa.csv to remove errors if possible.");
138
129
  program.addHelpText(
139
130
  "after",