33
44const __doc__ = `
55Usage:
6- genindex.js <source> <outputIndex> <outputTags> --config=<path>
6+ genindex.js --config=<path>
77`
88
99const assert = require ( 'assert' )
@@ -16,10 +16,60 @@ const docopt = require('docopt')
1616const sax = require ( 'sax' )
1717const toml = require ( 'toml' )
1818const lunr = require ( 'lunr' )
19+ const marked = require ( 'marked' )
1920
2021const PAT_HEADMATTER = / ^ \+ \+ \+ \n ( [ ^ ] + ) \n \+ \+ \+ /
2122const SNIPPET_LENGTH = 175
2223
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+
2373// Recursively step through an object and replace any numbers with a number
2474// representable in a short ASCII string.
2575function truncateNumbers ( r ) {
@@ -70,79 +120,6 @@ function* walk(root) {
70120 }
71121}
72122
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-
146123function processFile ( path ) {
147124 const rawdata = fs . readFileSync ( path , { encoding : 'utf-8' } )
148125 const match = rawdata . match ( PAT_HEADMATTER )
@@ -156,7 +133,19 @@ function processFile(path) {
156133 headmatter . slug = '/' + pathModule . parse ( path ) . name
157134 }
158135
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 ( ' ' )
160149 searchIndex . idx . add ( searchDoc )
161150
162151 let tags = [ ]
@@ -169,37 +158,51 @@ function processFile(path) {
169158 }
170159
171160 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+ }
176169 }
177170}
178171
179172function main ( ) {
180173 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 || { }
183177 let error = false
184178
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
187188 try {
188- headmatter = processFile ( path )
189+ doc = processFile ( path )
189190 } catch ( err ) {
190191 console . error ( `Error processing ${ path } : ${ err } ` )
191192 error = true
192193 continue
193194 }
194195
195- headmatter . options . forEach ( function ( option ) {
196+ doc . headmatter . options . forEach ( function ( option ) {
196197 if ( tagManifest [ option . name ] === undefined ) {
197198 console . error ( `Unknown tag "${ option } " in ${ path } ` )
198199 error = true
199200 }
200201 } )
201202
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 )
203206 }
204207
205208 if ( error ) {
@@ -215,13 +218,17 @@ function main() {
215218 } )
216219 }
217220
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 ( {
219226 tags : tags ,
220- tutorials : data
227+ tutorials : tutorials
221228 } ) )
222229
223230 const searchIndexJSON = searchIndex . toJSON ( )
224- fs . writeFileSync ( args [ '<outputIndex>' ] , JSON . stringify ( searchIndexJSON ) )
231+ fs . writeFileSync ( 'public/search.json' , JSON . stringify ( searchIndexJSON ) )
225232}
226233
227234main ( )
0 commit comments