@ca-plant-list/ca-plant-list 0.1.0 → 0.1.1

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
@@ -6,18 +6,13 @@ import { ErrorLog } from "./lib/errorlog.js";
6
6
  import { PageRenderer } from "./lib/pagerenderer.js";
7
7
  import commandLineArgs from "command-line-args";
8
8
 
9
- const OPTION_DEFS = [
10
- { name: "data", type: String, defaultValue: "./data" },
11
- ];
9
+ const options = commandLineArgs( DataLoader.getOptionDefs() );
12
10
 
13
- const options = commandLineArgs( OPTION_DEFS );
14
-
15
- const TAXA_DATA_DIR = options.data;
16
11
  const CONFIG_DATA_DIR = "./data";
17
12
  const OUTPUT_DIR = "./output";
18
13
 
19
14
  Config.init( CONFIG_DATA_DIR );
20
- DataLoader.load( TAXA_DATA_DIR );
15
+ DataLoader.load( options );
21
16
  PageRenderer.render( OUTPUT_DIR );
22
17
 
23
18
  ErrorLog.write( OUTPUT_DIR + "/errors.txt" );
@@ -25,6 +25,9 @@
25
25
  <li class="nav-item">
26
26
  <a class="nav-link" href="{{site.baseurl}}/index_lists.html">Plant Lists</a>
27
27
  </li>
28
+ <li class="nav-item">
29
+ <a class="nav-link" href="{{site.baseurl}}/rare_plants.html">Rare Plants</a>
30
+ </li>
28
31
  <li class="nav-item">
29
32
  <a class="nav-link" href="{{site.baseurl}}/common_name_search.html">Common Name Search</a>
30
33
  </li>
@@ -56,6 +56,10 @@ span.rare::before {
56
56
  font-weight: bold;
57
57
  }
58
58
 
59
+ .native-status {
60
+ font-weight: 600;
61
+ }
62
+
59
63
  div.wrapper {
60
64
  display: flex;
61
65
  flex-wrap: wrap;
@@ -64,7 +68,6 @@ div.wrapper {
64
68
 
65
69
  div.section {
66
70
  margin-bottom: .5rem;
67
- ;
68
71
  }
69
72
 
70
73
  div.section h2 {
package/lib/csv.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import * as fs from "node:fs";
2
- import { parse } from "csv-parse/sync";
3
2
  import path from "node:path";
3
+ import { finished } from "stream/promises";
4
+ import { parse as parseSync } from "csv-parse/sync";
5
+ import { parse } from "csv-parse";
4
6
 
5
7
  class CSV {
6
8
 
@@ -22,9 +24,7 @@ class CSV {
22
24
  return map;
23
25
  }
24
26
 
25
- static parseFile( dir, fileName, columns = true, delimiter ) {
26
- const content = fs.readFileSync( dir + "/" + fileName );
27
-
27
+ static #getOptions( fileName, columns, delimiter ) {
28
28
  const options = { relax_column_count_less: true };
29
29
  options.columns = columns;
30
30
  if ( path.extname( fileName ) === ".tsv" ) {
@@ -33,7 +33,35 @@ class CSV {
33
33
  } else {
34
34
  options.delimiter = delimiter ? delimiter : ",";
35
35
  }
36
- return parse( content, options );
36
+ return options;
37
+ }
38
+
39
+ static parseFile( dir, fileName, columns = true, delimiter ) {
40
+ const content = fs.readFileSync( dir + "/" + fileName );
41
+
42
+ const options = this.#getOptions( fileName, columns, delimiter );
43
+ return parseSync( content, options );
44
+ }
45
+
46
+ static async parseStream( dir, fileName, columns = true, delimiter, callback ) {
47
+ const options = this.#getOptions( fileName, columns, delimiter );
48
+ const processFile = async () => {
49
+ const records = [];
50
+ const parser = fs
51
+ .createReadStream( dir + "/" + fileName )
52
+ .pipe( parse( options ) );
53
+ parser.on( "readable", function () {
54
+ let record;
55
+ while ( ( record = parser.read() ) !== null ) {
56
+ callback( record );
57
+ }
58
+ } );
59
+ await finished( parser );
60
+ return records;
61
+ };
62
+ // Parse the CSV content
63
+ await processFile();
64
+
37
65
  }
38
66
 
39
67
  }
package/lib/dataloader.js CHANGED
@@ -4,12 +4,20 @@ import { Families } from "./families.js";
4
4
  import { Config } from "./config.js";
5
5
  import { Exceptions } from "./exceptions.js";
6
6
 
7
+ const OPTION_DEFS = [
8
+ { name: "datadir", type: String, defaultValue: "./data" },
9
+ ];
7
10
 
8
11
  class DataLoader {
9
12
 
10
- static load( taxaDir ) {
13
+ static getOptionDefs() {
14
+ return OPTION_DEFS;
15
+ }
16
+
17
+ static load( options ) {
11
18
 
12
19
  const defaultDataDir = Config.getPackageDir() + "/data";
20
+ const taxaDir = options.datadir;
13
21
 
14
22
  console.log( "loading data" );
15
23
 
package/lib/exceptions.js CHANGED
@@ -15,6 +15,16 @@ class Exceptions {
15
15
  return false;
16
16
  }
17
17
 
18
+ static getValue( name, cat, subcat ) {
19
+ const taxonData = this.#exceptions[ name ];
20
+ if ( taxonData ) {
21
+ const catData = taxonData[ cat ];
22
+ if ( catData ) {
23
+ return catData[ subcat ];
24
+ }
25
+ }
26
+ }
27
+
18
28
  static init( dir ) {
19
29
  try {
20
30
  this.#exceptions = JSON.parse( fs.readFileSync( dir + "/exceptions.json" ) );
@@ -5,7 +5,7 @@ import { Taxa } from "./taxa.js";
5
5
  import { HTMLPage } from "./htmlpage.js";
6
6
  import { PageTaxon } from "./pagetaxon.js";
7
7
  import { Config } from "./config.js";
8
- import { RPI } from "./rpi.js";
8
+ import { RarePlants } from "./rareplants.js";
9
9
 
10
10
  class PageRenderer {
11
11
 
@@ -93,37 +93,64 @@ class PageRenderer {
93
93
  include: ( t ) => t.getRPIRank() !== undefined,
94
94
  listInfo: [
95
95
  {
96
- name: RPI.getRankDescription( "1A" ),
96
+ name: RarePlants.getRPIRankDescription( "1A" ),
97
97
  filename: "list_rpi_1a",
98
98
  include: ( t ) => t.getRPIRank() === "1A",
99
99
  },
100
100
  {
101
- name: RPI.getRankDescription( "1B" ),
101
+ name: RarePlants.getRPIRankDescription( "1B" ),
102
102
  filename: "list_rpi_1b",
103
103
  include: ( t ) => t.getRPIRank() === "1B",
104
104
  },
105
105
  {
106
- name: RPI.getRankDescription( "2A" ),
106
+ name: RarePlants.getRPIRankDescription( "2A" ),
107
107
  filename: "list_rpi_2a",
108
108
  include: ( t ) => t.getRPIRank() === "2A",
109
109
  },
110
110
  {
111
- name: RPI.getRankDescription( "2B" ),
111
+ name: RarePlants.getRPIRankDescription( "2B" ),
112
112
  filename: "list_rpi_2b",
113
113
  include: ( t ) => t.getRPIRank() === "2B",
114
114
  },
115
115
  {
116
- name: RPI.getRankDescription( "3" ),
116
+ name: RarePlants.getRPIRankDescription( "3" ),
117
117
  filename: "list_rpi_3",
118
118
  include: ( t ) => t.getRPIRank() === "3",
119
119
  },
120
120
  {
121
- name: RPI.getRankDescription( "4" ),
121
+ name: RarePlants.getRPIRankDescription( "4" ),
122
122
  filename: "list_rpi_4",
123
123
  include: ( t ) => t.getRPIRank() === "4",
124
124
  },
125
125
  ]
126
126
  },
127
+ {
128
+ name: "California Endangered Species Act",
129
+ filename: "list_cesa",
130
+ include: ( t ) => t.getCESA() !== undefined,
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
+ },
127
154
  ]
128
155
  },
129
156
  ];
package/lib/pagetaxon.js CHANGED
@@ -3,7 +3,7 @@ import { HTML, HTML_OPTIONS } from "./html.js";
3
3
  import { Jepson } from "./jepson.js";
4
4
  import { HTMLPage } from "./htmlpage.js";
5
5
  import { Config } from "./config.js";
6
- import { RPI } from "./rpi.js";
6
+ import { RarePlants } from "./rareplants.js";
7
7
 
8
8
  class PageTaxon extends HTMLPage {
9
9
 
@@ -20,7 +20,7 @@ class PageTaxon extends HTMLPage {
20
20
  if ( jepsonID ) {
21
21
  links.push( Jepson.getEFloraLink( jepsonID ) );
22
22
  }
23
- const calRecNum = this.#taxon.getCalRecNum();
23
+ const calRecNum = this.#taxon.getCalfloraID();
24
24
  if ( calRecNum ) {
25
25
  links.push(
26
26
  HTML.getLink(
@@ -86,13 +86,36 @@ class PageTaxon extends HTMLPage {
86
86
  return html;
87
87
  }
88
88
 
89
+ #getRarityInfo() {
90
+ const cnpsRank = this.#taxon.getRPIRankAndThreat();
91
+ if ( !cnpsRank ) {
92
+ return "";
93
+ }
94
+ const ranks = [];
95
+
96
+ ranks.push( HTML.getElement( "span", "CNPS Rare Plant Rank:", { class: "label" } )
97
+ + HTML.getElement( "span", cnpsRank, { title: RarePlants.getRPIRankAndThreatDescriptions( cnpsRank ).join( "\n" ) } ) );
98
+ if ( this.#taxon.getCESA() ) {
99
+ ranks.push( HTML.getElement( "span", "CESA:", { class: "label" } ) + RarePlants.getCESADescription( this.#taxon.getCESA() ) );
100
+ }
101
+
102
+ return HTML.getElement(
103
+ "div",
104
+ "<ul>" + HTML.arrayToLI( ranks ) + "</ul>",
105
+ { class: "section" },
106
+ HTML_OPTIONS.NO_ESCAPE
107
+ );
108
+ }
109
+
89
110
  #getRelatedTaxaLinks() {
90
111
  const links = [];
91
112
  const genus = this.#taxon.getGenus();
92
113
  if ( genus ) {
93
114
  const taxa = genus.getTaxa();
94
- for ( const taxon of taxa ) {
95
- links.push( taxon.getHTMLLink( taxon.getName() !== this.#taxon.getName() ) );
115
+ if ( taxa.length > 1 ) {
116
+ for ( const taxon of taxa ) {
117
+ links.push( taxon.getHTMLLink( taxon.getName() !== this.#taxon.getName() ) );
118
+ }
96
119
  }
97
120
  }
98
121
  return links;
@@ -114,7 +137,8 @@ class PageTaxon extends HTMLPage {
114
137
  if ( cn.length > 0 ) {
115
138
  html += HTML.getElement( "div", cn.join( ", " ), { class: "section common-names" } );
116
139
  }
117
- html += HTML.getElement( "div", this.#taxon.getStatusDescription(), { class: "section" } );
140
+
141
+ html += HTML.getElement( "div", this.#taxon.getStatusDescription(), { class: "section native-status" } );
118
142
 
119
143
  const family = this.#taxon.getFamily();
120
144
  html += HTML.getElement(
@@ -124,22 +148,17 @@ class PageTaxon extends HTMLPage {
124
148
  HTML_OPTIONS.NO_ESCAPE
125
149
  );
126
150
 
127
- const cnpsRank = this.#taxon.getRPIRankAndThreat();
128
- if ( cnpsRank ) {
129
- html += HTML.getElement(
130
- "div",
131
- HTML.getElement( "span", "CNPS Rare Plant Rank:", { class: "label" } )
132
- + HTML.getElement( "span", cnpsRank, { title: RPI.getRankAndThreatDescriptions( cnpsRank ).join( "\n" ) } ),
133
- { class: "section" },
134
- HTML_OPTIONS.NO_ESCAPE
135
- );
136
- }
151
+ html += this.#getRarityInfo();
137
152
 
138
153
  html += "</div>";
139
154
 
140
155
  const introName = "intros/" + this.#taxon.getFileName( "md" );
141
156
  if ( fs.existsSync( "./jekyll/_includes/" + introName ) ) {
142
- html += HTML.getElement( "div", "{% capture my_include %}{% include " + introName + "%}{% endcapture %}{{ my_include | markdownify }}" );
157
+ html += HTML.getElement(
158
+ "div",
159
+ "{% capture my_include %}{% include " + introName + "%}{% endcapture %}{{ my_include | markdownify }}",
160
+ { class: "section" }
161
+ );
143
162
  }
144
163
 
145
164
  html += "<div class=\"wrapper\">";
@@ -13,18 +13,29 @@ const THREAT_DESCRIPS = {
13
13
  "3": "Not very threatened in California",
14
14
  };
15
15
 
16
- class RPI {
16
+ const CESA_DESCRIPS = {
17
+ "CC": "Candidate",
18
+ "CE": "Endangered",
19
+ "CR": "Rare",
20
+ "CT": "Threatened",
21
+ };
22
+
23
+ class RarePlants {
24
+
25
+ static getCESADescription( cesa ) {
26
+ return CESA_DESCRIPS[ cesa ];
27
+ }
17
28
 
18
- static getRankDescription( rank ) {
29
+ static getRPIRankDescription( rank ) {
19
30
  const pieces = rank.split( "." );
20
31
  return RANK_DESCRIPS[ pieces[ 0 ] ];
21
32
  }
22
33
 
23
- static getRankAndThreatDescriptions( rank ) {
34
+ static getRPIRankAndThreatDescriptions( rank ) {
24
35
  const pieces = rank.split( "." );
25
36
  return [ RANK_DESCRIPS[ pieces[ 0 ] ], THREAT_DESCRIPS[ pieces[ 1 ] ] ];
26
37
  }
27
38
 
28
39
  }
29
40
 
30
- export { RPI };
41
+ export { RarePlants };
package/lib/taxa.js CHANGED
@@ -62,7 +62,8 @@ class Taxa {
62
62
  jepsonID,
63
63
  row[ "calrecnum" ],
64
64
  row[ "inat id" ],
65
- row[ "CRPR" ]
65
+ row[ "CRPR" ],
66
+ row[ "CESA" ]
66
67
  );
67
68
  if ( !jepsonID ) {
68
69
  ErrorLog.log( name, "has no Jepson ID" );
package/lib/taxon.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Config } from "./config.js";
2
2
  import { ErrorLog } from "./errorlog.js";
3
3
  import { Genera } from "./genera.js";
4
- import { RPI } from "./rpi.js";
5
4
  import { HTML, HTML_OPTIONS } from "./html.js";
5
+ import { RarePlants } from "./rareplants.js";
6
6
 
7
7
  class Taxon {
8
8
 
@@ -13,11 +13,12 @@ class Taxon {
13
13
  #jepsonID;
14
14
  #calRecNum;
15
15
  #iNatID;
16
- #iNatName;
16
+ #iNatSyn;
17
17
  #rankRPI;
18
+ #cesa;
18
19
  #synonyms = [];
19
20
 
20
- constructor( name, commonNames, status, jepsonID, calRecNum, iNatID, rankRPI ) {
21
+ constructor( name, commonNames, status, jepsonID, calRecNum, iNatID, rankRPI, cesa ) {
21
22
  this.#name = name;
22
23
  this.#genus = name.split( " " )[ 0 ];
23
24
  this.#commonNames = commonNames ? commonNames.split( "," ).map( t => t.trim() ) : [];
@@ -26,6 +27,7 @@ class Taxon {
26
27
  this.#calRecNum = calRecNum;
27
28
  this.#iNatID = iNatID;
28
29
  this.#rankRPI = rankRPI;
30
+ this.#cesa = cesa;
29
31
  Genera.addTaxon( this );
30
32
  if ( !calRecNum ) {
31
33
  ErrorLog.log( this.getName(), "has no Calflora ID" );
@@ -38,7 +40,8 @@ class Taxon {
38
40
  addSynonym( syn, type ) {
39
41
  this.#synonyms.push( syn );
40
42
  if ( type === "INAT" ) {
41
- this.#iNatName = syn;
43
+ // Synonyms should be in Jepson format, but store iNatName in iNat format (no var or subsp, space after x).
44
+ this.#iNatSyn = syn;
42
45
  }
43
46
  }
44
47
 
@@ -46,10 +49,14 @@ class Taxon {
46
49
  return this.#name.replace( " subsp.", " ssp." ).replace( "×", "X" );
47
50
  }
48
51
 
49
- getCalRecNum() {
52
+ getCalfloraID() {
50
53
  return this.#calRecNum;
51
54
  }
52
55
 
56
+ getCESA() {
57
+ return this.#cesa;
58
+ }
59
+
53
60
  getCommonNames() {
54
61
  return this.#commonNames;
55
62
  }
@@ -76,7 +83,7 @@ class Taxon {
76
83
  const className = this.isNative() ? ( this.isRare() ? "rare" : "native" ) : "non-native";
77
84
  const attributes = { class: className };
78
85
  if ( className === "rare" ) {
79
- attributes[ "title" ] = RPI.getRankAndThreatDescriptions( this.getRPIRankAndThreat() ).join( "\n" );
86
+ attributes[ "title" ] = RarePlants.getRPIRankAndThreatDescriptions( this.getRPIRankAndThreat() ).join( "\n" );
80
87
  }
81
88
  return HTML.getElement( "span", HTML.getLink( href, this.getName() ), attributes, HTML_OPTIONS.NO_ESCAPE );
82
89
  }
@@ -86,7 +93,7 @@ class Taxon {
86
93
  }
87
94
 
88
95
  getINatName() {
89
- const name = this.#iNatName ? this.#iNatName : this.#name;
96
+ const name = this.#iNatSyn ? this.#iNatSyn : this.getName();
90
97
  return name.replace( / (subsp|var)\./, "" ).replace( "×", "× " );
91
98
  }
92
99
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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": {