@ca-plant-list/ca-plant-list 0.4.20 → 0.4.22

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/lib/config.js CHANGED
@@ -2,9 +2,15 @@ import * as path from "node:path";
2
2
  import * as url from "node:url";
3
3
  import { Files } from "./files.js";
4
4
 
5
+ /** @type {Object<string,string>} */
6
+ const COUNTY_NAMES = {
7
+ ALA: "Alameda",
8
+ CCA: "Contra Costa",
9
+ };
10
+
5
11
  class Config {
6
12
  static #packageDir = path.dirname(
7
- path.dirname(url.fileURLToPath(import.meta.url))
13
+ path.dirname(url.fileURLToPath(import.meta.url)),
8
14
  );
9
15
 
10
16
  /** @type {Object<string,Object<string,Object<string,{}>>>} */
@@ -55,6 +61,14 @@ class Config {
55
61
  return [];
56
62
  }
57
63
 
64
+ /**
65
+ * @returns {string[]}
66
+ */
67
+ getCountyNames() {
68
+ const counties = this.getCountyCodes();
69
+ return counties.map((c) => COUNTY_NAMES[c]);
70
+ }
71
+
58
72
  /**
59
73
  * @param {string} name
60
74
  * @param {string} dflt
package/lib/csv.js CHANGED
@@ -13,7 +13,7 @@ class CSV {
13
13
  */
14
14
  static #getOptions(fileName, columns, delimiter) {
15
15
  /** @type {import("csv-parse").Options} */
16
- const options = { relax_column_count_less: true };
16
+ const options = { relax_column_count_less: true, bom: true };
17
17
  options.columns = columns;
18
18
  if (path.extname(fileName) === ".tsv") {
19
19
  options.delimiter = "\t";
@@ -44,6 +44,7 @@ class CSV {
44
44
  * @param {boolean|import("csv-parse").ColumnOption[]} columns
45
45
  * @param {string|undefined} delimiter
46
46
  * @param {function (any):void} callback
47
+ * @deprecated Use parseFileStream
47
48
  */
48
49
  static async parseStream(
49
50
  dir,
@@ -72,6 +73,27 @@ class CSV {
72
73
  await processFile();
73
74
  }
74
75
 
76
+ /**
77
+ * @template T
78
+ * @param {string} fileName
79
+ * @param {function (T):void} callback
80
+ */
81
+ static async parseFileStream(fileName, callback) {
82
+ const options = this.#getOptions(fileName, true, undefined);
83
+ const processFile = async () => {
84
+ const parser = fs.createReadStream(fileName).pipe(parse(options));
85
+ parser.on("readable", function () {
86
+ let record;
87
+ while ((record = parser.read()) !== null) {
88
+ callback(record);
89
+ }
90
+ });
91
+ await finished(parser);
92
+ };
93
+ // Parse the CSV content
94
+ await processFile();
95
+ }
96
+
75
97
  /**
76
98
  * @param {string} fileName
77
99
  * @param {boolean|import("csv-parse").ColumnOption[]|function (string[]):string[]} [columns]
package/lib/errorlog.js CHANGED
@@ -17,7 +17,7 @@ class ErrorLog {
17
17
  }
18
18
 
19
19
  /**
20
- * @param {...string} args
20
+ * @param {...any} args
21
21
  */
22
22
  log(...args) {
23
23
  if (this.#echo) {
@@ -5,7 +5,64 @@
5
5
  taxon_id?: string;
6
6
  }} InatObsOptions */
7
7
 
8
- class ExternalSites {
8
+ export class ExternalSites {
9
+ /**
10
+ * @param {import("./taxon.js").Taxon} taxon
11
+ * @returns {URL|undefined}
12
+ */
13
+ static getCalscapeLink(taxon) {
14
+ const calscapeCN = taxon.getCalscapeCommonName();
15
+ if (!calscapeCN) {
16
+ return;
17
+ }
18
+ return new URL(
19
+ `https://www.calscape.org/${taxon.getCalscapeName().replaceAll(" ", "-")}-()`,
20
+ );
21
+ }
22
+
23
+ /**
24
+ * @param {import("./taxon.js").Taxon} taxon
25
+ * @param {import("./config.js").Config} config
26
+ * @returns {URL|undefined}
27
+ */
28
+ static getCCH2ObsLink(taxon, config) {
29
+ const url = new URL(
30
+ "https://www.cch2.org/portal/collections/listtabledisplay.php?usethes=1&taxontype=2&sortfield1=o.eventDate&sortorder=desc",
31
+ );
32
+ url.searchParams.set("county", config.getCountyNames().join(";"));
33
+ url.searchParams.set("taxa", taxon.getName());
34
+ return url;
35
+ }
36
+
37
+ /**
38
+ * @param {import("./taxon.js").Taxon} taxon
39
+ * @returns {URL|undefined}
40
+ */
41
+ static getCCH2RefLink(taxon) {
42
+ const id = taxon.getCCH2ID();
43
+ if (!id) {
44
+ return;
45
+ }
46
+ const url = new URL("https://www.cch2.org/portal/taxa/index.php");
47
+ url.searchParams.set("taxon", id);
48
+ return url;
49
+ }
50
+
51
+ /**
52
+ * @param {import("./taxon.js").Taxon} taxon
53
+ * @returns {URL|undefined}
54
+ */
55
+ static getFNARefLink(taxon) {
56
+ const name = taxon.getFNAName();
57
+ if (!name) {
58
+ return;
59
+ }
60
+ const url = new URL(
61
+ "http://floranorthamerica.org/" + name.replaceAll(" ", "_"),
62
+ );
63
+ return url;
64
+ }
65
+
9
66
  /**
10
67
  * @param {InatObsOptions} options
11
68
  */
@@ -44,5 +101,3 @@ class ExternalSites {
44
101
  return url.toString();
45
102
  }
46
103
  }
47
-
48
- export { ExternalSites };
package/lib/files.js CHANGED
@@ -13,15 +13,13 @@ class Files {
13
13
 
14
14
  /**
15
15
  * @param {string} fileName
16
- * @param {*} inStream
17
- * @access private
16
+ * @param {import("node:stream").Stream} inStream
18
17
  */
19
18
  static #createFileFromStream(fileName, inStream) {
20
19
  /**
21
- *
22
20
  * @param {string} fileName
23
- * @param {*} inStream
24
- * @param {*} resolve
21
+ * @param {import("node:stream").Stream} inStream
22
+ * @param {function(boolean):void} resolve
25
23
  */
26
24
  function implementation(fileName, inStream, resolve) {
27
25
  const outStream = fs.createWriteStream(fileName);
@@ -140,7 +138,7 @@ class Files {
140
138
  if (entry.path === fileNameToUnzip) {
141
139
  await this.#createFileFromStream(
142
140
  targetFilePath,
143
- entry.stream()
141
+ entry.stream(),
144
142
  );
145
143
  break;
146
144
  }
package/lib/genera.js CHANGED
@@ -56,6 +56,9 @@ class Genus {
56
56
  return this.#data.familyObj;
57
57
  }
58
58
 
59
+ /**
60
+ * @returns {import("./taxon.js").Taxon[]}
61
+ */
59
62
  getTaxa() {
60
63
  return this.#data.taxa.sort((a, b) =>
61
64
  a.getName().localeCompare(b.getName()),
package/lib/htmltaxon.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Config } from "./config.js";
2
2
  import { DateUtils } from "./dateutils.js";
3
+ import { ExternalSites } from "./externalsites.js";
3
4
  import { HTML } from "./html.js";
4
5
  import { Markdown } from "./markdown.js";
5
6
  import { RarePlants } from "./rareplants.js";
@@ -53,22 +54,44 @@ const DEFAULT_TAXA_COLUMNS = [
53
54
  TAXA_LIST_COLS.COMMON_NAME,
54
55
  ];
55
56
 
57
+ /** @type {Object<string,{label:string,href:function(import("./taxon.js").Taxon):URL|undefined}>} */
58
+ const REFLINKS = {
59
+ calscape: {
60
+ label: "Calscape",
61
+ href: (taxon) => ExternalSites.getCalscapeLink(taxon),
62
+ },
63
+ fna: {
64
+ label: "Flora of North America",
65
+ href: (taxon) => ExternalSites.getFNARefLink(taxon),
66
+ },
67
+ cch: {
68
+ label: "CCH2",
69
+ href: (taxon) => ExternalSites.getCCH2RefLink(taxon),
70
+ },
71
+ };
72
+
56
73
  class HTMLTaxon {
57
74
  /**
58
- * @param {import("./taxon.js").Taxon} taxon
59
- * @returns {string|undefined}
75
+ * @param {string[]} links
76
+ * @param {URL|string|undefined} href
77
+ * @param {string} label
60
78
  */
61
- static getCalscapeLink(taxon) {
62
- const calscapeCN = taxon.getCalscapeCommonName();
63
- if (!calscapeCN) {
79
+ static addLink(links, href, label) {
80
+ if (href === undefined) {
64
81
  return;
65
82
  }
66
- return HTML.getLink(
67
- `https://www.calscape.org/${taxon.getCalscapeName().replaceAll(" ", "-")}-()`,
68
- "Calscape",
69
- {},
70
- true,
71
- );
83
+ const link = HTML.getLink(href.toString(), label, {}, true);
84
+ links.push(link);
85
+ }
86
+
87
+ /**
88
+ * @param {string[]} links
89
+ * @param {import("./taxon.js").Taxon} taxon
90
+ * @param {import("./index.js").RefSourceCode} sourceCode
91
+ */
92
+ static addRefLink(links, taxon, sourceCode) {
93
+ const source = REFLINKS[sourceCode];
94
+ this.addLink(links, source.href(taxon), source.label);
72
95
  }
73
96
 
74
97
  /**
package/lib/index.d.ts CHANGED
@@ -2,15 +2,23 @@ import { Command } from "commander";
2
2
 
3
3
  // Types
4
4
 
5
+ export type NativeStatusCode = "N" | "NC" | "U" | "X";
6
+
7
+ type PhotoRights = "CC0" | "CC BY" | "CC BY-NC" | "C" | null;
8
+
9
+ type RefSourceCode = "calscape" | "cch" | "fna";
10
+
5
11
  export type TaxonData = {
6
12
  bloom_end: string;
7
13
  bloom_start: string;
8
14
  calrecnum: string;
9
15
  calscape_cn?: string;
16
+ cch2_id: string;
10
17
  CESA: string;
11
18
  "common name": string;
12
19
  CRPR: string;
13
20
  FESA: string;
21
+ fna: string;
14
22
  flower_color: string;
15
23
  GRank: string;
16
24
  "inat id": string;
@@ -18,7 +26,7 @@ export type TaxonData = {
18
26
  life_cycle: string;
19
27
  "RPI ID": string;
20
28
  SRank: string;
21
- status: "N" | "NC" | "U" | "X";
29
+ status: NativeStatusCode;
22
30
  taxon_name: string;
23
31
  };
24
32
 
@@ -58,6 +66,14 @@ export class Exceptions {
58
66
  hasException(name: string, cat: string, subcat: string): boolean;
59
67
  }
60
68
 
69
+ export class ExternalSites {
70
+ static getCCH2ObsLink(taxon: Taxon, config: Config): URL | undefined;
71
+ }
72
+
73
+ export class Family {
74
+ getName(): string;
75
+ }
76
+
61
77
  export class Files {
62
78
  static exists(fileName: string): boolean;
63
79
  static fetch(
@@ -66,11 +82,18 @@ export class Files {
66
82
  ): Promise<Headers>;
67
83
  static mkdir(dir: string): void;
68
84
  static rmDir(dir: string): void;
69
- static write(fileName: string, data: string, overwrite: boolean): void;
85
+ static write(fileName: string, data: string, overwrite?: boolean): void;
86
+ }
87
+
88
+ export class Genera {}
89
+
90
+ export class Genus {
91
+ getTaxa(): Taxon[];
70
92
  }
71
93
 
72
94
  export class HTML {
73
95
  static arrayToLI(items: string[]): string;
96
+ static escapeText(text: string): string;
74
97
  static getLink(
75
98
  href: string | undefined,
76
99
  linkText: string,
@@ -90,6 +113,16 @@ export class HTML {
90
113
  }
91
114
 
92
115
  export class HTMLTaxon {
116
+ static addLink(
117
+ links: string[],
118
+ href: URL | string | undefined,
119
+ label: string,
120
+ ): void;
121
+ static addRefLink(
122
+ links: string[],
123
+ taxon: Taxon,
124
+ sourceCode: RefSourceCode,
125
+ ): void;
93
126
  static getFooterHTML(taxon: Taxon): string;
94
127
  static getListSectionHTML(
95
128
  list: string[],
@@ -100,11 +133,10 @@ export class HTMLTaxon {
100
133
  }
101
134
 
102
135
  export class Jekyll {
136
+ static hasInclude(baseDir: string, path: string): boolean;
103
137
  static include(fileName: string): string;
104
138
  }
105
139
 
106
- type PhotoRights = "CC0" | "CC BY" | "CC BY-NC" | "C" | null;
107
-
108
140
  export class Photo {
109
141
  getAttribution(): string;
110
142
  getExt(): string;
@@ -118,37 +150,33 @@ export class Program {
118
150
  static getProgram(): Command;
119
151
  }
120
152
 
121
- export class Family {}
122
-
123
- export class Genera {}
124
-
125
- export class Genus {}
126
-
127
- export class Taxa {
153
+ export class Taxa<T> {
128
154
  constructor(
129
155
  inclusionList: Record<string, TaxonData> | true,
130
156
  errorLog: ErrorLog,
131
157
  showFlowerErrors: boolean,
132
- taxonFactory?: (td: TaxonData, g: Genera) => Taxon,
158
+ taxonFactory?: (td: TaxonData, g: Genera) => T,
133
159
  extraTaxa?: TaxonData[],
134
160
  extraSynonyms?: Record<string, string>[],
135
161
  );
136
- getTaxon(name: string): Taxon;
137
- getTaxonList(): Taxon[];
162
+ getTaxon(name: string): T;
163
+ getTaxonList(): T[];
138
164
  }
139
165
 
140
166
  export class Taxon {
167
+ constructor(data: TaxonData, genera: Genera);
141
168
  getBaseFileName(): string;
142
169
  getCalfloraID(): string;
143
170
  getCalfloraTaxonLink(): string;
144
- getCESA(): string | undefined;
145
- getCNDDBRank(): string | undefined;
171
+ getCESA(): string;
172
+ getCNDDBRank(): string;
146
173
  getCommonNames(): string[];
147
174
  getFamily(): Family;
148
- getFESA(): string | undefined;
175
+ getFileName(): string;
176
+ getFESA(): string;
149
177
  getGenus(): Genus;
150
178
  getGenusName(): string;
151
- getGlobalRank(): string | undefined;
179
+ getGlobalRank(): string;
152
180
  getINatID(): string;
153
181
  getINatTaxonLink(): string;
154
182
  getJepsonID(): string;
package/lib/index.js CHANGED
@@ -3,6 +3,7 @@ import { Config } from "./config.js";
3
3
  import { CSV } from "./csv.js";
4
4
  import { ErrorLog } from "./errorlog.js";
5
5
  import { Exceptions } from "./exceptions.js";
6
+ import { ExternalSites } from "./externalsites.js";
6
7
  import { Families } from "./families.js";
7
8
  import { Files } from "./files.js";
8
9
  import { HTML } from "./html.js";
@@ -11,7 +12,7 @@ import { Jekyll } from "./jekyll.js";
11
12
  import { PlantBook } from "./ebook/plantbook.js";
12
13
  import { Program } from "./program.js";
13
14
  import { Taxa } from "./taxa.js";
14
- import { Taxon, TAXA_COLNAMES } from "./taxon.js";
15
+ import { Taxon } from "./taxon.js";
15
16
 
16
17
  export {
17
18
  BasePageRenderer,
@@ -19,6 +20,7 @@ export {
19
20
  CSV,
20
21
  ErrorLog,
21
22
  Exceptions,
23
+ ExternalSites,
22
24
  Families,
23
25
  Files,
24
26
  HTML,
@@ -27,6 +29,5 @@ export {
27
29
  PlantBook,
28
30
  Program,
29
31
  Taxa,
30
- TAXA_COLNAMES,
31
32
  Taxon,
32
33
  };
package/lib/taxa.js CHANGED
@@ -255,7 +255,7 @@ class Taxa {
255
255
  for (const row of taxaCSV) {
256
256
  const name = row["taxon_name"];
257
257
 
258
- /** @type {import("./index.js").TaxonData|{status?:import("./taxon.js").StatusCode}} */
258
+ /** @type {import("./index.js").TaxonData|{status?:import("./index.js").NativeStatusCode}} */
259
259
  let taxon_overrides = {};
260
260
  if (inclusionList !== true) {
261
261
  taxon_overrides = inclusionList[name];
package/lib/taxon.js CHANGED
@@ -1,17 +1,6 @@
1
1
  import { HTML } from "./html.js";
2
2
  import { RarePlants } from "./rareplants.js";
3
3
 
4
- /**
5
- * @typedef {"N" | "NC" | "U" | "X"} StatusCode
6
- */
7
-
8
- const TAXA_COLNAMES = {
9
- BLOOM_START: "bloom_start",
10
- BLOOM_END: "bloom_end",
11
- COMMON_NAME: "common name",
12
- FLOWER_COLOR: "flower_color",
13
- };
14
-
15
4
  class Taxon {
16
5
  #genera;
17
6
  #name;
@@ -25,6 +14,8 @@ class Taxon {
25
14
  #iNatID;
26
15
  /**@type {string|undefined} */
27
16
  #iNatSyn;
17
+ #cch2id;
18
+ #fnaName;
28
19
  #calscapeCN;
29
20
  #lifeCycle;
30
21
  #flowerColors;
@@ -62,6 +53,8 @@ class Taxon {
62
53
  this.#jepsonID = data["jepson id"];
63
54
  this.#calRecNum = data["calrecnum"];
64
55
  this.#iNatID = data["inat id"];
56
+ this.#cch2id = data.cch2_id;
57
+ this.#fnaName = data.fna ?? "";
65
58
  this.#calscapeCN =
66
59
  data.calscape_cn === "" ? undefined : data.calscape_cn;
67
60
  this.#lifeCycle = data.life_cycle;
@@ -173,12 +166,25 @@ class Taxon {
173
166
  return name.replace(" subsp.", " ssp.");
174
167
  }
175
168
 
169
+ /**
170
+ * @returns {string}
171
+ */
172
+ getCCH2ID() {
173
+ return this.#cch2id;
174
+ }
175
+
176
+ /**
177
+ * @returns {string}
178
+ */
176
179
  getCESA() {
177
- return this.#cesa;
180
+ return this.#cesa ?? "";
178
181
  }
179
182
 
183
+ /**
184
+ * @returns {string}
185
+ */
180
186
  getCNDDBRank() {
181
- return this.#rankCNDDB;
187
+ return this.#rankCNDDB ?? "";
182
188
  }
183
189
 
184
190
  getCommonNames() {
@@ -189,14 +195,27 @@ class Taxon {
189
195
  return this.getGenus().getFamily();
190
196
  }
191
197
 
198
+ /**
199
+ * @returns {string}
200
+ */
192
201
  getFESA() {
193
- return this.#fesa;
202
+ return this.#fesa ?? "";
194
203
  }
195
204
 
196
205
  getFileName(ext = "html") {
197
206
  return this.getBaseFileName() + "." + ext;
198
207
  }
199
208
 
209
+ /**
210
+ * @returns {string}
211
+ */
212
+ getFNAName() {
213
+ if (this.#fnaName === "true") {
214
+ return this.getName();
215
+ }
216
+ return this.#fnaName;
217
+ }
218
+
200
219
  getFlowerColors() {
201
220
  return this.#flowerColors;
202
221
  }
@@ -209,8 +228,11 @@ class Taxon {
209
228
  return this.#genus;
210
229
  }
211
230
 
231
+ /**
232
+ * @returns {string}
233
+ */
212
234
  getGlobalRank() {
213
- return this.#rankGlobal;
235
+ return this.#rankGlobal ?? "";
214
236
  }
215
237
 
216
238
  /**
@@ -289,6 +311,9 @@ class Taxon {
289
311
  return this.#rpiID;
290
312
  }
291
313
 
314
+ /**
315
+ * @returns {string}
316
+ */
292
317
  getRPIRank() {
293
318
  if (!this.#rankRPI) {
294
319
  return this.#rankRPI;
@@ -303,12 +328,18 @@ class Taxon {
303
328
  return this.#rankRPI;
304
329
  }
305
330
 
331
+ /**
332
+ * @deprecated
333
+ */
306
334
  getRPIRankAndThreatTooltip() {
307
335
  return RarePlants.getRPIRankAndThreatDescriptions(
308
336
  this.getRPIRankAndThreat(),
309
337
  ).join("<br>");
310
338
  }
311
339
 
340
+ /**
341
+ * @deprecated
342
+ */
312
343
  getRPITaxonLink() {
313
344
  const rpiID = this.getRPIID();
314
345
  if (!rpiID) {
@@ -323,9 +354,6 @@ class Taxon {
323
354
  return link;
324
355
  }
325
356
 
326
- /**
327
- * @returns {StatusCode}
328
- */
329
357
  getStatus() {
330
358
  return this.#status;
331
359
  }
@@ -340,6 +368,8 @@ class Taxon {
340
368
  return "Native";
341
369
  case "NC":
342
370
  return config.getLabel("status-NC", "Introduced");
371
+ case "U":
372
+ return "Nativity Uncertain";
343
373
  case "X":
344
374
  return "Introduced";
345
375
  }
@@ -385,4 +415,4 @@ class Taxon {
385
415
  }
386
416
  }
387
417
 
388
- export { TAXA_COLNAMES, Taxon };
418
+ export { Taxon };
@@ -23,7 +23,6 @@ export class Calflora {
23
23
  static #taxa = {};
24
24
 
25
25
  /**
26
- *
27
26
  * @param {string} toolsDataDir
28
27
  * @param {string} dataDir
29
28
  * @param {import("../taxa.js").Taxa} taxa