Skip to content

Commit 87c54aa

Browse files
authored
Merge pull request #8 from 10gen/new-build
New build system
2 parents f250826 + 97ce1e8 commit 87c54aa

File tree

5 files changed

+113
-100
lines changed

5 files changed

+113
-100
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/build
2+
content.html
23
node_modules/
34
public
4-
*.log
5+
*.log

Makefile

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ STAGING_BUCKET=docs-mongodb-org-staging
44
STAGING_PREFIX=tutorials
55
STAGING_URL=http://docs-mongodb-org-staging.s3-website-us-east-1.amazonaws.com/${STAGING_PREFIX}/${USER}/${GIT_BRANCH}
66

7-
.PHONY: build server help stage
7+
.PHONY: build server help stage content-html
88

99
help: ## Show this help message
1010
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
1111

12-
build: | tools/node_modules ## Build into public/
12+
content-html: | tools/node_modules
13+
$(NODE) tools/genindex.js --config config.toml
14+
15+
build: | content-html ## Build into public/
1316
hugo -b "${STAGING_URL}"
14-
$(NODE) tools/genindex.js content public/search.json public/tags.json --config config.toml
1517

16-
server: ## Host the documentation on port 1313
17-
$(NODE) tools/genindex.js content public/search.json public/tags.json --config config.toml
18+
server: | content-html ## Host the documentation on port 1313
1819
hugo server --renderToDisk -b 'localhost'
1920

20-
stage: build # Upload built artifacts to the staging URL
21+
stage: build ## Upload built artifacts to the staging URL
2122
mut-publish public/ ${STAGING_BUCKET} --prefix=${STAGING_PREFIX} --stage ${ARGS}
2223
@echo "Hosted at ${STAGING_URL}"
2324

config.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ pygmentsuseclasses = true
55
PygmentsCodeFences = true
66
PygmentsStyle = "monokai"
77

8+
sourceContentDir = "content"
9+
contentDir = "content-html"
10+
811
[taxonomies]
9-
category = ""
12+
category = ""
1013
tags = ""
1114

1215
[tags]

tools/genindex.js

Lines changed: 96 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
const __doc__ = `
55
Usage:
6-
genindex.js <source> <outputIndex> <outputTags> --config=<path>
6+
genindex.js --config=<path>
77
`
88

99
const assert = require('assert')
@@ -16,10 +16,60 @@ const docopt = require('docopt')
1616
const sax = require('sax')
1717
const toml = require('toml')
1818
const lunr = require('lunr')
19+
const marked = require('marked')
1920

2021
const PAT_HEADMATTER = /^\+\+\+\n([^]+)\n\+\+\+/
2122
const SNIPPET_LENGTH = 175
2223

24+
function escape(html, encode) {
25+
return html
26+
.replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
27+
.replace(/</g, '&lt;')
28+
.replace(/>/g, '&gt;')
29+
.replace(/"/g, '&quot;')
30+
.replace(/'/g, '&#39;');
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.
2575
function 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-
146123
function 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

179172
function 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

227234
main()

tools/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
2-
"dependencies" : {
2+
"dependencies": {
33
"docopt": "0.6.2",
4-
"toml" : "2.3.2",
4+
"lunr": "i80and/lunr.js#19a85e62ae8103a48ce5a1fad507f758cf6016d9",
5+
"marked": "^0.3.6",
56
"sax": "1.2.2",
6-
"lunr": "i80and/lunr.js#19a85e62ae8103a48ce5a1fad507f758cf6016d9"
7+
"toml": "2.3.2"
78
}
89
}

0 commit comments

Comments
 (0)