3
3
4
4
const __doc__ = `
5
5
Usage:
6
- genindex.js <source> <outputIndex> <outputTags> --config=<path>
6
+ genindex.js --config=<path>
7
7
`
8
8
9
9
const assert = require ( 'assert' )
@@ -16,10 +16,60 @@ const docopt = require('docopt')
16
16
const sax = require ( 'sax' )
17
17
const toml = require ( 'toml' )
18
18
const lunr = require ( 'lunr' )
19
+ const marked = require ( 'marked' )
19
20
20
21
const PAT_HEADMATTER = / ^ \+ \+ \+ \n ( [ ^ ] + ) \n \+ \+ \+ /
21
22
const SNIPPET_LENGTH = 175
22
23
24
+ function escape ( html , encode ) {
25
+ return html
26
+ . replace ( ! encode ? / & (? ! # ? \w + ; ) / g : / & / g, '&' )
27
+ . replace ( / < / g, '<' )
28
+ . replace ( / > / g, '>' )
29
+ . replace ( / " / g, '"' )
30
+ . replace ( / ' / g, ''' ) ;
31
+ }
32
+
33
+ function makeRenderer ( ) {
34
+ const renderer = new marked . Renderer ( )
35
+ let lastLevel = 0
36
+
37
+ renderer . heading = function ( text , level , raw ) {
38
+ let prefix = ''
39
+ if ( level <= lastLevel ) {
40
+ prefix = '\n</section>' . repeat ( lastLevel - level + 1 )
41
+ }
42
+ lastLevel = level
43
+
44
+ return prefix + '\n<section>\n<h'
45
+ + level
46
+ + ' id="'
47
+ + this . options . headerPrefix
48
+ + raw . toLowerCase ( ) . replace ( / [ ^ \w ] + / g, '-' )
49
+ + '">'
50
+ + text
51
+ + '</h'
52
+ + level
53
+ + '>\n'
54
+ }
55
+
56
+ renderer . code = function ( code , lang ) {
57
+ if ( ! lang ) {
58
+ return '<div class="highlight"><pre><code>' + escape ( code ) + '\n</code></pre></div>'
59
+ }
60
+
61
+ return `{{< highlight ${ escape ( lang , true ) } >}}`
62
+ + code
63
+ + '\n{{< /highlight >}}\n'
64
+ }
65
+
66
+ renderer . flush = function ( ) {
67
+ return '\n</section>' . repeat ( lastLevel )
68
+ }
69
+
70
+ return renderer
71
+ }
72
+
23
73
// Recursively step through an object and replace any numbers with a number
24
74
// representable in a short ASCII string.
25
75
function truncateNumbers ( r ) {
@@ -70,79 +120,6 @@ function* walk(root) {
70
120
}
71
121
}
72
122
73
- function parseXML ( path , headmatter , xml ) {
74
- const spawnOutput = child_process . spawnSync ( 'mmark' , [ '-xml' ] , { encoding : 'utf-8' , input : xml } )
75
- if ( spawnOutput . status != 0 ) {
76
- throw new Error ( 'Command "mmark" failed' )
77
- }
78
-
79
- const text = `<root>${ spawnOutput . output [ 1 ] } </root>`
80
- const parser = sax . parser ( true , {
81
- trim : true ,
82
- normalize : true
83
- } )
84
-
85
- const doc = {
86
- id : searchIndex . docId ,
87
- title : headmatter . title ,
88
- tags : Object . keys ( headmatter . tags ) ,
89
- minorTitles : [ ] ,
90
- body : [ ]
91
- }
92
- searchIndex . docId += 1
93
- searchIndex . slugs . push ( headmatter . slug )
94
-
95
- let sectionDepth = 0
96
- let inName = false
97
- let error = false
98
-
99
- parser . onerror = function ( error ) {
100
- console . error ( 'Error parsing ' + path )
101
- console . error ( error )
102
- error = true
103
- }
104
-
105
- parser . ontext = function ( text ) {
106
- if ( inName ) {
107
- assert . ok ( sectionDepth >= 1 )
108
- if ( sectionDepth === 1 ) {
109
- doc . title += ' ' + text
110
- } else {
111
- doc . minorTitles . push ( text )
112
- }
113
-
114
- return
115
- }
116
-
117
- doc . body . push ( text )
118
- }
119
-
120
- parser . onopentag = function ( node ) {
121
- if ( node . name === 'section' ) {
122
- sectionDepth += 1
123
- } else if ( node . name === 'name' ) {
124
- inName = true
125
- }
126
- }
127
-
128
- parser . onclosetag = function ( name ) {
129
- if ( name === 'section' ) {
130
- sectionDepth -= 1
131
- } else if ( name === 'name' ) {
132
- assert . equal ( inName , true )
133
- inName = false
134
- }
135
- }
136
-
137
- parser . write ( text ) . close ( )
138
- if ( error ) { throw new Error ( 'Parse error' ) }
139
-
140
- doc . title = doc . title . trim ( )
141
- doc . body = doc . body . join ( ' ' ) . trim ( )
142
-
143
- return doc
144
- }
145
-
146
123
function processFile ( path ) {
147
124
const rawdata = fs . readFileSync ( path , { encoding : 'utf-8' } )
148
125
const match = rawdata . match ( PAT_HEADMATTER )
@@ -156,7 +133,19 @@ function processFile(path) {
156
133
headmatter . slug = '/' + pathModule . parse ( path ) . name
157
134
}
158
135
159
- const searchDoc = parseXML ( path , headmatter , rawdata . slice ( match [ 0 ] . length ) )
136
+ const searchDoc = {
137
+ id : searchIndex . docId ,
138
+ title : headmatter . title ,
139
+ tags : Object . keys ( headmatter . tags ) ,
140
+ minorTitles : [ ] ,
141
+ body : [ ]
142
+ }
143
+ searchIndex . docId += 1
144
+ searchIndex . slugs . push ( headmatter . slug )
145
+
146
+ const renderer = makeRenderer ( )
147
+ const html = marked ( rawdata . slice ( match [ 0 ] . length ) , { renderer : renderer } ) + renderer . flush ( )
148
+ searchDoc . body = searchDoc . body . join ( ' ' )
160
149
searchIndex . idx . add ( searchDoc )
161
150
162
151
let tags = [ ]
@@ -169,37 +158,51 @@ function processFile(path) {
169
158
}
170
159
171
160
return {
172
- url : headmatter . slug ,
173
- title : headmatter . title ,
174
- snippet : searchDoc . body . substring ( 0 , SNIPPET_LENGTH ) ,
175
- options : tags ,
161
+ html : html ,
162
+ headmatterSource : match [ 0 ] ,
163
+ headmatter : {
164
+ url : headmatter . slug ,
165
+ title : headmatter . title ,
166
+ snippet : searchDoc . body . substring ( 0 , SNIPPET_LENGTH ) ,
167
+ options : tags ,
168
+ }
176
169
}
177
170
}
178
171
179
172
function main ( ) {
180
173
const args = docopt . docopt ( __doc__ )
181
- const data = [ ]
182
- const tagManifest = toml . parse ( fs . readFileSync ( args [ '--config' ] ) ) . tags || { }
174
+ const tutorials = [ ]
175
+ const config = toml . parse ( fs . readFileSync ( args [ '--config' ] ) )
176
+ const tagManifest = config . tags || { }
183
177
let error = false
184
178
185
- for ( const path of walk ( args [ '<source>' ] ) ) {
186
- let headmatter
179
+ const sourceContentDir = config . sourceContentDir . replace ( / \/ $ / , '' )
180
+ const outputContentDir = config . contentDir . replace ( / \/ $ / , '' )
181
+
182
+ try {
183
+ fs . mkdirSync ( outputContentDir )
184
+ } catch ( error ) { }
185
+
186
+ for ( const path of walk ( sourceContentDir ) ) {
187
+ let doc
187
188
try {
188
- headmatter = processFile ( path )
189
+ doc = processFile ( path )
189
190
} catch ( err ) {
190
191
console . error ( `Error processing ${ path } : ${ err } ` )
191
192
error = true
192
193
continue
193
194
}
194
195
195
- headmatter . options . forEach ( function ( option ) {
196
+ doc . headmatter . options . forEach ( function ( option ) {
196
197
if ( tagManifest [ option . name ] === undefined ) {
197
198
console . error ( `Unknown tag "${ option } " in ${ path } ` )
198
199
error = true
199
200
}
200
201
} )
201
202
202
- data . push ( headmatter )
203
+ tutorials . push ( doc . headmatter )
204
+ const outputPath = path . replace ( sourceContentDir , outputContentDir ) . replace ( / \. [ a - z ] + $ / , '.html' )
205
+ fs . writeFileSync ( outputPath , doc . headmatterSource + '\n' + doc . html )
203
206
}
204
207
205
208
if ( error ) {
@@ -215,13 +218,17 @@ function main() {
215
218
} )
216
219
}
217
220
218
- fs . writeFileSync ( args [ '<outputTags>' ] , JSON . stringify ( {
221
+ try {
222
+ fs . mkdirSync ( 'public' )
223
+ } catch ( error ) { }
224
+
225
+ fs . writeFileSync ( 'public/tags.json' , JSON . stringify ( {
219
226
tags : tags ,
220
- tutorials : data
227
+ tutorials : tutorials
221
228
} ) )
222
229
223
230
const searchIndexJSON = searchIndex . toJSON ( )
224
- fs . writeFileSync ( args [ '<outputIndex>' ] , JSON . stringify ( searchIndexJSON ) )
231
+ fs . writeFileSync ( 'public/search.json' , JSON . stringify ( searchIndexJSON ) )
225
232
}
226
233
227
234
main ( )
0 commit comments