@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/data/exceptions.json +0 -1
- package/data/inatobsphotos.csv +67 -55
- package/data/inattaxonphotos.csv +145 -95
- package/data/synonyms.csv +4 -0
- package/data/taxa.csv +184 -183
- package/lib/csv.js +5 -0
- package/lib/ebook/ebookpage.js +6 -3
- package/lib/ebook/pages/pageListFamilies.js +2 -4
- package/lib/ebook/pages/pageListFlowers.js +3 -5
- package/lib/ebook/pages/page_list_flower_color.js +2 -4
- package/lib/ebook/pages/page_list_species.js +2 -4
- package/lib/ebook/pages/taxonpage.js +3 -3
- package/lib/ebook/pages/tocpage.js +1 -1
- package/lib/ebook/plantbook.js +3 -2
- package/lib/tools/rpi.js +92 -9
- package/package.json +13 -13
- package/scripts/cpl-tools.js +2 -0
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();
|
package/lib/ebook/ebookpage.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
75
|
+
const dimensions = await imageSizeFromFile(
|
|
76
76
|
this.#images.getCompressedFilePath(photo),
|
|
77
77
|
);
|
|
78
78
|
let img = XHTML.textElement("img", "", {
|
package/lib/ebook/plantbook.js
CHANGED
|
@@ -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
|
-
)
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
149
|
-
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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.
|
|
39
|
+
"@11ty/eleventy": "^3.1.2",
|
|
40
40
|
"@htmltools/scrape": "^0.1.1",
|
|
41
|
-
"archiver": "^
|
|
41
|
+
"archiver": "^7.0.1",
|
|
42
42
|
"cli-progress": "^3.12.0",
|
|
43
|
-
"commander": "^14.0.
|
|
44
|
-
"csv-parse": "^
|
|
45
|
-
"csv-stringify": "^6.
|
|
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": "^
|
|
47
|
+
"image-size": "^2.0.2",
|
|
48
48
|
"markdown-it": "^14.1.0",
|
|
49
|
-
"sharp": "^0.
|
|
50
|
-
"svgo-ll": "^6.0
|
|
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.
|
|
61
|
-
"jest": "^
|
|
60
|
+
"eslint": "^9.39.0",
|
|
61
|
+
"jest": "^30.2.0",
|
|
62
62
|
"prettier": "^3.6.2",
|
|
63
|
-
"puppeteer": "^24.
|
|
64
|
-
"typescript": "^5.9.
|
|
63
|
+
"puppeteer": "^24.27.0",
|
|
64
|
+
"typescript": "^5.9.3"
|
|
65
65
|
}
|
|
66
66
|
}
|
package/scripts/cpl-tools.js
CHANGED
|
@@ -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:
|