@ca-plant-list/ca-plant-list 0.1.13 → 0.1.15

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/build-site.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { Config } from "./lib/config.js";
4
3
  import { DataLoader } from "./lib/dataloader.js";
5
4
  import { ErrorLog } from "./lib/errorlog.js";
6
5
  import { PageRenderer } from "./lib/pagerenderer.js";
@@ -8,11 +7,9 @@ import commandLineArgs from "command-line-args";
8
7
 
9
8
  const options = commandLineArgs( DataLoader.getOptionDefs() );
10
9
 
11
- const CONFIG_DATA_DIR = "./data";
12
10
  const OUTPUT_DIR = "./output";
13
11
 
14
- Config.init( CONFIG_DATA_DIR );
15
12
  DataLoader.load( options );
16
13
  PageRenderer.render( OUTPUT_DIR );
17
14
 
18
- ErrorLog.write( OUTPUT_DIR + "/errors.txt" );
15
+ ErrorLog.write( OUTPUT_DIR + "/errors.tsv" );
package/data/genera.json CHANGED
@@ -11,6 +11,10 @@
11
11
  "family": "Lycopodiaceae",
12
12
  "id": "9179"
13
13
  },
14
+ "Rhinotropis": {
15
+ "family": "Polygalaceae",
16
+ "id": 99965
17
+ },
14
18
  "Selaginella": {
15
19
  "family": "Selaginellaceae",
16
20
  "id": "8877"
@@ -1,7 +1,10 @@
1
1
  /* Defaults */
2
2
 
3
3
  :root {
4
- --bs-body-bg: #ECF87F;
4
+ /* See https://visme.co/blog/website-color-schemes/ theme 4 */
5
+ --bs-body-bg: #5cdb95;
6
+ --bs-link-color: #05386b;
7
+ --pl-navbar-bg: #379683;
5
8
  }
6
9
 
7
10
  .navbar-nav {
@@ -14,7 +17,7 @@
14
17
  --bs-navbar-brand-hover-color: rgba(255, 255, 255, .5);
15
18
  --bs-navbar-brand-color: white;
16
19
  --bs-navbar-nav-link-padding-x: 1rem;
17
- background-color: #3D550C;
20
+ background-color: var(--pl-navbar-bg);
18
21
  }
19
22
 
20
23
  span.label {
package/lib/config.js CHANGED
@@ -1,12 +1,20 @@
1
1
  import * as path from "node:path";
2
2
  import * as url from "node:url";
3
- import * as fs from "node:fs";
3
+ import { Files } from "./files.js";
4
4
 
5
5
  class Config {
6
6
 
7
7
  static #config = {};
8
8
  static #packageDir = path.dirname( path.dirname( url.fileURLToPath( import.meta.url ) ) );
9
9
 
10
+ static {
11
+ try {
12
+ this.#config = JSON.parse( Files.read( "./data/config.json" ) );
13
+ } catch ( e ) {
14
+ console.log( e );
15
+ }
16
+ }
17
+
10
18
  static getConfigValue( prefix, name, subcat, dflt ) {
11
19
  const obj = this.#config[ prefix ];
12
20
  if ( obj ) {
@@ -26,6 +34,10 @@ class Config {
26
34
  return dflt;
27
35
  }
28
36
 
37
+ static getCountyCodes() {
38
+ return this.#config[ "counties" ];
39
+ }
40
+
29
41
  static getLabel( name, dflt ) {
30
42
  return this.getConfigValue( "labels", name, undefined, dflt );
31
43
  }
@@ -34,14 +46,6 @@ class Config {
34
46
  return this.#packageDir;
35
47
  }
36
48
 
37
- static init( dir ) {
38
- try {
39
- this.#config = JSON.parse( fs.readFileSync( dir + "/config.json" ) );
40
- } catch ( e ) {
41
- console.log( e );
42
- }
43
- }
44
-
45
49
  }
46
50
 
47
51
  export { Config };
package/lib/dataloader.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { Taxa } from "./taxa.js";
2
- import { Genera } from "./genera.js";
3
2
  import { Families } from "./families.js";
4
- import { Config } from "./config.js";
5
3
  import { Exceptions } from "./exceptions.js";
6
4
 
7
5
  const OPTION_DEFS = [
@@ -15,12 +13,8 @@ class DataLoader {
15
13
  }
16
14
 
17
15
  static init( taxaDir ) {
18
-
19
- const defaultDataDir = Config.getPackageDir() + "/data";
20
-
21
16
  Exceptions.init( taxaDir );
22
- Families.init( defaultDataDir );
23
- Genera.init( defaultDataDir );
17
+ Families.init();
24
18
  }
25
19
 
26
20
  static load( options ) {
package/lib/exceptions.js CHANGED
@@ -1,35 +1,39 @@
1
- import * as fs from "node:fs";
1
+ import { Files } from "./files.js";
2
2
 
3
3
  class Exceptions {
4
4
 
5
5
  static #exceptions = {};
6
6
 
7
- static hasException( name, cat, subcat ) {
7
+ static getExceptions() {
8
+ return Object.entries( this.#exceptions );
9
+ }
10
+
11
+ static getValue( name, cat, subcat, defaultValue ) {
8
12
  const taxonData = this.#exceptions[ name ];
9
13
  if ( taxonData ) {
10
14
  const catData = taxonData[ cat ];
11
15
  if ( catData ) {
12
- return catData[ subcat ] !== undefined;
16
+ const val = catData[ subcat ];
17
+ return ( val === undefined ) ? defaultValue : val;
13
18
  }
14
19
  }
15
- return false;
20
+ return defaultValue;
16
21
  }
17
22
 
18
- static getValue( name, cat, subcat, defaultValue ) {
23
+ static hasException( name, cat, subcat ) {
19
24
  const taxonData = this.#exceptions[ name ];
20
25
  if ( taxonData ) {
21
26
  const catData = taxonData[ cat ];
22
27
  if ( catData ) {
23
- const val = catData[ subcat ];
24
- return ( val === undefined ) ? defaultValue : val;
28
+ return catData[ subcat ] !== undefined;
25
29
  }
26
30
  }
27
- return defaultValue;
31
+ return false;
28
32
  }
29
33
 
30
34
  static init( dir ) {
31
35
  try {
32
- this.#exceptions = JSON.parse( fs.readFileSync( dir + "/exceptions.json" ) );
36
+ this.#exceptions = JSON.parse( Files.read( dir + "/exceptions.json" ) );
33
37
  } catch ( e ) {
34
38
  console.log( e );
35
39
  }
package/lib/families.js CHANGED
@@ -3,6 +3,8 @@ import { HTML } from "./html.js";
3
3
  import { Jepson } from "./jepson.js";
4
4
  import { Taxa } from "./taxa.js";
5
5
  import { Files } from "./files.js";
6
+ import { Config } from "./config.js";
7
+ import { Genera } from "./genera.js";
6
8
 
7
9
  class Families {
8
10
 
@@ -12,11 +14,17 @@ class Families {
12
14
  return this.#families[ familyName ];
13
15
  }
14
16
 
15
- static init( dataDir ) {
17
+ static init() {
18
+
19
+ const dataDir = Config.getPackageDir() + "/data";
20
+
16
21
  this.#families = JSON.parse( Files.read( dataDir + "/families.json" ) );
17
22
  for ( const [ k, v ] of Object.entries( this.#families ) ) {
18
23
  this.#families[ k ] = new Family( k, { id: v } );
19
24
  }
25
+
26
+ Genera.init( dataDir );
27
+
20
28
  }
21
29
 
22
30
  static renderPages( outputDir, columns ) {
package/lib/files.js CHANGED
@@ -22,11 +22,25 @@ class Files {
22
22
  return fs.existsSync( path );
23
23
  }
24
24
 
25
- static async fetch( url, targetFileName ) {
26
- const response = await fetch( url );
25
+ /**
26
+ * Retrieve data from a URL and write it to a file. If the response status is anything other than 200, an Error is thrown.
27
+ * @param {string|URL} url
28
+ * @param {string|undefined} targetFileName If targetFileName is undefined, the data will be retrieved but not written to a file.
29
+ * @param {Object} [headers={}] Request Headers.
30
+ * @returns {Headers} The Response headers.
31
+ */
32
+ static async fetch( url, targetFileName, headers = {} ) {
33
+ const response = await fetch( url, headers );
34
+ if ( response.status !== 200 ) {
35
+ throw new Error( response.status + " retrieving " + url );
36
+ }
27
37
  const data = await response.blob();
28
- const buffer = await data.arrayBuffer();
29
- fs.writeFileSync( targetFileName, Buffer.from( buffer ) );
38
+ const arrayBuffer = await data.arrayBuffer();
39
+ const buffer = Buffer.from( arrayBuffer );
40
+ if ( targetFileName ) {
41
+ fs.writeFileSync( targetFileName, buffer );
42
+ }
43
+ return response.headers;
30
44
  }
31
45
 
32
46
  static mkdir( path ) {
@@ -41,8 +55,8 @@ class Files {
41
55
  fs.rmSync( dir, { force: true, recursive: true, maxRetries: 2, retryDelay: 1000 } );
42
56
  }
43
57
 
44
- static write( path, data ) {
45
- if ( this.exists( path ) ) {
58
+ static write( path, data, overwrite = false ) {
59
+ if ( !overwrite && this.exists( path ) ) {
46
60
  throw new Error( path + " already exists" );
47
61
  }
48
62
  fs.writeFileSync( path, data );
package/lib/genera.js CHANGED
@@ -1,5 +1,5 @@
1
- import * as fs from "node:fs";
2
1
  import { Families } from "./families.js";
2
+ import { Files } from "./files.js";
3
3
 
4
4
  class Genera {
5
5
 
@@ -39,7 +39,7 @@ class Genera {
39
39
  }
40
40
 
41
41
  static init( dataDir ) {
42
- this.#genera = JSON.parse( fs.readFileSync( dataDir + "/genera.json" ) );
42
+ this.#genera = JSON.parse( Files.read( dataDir + "/genera.json" ) );
43
43
  }
44
44
 
45
45
  }
@@ -14,6 +14,10 @@ class GenericPage {
14
14
  this.#js = js;
15
15
  }
16
16
 
17
+ getBaseFileName() {
18
+ return this.#baseFileName;
19
+ }
20
+
17
21
  getDefaultIntro() {
18
22
  let html = this.#getFrontMatter();
19
23
  html += HTML.textElement( "h1", this.#title );
package/lib/index.d.ts CHANGED
@@ -2,11 +2,18 @@ export class Files {
2
2
  static copyDir(srcDir: any, targetDir: any): void;
3
3
  static createFileFromStream(fileName: any, inStream: any): Promise<any>;
4
4
  static exists(path: any): boolean;
5
- static fetch(url: any, targetFileName: any): Promise<void>;
5
+ /**
6
+ * Retrieve data from a URL and write it to a file. If the response status is anything other than 200, an Error is thrown.
7
+ * @param {string|URL} url
8
+ * @param {string|undefined} targetFileName If targetFileName is undefined, the data will be retrieved but not written to a file.
9
+ * @param {Object} [headers={}] Request Headers.
10
+ * @returns {Headers} The Response headers.
11
+ */
12
+ static fetch(url: string | URL, targetFileName: string | undefined, headers?: any): Headers;
6
13
  static mkdir(path: any): void;
7
14
  static read(path: any): string;
8
15
  static rmDir(dir: any): void;
9
- static write(path: any, data: any): void;
16
+ static write(path: any, data: any, overwrite?: boolean): void;
10
17
  static zipFileExtract(zipFilePath: any, fileNameToUnzip: any, targetFilePath: any): Promise<void>;
11
18
  }
12
19
  export namespace HTML_OPTIONS {
@@ -1,11 +1,11 @@
1
1
  import { HTML } from "./html.js";
2
2
  import { Taxa, TAXA_LIST_COLS } from "./taxa.js";
3
- import { HTMLPage } from "./htmlpage.js";
4
3
  import { PageTaxon } from "./pagetaxon.js";
5
4
  import { Config } from "./config.js";
6
5
  import { RarePlants } from "./rareplants.js";
7
6
  import { BasePageRenderer } from "./basepagerenderer.js";
8
7
  import { Files } from "./files.js";
8
+ import { GenericPage } from "./genericpage.js";
9
9
 
10
10
  const RPI_CESA = [ TAXA_LIST_COLS.SPECIES, TAXA_LIST_COLS.COMMON_NAME, TAXA_LIST_COLS.CESA ];
11
11
  const RPI_COLUMNS = [ TAXA_LIST_COLS.SPECIES_BARE, TAXA_LIST_COLS.COMMON_NAME, TAXA_LIST_COLS.CNPS_RANK ];
@@ -50,7 +50,7 @@ class PageRenderer extends BasePageRenderer {
50
50
  Files.write( outputDir + "/inat_" + list.filename + ".txt", iNatTaxa.join( "\n" ) );
51
51
 
52
52
  const cols = columns ? columns : list.columns;
53
- new PageTaxonList().render( outputDir, taxa, list.filename, list.name, cols );
53
+ new PageTaxonList( outputDir, list.name, list.filename ).render( taxa, cols );
54
54
 
55
55
  // Check for sublists.
56
56
  const subListHTML = list.listInfo ? getListArray( list.listInfo, { class: "indent" }, cols ) : "";
@@ -128,28 +128,6 @@ class PageRenderer extends BasePageRenderer {
128
128
  filename: "list_cesa",
129
129
  include: ( t ) => t.getCESA() !== undefined,
130
130
  columns: RPI_CESA,
131
- listInfo: [
132
- {
133
- name: RarePlants.getCESADescription( "CE" ),
134
- filename: "list_rpi_ce",
135
- include: ( t ) => t.getCESA() === "CE",
136
- },
137
- {
138
- name: RarePlants.getCESADescription( "CT" ),
139
- filename: "list_rpi_ct",
140
- include: ( t ) => t.getCESA() === "CT",
141
- },
142
- {
143
- name: RarePlants.getCESADescription( "CR" ),
144
- filename: "list_rpi_cr",
145
- include: ( t ) => t.getCESA() === "CR",
146
- },
147
- {
148
- name: RarePlants.getCESADescription( "CC" ),
149
- filename: "list_rpi_cc",
150
- include: ( t ) => t.getCESA() === "CC",
151
- },
152
- ]
153
131
  },
154
132
  ]
155
133
  },
@@ -176,13 +154,15 @@ class PageRenderer extends BasePageRenderer {
176
154
 
177
155
  }
178
156
 
179
- class PageTaxonList extends HTMLPage {
157
+ class PageTaxonList extends GenericPage {
180
158
 
181
- render( outputDir, taxa, baseName, title, columns ) {
159
+ constructor( outputDir, title, baseName ) {
160
+ super( outputDir, title, baseName );
161
+ }
182
162
 
183
- let html = this.getFrontMatter( title );
163
+ render( taxa, columns ) {
184
164
 
185
- html += HTML.textElement( "h1", title );
165
+ let html = this.getDefaultIntro();
186
166
 
187
167
  html += "<div class=\"wrapper\">";
188
168
 
@@ -193,14 +173,14 @@ class PageTaxonList extends HTMLPage {
193
173
  html += "<div class=\"section\">";
194
174
  html += HTML.textElement( "h2", "Download" );
195
175
  html += "<ul>";
196
- html += "<li>" + HTML.getLink( "./calflora_" + baseName + ".txt", "Calflora List" ) + "</li>";
197
- html += "<li>" + HTML.getLink( "./inat_" + baseName + ".txt", "iNaturalist List" ) + "</li>";
176
+ html += "<li>" + HTML.getLink( "./calflora_" + this.getBaseFileName() + ".txt", "Calflora List" ) + "</li>";
177
+ html += "<li>" + HTML.getLink( "./inat_" + this.getBaseFileName() + ".txt", "iNaturalist List" ) + "</li>";
198
178
  html += "</ul>";
199
179
  html += "</div>";
200
180
 
201
181
  html += "</div>";
202
182
 
203
- this.writeFile( outputDir, baseName + ".html", html );
183
+ this.writeFile( html );
204
184
 
205
185
  }
206
186
  }
package/lib/pagetaxon.js CHANGED
@@ -40,7 +40,7 @@ class PageTaxon extends GenericPage {
40
40
  links.push(
41
41
  HTML.getLink(
42
42
  "https://www.calflora.org/entry/observ.html?track=m#srch=t&grezc=5&cols=b&lpcli=t&cc="
43
- + Config.getConfigValue( "calflora", "counties" ).join( "!" ) + "&incobs=f&taxon="
43
+ + Config.getCountyCodes().join( "!" ) + "&incobs=f&taxon="
44
44
  + this.#taxon.getCalfloraName().replaceAll( " ", "+" ),
45
45
  "Calflora",
46
46
  {},
package/lib/taxa.js CHANGED
@@ -3,6 +3,7 @@ import { ErrorLog } from "./errorlog.js";
3
3
  import { HTML } from "./html.js";
4
4
  import { CSV } from "./csv.js";
5
5
  import { RarePlants } from "./rareplants.js";
6
+ import { Exceptions } from "./exceptions.js";
6
7
 
7
8
  const TAXA_LIST_COLS = {
8
9
  CESA: {
@@ -95,7 +96,7 @@ class Taxa {
95
96
  row[ "CRPR" ],
96
97
  row[ "CESA" ]
97
98
  );
98
- if ( !jepsonID ) {
99
+ if ( !jepsonID && !Exceptions.hasException( name, "jepson", "badjepsonid" ) ) {
99
100
  ErrorLog.log( name, "has no Jepson ID" );
100
101
  }
101
102
  break;
package/lib/taxon.js CHANGED
@@ -192,6 +192,10 @@ class Taxon {
192
192
  return this.#status === "N" || this.#status === "NC";
193
193
  }
194
194
 
195
+ /**
196
+ * Determine whether a species is a local native.
197
+ * @returns {boolean} true if taxon is a local native; false if not a CA native, or native elsewhere in CA.
198
+ */
195
199
  isNative() {
196
200
  return this.#status === "N";
197
201
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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": {
@@ -11,6 +11,7 @@
11
11
  "type": "module",
12
12
  "exports": {
13
13
  ".": "./lib/index.js",
14
+ "./Families": "./lib/families.js",
14
15
  "./Files": "./lib/files.js",
15
16
  "./HTML": "./lib/html.js",
16
17
  "./Jekyll": "./lib/jekyll.js"
@@ -21,6 +22,7 @@
21
22
  },
22
23
  "dependencies": {
23
24
  "command-line-args": "^5.2.1",
25
+ "command-line-usage": "^6.1.3",
24
26
  "csv-parse": "^5.3.1",
25
27
  "unzipper": "^0.10.11"
26
28
  },