@ca-plant-list/ca-plant-list 0.4.35 → 0.4.37

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.
@@ -0,0 +1 @@
1
+ In forested areas.
@@ -0,0 +1 @@
1
+ In rocky areas. Leaves densely hairy.
@@ -0,0 +1 @@
1
+ Usually in moist or forested areas. [Phyllaries](./g/phyllary.html) with black hairs.
@@ -0,0 +1 @@
1
+ Leaves hairy. Plant mostly prostrate; inflorescence low.
@@ -0,0 +1 @@
1
+ Inflorescence hairy. Beak 3-5 mm (shorter than _P. groenlandica_).
@@ -0,0 +1 @@
1
+ Inflorescence not hairy. Beak 6-13 mm (longer than _P. attolens_).
@@ -0,0 +1 @@
1
+ Leaves gray. Scented.
@@ -0,0 +1 @@
1
+ Leaves green. Scented.
@@ -0,0 +1 @@
1
+ Leaves green. Scented.
@@ -1 +1 @@
1
- Leaves generally narrow and pointed at both ends.
1
+ Leaves generally pointed at both ends. Older leaves usually hairless; younger leaves often somewhat hairy.
@@ -1,48 +1,47 @@
1
1
  * {
2
- font-family: Georgia, 'Times New Roman', Times, serif;
2
+ font-family: Georgia, 'Times New Roman', Times, serif;
3
3
  }
4
4
 
5
5
  div.flr {
6
- background-color: lightgray;
7
- display: flex;
8
- align-items: center;
6
+ background-color: lightgray;
7
+ display: flex;
8
+ align-items: center;
9
9
  }
10
10
 
11
11
  div.hdr {
12
- display: flex;
13
- column-gap: 2rem;
14
- row-gap: .5rem;
15
- flex-flow: row wrap;
16
- justify-content: space-between;
12
+ display: flex;
13
+ column-gap: 2rem;
14
+ row-gap: .5rem;
15
+ flex-flow: row wrap;
16
+ justify-content: space-between;
17
17
  }
18
18
 
19
19
  div.section {
20
- margin-bottom: .5rem;
20
+ margin-bottom: .5rem;
21
21
  }
22
22
 
23
23
  div.photos {
24
- display: flex;
24
+ display: flex;
25
25
  }
26
26
 
27
27
  figure {
28
- margin: auto;
28
+ margin: auto;
29
29
  }
30
30
 
31
31
  img.flr-color {
32
- padding-right: .25rem;
33
- width: 1rem;
32
+ padding-right: .25rem;
33
+ width: 1rem;
34
34
  }
35
35
 
36
36
  span.flr-time,
37
37
  span.lc {
38
- font-weight: bold;
38
+ font-weight: bold;
39
39
  }
40
40
 
41
41
  span.lcs {
42
- margin-right: 1rem;
42
+ margin-right: 1rem;
43
43
  }
44
44
 
45
- /* Glossary */
46
45
  div.glossary img {
47
- max-height: 300px;
46
+ width: 100%
48
47
  }
package/lib/csv.js CHANGED
@@ -134,7 +134,6 @@ export class CSV {
134
134
  }
135
135
 
136
136
  /**
137
- *
138
137
  * @param {string} fileName
139
138
  * @param {string[][]} data
140
139
  * @param {string[]} [headerData]
@@ -146,7 +145,6 @@ export class CSV {
146
145
  }
147
146
 
148
147
  /**
149
- *
150
148
  * @param {string} fileName
151
149
  * @param {Object<string,any>[]} data
152
150
  * @param {string[]} headerData
package/lib/htmltaxon.js CHANGED
@@ -252,12 +252,19 @@ class HTMLTaxon {
252
252
  * @returns {string}
253
253
  */
254
254
  static getFooterHTML(taxon) {
255
- const footerTextPath = path.join(
255
+ const footerTextPathCommon = path.join(
256
256
  Config.getPackageDir(),
257
257
  "/data/text/",
258
258
  `${taxon.getBaseFileName()}.footer.md`,
259
259
  );
260
- return HTMLFragments.getMarkdownSection(footerTextPath);
260
+ const footerTextPathLocal = path.join(
261
+ "./data/footers/",
262
+ `${taxon.getBaseFileName()}.md`,
263
+ );
264
+ return (
265
+ HTMLFragments.getMarkdownSection(footerTextPathCommon) +
266
+ HTMLFragments.getMarkdownSection(footerTextPathLocal)
267
+ );
261
268
  }
262
269
 
263
270
  /**
package/lib/index.d.ts CHANGED
@@ -231,6 +231,7 @@ export class Taxa<T> {
231
231
  extraTaxa?: TaxonOverrides[],
232
232
  extraSynonyms?: Record<string, string>[],
233
233
  );
234
+ static getIncludeList(dataDir: string): true | Record<string, TaxonData>;
234
235
  getTaxon(name: string): T;
235
236
  getTaxonList(): T[];
236
237
  }
package/lib/markdown.js CHANGED
@@ -2,7 +2,7 @@ import markdownIt from "markdown-it";
2
2
  import { Files } from "./files.js";
3
3
 
4
4
  export class Markdown {
5
- static #md = new markdownIt({ xhtmlOut: true });
5
+ static #md = new markdownIt({ html: true, xhtmlOut: true });
6
6
 
7
7
  /**
8
8
  * @param {string} filePath
package/lib/program.js CHANGED
@@ -1,7 +1,4 @@
1
1
  import { Command } from "commander";
2
- import { Files } from "./files.js";
3
- import { CSV } from "./csv.js";
4
- import path from "node:path";
5
2
 
6
3
  /**
7
4
  * @typedef {{
@@ -12,29 +9,6 @@ import path from "node:path";
12
9
  */
13
10
 
14
11
  class Program {
15
- /**
16
- * @param {string} dataDir
17
- */
18
- static getIncludeList(dataDir) {
19
- // Read inclusion list.
20
- const includeFileName = "taxa_include.csv";
21
- const includeFilePath = dataDir + "/" + includeFileName;
22
- if (!Files.exists(includeFilePath)) {
23
- console.log(includeFilePath + " not found; loading all taxa");
24
- return true;
25
- }
26
-
27
- /** @type {import("./index.js").TaxonData[]} */
28
- // @ts-ignore
29
- const includeCSV = CSV.readFile(path.join(dataDir, includeFileName));
30
- /** @type {Object<string,import("./index.js").TaxonData>} */
31
- const include = {};
32
- for (const row of includeCSV) {
33
- include[row["taxon_name"]] = row;
34
- }
35
- return include;
36
- }
37
-
38
12
  static getProgram() {
39
13
  const program = new Command();
40
14
  program
@@ -56,16 +56,16 @@ export class SiteGenerator {
56
56
  Config.getPackageDir() + "/data/illustrations/inkscape";
57
57
  const entries = Files.getDirEntries(srcDir);
58
58
  for (const entry of entries) {
59
- const srcFile = Files.join(srcDir, entry);
59
+ const srcFile = path.join(srcDir, entry);
60
60
  const srcSVG = Files.read(srcFile);
61
61
  const result = optimize(srcSVG, {
62
- plugins: ["preset-default", "removeDimensions"],
62
+ enable: ["removeDimensions"],
63
63
  });
64
- Files.write(Files.join(outputDir, entry), result.data);
64
+ Files.write(path.join(outputDir, entry), result.data);
65
65
  }
66
66
  }
67
67
 
68
- const outputDir = Files.join(this.#baseDir, "i");
68
+ const outputDir = path.join(this.#baseDir, "i");
69
69
  Files.mkdir(outputDir);
70
70
 
71
71
  optimizeSVG(outputDir);
@@ -9,8 +9,8 @@ import { Families } from "./families.js";
9
9
  import { FlowerColor } from "../flowercolor.js";
10
10
  import { TaxaCSV } from "../tools/taxacsv.js";
11
11
  import { ErrorLog } from "../errorlog.js";
12
- import { Program } from "../program.js";
13
12
  import { Photo } from "../photo.js";
13
+ import { Files } from "../files.js";
14
14
 
15
15
  /**
16
16
  * @typedef {{Current: string;Former: string;Type: string;}} SynonymData
@@ -28,7 +28,7 @@ const FLOWER_COLORS = [
28
28
  { name: "brown", color: "brown" },
29
29
  ];
30
30
 
31
- class Taxa {
31
+ export class Taxa {
32
32
  #families;
33
33
  #errorLog;
34
34
  /** @type {Object<string,Taxon>} */
@@ -152,6 +152,29 @@ class Taxa {
152
152
  );
153
153
  }
154
154
 
155
+ /**
156
+ * @param {string} dataDir
157
+ * @returns {true|Object<string,import("../index.js").TaxonData>}
158
+ */
159
+ static getIncludeList(dataDir) {
160
+ // Read inclusion list.
161
+ const includeFileName = "taxa_include.csv";
162
+ const includeFilePath = dataDir + "/" + includeFileName;
163
+ if (!Files.exists(includeFilePath)) {
164
+ console.log(includeFilePath + " not found; loading all taxa");
165
+ return true;
166
+ }
167
+
168
+ /** @type {import("../index.js").TaxonData[]} */
169
+ const includeCSV = CSV.readFile(path.join(dataDir, includeFileName));
170
+ /** @type {Object<string,import("../index.js").TaxonData>} */
171
+ const include = {};
172
+ for (const row of includeCSV) {
173
+ include[row["taxon_name"]] = row;
174
+ }
175
+ return include;
176
+ }
177
+
155
178
  /**
156
179
  * @param {string} name
157
180
  */
@@ -193,7 +216,7 @@ class Taxa {
193
216
  taxa = await taxaLoaderClass.TaxaLoader.loadTaxa(options, errorLog);
194
217
  } else {
195
218
  taxa = new Taxa(
196
- Program.getIncludeList(options.datadir),
219
+ Taxa.getIncludeList(options.datadir),
197
220
  errorLog,
198
221
  options.showFlowerErrors,
199
222
  );
@@ -297,5 +320,3 @@ class Taxa {
297
320
  }
298
321
  }
299
322
  }
300
-
301
- export { Taxa };
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.35",
3
+ "version": "0.4.37",
4
4
  "description": "Tools to create files for a website listing plants in an area of California.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,14 +40,14 @@
40
40
  "@htmltools/scrape": "^0.1.1",
41
41
  "archiver": "^5.3.1",
42
42
  "cli-progress": "^3.12.0",
43
- "commander": "^13.1.0",
43
+ "commander": "^14.0.1",
44
44
  "csv-parse": "^5.6.0",
45
45
  "csv-stringify": "^6.5.2",
46
46
  "exceljs": "^4.4.0",
47
47
  "image-size": "^1.1.1",
48
48
  "markdown-it": "^14.1.0",
49
49
  "sharp": "^0.33.5",
50
- "svgo-ll": "^5.6.0",
50
+ "svgo-ll": "^6.0.1",
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.20.1",
60
+ "eslint": "^9.35.0",
61
61
  "jest": "^29.7.0",
62
- "prettier": "^3.5.1",
62
+ "prettier": "^3.6.2",
63
63
  "puppeteer": "^24.1.1",
64
- "typescript": "^5.7.3"
64
+ "typescript": "^5.9.2"
65
65
  }
66
66
  }
@@ -71,7 +71,7 @@ async function buildBook(outputDir, dataDir, showFlowerErrors, maxTaxa) {
71
71
  const errorLog = new ErrorLog(outputDir + "/errors.tsv");
72
72
 
73
73
  const taxa = new Taxa(
74
- Program.getIncludeList(dataDir),
74
+ Taxa.getIncludeList(dataDir),
75
75
  errorLog,
76
76
  showFlowerErrors,
77
77
  );
@@ -16,7 +16,7 @@ async function build(options) {
16
16
  Files.rmDir(outputDir);
17
17
  const errorLog = new ErrorLog(outputDir + "/errors.tsv");
18
18
  const taxa = new Taxa(
19
- Program.getIncludeList(options.datadir),
19
+ Taxa.getIncludeList(options.datadir),
20
20
  errorLog,
21
21
  options.showFlowerErrors,
22
22
  );
@@ -506,7 +506,7 @@ async function getTaxa(options) {
506
506
  taxa = await taxaLoaderClass.TaxaLoader.loadTaxa(options, errorLog);
507
507
  } else {
508
508
  taxa = new Taxa(
509
- Program.getIncludeList(options.datadir),
509
+ Taxa.getIncludeList(options.datadir),
510
510
  errorLog,
511
511
  options.showFlowerErrors,
512
512
  );
@@ -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:
@@ -44,7 +44,7 @@ async function fetchInatTaxa(taxa) {
44
44
  async function getTaxonPhotos(options) {
45
45
  const errorLog = new ErrorLog(options.outputdir + "/errors.tsv");
46
46
  const taxa = new Taxa(
47
- Program.getIncludeList(options.datadir),
47
+ Taxa.getIncludeList(options.datadir),
48
48
  errorLog,
49
49
  false,
50
50
  );