@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 +3 -12
- package/jekyll/_layouts/html.html +3 -0
- package/jekyll/assets/css/main.css +26 -3
- package/lib/config.js +7 -0
- package/lib/csv.js +33 -5
- package/lib/dataloader.js +17 -3
- package/lib/exceptions.js +38 -0
- package/lib/html.js +4 -1
- package/lib/pagerenderer.js +135 -23
- package/lib/pagetaxon.js +36 -13
- package/lib/rareplants.js +41 -0
- package/lib/taxa.js +11 -2
- package/lib/taxon.js +42 -9
- package/package.json +21 -2
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
|
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(
|
25
|
-
PageRenderer.render(
|
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
|
-
|
40
|
+
span.native {
|
41
|
+
font-weight: bold;
|
42
|
+
}
|
43
|
+
|
44
|
+
span.rare {
|
32
45
|
font-weight: bold;
|
33
|
-
|
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
|
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
@@ -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
|
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
|
-
|
12
|
-
|
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
|
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" );
|
package/lib/pagerenderer.js
CHANGED
@@ -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(
|
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(
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
41
|
-
|
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
|
-
|
54
|
-
|
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",
|
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.
|
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
|
-
|
94
|
-
|
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
|
-
|
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(
|
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
|
-
|
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(
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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.#
|
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.
|
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
|
+
}
|