@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 +2 -7
- package/jekyll/_layouts/html.html +3 -0
- package/jekyll/assets/css/main.css +4 -1
- package/lib/csv.js +33 -5
- package/lib/dataloader.js +9 -1
- package/lib/exceptions.js +10 -0
- package/lib/pagerenderer.js +34 -7
- package/lib/pagetaxon.js +35 -16
- package/lib/{rpi.js → rareplants.js} +15 -4
- package/lib/taxa.js +2 -1
- package/lib/taxon.js +14 -7
- package/package.json +1 -1
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
|
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(
|
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
|
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
|
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
|
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" ) );
|
package/lib/pagerenderer.js
CHANGED
@@ -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 {
|
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:
|
96
|
+
name: RarePlants.getRPIRankDescription( "1A" ),
|
97
97
|
filename: "list_rpi_1a",
|
98
98
|
include: ( t ) => t.getRPIRank() === "1A",
|
99
99
|
},
|
100
100
|
{
|
101
|
-
name:
|
101
|
+
name: RarePlants.getRPIRankDescription( "1B" ),
|
102
102
|
filename: "list_rpi_1b",
|
103
103
|
include: ( t ) => t.getRPIRank() === "1B",
|
104
104
|
},
|
105
105
|
{
|
106
|
-
name:
|
106
|
+
name: RarePlants.getRPIRankDescription( "2A" ),
|
107
107
|
filename: "list_rpi_2a",
|
108
108
|
include: ( t ) => t.getRPIRank() === "2A",
|
109
109
|
},
|
110
110
|
{
|
111
|
-
name:
|
111
|
+
name: RarePlants.getRPIRankDescription( "2B" ),
|
112
112
|
filename: "list_rpi_2b",
|
113
113
|
include: ( t ) => t.getRPIRank() === "2B",
|
114
114
|
},
|
115
115
|
{
|
116
|
-
name:
|
116
|
+
name: RarePlants.getRPIRankDescription( "3" ),
|
117
117
|
filename: "list_rpi_3",
|
118
118
|
include: ( t ) => t.getRPIRank() === "3",
|
119
119
|
},
|
120
120
|
{
|
121
|
-
name:
|
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 {
|
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.
|
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
|
-
|
95
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
|
-
|
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
|
29
|
+
static getRPIRankDescription( rank ) {
|
19
30
|
const pieces = rank.split( "." );
|
20
31
|
return RANK_DESCRIPS[ pieces[ 0 ] ];
|
21
32
|
}
|
22
33
|
|
23
|
-
static
|
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 {
|
41
|
+
export { RarePlants };
|
package/lib/taxa.js
CHANGED
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
|
-
#
|
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
|
-
|
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
|
-
|
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" ] =
|
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.#
|
96
|
+
const name = this.#iNatSyn ? this.#iNatSyn : this.getName();
|
90
97
|
return name.replace( / (subsp|var)\./, "" ).replace( "×", "× " );
|
91
98
|
}
|
92
99
|
|