@ca-plant-list/ca-plant-list 0.4.36 → 0.4.38

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/csv.js CHANGED
@@ -10,6 +10,7 @@ export class CSV {
10
10
  * @param {string} fileName
11
11
  * @param {import("csv-parse").ColumnOption[]|boolean|function (string[]):string[]} columns
12
12
  * @param {string|undefined} delimiter
13
+ * @returns {import("csv-parse").Options}
13
14
  */
14
15
  static #getOptions(fileName, columns, delimiter) {
15
16
  /** @type {import("csv-parse").Options} */
@@ -104,6 +105,8 @@ export class CSV {
104
105
  static readFile(fileName, columns = true, delimiter) {
105
106
  const content = fs.readFileSync(fileName);
106
107
  const options = this.#getOptions(fileName, columns, delimiter);
108
+ /** @type {T[]} */
109
+ // @ts-ignore - need to get options @type to have correct column options
107
110
  return parseSync(content, options);
108
111
  }
109
112
 
@@ -126,6 +129,8 @@ export class CSV {
126
129
  const content = fs.readFileSync(fileName);
127
130
  const options = this.#getOptions(fileName, getHeaders, delimiter);
128
131
 
132
+ /** @type {T[]} */
133
+ // @ts-ignore - need to get options @type to have correct column options
129
134
  const data = parseSync(content, options);
130
135
  if (headers === undefined) {
131
136
  throw new Error();
@@ -16,9 +16,9 @@ export class EBookPage {
16
16
  this.#rootPrefix = rootPrefix;
17
17
  }
18
18
 
19
- create() {
19
+ async create() {
20
20
  let html = this.#renderPageStart(this.#title);
21
- html += this.renderPageBody();
21
+ html += await this.renderPageBody();
22
22
  html += this.#renderPageEnd();
23
23
  fs.writeFileSync(this.#fileName, html);
24
24
  }
@@ -31,7 +31,10 @@ export class EBookPage {
31
31
  return "<body>";
32
32
  }
33
33
 
34
- renderPageBody() {
34
+ /**
35
+ * @returns {Promise<string>}
36
+ */
37
+ async renderPageBody() {
35
38
  throw new Error("must be implemented by subclass");
36
39
  }
37
40
 
@@ -1,7 +1,7 @@
1
1
  import { EBookPage } from "../ebookpage.js";
2
2
  import { XHTML } from "../xhtml.js";
3
3
 
4
- class PageListFamilies extends EBookPage {
4
+ export class PageListFamilies extends EBookPage {
5
5
  #families;
6
6
 
7
7
  /**
@@ -13,7 +13,7 @@ class PageListFamilies extends EBookPage {
13
13
  this.#families = families;
14
14
  }
15
15
 
16
- renderPageBody() {
16
+ async renderPageBody() {
17
17
  const html = XHTML.textElement("h1", this.getTitle());
18
18
 
19
19
  const links = [];
@@ -27,5 +27,3 @@ class PageListFamilies extends EBookPage {
27
27
  return html + XHTML.wrap("ol", XHTML.arrayToLI(links));
28
28
  }
29
29
  }
30
-
31
- export { PageListFamilies };
@@ -5,7 +5,7 @@ import { EBook } from "../ebook.js";
5
5
 
6
6
  const FN_FLOWER_TIME_INDEX = "fm.html";
7
7
 
8
- class PageListFlowers {
8
+ export class PageListFlowers {
9
9
  /**
10
10
  * @param {string} contentDir
11
11
  * @param {import("../../types.js").Taxa} taxa
@@ -70,7 +70,7 @@ class PageListFlowerTimeIndex extends EBookPage {
70
70
  super(outputDir + "/" + FN_FLOWER_TIME_INDEX, "Flowering Times");
71
71
  }
72
72
 
73
- renderPageBody() {
73
+ async renderPageBody() {
74
74
  const html = XHTML.textElement("h1", this.getTitle());
75
75
  return html + PageListFlowers.renderMonthLinks();
76
76
  }
@@ -106,7 +106,7 @@ class PageListFlowerTime extends EBookPage {
106
106
  return "list_fm_" + m1 + ".html";
107
107
  }
108
108
 
109
- renderPageBody() {
109
+ async renderPageBody() {
110
110
  const html = XHTML.textElement("h1", this.getTitle());
111
111
 
112
112
  /** @type {[number,number]} */
@@ -123,5 +123,3 @@ class PageListFlowerTime extends EBookPage {
123
123
  return html + XHTML.wrap("ol", XHTML.arrayToLI(links));
124
124
  }
125
125
  }
126
-
127
- export { PageListFlowers, PageListFlowerTime };
@@ -1,7 +1,7 @@
1
1
  import { EBookPage } from "../ebookpage.js";
2
2
  import { XHTML } from "../xhtml.js";
3
3
 
4
- class PageListFlowerColor extends EBookPage {
4
+ export class PageListFlowerColor extends EBookPage {
5
5
  #color;
6
6
 
7
7
  /**
@@ -16,7 +16,7 @@ class PageListFlowerColor extends EBookPage {
16
16
  this.#color = color;
17
17
  }
18
18
 
19
- renderPageBody() {
19
+ async renderPageBody() {
20
20
  const html = XHTML.textElement("h1", this.getTitle());
21
21
 
22
22
  const links = [];
@@ -27,5 +27,3 @@ class PageListFlowerColor extends EBookPage {
27
27
  return html + XHTML.wrap("ol", XHTML.arrayToLI(links));
28
28
  }
29
29
  }
30
-
31
- export { PageListFlowerColor };
@@ -2,7 +2,7 @@ import { HTMLTaxon } from "../../htmltaxon.js";
2
2
  import { EBookPage } from "../ebookpage.js";
3
3
  import { XHTML } from "../xhtml.js";
4
4
 
5
- class PageListSpecies extends EBookPage {
5
+ export class PageListSpecies extends EBookPage {
6
6
  #taxa;
7
7
 
8
8
  /**
@@ -17,7 +17,7 @@ class PageListSpecies extends EBookPage {
17
17
  this.#taxa = taxa;
18
18
  }
19
19
 
20
- renderPageBody() {
20
+ async renderPageBody() {
21
21
  const html = XHTML.textElement("h1", this.getTitle());
22
22
 
23
23
  const links = [];
@@ -28,5 +28,3 @@ class PageListSpecies extends EBookPage {
28
28
  return html + XHTML.wrap("ol", XHTML.arrayToLI(links));
29
29
  }
30
30
  }
31
-
32
- export { PageListSpecies };
@@ -1,3 +1,4 @@
1
+ import { imageSizeFromFile } from "image-size/fromFile";
1
2
  import { EBookPage } from "../ebookpage.js";
2
3
  import { XHTML } from "../xhtml.js";
3
4
  import { Markdown } from "../../markdown.js";
@@ -5,7 +6,6 @@ import { HTMLTaxon } from "../../htmltaxon.js";
5
6
  import { Config } from "../../config.js";
6
7
  import { Files } from "../../files.js";
7
8
  import { Images } from "../images.js";
8
- import imageSize from "image-size";
9
9
 
10
10
  class TaxonPage extends EBookPage {
11
11
  #config;
@@ -25,7 +25,7 @@ class TaxonPage extends EBookPage {
25
25
  this.#images = images;
26
26
  }
27
27
 
28
- renderPageBody() {
28
+ async renderPageBody() {
29
29
  /**
30
30
  * @param {string} name
31
31
  */
@@ -72,7 +72,7 @@ class TaxonPage extends EBookPage {
72
72
 
73
73
  let photoHTML = "";
74
74
  for (const photo of photos) {
75
- const dimensions = imageSize.imageSize(
75
+ const dimensions = await imageSizeFromFile(
76
76
  this.#images.getCompressedFilePath(photo),
77
77
  );
78
78
  let img = XHTML.textElement("img", "", {
@@ -14,7 +14,7 @@ class TOCPage extends EBookPage {
14
14
  this.#taxa = taxa;
15
15
  }
16
16
 
17
- renderPageBody() {
17
+ async renderPageBody() {
18
18
  let html = '<nav id="toc" role="doc-toc" epub:type="toc">';
19
19
  html += '<h1 epub:type="title">Table of Contents</h1>';
20
20
 
@@ -56,12 +56,13 @@ class PlantBook extends EBook {
56
56
 
57
57
  for (let index = 0; index < taxonList.length; index++) {
58
58
  const taxon = taxonList[index];
59
- new TaxonPage(
59
+ const page = new TaxonPage(
60
60
  contentDir,
61
61
  this.#config,
62
62
  taxon,
63
63
  this.#images,
64
- ).create();
64
+ );
65
+ await page.create();
65
66
  meter.update(index + 1);
66
67
  }
67
68
  meter.stop();
package/lib/tools/rpi.js CHANGED
@@ -1,30 +1,53 @@
1
1
  import path from "node:path";
2
2
  import { CSV } from "../csv.js";
3
3
  import { Files } from "../files.js";
4
+ import { TaxaCSV } from "./taxacsv.js";
5
+
6
+ /**
7
+ * @typedef {"CESA"|"CNDDB"|"FESA"|"Global"} Rank
8
+ * @typedef {Map<string,Map<Rank,string|undefined>>} RanksToUpdate
9
+ */
4
10
 
5
11
  const HTML_FILE_NAME = "rpi.html";
6
12
  const URL_RPI_LIST =
7
13
  "https://rareplants.cnps.org/Search/result?frm=T&life=tree:herb:shrub:vine:leaf:stem";
8
14
 
9
- class RPI {
15
+ export class RPI {
10
16
  /** @type {Object<string,Object<string,string>>} */
11
17
  static #rpiData = {};
12
18
 
13
19
  /**
14
20
  * @param {string} toolsDataDir
21
+ * @param {string} dataDir
15
22
  * @param {import("../types.js").Taxa} taxa
16
23
  * @param {import("../config.js").Config} config
17
24
  * @param {import("../exceptions.js").Exceptions} exceptions
18
25
  * @param {import("../errorlog.js").ErrorLog} errorLog
26
+ * @param {boolean} update
19
27
  */
20
- static async analyze(toolsDataDir, taxa, config, exceptions, errorLog) {
28
+ static async analyze(
29
+ toolsDataDir,
30
+ dataDir,
31
+ taxa,
32
+ config,
33
+ exceptions,
34
+ errorLog,
35
+ update,
36
+ ) {
21
37
  /**
22
38
  * @param {string} name
23
- * @param {string} label
39
+ * @param {Rank} label
24
40
  * @param {string|undefined} rpiRank
25
41
  * @param {string|undefined} taxonRank
42
+ * @param {RanksToUpdate} ranksToUpdate
26
43
  */
27
- function checkStatusMatch(name, label, rpiRank, taxonRank) {
44
+ function checkStatusMatch(
45
+ name,
46
+ label,
47
+ rpiRank,
48
+ taxonRank,
49
+ ranksToUpdate,
50
+ ) {
28
51
  if (rpiRank !== taxonRank) {
29
52
  errorLog.log(
30
53
  name,
@@ -34,6 +57,12 @@ class RPI {
34
57
  String(taxonRank),
35
58
  String(rpiRank),
36
59
  );
60
+ let map = ranksToUpdate.get(name);
61
+ if (map === undefined) {
62
+ map = new Map();
63
+ ranksToUpdate.set(name, map);
64
+ }
65
+ map.set(label, rpiRank);
37
66
  }
38
67
  }
39
68
 
@@ -67,6 +96,9 @@ class RPI {
67
96
  const ignoreGlobalRank = config.getConfigValue("rpi", "ignoreglobal");
68
97
  const ignoreCNDDBRank = config.getConfigValue("rpi", "ignorecnddb");
69
98
 
99
+ /** @type {RanksToUpdate} */
100
+ const ranksToUpdate = new Map();
101
+
70
102
  const csv = CSV.readFile(path.join(toolsDataPath, fileName));
71
103
  for (const row of csv) {
72
104
  const rpiName = row["ScientificName"].replace(" ssp.", " subsp.");
@@ -145,10 +177,28 @@ class RPI {
145
177
  );
146
178
  }
147
179
  }
148
- checkStatusMatch(name, "CESA", cesa, taxon.getCESA());
149
- checkStatusMatch(name, "FESA", fesa, taxon.getFESA());
180
+ checkStatusMatch(
181
+ name,
182
+ "CESA",
183
+ cesa,
184
+ taxon.getCESA(),
185
+ ranksToUpdate,
186
+ );
187
+ checkStatusMatch(
188
+ name,
189
+ "FESA",
190
+ fesa,
191
+ taxon.getFESA(),
192
+ ranksToUpdate,
193
+ );
150
194
  if (!ignoreCNDDBRank) {
151
- checkStatusMatch(name, "CNDDB", cnddb, taxon.getCNDDBRank());
195
+ checkStatusMatch(
196
+ name,
197
+ "CNDDB",
198
+ cnddb,
199
+ taxon.getCNDDBRank(),
200
+ ranksToUpdate,
201
+ );
152
202
  }
153
203
  if (!ignoreGlobalRank) {
154
204
  checkStatusMatch(
@@ -156,6 +206,7 @@ class RPI {
156
206
  "Global",
157
207
  globalRank,
158
208
  taxon.getGlobalRank(),
209
+ ranksToUpdate,
159
210
  );
160
211
  }
161
212
 
@@ -199,6 +250,10 @@ class RPI {
199
250
  this.#checkExceptions(taxa, config, exceptions, errorLog);
200
251
 
201
252
  this.#scrape(toolsDataDir, taxa, exceptions, errorLog);
253
+
254
+ if (update) {
255
+ this.#updateRanks(dataDir, ranksToUpdate);
256
+ }
202
257
  }
203
258
 
204
259
  /**
@@ -429,6 +484,34 @@ class RPI {
429
484
  }
430
485
  return false;
431
486
  }
432
- }
433
487
 
434
- export { RPI };
488
+ /**
489
+ * @param {string} dataDir
490
+ * @param {RanksToUpdate} ranksToUpdate
491
+ */
492
+ static #updateRanks(dataDir, ranksToUpdate) {
493
+ const taxa = new TaxaCSV(dataDir);
494
+
495
+ for (const taxonData of taxa.getTaxa()) {
496
+ const ranks = ranksToUpdate.get(taxonData.taxon_name);
497
+ if (!ranks) {
498
+ continue;
499
+ }
500
+ for (const [k, v] of ranks.entries()) {
501
+ switch (k) {
502
+ case "CNDDB":
503
+ taxonData["SRank"] = v ?? "";
504
+ break;
505
+ case "Global":
506
+ taxonData["GRank"] = v ?? "";
507
+ break;
508
+ default:
509
+ taxonData[k] = v ?? "";
510
+ break;
511
+ }
512
+ }
513
+ }
514
+
515
+ taxa.write();
516
+ }
517
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.4.36",
3
+ "version": "0.4.38",
4
4
  "description": "Tools to create files for a website listing plants in an area of California.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,18 +36,18 @@
36
36
  "inatobsphotos": "scripts/inatobsphotos.js"
37
37
  },
38
38
  "dependencies": {
39
- "@11ty/eleventy": "^3.0.0",
39
+ "@11ty/eleventy": "^3.1.2",
40
40
  "@htmltools/scrape": "^0.1.1",
41
- "archiver": "^5.3.1",
41
+ "archiver": "^7.0.1",
42
42
  "cli-progress": "^3.12.0",
43
- "commander": "^14.0.1",
44
- "csv-parse": "^5.6.0",
45
- "csv-stringify": "^6.5.2",
43
+ "commander": "^14.0.2",
44
+ "csv-parse": "^6.1.0",
45
+ "csv-stringify": "^6.6.0",
46
46
  "exceljs": "^4.4.0",
47
- "image-size": "^1.1.1",
47
+ "image-size": "^2.0.2",
48
48
  "markdown-it": "^14.1.0",
49
- "sharp": "^0.33.5",
50
- "svgo-ll": "^6.0.1",
49
+ "sharp": "^0.34.4",
50
+ "svgo-ll": "^6.1.0",
51
51
  "unzipper": "^0.12.3"
52
52
  },
53
53
  "devDependencies": {
@@ -57,10 +57,10 @@
57
57
  "@types/markdown-it": "^14.1.2",
58
58
  "@types/node": "^22.10.7",
59
59
  "@types/unzipper": "^0.10.9",
60
- "eslint": "^9.35.0",
61
- "jest": "^29.7.0",
60
+ "eslint": "^9.39.0",
61
+ "jest": "^30.2.0",
62
62
  "prettier": "^3.6.2",
63
- "puppeteer": "^24.1.1",
64
- "typescript": "^5.9.2"
63
+ "puppeteer": "^24.27.0",
64
+ "typescript": "^5.9.3"
65
65
  }
66
66
  }
@@ -145,10 +145,12 @@ async function build(program, options) {
145
145
  case TOOLS.RPI:
146
146
  await RPI.analyze(
147
147
  TOOLS_DATA_DIR,
148
+ options.datadir,
148
149
  taxa,
149
150
  config,
150
151
  exceptions,
151
152
  errorLog,
153
+ !!options.update,
152
154
  );
153
155
  break;
154
156
  case TOOLS.TEXT: