@ca-plant-list/ca-plant-list 0.0.2 → 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
@@ -1,27 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import * as path from "node:path";
4
- import * as url from "node:url";
5
3
  import { Config } from "./lib/config.js";
6
4
  import { DataLoader } from "./lib/dataloader.js";
7
5
  import { ErrorLog } from "./lib/errorlog.js";
8
6
  import { PageRenderer } from "./lib/pagerenderer.js";
9
7
  import commandLineArgs from "command-line-args";
10
8
 
11
- const OPTION_DEFS = [
12
- { name: "data", type: String, defaultValue: "./data" },
13
- ];
9
+ const options = commandLineArgs( DataLoader.getOptionDefs() );
14
10
 
15
- const options = commandLineArgs( OPTION_DEFS );
16
-
17
- const TAXA_DATA_DIR = options.data;
18
11
  const CONFIG_DATA_DIR = "./data";
19
- const PACKAGE_DIR = path.dirname( url.fileURLToPath( import.meta.url ) );
20
- const DEFAULT_DATA_DIR = PACKAGE_DIR + "/data";
21
12
  const OUTPUT_DIR = "./output";
22
13
 
23
14
  Config.init( CONFIG_DATA_DIR );
24
- DataLoader.load( TAXA_DATA_DIR, DEFAULT_DATA_DIR );
25
- PageRenderer.render( PACKAGE_DIR, OUTPUT_DIR );
15
+ DataLoader.load( options );
16
+ PageRenderer.render( OUTPUT_DIR );
26
17
 
27
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>
@@ -17,6 +17,15 @@
17
17
  background-color: #3D550C;
18
18
  }
19
19
 
20
+ span[title] {
21
+ text-decoration: underline dotted;
22
+ }
23
+
24
+ span.label {
25
+ font-weight: 600;
26
+ padding-right: .5rem;
27
+ }
28
+
20
29
  td {
21
30
  border: solid 1px;
22
31
  vertical-align: top;
@@ -28,9 +37,16 @@ th {
28
37
 
29
38
  /* Lists */
30
39
 
31
- a.native {
40
+ span.native {
41
+ font-weight: bold;
42
+ }
43
+
44
+ span.rare {
32
45
  font-weight: bold;
33
- font-style: italic;
46
+ }
47
+
48
+ span.rare::before {
49
+ content: "\2606";
34
50
  }
35
51
 
36
52
  /* Taxon Page */
@@ -40,6 +56,10 @@ a.native {
40
56
  font-weight: bold;
41
57
  }
42
58
 
59
+ .native-status {
60
+ font-weight: 600;
61
+ }
62
+
43
63
  div.wrapper {
44
64
  display: flex;
45
65
  flex-wrap: wrap;
@@ -48,7 +68,6 @@ div.wrapper {
48
68
 
49
69
  div.section {
50
70
  margin-bottom: .5rem;
51
- ;
52
71
  }
53
72
 
54
73
  div.section h2 {
@@ -62,4 +81,8 @@ div.section ul {
62
81
 
63
82
  div.section li {
64
83
  display: block;
84
+ }
85
+
86
+ div.section ul.indent {
87
+ padding-left: 1rem;
65
88
  }
package/lib/config.js CHANGED
@@ -1,8 +1,11 @@
1
+ import * as path from "node:path";
2
+ import * as url from "node:url";
1
3
  import * as fs from "node:fs";
2
4
 
3
5
  class Config {
4
6
 
5
7
  static #config = {};
8
+ static #packageDir = path.dirname( path.dirname( url.fileURLToPath( import.meta.url ) ) );
6
9
 
7
10
  static getConfigValue( prefix, name, dflt ) {
8
11
  const obj = this.#config[ prefix ];
@@ -18,6 +21,10 @@ class Config {
18
21
  return this.getConfigValue( "labels", name, dflt );
19
22
  }
20
23
 
24
+ static getPackageDir() {
25
+ return this.#packageDir;
26
+ }
27
+
21
28
  static init( dir ) {
22
29
  try {
23
30
  this.#config = JSON.parse( fs.readFileSync( dir + "/config.json" ) );
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
@@ -1,15 +1,29 @@
1
1
  import { Taxa } from "./taxa.js";
2
2
  import { Genera } from "./genera.js";
3
3
  import { Families } from "./families.js";
4
+ import { Config } from "./config.js";
5
+ import { Exceptions } from "./exceptions.js";
6
+
7
+ const OPTION_DEFS = [
8
+ { name: "datadir", type: String, defaultValue: "./data" },
9
+ ];
4
10
 
5
11
  class DataLoader {
6
12
 
7
- static load( taxaDir, dataDir ) {
13
+ static getOptionDefs() {
14
+ return OPTION_DEFS;
15
+ }
16
+
17
+ static load( options ) {
18
+
19
+ const defaultDataDir = Config.getPackageDir() + "/data";
20
+ const taxaDir = options.datadir;
8
21
 
9
22
  console.log( "loading data" );
10
23
 
11
- Families.init( dataDir );
12
- Genera.init( dataDir );
24
+ Exceptions.init( taxaDir );
25
+ Families.init( defaultDataDir );
26
+ Genera.init( defaultDataDir );
13
27
  Taxa.init( taxaDir );
14
28
 
15
29
  }
@@ -0,0 +1,38 @@
1
+ import * as fs from "node:fs";
2
+
3
+ class Exceptions {
4
+
5
+ static #exceptions = {};
6
+
7
+ static hasException( name, cat, subcat ) {
8
+ const taxonData = this.#exceptions[ name ];
9
+ if ( taxonData ) {
10
+ const catData = taxonData[ cat ];
11
+ if ( catData ) {
12
+ return catData[ subcat ] !== undefined;
13
+ }
14
+ }
15
+ return false;
16
+ }
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
+
28
+ static init( dir ) {
29
+ try {
30
+ this.#exceptions = JSON.parse( fs.readFileSync( dir + "/exceptions.json" ) );
31
+ } catch ( e ) {
32
+ console.log( e );
33
+ }
34
+ }
35
+
36
+ }
37
+
38
+ export { Exceptions };
package/lib/html.js CHANGED
@@ -29,7 +29,10 @@ class HTML {
29
29
  }
30
30
 
31
31
  static getLink( href, linkText, attributes = {}, options = 0 ) {
32
- let html = "<a href=\"" + this.escapeAttribute( href ) + "\"";
32
+ let html = "<a";
33
+ if ( href !== undefined ) {
34
+ html += this.renderAttribute( "href", href );
35
+ }
33
36
  html += this.renderAttributes( attributes );
34
37
  if ( options & HTML_OPTIONS.OPEN_NEW ) {
35
38
  html += this.renderAttribute( "target", "_blank" );
@@ -1,19 +1,20 @@
1
1
  import * as fs from "node:fs";
2
2
  import { Families } from "./families.js";
3
- import { HTML } from "./html.js";
3
+ import { HTML, HTML_OPTIONS } from "./html.js";
4
4
  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 { RarePlants } from "./rareplants.js";
8
9
 
9
10
  class PageRenderer {
10
11
 
11
- static render( packageDir, outputDir ) {
12
+ static render( outputDir ) {
12
13
 
13
14
  // Copy static files
14
15
  fs.rmSync( outputDir, { force: true, recursive: true } );
15
16
  // First copy default Jekyll files from package.
16
- fs.cpSync( packageDir + "/jekyll", outputDir, { recursive: true } );
17
+ fs.cpSync( Config.getPackageDir() + "/jekyll", outputDir, { recursive: true } );
17
18
  // Then copy Jekyll files from current dir (which may override default files).
18
19
  fs.cpSync( "jekyll", outputDir, { recursive: true } );
19
20
 
@@ -31,34 +32,145 @@ class PageRenderer {
31
32
 
32
33
  static renderLists( outputDir ) {
33
34
 
34
- const listInfo = [
35
- { name: Config.getLabel( "native", "Native" ), filename: "list_native", include: ( t ) => t.isNative() },
36
- { name: Config.getLabel( "introduced", "Introduced" ), filename: "list_introduced", include: ( t ) => !t.isNative() },
37
- { name: "All Plants", filename: "list_all", include: () => true },
38
- ];
35
+ function getListArray( listInfo, attributes = {} ) {
36
+ const listArray = [];
37
+ for ( const list of listInfo ) {
38
+ const taxa = [];
39
+ const calfloraTaxa = [];
40
+ const iNatTaxa = [];
41
+ for ( const taxon of Taxa.getTaxa() ) {
42
+ if ( list.include( taxon ) ) {
43
+ taxa.push( taxon );
44
+ calfloraTaxa.push( taxon.getCalfloraName() );
45
+ iNatTaxa.push( taxon.getINatName() );
46
+ }
47
+ }
39
48
 
40
- const listsHTML = [];
41
- for ( const list of listInfo ) {
42
- const taxa = [];
43
- const calfloraTaxa = [];
44
- const iNatTaxa = [];
45
- for ( const taxon of Taxa.getTaxa() ) {
46
- if ( list.include( taxon ) ) {
47
- taxa.push( taxon );
48
- calfloraTaxa.push( taxon.getCalfloraName() );
49
- iNatTaxa.push( taxon.getINatName() );
49
+ if ( taxa.length === 0 ) {
50
+ continue;
50
51
  }
52
+
53
+ fs.writeFileSync( outputDir + "/calflora_" + list.filename + ".txt", calfloraTaxa.join( "\n" ) );
54
+ fs.writeFileSync( outputDir + "/inat_" + list.filename + ".txt", iNatTaxa.join( "\n" ) );
55
+
56
+ new PageTaxonList().render( outputDir, taxa, list.filename, list.name );
57
+
58
+ // Check for sublists.
59
+ const subListHTML = list.listInfo ? getListArray( list.listInfo, { class: "indent" } ) : "";
60
+
61
+ listArray.push( HTML.getLink( "./" + list.filename + ".html", list.name ) + " (" + taxa.length + ")" + subListHTML );
51
62
  }
63
+ return renderList( listArray, attributes );
64
+ }
65
+
66
+ function renderList( listsHTML, attributes = {} ) {
67
+ return HTML.getElement( "ul", HTML.arrayToLI( listsHTML ), attributes, HTML_OPTIONS.NO_ESCAPE );
68
+ }
52
69
 
53
- fs.writeFileSync( outputDir + "/calflora_" + list.filename + ".txt", calfloraTaxa.join( "\n" ) );
54
- fs.writeFileSync( outputDir + "/inat_" + list.filename + ".txt", iNatTaxa.join( "\n" ) );
70
+ function renderSection( title, listsHTML ) {
71
+ let html = "<div class=\"section\">";
72
+ html += HTML.getElement( "h2", title );
73
+ html += listsHTML;
74
+ html += "</div>";
75
+ return html;
76
+ }
77
+
78
+ const sections = [
79
+ {
80
+ title: "All Species",
81
+ listInfo: [
82
+ { name: Config.getLabel( "native", "Native" ), filename: "list_native", include: ( t ) => t.isNative() },
83
+ { name: Config.getLabel( "introduced", "Introduced" ), filename: "list_introduced", include: ( t ) => !t.isNative() },
84
+ { name: "All Plants", filename: "list_all", include: () => true },
85
+ ]
86
+ },
87
+ {
88
+ title: "Rare Plants",
89
+ listInfo: [
90
+ {
91
+ name: "CNPS Ranked Plants",
92
+ filename: "list_rpi",
93
+ include: ( t ) => t.getRPIRank() !== undefined,
94
+ listInfo: [
95
+ {
96
+ name: RarePlants.getRPIRankDescription( "1A" ),
97
+ filename: "list_rpi_1a",
98
+ include: ( t ) => t.getRPIRank() === "1A",
99
+ },
100
+ {
101
+ name: RarePlants.getRPIRankDescription( "1B" ),
102
+ filename: "list_rpi_1b",
103
+ include: ( t ) => t.getRPIRank() === "1B",
104
+ },
105
+ {
106
+ name: RarePlants.getRPIRankDescription( "2A" ),
107
+ filename: "list_rpi_2a",
108
+ include: ( t ) => t.getRPIRank() === "2A",
109
+ },
110
+ {
111
+ name: RarePlants.getRPIRankDescription( "2B" ),
112
+ filename: "list_rpi_2b",
113
+ include: ( t ) => t.getRPIRank() === "2B",
114
+ },
115
+ {
116
+ name: RarePlants.getRPIRankDescription( "3" ),
117
+ filename: "list_rpi_3",
118
+ include: ( t ) => t.getRPIRank() === "3",
119
+ },
120
+ {
121
+ name: RarePlants.getRPIRankDescription( "4" ),
122
+ filename: "list_rpi_4",
123
+ include: ( t ) => t.getRPIRank() === "4",
124
+ },
125
+ ]
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
+ },
154
+ ]
155
+ },
156
+ ];
157
+
158
+ let html = "<div class=\"wrapper\">";
159
+ for ( const section of sections ) {
160
+
161
+ const listHTML = getListArray( section.listInfo );
162
+
163
+ if ( listHTML.length > 0 ) {
164
+ html += renderSection( section.title, listHTML );
165
+ }
55
166
 
56
- new PageTaxonList().render( outputDir, taxa, list.filename, list.name );
57
- listsHTML.push( HTML.getLink( "./" + list.filename + ".html", list.name ) + " (" + taxa.length + ")" );
58
167
  }
168
+ html += renderSection( "Taxonomy", renderList( [ HTML.getLink( "./list_families.html", "Plant Families" ) ] ) );
169
+
170
+ html += "</div>";
59
171
 
60
172
  // Write lists to includes directory so it can be inserted into pages.
61
- fs.writeFileSync( outputDir + "/_includes/plantlists.html", "<ul>" + HTML.arrayToLI( listsHTML ) + "</ul>" );
173
+ fs.writeFileSync( outputDir + "/_includes/plantlists.html", html );
62
174
 
63
175
  }
64
176
 
package/lib/pagetaxon.js CHANGED
@@ -3,6 +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 { RarePlants } from "./rareplants.js";
6
7
 
7
8
  class PageTaxon extends HTMLPage {
8
9
 
@@ -19,7 +20,7 @@ class PageTaxon extends HTMLPage {
19
20
  if ( jepsonID ) {
20
21
  links.push( Jepson.getEFloraLink( jepsonID ) );
21
22
  }
22
- const calRecNum = this.#taxon.getCalRecNum();
23
+ const calRecNum = this.#taxon.getCalfloraID();
23
24
  if ( calRecNum ) {
24
25
  links.push(
25
26
  HTML.getLink(
@@ -85,20 +86,35 @@ class PageTaxon extends HTMLPage {
85
86
  return html;
86
87
  }
87
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
+
88
110
  #getRelatedTaxaLinks() {
89
111
  const links = [];
90
112
  const genus = this.#taxon.getGenus();
91
113
  if ( genus ) {
92
114
  const taxa = genus.getTaxa();
93
- for ( const taxon of taxa ) {
94
- if ( taxon.getName() !== this.#taxon.getName() ) {
95
- links.push(
96
- HTML.getLink(
97
- "./" + taxon.getFileName(),
98
- taxon.getName(),
99
- { class: taxon.getHTMLClassName() }
100
- )
101
- );
115
+ if ( taxa.length > 1 ) {
116
+ for ( const taxon of taxa ) {
117
+ links.push( taxon.getHTMLLink( taxon.getName() !== this.#taxon.getName() ) );
102
118
  }
103
119
  }
104
120
  }
@@ -121,21 +137,28 @@ class PageTaxon extends HTMLPage {
121
137
  if ( cn.length > 0 ) {
122
138
  html += HTML.getElement( "div", cn.join( ", " ), { class: "section common-names" } );
123
139
  }
124
- html += HTML.getElement( "div", this.#taxon.getStatusDescription(), { class: "section" } );
140
+
141
+ html += HTML.getElement( "div", this.#taxon.getStatusDescription(), { class: "section native-status" } );
125
142
 
126
143
  const family = this.#taxon.getFamily();
127
144
  html += HTML.getElement(
128
145
  "div",
129
- "Family: " + HTML.getLink( "./" + family.getFileName(), family.getName() ),
146
+ HTML.getElement( "span", "Family:", { class: "label" } ) + HTML.getLink( "./" + family.getFileName(), family.getName() ),
130
147
  { class: "section" },
131
148
  HTML_OPTIONS.NO_ESCAPE
132
149
  );
133
150
 
151
+ html += this.#getRarityInfo();
152
+
134
153
  html += "</div>";
135
154
 
136
155
  const introName = "intros/" + this.#taxon.getFileName( "md" );
137
156
  if ( fs.existsSync( "./jekyll/_includes/" + introName ) ) {
138
- 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
+ );
139
162
  }
140
163
 
141
164
  html += "<div class=\"wrapper\">";
@@ -0,0 +1,41 @@
1
+ const RANK_DESCRIPS = {
2
+ "1A": "Presumed Extirpated or Extinct",
3
+ "1B": "Rare or Endangered",
4
+ "2A": "Extirpated in California",
5
+ "2B": "Rare or Endangered in California",
6
+ "3": "Needs Review",
7
+ "4": "Uncommon in California",
8
+ };
9
+
10
+ const THREAT_DESCRIPS = {
11
+ "1": "Seriously threatened in California",
12
+ "2": "Moderately threatened in California",
13
+ "3": "Not very threatened in California",
14
+ };
15
+
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
+ }
28
+
29
+ static getRPIRankDescription( rank ) {
30
+ const pieces = rank.split( "." );
31
+ return RANK_DESCRIPS[ pieces[ 0 ] ];
32
+ }
33
+
34
+ static getRPIRankAndThreatDescriptions( rank ) {
35
+ const pieces = rank.split( "." );
36
+ return [ RANK_DESCRIPS[ pieces[ 0 ] ], THREAT_DESCRIPS[ pieces[ 1 ] ] ];
37
+ }
38
+
39
+ }
40
+
41
+ export { RarePlants };
package/lib/taxa.js CHANGED
@@ -19,7 +19,7 @@ class Taxa {
19
19
  html += "<tr>";
20
20
  html += HTML.getElement(
21
21
  "td",
22
- HTML.getLink( "./" + taxon.getFileName(), taxon.getName(), { class: taxon.getHTMLClassName() } ),
22
+ taxon.getHTMLLink(),
23
23
  undefined,
24
24
  HTML_OPTIONS.NO_ESCAPE
25
25
  );
@@ -55,7 +55,16 @@ class Taxa {
55
55
  ErrorLog.log( name, "has multiple entries" );
56
56
  }
57
57
  const commonName = row[ "common name" ];
58
- this.#taxa[ name ] = new Taxon( name, commonName, status, jepsonID, row[ "calrecnum" ], row[ "inat id" ] );
58
+ this.#taxa[ name ] = new Taxon(
59
+ name,
60
+ commonName,
61
+ status,
62
+ jepsonID,
63
+ row[ "calrecnum" ],
64
+ row[ "inat id" ],
65
+ row[ "CRPR" ],
66
+ row[ "CESA" ]
67
+ );
59
68
  if ( !jepsonID ) {
60
69
  ErrorLog.log( name, "has no Jepson ID" );
61
70
  }
package/lib/taxon.js CHANGED
@@ -1,6 +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 { HTML, HTML_OPTIONS } from "./html.js";
5
+ import { RarePlants } from "./rareplants.js";
4
6
 
5
7
  class Taxon {
6
8
 
@@ -11,10 +13,12 @@ class Taxon {
11
13
  #jepsonID;
12
14
  #calRecNum;
13
15
  #iNatID;
14
- #iNatName;
16
+ #iNatSyn;
17
+ #rankRPI;
18
+ #cesa;
15
19
  #synonyms = [];
16
20
 
17
- constructor( name, commonNames, status, jepsonID, calRecNum, iNatID ) {
21
+ constructor( name, commonNames, status, jepsonID, calRecNum, iNatID, rankRPI, cesa ) {
18
22
  this.#name = name;
19
23
  this.#genus = name.split( " " )[ 0 ];
20
24
  this.#commonNames = commonNames ? commonNames.split( "," ).map( t => t.trim() ) : [];
@@ -22,6 +26,8 @@ class Taxon {
22
26
  this.#jepsonID = jepsonID;
23
27
  this.#calRecNum = calRecNum;
24
28
  this.#iNatID = iNatID;
29
+ this.#rankRPI = rankRPI;
30
+ this.#cesa = cesa;
25
31
  Genera.addTaxon( this );
26
32
  if ( !calRecNum ) {
27
33
  ErrorLog.log( this.getName(), "has no Calflora ID" );
@@ -34,7 +40,8 @@ class Taxon {
34
40
  addSynonym( syn, type ) {
35
41
  this.#synonyms.push( syn );
36
42
  if ( type === "INAT" ) {
37
- 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;
38
45
  }
39
46
  }
40
47
 
@@ -42,10 +49,14 @@ class Taxon {
42
49
  return this.#name.replace( " subsp.", " ssp." ).replace( "×", "X" );
43
50
  }
44
51
 
45
- getCalRecNum() {
52
+ getCalfloraID() {
46
53
  return this.#calRecNum;
47
54
  }
48
55
 
56
+ getCESA() {
57
+ return this.#cesa;
58
+ }
59
+
49
60
  getCommonNames() {
50
61
  return this.#commonNames;
51
62
  }
@@ -63,20 +74,26 @@ class Taxon {
63
74
  return Genera.getGenus( this.#genus );
64
75
  }
65
76
 
66
- getHTMLClassName() {
67
- return this.isNative() ? "native" : "non-native";
68
- }
69
-
70
77
  getGenusName() {
71
78
  return this.#genus;
72
79
  }
73
80
 
81
+ getHTMLLink( href = true ) {
82
+ href = href ? ( "./" + this.getFileName() ) : undefined;
83
+ const className = this.isNative() ? ( this.isRare() ? "rare" : "native" ) : "non-native";
84
+ const attributes = { class: className };
85
+ if ( className === "rare" ) {
86
+ attributes[ "title" ] = RarePlants.getRPIRankAndThreatDescriptions( this.getRPIRankAndThreat() ).join( "\n" );
87
+ }
88
+ return HTML.getElement( "span", HTML.getLink( href, this.getName() ), attributes, HTML_OPTIONS.NO_ESCAPE );
89
+ }
90
+
74
91
  getINatID() {
75
92
  return this.#iNatID;
76
93
  }
77
94
 
78
95
  getINatName() {
79
- const name = this.#iNatName ? this.#iNatName : this.#name;
96
+ const name = this.#iNatSyn ? this.#iNatSyn : this.getName();
80
97
  return name.replace( / (subsp|var)\./, "" ).replace( "×", "× " );
81
98
  }
82
99
 
@@ -88,6 +105,17 @@ class Taxon {
88
105
  return this.#name;
89
106
  }
90
107
 
108
+ getRPIRank() {
109
+ if ( !this.#rankRPI ) {
110
+ return this.#rankRPI;
111
+ }
112
+ return this.#rankRPI.split( "." )[ 0 ];
113
+ }
114
+
115
+ getRPIRankAndThreat() {
116
+ return this.#rankRPI;
117
+ }
118
+
91
119
  getStatus() {
92
120
  return this.#status;
93
121
  }
@@ -103,6 +131,7 @@ class Taxon {
103
131
  }
104
132
  throw new Error( this.#status );
105
133
  }
134
+
106
135
  getSynonyms() {
107
136
  return this.#synonyms;
108
137
  }
@@ -111,6 +140,10 @@ class Taxon {
111
140
  return this.#status === "N";
112
141
  }
113
142
 
143
+ isRare() {
144
+ return this.getRPIRank() !== undefined;
145
+ }
146
+
114
147
  }
115
148
 
116
149
  export { Taxon };
package/package.json CHANGED
@@ -1,9 +1,28 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.0.2",
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
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/ca-plants/ca-plant-list.git"
9
+ },
10
+ "homepage": "https://github.com/ca-plants/ca-plant-list",
6
11
  "type": "module",
12
+ "exports": {
13
+ "./CSV": {
14
+ "import": "./lib/csv.js"
15
+ },
16
+ "./DataLoader": {
17
+ "import": "./lib/dataloader.js"
18
+ },
19
+ "./Exceptions": {
20
+ "import": "./lib/exceptions.js"
21
+ },
22
+ "./Taxa": {
23
+ "import": "./lib/taxa.js"
24
+ }
25
+ },
7
26
  "bin": {
8
27
  "ca-plant-list": "build-site.js"
9
28
  },
@@ -14,4 +33,4 @@
14
33
  "devDependencies": {
15
34
  "eslint": "^8.26.0"
16
35
  }
17
- }
36
+ }