@ca-plant-list/ca-plant-list 0.4.3 → 0.4.6

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/index.d.ts CHANGED
@@ -13,26 +13,29 @@ export class Config {
13
13
  }
14
14
 
15
15
  export class CSV {
16
- static parseFile(dir: string, fileName: string);
16
+ static parseFile(dir: string, fileName: string): void;
17
17
  }
18
18
 
19
19
  export class ErrorLog {
20
20
  constructor(fileName: string, echo?: boolean);
21
- log(...msg: string[]);
21
+ log(...msg: string[]): void;
22
22
  write(): void;
23
23
  }
24
24
 
25
25
  export class Exceptions {
26
26
  constructor(dataDir: string);
27
- hasException(name: string, cat: string, subcat: string);
27
+ hasException(name: string, cat: string, subcat: string): boolean;
28
28
  }
29
29
 
30
30
  export class Files {
31
31
  static exists(fileName: string): boolean;
32
- static async fetch(url: string | URL, targetFileName: string | undefined);
33
- static mkdir(dir: string);
34
- static rmDir(dir: string);
35
- static write(fileName: string, data: string, overwrite: boolean);
32
+ static fetch(
33
+ url: string | URL,
34
+ targetFileName: string | undefined
35
+ ): Promise<Headers>;
36
+ static mkdir(dir: string): void;
37
+ static rmDir(dir: string): void;
38
+ static write(fileName: string, data: string, overwrite: boolean): void;
36
39
  }
37
40
 
38
41
  export class Program {
@@ -42,13 +45,13 @@ export class Program {
42
45
 
43
46
  export class Taxa {
44
47
  constructor(
45
- inclusionList: Object<string, TaxonData> | true,
48
+ inclusionList: Record<string, TaxonData> | true,
46
49
  errorLog: ErrorLog,
47
50
  showFlowerErrors: boolean,
48
51
  taxonFactory?: (td: TaxonData, g: Genera) => Taxon,
49
52
  extraTaxa?: TaxonData[],
50
53
  extraSynonyms?: SynonymData[]
51
54
  );
52
- getTaxon(string): Taxon;
55
+ getTaxon(name: string): Taxon;
53
56
  getTaxonList(): Taxon[];
54
57
  }
package/lib/markdown.js CHANGED
@@ -6,8 +6,12 @@ class Markdown {
6
6
 
7
7
  /**
8
8
  * @param {string} filePath
9
+ * @returns {string|undefined}
9
10
  */
10
11
  static fileToHTML(filePath) {
12
+ if (!Files.exists(filePath)) {
13
+ return;
14
+ }
11
15
  return this.strToHTML(Files.read(filePath));
12
16
  }
13
17
 
@@ -7,260 +7,259 @@ import { HTML } from "./html.js";
7
7
  import { HTMLTaxon, TAXA_LIST_COLS } from "./htmltaxon.js";
8
8
 
9
9
  const ENDANGERED_COLS = [
10
- TAXA_LIST_COLS.SPECIES,
11
- TAXA_LIST_COLS.COMMON_NAME,
12
- TAXA_LIST_COLS.CESA,
13
- TAXA_LIST_COLS.FESA,
10
+ TAXA_LIST_COLS.SPECIES,
11
+ TAXA_LIST_COLS.COMMON_NAME,
12
+ TAXA_LIST_COLS.CESA,
13
+ TAXA_LIST_COLS.FESA,
14
14
  ];
15
15
  const RPI_COLUMNS = [
16
- TAXA_LIST_COLS.SPECIES_BARE,
17
- TAXA_LIST_COLS.COMMON_NAME,
18
- TAXA_LIST_COLS.CNPS_RANK,
16
+ TAXA_LIST_COLS.SPECIES_BARE,
17
+ TAXA_LIST_COLS.COMMON_NAME,
18
+ TAXA_LIST_COLS.CNPS_RANK,
19
19
  ];
20
20
 
21
21
  class PageRenderer extends BasePageRenderer {
22
- /**
23
- * @param {string} outputDir
24
- * @param {Config} config
25
- * @param {Taxa} taxa
26
- */
27
- static render(outputDir, config, taxa) {
28
- super.renderBasePages(outputDir, taxa);
22
+ /**
23
+ * @param {string} outputDir
24
+ * @param {import('./config.js').Config} config
25
+ * @param {Taxa} taxa
26
+ */
27
+ static render(outputDir, config, taxa) {
28
+ super.renderBasePages(outputDir, taxa);
29
29
 
30
- this.renderLists(outputDir, config, taxa);
30
+ this.renderLists(outputDir, config, taxa);
31
31
 
32
- const taxonList = taxa.getTaxonList();
33
- for (const taxon of taxonList) {
34
- new PageTaxon(outputDir, config, taxon).render();
35
- }
32
+ const taxonList = taxa.getTaxonList();
33
+ for (const taxon of taxonList) {
34
+ new PageTaxon(outputDir, config, taxon).render();
36
35
  }
36
+ }
37
37
 
38
+ /**
39
+ * @param {string} outputDir
40
+ * @param {Config} config
41
+ * @param {Taxa} taxa
42
+ */
43
+ static renderLists(outputDir, config, taxa) {
38
44
  /**
39
- * @param {string} outputDir
40
- * @param {Config} config
41
- * @param {Taxa} taxa
45
+ * @param {ListInfo[]} listInfo
46
+ * @param {Object<string,string>} attributes
47
+ * @param {TaxaCol[]} [columns]
48
+ * @returns {string}
42
49
  */
43
- static renderLists(outputDir, config, taxa) {
44
- /**
45
- * @param {ListInfo[]} listInfo
46
- * @param {Object<string,string>} attributes
47
- * @param {TaxaCol[]} [columns]
48
- * @returns {string}
49
- */
50
- function getListArray(listInfo, attributes = {}, columns) {
51
- const listArray = [];
52
- for (const list of listInfo) {
53
- const listTaxa = [];
54
- const calfloraTaxa = [];
55
- const iNatTaxa = [];
56
- for (const taxon of taxa.getTaxonList()) {
57
- if (list.include(taxon)) {
58
- listTaxa.push(taxon);
59
- calfloraTaxa.push(taxon.getCalfloraName());
60
- iNatTaxa.push(taxon.getINatName());
61
- }
62
- }
63
-
64
- if (listTaxa.length === 0) {
65
- continue;
66
- }
67
-
68
- Files.write(
69
- outputDir + "/calflora_" + list.filename + ".txt",
70
- calfloraTaxa.join("\n")
71
- );
72
- Files.write(
73
- outputDir + "/inat_" + list.filename + ".txt",
74
- iNatTaxa.join("\n")
75
- );
76
-
77
- const cols = columns ? columns : list.columns;
78
- new PageTaxonList(outputDir, list.name, list.filename).render(
79
- listTaxa,
80
- cols
81
- );
82
-
83
- // Check for sublists.
84
- const subListHTML = list.listInfo
85
- ? getListArray(list.listInfo, { class: "indent" }, cols)
86
- : "";
87
-
88
- listArray.push(
89
- HTML.getLink("./" + list.filename + ".html", list.name) +
90
- " (" +
91
- listTaxa.length +
92
- ")" +
93
- subListHTML
94
- );
95
- }
96
-
97
- return renderList(listArray, attributes);
50
+ function getListArray(listInfo, attributes = {}, columns) {
51
+ const listArray = [];
52
+ for (const list of listInfo) {
53
+ const listTaxa = [];
54
+ const calfloraTaxa = [];
55
+ const iNatTaxa = [];
56
+ for (const taxon of taxa.getTaxonList()) {
57
+ if (list.include(taxon)) {
58
+ listTaxa.push(taxon);
59
+ calfloraTaxa.push(taxon.getCalfloraName());
60
+ iNatTaxa.push(taxon.getINatName());
61
+ }
98
62
  }
99
63
 
100
- /**
101
- * @param {string[]} listsHTML
102
- * @param {Object<string,string>} attributes
103
- */
104
- function renderList(listsHTML, attributes = {}) {
105
- return HTML.wrap("ul", HTML.arrayToLI(listsHTML), attributes);
64
+ if (listTaxa.length === 0) {
65
+ continue;
106
66
  }
107
67
 
108
- /**
109
- * @param {string} title
110
- * @param {string} listsHTML
111
- */
112
- function renderSection(title, listsHTML) {
113
- let html = '<div class="section">';
114
- html += HTML.textElement("h2", title);
115
- html += listsHTML;
116
- html += "</div>";
117
- return html;
118
- }
68
+ Files.write(
69
+ outputDir + "/calflora_" + list.filename + ".txt",
70
+ calfloraTaxa.join("\n")
71
+ );
72
+ Files.write(
73
+ outputDir + "/inat_" + list.filename + ".txt",
74
+ iNatTaxa.join("\n")
75
+ );
119
76
 
120
- /** @typedef {{name:string,filename:string,include:function(Taxon):boolean,columns?:TaxaCol[],listInfo?:ListInfo[]}} ListInfo */
121
- /** @type {{title:string,listInfo:ListInfo[]}[]} */
122
- const sections = [
123
- {
124
- title: "All Species",
125
- listInfo: [
126
- {
127
- name: config.getLabel("native", "Native"),
128
- filename: "list_native",
129
- include: (t) => t.isNative(),
130
- },
131
- {
132
- name: config.getLabel("introduced", "Introduced"),
133
- filename: "list_introduced",
134
- include: (t) => !t.isNative(),
135
- },
136
- {
137
- name: "All Plants",
138
- filename: "list_all",
139
- include: () => true,
140
- },
141
- ],
142
- },
143
- {
144
- title: "Rare Plants",
145
- listInfo: [
146
- {
147
- name: "CNPS Ranked Plants",
148
- filename: "list_rpi",
149
- include: (t) => t.getRPIRank() !== undefined,
150
- columns: RPI_COLUMNS,
151
- listInfo: [
152
- {
153
- name: RarePlants.getRPIRankDescription("1A"),
154
- filename: "list_rpi_1a",
155
- include: (t) => t.getRPIRank() === "1A",
156
- },
157
- {
158
- name: RarePlants.getRPIRankDescription("1B"),
159
- filename: "list_rpi_1b",
160
- include: (t) => t.getRPIRank() === "1B",
161
- },
162
- {
163
- name: RarePlants.getRPIRankDescription("2A"),
164
- filename: "list_rpi_2a",
165
- include: (t) => t.getRPIRank() === "2A",
166
- },
167
- {
168
- name: RarePlants.getRPIRankDescription("2B"),
169
- filename: "list_rpi_2b",
170
- include: (t) => t.getRPIRank() === "2B",
171
- },
172
- {
173
- name: RarePlants.getRPIRankDescription("3"),
174
- filename: "list_rpi_3",
175
- include: (t) => t.getRPIRank() === "3",
176
- },
177
- {
178
- name: RarePlants.getRPIRankDescription("4"),
179
- filename: "list_rpi_4",
180
- include: (t) => t.getRPIRank() === "4",
181
- },
182
- ],
183
- },
184
- {
185
- name: "Endangered Species",
186
- filename: "list_endangered",
187
- include: (t) =>
188
- t.getCESA() !== undefined ||
189
- t.getFESA() !== undefined,
190
- columns: ENDANGERED_COLS,
191
- },
192
- ],
193
- },
194
- ];
77
+ const cols = columns ? columns : list.columns;
78
+ new PageTaxonList(outputDir, list.name, list.filename).render(
79
+ listTaxa,
80
+ cols
81
+ );
195
82
 
196
- let html = '<div class="wrapper">';
197
- for (const section of sections) {
198
- const listHTML = getListArray(section.listInfo);
83
+ // Check for sublists.
84
+ const subListHTML = list.listInfo
85
+ ? getListArray(list.listInfo, { class: "indent" }, cols)
86
+ : "";
199
87
 
200
- if (listHTML.length > 0) {
201
- html += renderSection(section.title, listHTML);
202
- }
203
- }
204
- html += renderSection(
205
- "Taxonomy",
206
- renderList([HTML.getLink("./list_families.html", "Plant Families")])
88
+ listArray.push(
89
+ HTML.getLink("./" + list.filename + ".html", list.name) +
90
+ " (" +
91
+ listTaxa.length +
92
+ ")" +
93
+ subListHTML
207
94
  );
95
+ }
208
96
 
209
- html += "</div>";
210
-
211
- // Write lists to includes directory so it can be inserted into pages.
212
- Files.write(outputDir + "/_includes/plantlists.html", html);
97
+ return renderList(listArray, attributes);
213
98
  }
214
- }
215
99
 
216
- class PageTaxonList extends GenericPage {
217
100
  /**
218
- * @param {string} outputDir
219
- * @param {string} title
220
- * @param {string} baseName
101
+ * @param {string[]} listsHTML
102
+ * @param {Object<string,string>} attributes
221
103
  */
222
- constructor(outputDir, title, baseName) {
223
- super(outputDir, title, baseName);
104
+ function renderList(listsHTML, attributes = {}) {
105
+ return HTML.wrap("ul", HTML.arrayToLI(listsHTML), attributes);
224
106
  }
225
107
 
226
108
  /**
227
- *
228
- * @param {Taxon[]} taxa
229
- * @param {TaxaCol[]|undefined} columns
109
+ * @param {string} title
110
+ * @param {string} listsHTML
230
111
  */
231
- render(taxa, columns) {
232
- let html = this.getDefaultIntro();
112
+ function renderSection(title, listsHTML) {
113
+ let html = '<div class="section nobullet">';
114
+ html += HTML.textElement("h2", title);
115
+ html += listsHTML;
116
+ html += "</div>";
117
+ return html;
118
+ }
233
119
 
234
- html += '<div class="wrapper">';
120
+ /** @typedef {{name:string,filename:string,include:function(Taxon):boolean,columns?:TaxaCol[],listInfo?:ListInfo[]}} ListInfo */
121
+ /** @type {{title:string,listInfo:ListInfo[]}[]} */
122
+ const sections = [
123
+ {
124
+ title: "All Species",
125
+ listInfo: [
126
+ {
127
+ name: config.getLabel("native", "Native"),
128
+ filename: "list_native",
129
+ include: (t) => t.isNative(),
130
+ },
131
+ {
132
+ name: config.getLabel("introduced", "Introduced"),
133
+ filename: "list_introduced",
134
+ include: (t) => !t.isNative(),
135
+ },
136
+ {
137
+ name: "All Plants",
138
+ filename: "list_all",
139
+ include: () => true,
140
+ },
141
+ ],
142
+ },
143
+ {
144
+ title: "Rare Plants",
145
+ listInfo: [
146
+ {
147
+ name: "CNPS Ranked Plants",
148
+ filename: "list_rpi",
149
+ include: (t) => t.getRPIRank() !== undefined,
150
+ columns: RPI_COLUMNS,
151
+ listInfo: [
152
+ {
153
+ name: RarePlants.getRPIRankDescription("1A"),
154
+ filename: "list_rpi_1a",
155
+ include: (t) => t.getRPIRank() === "1A",
156
+ },
157
+ {
158
+ name: RarePlants.getRPIRankDescription("1B"),
159
+ filename: "list_rpi_1b",
160
+ include: (t) => t.getRPIRank() === "1B",
161
+ },
162
+ {
163
+ name: RarePlants.getRPIRankDescription("2A"),
164
+ filename: "list_rpi_2a",
165
+ include: (t) => t.getRPIRank() === "2A",
166
+ },
167
+ {
168
+ name: RarePlants.getRPIRankDescription("2B"),
169
+ filename: "list_rpi_2b",
170
+ include: (t) => t.getRPIRank() === "2B",
171
+ },
172
+ {
173
+ name: RarePlants.getRPIRankDescription("3"),
174
+ filename: "list_rpi_3",
175
+ include: (t) => t.getRPIRank() === "3",
176
+ },
177
+ {
178
+ name: RarePlants.getRPIRankDescription("4"),
179
+ filename: "list_rpi_4",
180
+ include: (t) => t.getRPIRank() === "4",
181
+ },
182
+ ],
183
+ },
184
+ {
185
+ name: "Endangered Species",
186
+ filename: "list_endangered",
187
+ include: (t) =>
188
+ t.getCESA() !== undefined || t.getFESA() !== undefined,
189
+ columns: ENDANGERED_COLS,
190
+ },
191
+ ],
192
+ },
193
+ ];
235
194
 
236
- html += '<div class="section">';
237
- html += HTMLTaxon.getTaxaTable(taxa, columns);
238
- html += "</div>";
195
+ let html = '<div class="wrapper">';
196
+ for (const section of sections) {
197
+ const listHTML = getListArray(section.listInfo);
239
198
 
240
- html += '<div class="section">';
241
- html += HTML.textElement("h2", "Download");
242
- html += "<ul>";
243
- html +=
244
- "<li>" +
245
- HTML.getLink(
246
- "./calflora_" + this.getBaseFileName() + ".txt",
247
- "Calflora List"
248
- ) +
249
- "</li>";
250
- html +=
251
- "<li>" +
252
- HTML.getLink(
253
- "./inat_" + this.getBaseFileName() + ".txt",
254
- "iNaturalist List"
255
- ) +
256
- "</li>";
257
- html += "</ul>";
258
- html += "</div>";
199
+ if (listHTML.length > 0) {
200
+ html += renderSection(section.title, listHTML);
201
+ }
202
+ }
203
+ html += renderSection(
204
+ "Taxonomy",
205
+ renderList([HTML.getLink("./list_families.html", "Plant Families")])
206
+ );
259
207
 
260
- html += "</div>";
208
+ html += "</div>";
261
209
 
262
- this.writeFile(html);
263
- }
210
+ // Write lists to includes directory so it can be inserted into pages.
211
+ Files.write(outputDir + "/_includes/plantlists.html", html);
212
+ }
213
+ }
214
+
215
+ class PageTaxonList extends GenericPage {
216
+ /**
217
+ * @param {string} outputDir
218
+ * @param {string} title
219
+ * @param {string} baseName
220
+ */
221
+ constructor(outputDir, title, baseName) {
222
+ super(outputDir, title, baseName);
223
+ }
224
+
225
+ /**
226
+ *
227
+ * @param {Taxon[]} taxa
228
+ * @param {TaxaCol[]|undefined} columns
229
+ */
230
+ render(taxa, columns) {
231
+ let html = this.getDefaultIntro();
232
+
233
+ html += '<div class="wrapper">';
234
+
235
+ html += '<div class="section">';
236
+ html += HTMLTaxon.getTaxaTable(taxa, columns);
237
+ html += "</div>";
238
+
239
+ html += '<div class="section nobullet">';
240
+ html += HTML.textElement("h2", "Download");
241
+ html += "<ul>";
242
+ html +=
243
+ "<li>" +
244
+ HTML.getLink(
245
+ "./calflora_" + this.getBaseFileName() + ".txt",
246
+ "Calflora List"
247
+ ) +
248
+ "</li>";
249
+ html +=
250
+ "<li>" +
251
+ HTML.getLink(
252
+ "./inat_" + this.getBaseFileName() + ".txt",
253
+ "iNaturalist List"
254
+ ) +
255
+ "</li>";
256
+ html += "</ul>";
257
+ html += "</div>";
258
+
259
+ html += "</div>";
260
+
261
+ this.writeFile(html);
262
+ }
264
263
  }
265
264
 
266
265
  export { PageRenderer };
package/lib/photo.js ADDED
@@ -0,0 +1,44 @@
1
+ const CC0 = "CC0";
2
+ const CC_BY = "CC BY";
3
+ const CC_BY_NC = "CC BY-NC";
4
+ const COPYRIGHT = "C";
5
+
6
+ class Photo {
7
+ /** @type {string?} */
8
+ #url;
9
+ /** @type {string?} */
10
+ rightsHolder;
11
+ /** @type {null | typeof COPYRIGHT | typeof CC_BY | typeof CC_BY_NC | typeof CC0} */
12
+ rights;
13
+
14
+ /**
15
+ * @param {string?} url
16
+ * @param {string?} rightsHolder
17
+ * @param {null | typeof COPYRIGHT | typeof CC_BY | typeof CC_BY_NC | typeof CC0} rights
18
+ */
19
+ constructor( url, rightsHolder, rights ) {
20
+ this.#url = url;
21
+ this.rightsHolder = rightsHolder;
22
+ this.rights = rights;
23
+ }
24
+
25
+ getUrl() {
26
+ return this.#url;
27
+ }
28
+
29
+ /**
30
+ * Return URL of page from whence this photo came
31
+ * @return {string?}
32
+ */
33
+ getSourceUrl() {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ export {
39
+ CC0,
40
+ CC_BY,
41
+ CC_BY_NC,
42
+ COPYRIGHT,
43
+ Photo
44
+ };
package/lib/taxa.js CHANGED
@@ -1,9 +1,13 @@
1
+ import * as fs from "node:fs";
2
+ import path from "node:path";
3
+
1
4
  import { Config } from "./config.js";
2
5
  import { CSV } from "./csv.js";
3
6
  import { Genera } from "./genera.js";
4
7
  import { Taxon } from "./taxon.js";
5
8
  import { Families } from "./families.js";
6
9
  import { FlowerColor } from "./flowercolor.js";
10
+ import { InatPhoto } from "./inat_photo.js";
7
11
 
8
12
  const FLOWER_COLORS = [
9
13
  { name: "white", color: "white" },
@@ -35,6 +39,7 @@ class Taxa {
35
39
  * @param {function(TaxonData,Genera):Taxon} taxonFactory
36
40
  * @param {TaxonData[]} [extraTaxa=[]]
37
41
  * @param {SynonymData[]} [extraSynonyms=[]]
42
+ * @param {boolean} includePhotos
38
43
  */
39
44
  constructor(
40
45
  inclusionList,
@@ -42,7 +47,8 @@ class Taxa {
42
47
  showFlowerErrors,
43
48
  taxonFactory = (td, g) => new Taxon(td, g),
44
49
  extraTaxa = [],
45
- extraSynonyms = []
50
+ extraSynonyms = [],
51
+ includePhotos = true
46
52
  ) {
47
53
  this.#isSubset = inclusionList !== true;
48
54
 
@@ -76,11 +82,39 @@ class Taxa {
76
82
  a.getName().localeCompare(b.getName())
77
83
  );
78
84
 
85
+
86
+ if ( includePhotos ) {
87
+ this.#loadInatPhotos( dataDir );
88
+ }
89
+
79
90
  const synCSV = CSV.parseFile(dataDir, "synonyms.csv");
80
91
  this.#loadSyns(synCSV, inclusionList);
81
92
  this.#loadSyns(extraSynonyms, inclusionList);
82
93
  }
83
94
 
95
+ /**
96
+ * @param {string} dataDir
97
+ */
98
+ #loadInatPhotos( dataDir ) {
99
+ const photosFileName = "inattaxonphotos.csv";
100
+ if ( fs.existsSync( path.join( dataDir, photosFileName ) ) ) {
101
+ /** @type {InatCsvPhoto[]} */
102
+ const csvPhotos = CSV.parseFile( dataDir, photosFileName );
103
+ for ( const csvPhoto of csvPhotos ) {
104
+ const taxon = this.getTaxon(csvPhoto.name);
105
+ if(!taxon) {
106
+ continue;
107
+ }
108
+ taxon.addPhoto(new InatPhoto(
109
+ csvPhoto.id,
110
+ csvPhoto.ext,
111
+ csvPhoto.licenseCode,
112
+ csvPhoto.attrName
113
+ ) );
114
+ }
115
+ }
116
+ }
117
+
84
118
  getFamilies() {
85
119
  return this.#families;
86
120
  }
@@ -175,9 +209,9 @@ class Taxa {
175
209
  const color = this.#flower_colors[colorName];
176
210
  if (!color) {
177
211
  throw new Error(
178
- 'flower color "' +
212
+ "flower color \"" +
179
213
  colorName +
180
- '" not found for ' +
214
+ "\" not found for " +
181
215
  name
182
216
  );
183
217
  }