@ca-plant-list/ca-plant-list 0.3.2 → 0.3.3
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/.vscode/settings.json +4 -2
- package/data/exceptions.json +0 -3
- package/data/glossary/ovary.md +3 -0
- package/data/glossary/pistil.md +3 -0
- package/data/glossary/stigma.md +3 -0
- package/data/glossary/style.md +3 -0
- package/data/illustrations/inkscape/pistil.svg +156 -0
- package/data/synonyms.csv +8 -2
- package/data/taxa.csv +54 -52
- package/data/text/Ceanothus-cuneatus-var-cuneatus.md +1 -0
- package/data/text/Ceanothus-leucodermis.md +1 -0
- package/data/text/Delphinium-californicum-subsp-californicum.md +1 -0
- package/data/text/Delphinium-decorum-subsp-decorum.md +1 -0
- package/data/text/Delphinium-hesperium-subsp-hesperium.md +1 -0
- package/data/text/Delphinium-patens-subsp-patens.md +1 -0
- package/data/text/Galium-andrewsii-subsp-gatense.md +1 -0
- package/data/text/Helianthella-californica-var-californica.md +1 -0
- package/data/text/Helianthella-castanea.md +1 -0
- package/data/text/Iris-macrosiphon.md +1 -0
- package/data/text/Lasthenia-californica-subsp-californica.md +1 -0
- package/data/text/Lasthenia-gracilis.md +1 -0
- package/data/text/Lithophragma-affine.md +1 -1
- package/data/text/Lithophragma-parviflorum-var-parviflorum.md +1 -1
- package/data/text/Lomatium-californicum.md +0 -0
- package/data/text/Lomatium-dasycarpum-subsp-dasycarpum.md +1 -0
- package/data/text/Lomatium-utriculatum.md +1 -0
- package/data/text/Nemophila-heterophylla.md +1 -0
- package/data/text/Nemophila-parviflora-var-parviflora.md +1 -0
- package/data/text/Plectritis-ciliosa.md +1 -0
- package/data/text/Plectritis-macrocera.md +1 -0
- package/data/text/Primula-clevelandii-var-patula.md +1 -0
- package/data/text/Primula-hendersonii.md +1 -0
- package/data/text/Sidalcea-diploscypha.md +1 -0
- package/data/text/Thysanocarpus-curvipes.md +1 -0
- package/data/text/Thysanocarpus-laciniatus.md +1 -0
- package/data/text/Viola-douglasii.md +1 -0
- package/data/text/Viola-pedunculata.md +1 -0
- package/data/text/Viola-purpurea-subsp-quercetorum.md +1 -0
- package/ebook/css/main.css +5 -0
- package/lib/basepagerenderer.js +30 -28
- package/lib/dateutils.js +36 -16
- package/lib/ebook/pages/taxonpage.js +63 -36
- package/lib/errorlog.js +16 -11
- package/lib/families.js +109 -71
- package/lib/files.js +103 -45
- package/lib/generictaxaloader.js +15 -7
- package/lib/index.js +38 -3
- package/lib/taxa.js +164 -87
- package/lib/taxaloader.js +28 -15
- package/lib/taxaprocessor.js +6 -8
- package/lib/taxon.js +109 -56
- package/package.json +4 -6
- package/scripts/build-ebook.js +30 -25
- package/lib/index.d.ts +0 -345
package/lib/taxon.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
+
import { HTML } from "./index.js";
|
1
2
|
import { Genera } from "./genera.js";
|
2
|
-
import { HTML } from "./html.js";
|
3
3
|
import { RarePlants } from "./rareplants.js";
|
4
4
|
|
5
5
|
const TAXA_COLNAMES = {
|
@@ -10,15 +10,16 @@ const TAXA_COLNAMES = {
|
|
10
10
|
};
|
11
11
|
|
12
12
|
class Taxon {
|
13
|
-
|
14
13
|
#name;
|
15
14
|
#genus;
|
16
15
|
#commonNames;
|
17
16
|
#status;
|
18
17
|
#jepsonID;
|
19
18
|
#calRecNum;
|
19
|
+
/**@type {string|undefined} */
|
20
20
|
#cfSyn;
|
21
21
|
#iNatID;
|
22
|
+
/**@type {string|undefined} */
|
22
23
|
#iNatSyn;
|
23
24
|
#flowerColors;
|
24
25
|
#bloomStart;
|
@@ -29,38 +30,52 @@ class Taxon {
|
|
29
30
|
#fesa;
|
30
31
|
#rankCNDDB;
|
31
32
|
#rankGlobal;
|
33
|
+
/** @type {string[]} */
|
32
34
|
#synonyms = [];
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
const
|
39
|
-
const
|
40
|
-
const
|
36
|
+
/**
|
37
|
+
* @param {import("./index.js").TaxonData} data
|
38
|
+
*/
|
39
|
+
constructor(data) {
|
40
|
+
const name = data["taxon_name"];
|
41
|
+
const commonNames = data["common name"];
|
42
|
+
const cesa = data["CESA"];
|
43
|
+
const fesa = data["FESA"];
|
44
|
+
const rankGlobal = data["GRank"];
|
45
|
+
const rankCNDDB = data["SRank"];
|
41
46
|
this.#name = name;
|
42
|
-
this.#genus = name.split(
|
43
|
-
this.#commonNames = commonNames
|
44
|
-
|
45
|
-
|
46
|
-
this.#
|
47
|
-
this.#
|
48
|
-
|
49
|
-
this.#
|
50
|
-
|
51
|
-
this.#
|
52
|
-
|
53
|
-
|
47
|
+
this.#genus = name.split(" ")[0];
|
48
|
+
this.#commonNames = commonNames
|
49
|
+
? commonNames.split(",").map((t) => t.trim())
|
50
|
+
: [];
|
51
|
+
this.#status = data["status"];
|
52
|
+
this.#jepsonID = data["jepson id"];
|
53
|
+
this.#calRecNum = data["calrecnum"];
|
54
|
+
this.#iNatID = data["inat id"];
|
55
|
+
const colors = data["flower_color"];
|
56
|
+
this.#flowerColors = colors ? colors.split(",") : undefined;
|
57
|
+
if (data["bloom_start"]) {
|
58
|
+
this.#bloomStart = parseInt(data["bloom_start"]);
|
59
|
+
}
|
60
|
+
if (data["bloom_end"]) {
|
61
|
+
this.#bloomEnd = parseInt(data["bloom_end"]);
|
62
|
+
}
|
63
|
+
this.#rpiID = data["RPI ID"];
|
64
|
+
this.#rankRPI = data["CRPR"];
|
54
65
|
this.#cesa = cesa ? cesa : undefined;
|
55
66
|
this.#fesa = fesa ? fesa : undefined;
|
56
67
|
this.#rankCNDDB = rankCNDDB ? rankCNDDB : undefined;
|
57
68
|
this.#rankGlobal = rankGlobal ? rankGlobal : undefined;
|
58
|
-
Genera.addTaxon(
|
69
|
+
Genera.addTaxon(this);
|
59
70
|
}
|
60
71
|
|
61
|
-
|
62
|
-
|
63
|
-
|
72
|
+
/**
|
73
|
+
* @param {string} syn
|
74
|
+
* @param {string} type
|
75
|
+
*/
|
76
|
+
addSynonym(syn, type) {
|
77
|
+
this.#synonyms.push(syn);
|
78
|
+
switch (type) {
|
64
79
|
case "CF":
|
65
80
|
// Synonym is in Calflora format.
|
66
81
|
this.#cfSyn = syn;
|
@@ -74,22 +89,28 @@ class Taxon {
|
|
74
89
|
|
75
90
|
getBaseFileName() {
|
76
91
|
// Convert spaces to "-" and remove ".".
|
77
|
-
return this.#name.replaceAll(
|
92
|
+
return this.#name.replaceAll(" ", "-").replaceAll(".", "");
|
78
93
|
}
|
79
94
|
|
95
|
+
/**
|
96
|
+
* @returns {number|undefined}
|
97
|
+
*/
|
80
98
|
getBloomEnd() {
|
81
99
|
return this.#bloomEnd;
|
82
100
|
}
|
83
101
|
|
102
|
+
/**
|
103
|
+
* @returns {number|undefined}
|
104
|
+
*/
|
84
105
|
getBloomStart() {
|
85
106
|
return this.#bloomStart;
|
86
107
|
}
|
87
108
|
|
88
109
|
getCalfloraName() {
|
89
|
-
if (
|
110
|
+
if (this.#cfSyn) {
|
90
111
|
return this.#cfSyn;
|
91
112
|
}
|
92
|
-
return this.getName().replace(
|
113
|
+
return this.getName().replace(" subsp.", " ssp.").replace("×", "X");
|
93
114
|
}
|
94
115
|
|
95
116
|
getCalfloraID() {
|
@@ -98,11 +119,16 @@ class Taxon {
|
|
98
119
|
|
99
120
|
getCalfloraTaxonLink() {
|
100
121
|
const calfloraID = this.getCalfloraID();
|
101
|
-
if (
|
122
|
+
if (!calfloraID) {
|
102
123
|
return;
|
103
124
|
}
|
104
|
-
const link = HTML.getLink(
|
105
|
-
|
125
|
+
const link = HTML.getLink(
|
126
|
+
"https://www.calflora.org/app/taxon?crn=" + calfloraID,
|
127
|
+
"Calflora",
|
128
|
+
{},
|
129
|
+
true
|
130
|
+
);
|
131
|
+
return this.#cfSyn ? link + " (" + this.#cfSyn + ")" : link;
|
106
132
|
}
|
107
133
|
|
108
134
|
getCESA() {
|
@@ -118,14 +144,14 @@ class Taxon {
|
|
118
144
|
}
|
119
145
|
|
120
146
|
getFamily() {
|
121
|
-
return Genera.getFamily(
|
147
|
+
return Genera.getFamily(this.#genus);
|
122
148
|
}
|
123
149
|
|
124
150
|
getFESA() {
|
125
151
|
return this.#fesa;
|
126
152
|
}
|
127
153
|
|
128
|
-
getFileName(
|
154
|
+
getFileName(ext = "html") {
|
129
155
|
return this.getBaseFileName() + "." + ext;
|
130
156
|
}
|
131
157
|
|
@@ -134,7 +160,7 @@ class Taxon {
|
|
134
160
|
}
|
135
161
|
|
136
162
|
getGenus() {
|
137
|
-
return Genera.getGenus(
|
163
|
+
return Genera.getGenus(this.#genus);
|
138
164
|
}
|
139
165
|
|
140
166
|
getGenusName() {
|
@@ -145,18 +171,29 @@ class Taxon {
|
|
145
171
|
return this.#rankGlobal;
|
146
172
|
}
|
147
173
|
|
148
|
-
|
149
|
-
|
174
|
+
/**
|
175
|
+
*
|
176
|
+
* @param {boolean|string|undefined} href
|
177
|
+
* @param {boolean} includeRPI
|
178
|
+
*/
|
179
|
+
getHTMLLink(href = true, includeRPI = true) {
|
180
|
+
href = href ? "./" + this.getFileName() : undefined;
|
150
181
|
let className = this.isNative() ? "native" : "non-native";
|
151
182
|
let isRare = false;
|
152
|
-
if (
|
183
|
+
if (includeRPI && this.isRare()) {
|
153
184
|
isRare = true;
|
154
185
|
className += " rare";
|
155
186
|
}
|
156
187
|
const attributes = { class: className };
|
157
|
-
const link = HTML.wrap(
|
158
|
-
|
159
|
-
|
188
|
+
const link = HTML.wrap(
|
189
|
+
"span",
|
190
|
+
HTML.getLink(href, this.getName()),
|
191
|
+
attributes
|
192
|
+
);
|
193
|
+
if (isRare) {
|
194
|
+
return HTML.getToolTip(link, this.getRPIRankAndThreatTooltip(), {
|
195
|
+
icon: false,
|
196
|
+
});
|
160
197
|
}
|
161
198
|
return link;
|
162
199
|
}
|
@@ -167,7 +204,7 @@ class Taxon {
|
|
167
204
|
|
168
205
|
getINatName() {
|
169
206
|
const name = this.#iNatSyn ? this.#iNatSyn : this.getName();
|
170
|
-
return name.replace(
|
207
|
+
return name.replace(/ (subsp|var)\./, "").replace("×", "× ");
|
171
208
|
}
|
172
209
|
|
173
210
|
getINatSyn() {
|
@@ -176,11 +213,16 @@ class Taxon {
|
|
176
213
|
|
177
214
|
getINatTaxonLink() {
|
178
215
|
const iNatID = this.getINatID();
|
179
|
-
if (
|
216
|
+
if (!iNatID) {
|
180
217
|
return "";
|
181
218
|
}
|
182
|
-
const link = HTML.getLink(
|
183
|
-
|
219
|
+
const link = HTML.getLink(
|
220
|
+
"https://www.inaturalist.org/taxa/" + iNatID,
|
221
|
+
"iNaturalist",
|
222
|
+
{},
|
223
|
+
true
|
224
|
+
);
|
225
|
+
return this.#iNatSyn ? link + " (" + this.#iNatSyn + ")" : link;
|
184
226
|
}
|
185
227
|
|
186
228
|
getJepsonID() {
|
@@ -196,10 +238,10 @@ class Taxon {
|
|
196
238
|
}
|
197
239
|
|
198
240
|
getRPIRank() {
|
199
|
-
if (
|
241
|
+
if (!this.#rankRPI) {
|
200
242
|
return this.#rankRPI;
|
201
243
|
}
|
202
|
-
return this.#rankRPI.split(
|
244
|
+
return this.#rankRPI.split(".")[0];
|
203
245
|
}
|
204
246
|
|
205
247
|
getRPIRankAndThreat() {
|
@@ -207,15 +249,22 @@ class Taxon {
|
|
207
249
|
}
|
208
250
|
|
209
251
|
getRPIRankAndThreatTooltip() {
|
210
|
-
return RarePlants.getRPIRankAndThreatDescriptions(
|
252
|
+
return RarePlants.getRPIRankAndThreatDescriptions(
|
253
|
+
this.getRPIRankAndThreat()
|
254
|
+
).join("<br>");
|
211
255
|
}
|
212
256
|
|
213
257
|
getRPITaxonLink() {
|
214
258
|
const rpiID = this.getRPIID();
|
215
|
-
if (
|
259
|
+
if (!rpiID) {
|
216
260
|
return "";
|
217
261
|
}
|
218
|
-
const link = HTML.getLink(
|
262
|
+
const link = HTML.getLink(
|
263
|
+
"https://rareplants.cnps.org/Plants/Details/" + rpiID,
|
264
|
+
"CNPS Rare Plant Inventory",
|
265
|
+
{},
|
266
|
+
true
|
267
|
+
);
|
219
268
|
return link;
|
220
269
|
}
|
221
270
|
|
@@ -223,16 +272,20 @@ class Taxon {
|
|
223
272
|
return this.#status;
|
224
273
|
}
|
225
274
|
|
226
|
-
|
227
|
-
|
275
|
+
/**
|
276
|
+
* @param {*} config
|
277
|
+
* @returns
|
278
|
+
*/
|
279
|
+
getStatusDescription(config) {
|
280
|
+
switch (this.#status) {
|
228
281
|
case "N":
|
229
282
|
return "Native";
|
230
283
|
case "NC":
|
231
|
-
return config.getLabel(
|
284
|
+
return config.getLabel("status-NC", "Introduced");
|
232
285
|
case "X":
|
233
286
|
return "Introduced";
|
234
287
|
}
|
235
|
-
throw new Error(
|
288
|
+
throw new Error(this.#status);
|
236
289
|
}
|
237
290
|
|
238
291
|
getSynonyms() {
|
@@ -257,7 +310,7 @@ class Taxon {
|
|
257
310
|
|
258
311
|
shouldHaveFlowers() {
|
259
312
|
const sectionName = this.getFamily().getSectionName();
|
260
|
-
switch (
|
313
|
+
switch (sectionName) {
|
261
314
|
case "Ceratophyllales":
|
262
315
|
case "Eudicots":
|
263
316
|
case "Magnoliids":
|
@@ -269,9 +322,9 @@ class Taxon {
|
|
269
322
|
case "Lycophytes":
|
270
323
|
return false;
|
271
324
|
default:
|
272
|
-
throw new Error(
|
325
|
+
throw new Error(sectionName);
|
273
326
|
}
|
274
327
|
}
|
275
328
|
}
|
276
329
|
|
277
|
-
export { TAXA_COLNAMES, Taxon };
|
330
|
+
export { TAXA_COLNAMES, Taxon };
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ca-plant-list/ca-plant-list",
|
3
|
-
"version": "0.3.
|
3
|
+
"version": "0.3.3",
|
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": {
|
@@ -10,11 +10,7 @@
|
|
10
10
|
"homepage": "https://github.com/ca-plants/ca-plant-list",
|
11
11
|
"type": "module",
|
12
12
|
"exports": {
|
13
|
-
".": "./lib/index.js"
|
14
|
-
"./Families": "./lib/families.js",
|
15
|
-
"./Files": "./lib/files.js",
|
16
|
-
"./HTML": "./lib/html.js",
|
17
|
-
"./Jekyll": "./lib/jekyll.js"
|
13
|
+
".": "./lib/index.js"
|
18
14
|
},
|
19
15
|
"types": "./lib/index.d.ts",
|
20
16
|
"bin": {
|
@@ -35,6 +31,8 @@
|
|
35
31
|
},
|
36
32
|
"devDependencies": {
|
37
33
|
"@types/node": "^18.11.9",
|
34
|
+
"@types/unzipper": "^0.10.9",
|
35
|
+
"dts-bundle-generator": "^9.3.1",
|
38
36
|
"eslint": "^8.26.0",
|
39
37
|
"typescript": "^5.3.3"
|
40
38
|
}
|
package/scripts/build-ebook.js
CHANGED
@@ -6,64 +6,69 @@ import { Files } from "../lib/files.js";
|
|
6
6
|
import { TaxaProcessor } from "../lib/taxaprocessor.js";
|
7
7
|
import { CommandProcessor } from "../lib/commandprocessor.js";
|
8
8
|
|
9
|
-
const OPTION_DEFS = [
|
10
|
-
{ name: "locationsdir", type: String },
|
11
|
-
];
|
9
|
+
const OPTION_DEFS = [{ name: "locationsdir", type: String }];
|
12
10
|
|
13
11
|
const OPTION_HELP = [
|
14
12
|
{
|
15
13
|
name: "locationsdir",
|
16
14
|
type: String,
|
17
15
|
typeLabel: "{underline path}",
|
18
|
-
description:
|
19
|
-
|
20
|
-
|
16
|
+
description:
|
17
|
+
"If this option is specified, multiple ebooks will be generated. {bold locationsdir} must be a subdirectory" +
|
18
|
+
" of the current directory, and each subdirectory of {bold locationsdir} is processed in turn to generate an ebook." +
|
19
|
+
" Each ebook is placed in a subdirectory of {bold outputdir}.",
|
21
20
|
},
|
22
21
|
];
|
23
22
|
|
24
23
|
class BookCommand extends CommandProcessor {
|
25
|
-
|
26
24
|
constructor() {
|
27
|
-
super(
|
25
|
+
super(
|
26
|
+
"ca-plant-book",
|
27
|
+
"A tool to generate an ebook with local plant data.",
|
28
|
+
OPTION_DEFS,
|
29
|
+
OPTION_HELP
|
30
|
+
);
|
28
31
|
}
|
29
|
-
|
30
32
|
}
|
31
33
|
|
32
|
-
async function commandRunner(
|
34
|
+
async function commandRunner(tp) {
|
33
35
|
const options = tp.getOptions();
|
34
|
-
const ebook = new PlantBook(
|
36
|
+
const ebook = new PlantBook(
|
37
|
+
options.outputdir,
|
38
|
+
new Config(options.datadir),
|
39
|
+
tp.getTaxa()
|
40
|
+
);
|
35
41
|
await ebook.create();
|
36
42
|
}
|
37
43
|
|
38
|
-
async function generateEBooks(
|
44
|
+
async function generateEBooks(options) {
|
39
45
|
const locationsDir = options.locationsdir;
|
40
46
|
|
41
47
|
// If there is a "locations" directory, generate a book for all subdirectories.
|
42
|
-
if (
|
48
|
+
if (locationsDir) {
|
43
49
|
// Generate ebook for each location.
|
44
50
|
const outputBase = options.outputdir;
|
45
|
-
const subdirs = Files.getDirEntries(
|
46
|
-
for (
|
47
|
-
console.log(
|
51
|
+
const subdirs = Files.getDirEntries(locationsDir);
|
52
|
+
for (const subdir of subdirs) {
|
53
|
+
console.log("Generating " + subdir);
|
48
54
|
const suffix = "/" + subdir;
|
49
55
|
const path = locationsDir + suffix;
|
50
|
-
if (
|
56
|
+
if (Files.isDir(path)) {
|
51
57
|
options.datadir = path;
|
52
58
|
options.outputdir = outputBase + suffix;
|
53
|
-
const gen = new TaxaProcessor(
|
54
|
-
await gen.process(
|
59
|
+
const gen = new TaxaProcessor(options);
|
60
|
+
await gen.process(commandRunner);
|
55
61
|
}
|
56
62
|
}
|
57
63
|
} else {
|
58
64
|
// Otherwise use the default directory.
|
59
|
-
const gen = new TaxaProcessor(
|
60
|
-
await gen.process(
|
65
|
+
const gen = new TaxaProcessor(options);
|
66
|
+
await gen.process(commandRunner);
|
61
67
|
}
|
62
|
-
|
63
68
|
}
|
64
69
|
|
65
70
|
const cmd = new BookCommand();
|
66
71
|
const options = cmd.getOptions();
|
67
|
-
if (
|
68
|
-
generateEBooks(
|
69
|
-
}
|
72
|
+
if (!options.help) {
|
73
|
+
await generateEBooks(options);
|
74
|
+
}
|