@@ -3,44 +3,48 @@ import pathlib from 'node:path';
3
3
import { parseArgs } from 'node:util' ;
4
4
import { JSDOM , VirtualConsole } from 'jsdom' ;
5
5
6
- const { positionals : cliArgs , values : cliOpts } = parseArgs ( {
6
+ const { positionals : cliArgs } = parseArgs ( {
7
7
allowPositionals : true ,
8
- options : {
9
- strict : { type : 'boolean' } ,
10
- } ,
8
+ options : { } ,
11
9
} ) ;
12
10
if ( cliArgs . length < 3 ) {
13
11
const self = pathlib . relative ( process . cwd ( ) , process . argv [ 1 ] ) ;
14
- console . error ( `Usage: node ${ self } [--strict] <template.html> <data.json> <file.html>...
12
+ console . error ( `Usage: node ${ self } <template.html> <data.json> <file.html>...
15
13
16
14
{{identifier}} substrings in template.html are replaced from data.json, then
17
15
the result is inserted at the start of the body element in each file.html.` ) ;
18
16
process . exit ( 64 ) ;
19
17
}
20
18
21
- const main = async ( args , options ) => {
19
+ const main = async args => {
22
20
const [ templateFile , dataFile , ...files ] = args ;
23
- const { strict } = options ;
24
21
25
- // Evaluate the template and parse it into nodes for inserting.
26
- // Everything will be prepended to body elements except metadata elements,
27
- // which will be appended to head elements.
22
+ // Substitute data into the template.
23
+ const template = fs . readFileSync ( templateFile , 'utf8' ) ;
24
+ const { default : data } =
25
+ await import ( pathlib . resolve ( dataFile ) , { with : { type : 'json' } } ) ;
26
+ const formatErrors = [ ] ;
27
+ const placeholderPatt = / [ { ] [ { ] (?: ( [ \p{ ID_Start} $ _ ] [ \p{ ID_Continue} $ ] * ) [ } ] [ } ] | .* ?(?: [ } ] [ } ] | (? = [ { ] [ { ] ) | $ ) ) / gsu;
28
+ const resolved = template . replaceAll ( placeholderPatt , ( m , name , i ) => {
29
+ if ( ! name ) {
30
+ const trunc = m . replace ( / ( [ ^ \n ] { 29 } (? ! $ ) | [ ^ \n ] { , 29 } (? = \n ) ) .* / s, '$1…' ) ;
31
+ formatErrors . push ( Error ( `bad placeholder at index ${ i } : ${ trunc } ` ) ) ;
32
+ } else if ( ! Object . hasOwn ( data , name ) ) {
33
+ formatErrors . push ( Error ( `no data for ${ m } ` ) ) ;
34
+ }
35
+ return data [ name ] ;
36
+ } ) ;
37
+ if ( formatErrors . length > 0 ) throw AggregateError ( formatErrors ) ;
38
+
39
+ // Parse the template into DOM nodes for appending to page <head>s (metadata
40
+ // such as <style> elements) or prepending to page <body>s (everything else).
28
41
// https://html.spec.whatwg.org/multipage/dom.html#metadata-content-2
29
42
const metadataNames =
30
43
'base, link, meta, noscript, script, style, template, title'
31
44
. toUpperCase ( )
32
45
. split ( ', ' ) ;
33
- const template = fs . readFileSync ( templateFile , 'utf8' ) ;
34
- const { default : data } =
35
- await import ( pathlib . resolve ( dataFile ) , { with : { type : 'json' } } ) ;
36
- const namePatt = / [ { ] [ { ] ( [ \p{ ID_Start} $ _ ] [ \p{ ID_Continue} $ ] * ) [ } ] [ } ] / gu;
37
- const resolved = template . replaceAll ( namePatt , ( _ , name ) => {
38
- if ( Object . hasOwn ( data , name ) ) return data [ name ] ;
39
- if ( strict ) throw Error ( `no data for {{${ name } }}` ) ;
40
- return '' ;
41
- } ) ;
46
+ const insertDom = JSDOM . fragment ( resolved ) ;
42
47
const headInserts = [ ] , bodyInserts = [ ] ;
43
- let insertDom = JSDOM . fragment ( resolved ) ;
44
48
for ( const node of insertDom . childNodes ) {
45
49
if ( metadataNames . includes ( node . nodeName ) ) headInserts . push ( node ) ;
46
50
else bodyInserts . push ( node ) ;
@@ -67,7 +71,7 @@ const main = async (args, options) => {
67
71
if ( failures . length > 0 ) throw AggregateError ( failures ) ;
68
72
} ;
69
73
70
- main ( cliArgs , cliOpts ) . catch ( err => {
74
+ main ( cliArgs ) . catch ( err => {
71
75
console . error ( err ) ;
72
76
process . exit ( 1 ) ;
73
77
} ) ;
0 commit comments