forked from danielgtaylor/aglio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.coffee
165 lines (138 loc) · 5.67 KB
/
main.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
fs = require 'fs'
path = require 'path'
Drafter = require 'drafter'
INCLUDE = /( *)<!-- include\((.*)\) -->/gmi
ROOT = path.dirname __dirname
# Legacy template names
LEGACY_TEMPLATES = [
'default', 'default-collapsed', 'flatly', 'flatly-collapsed', 'slate',
'slate-collapsed', 'cyborg', 'cyborg-collapsed']
# Utility for benchmarking
benchmark =
start: (message) -> if process.env.BENCHMARK then console.time message
end: (message) -> if process.env.BENCHMARK then console.timeEnd message
# Replace the include directive with the contents of the included
# file in the input.
includeReplace = (includePath, match, spaces, filename) ->
fullPath = path.join includePath, filename
lines = fs.readFileSync(fullPath, 'utf-8').replace(/\r\n?/g, '\n').split('\n')
content = spaces + lines.join "\n#{spaces}"
# The content can itself include other files, so check those
# as well! Beware of circular includes!
includeDirective path.dirname(fullPath), content
# Handle the include directive, which inserts the contents of one
# file into another. We find the directive using a regular expression
# and replace it using the method above.
includeDirective = (includePath, input) ->
input.replace INCLUDE, includeReplace.bind(this, includePath)
# Get a list of all paths from included files. This *excludes* the
# input path itself.
exports.collectPathsSync = (input, includePath) ->
paths = []
input.replace INCLUDE, (match, spaces, filename) ->
fullPath = path.join(includePath, filename)
paths.push fullPath
content = fs.readFileSync fullPath, 'utf-8'
paths = paths.concat exports.collectPathsSync(content, path.dirname(fullPath))
paths
# Get the theme module for a given theme name
exports.getTheme = (name) ->
name = 'olio' if not name or name in LEGACY_TEMPLATES
require "aglio-theme-#{name}"
# Render an API Blueprint string using a given template
exports.render = (input, options, done) ->
# Support a template name as the options argument
if typeof options is 'string' or options instanceof String
options =
theme: options
# Defaults
options.filterInput ?= true
options.includePath ?= process.cwd()
options.theme ?= 'default'
# For backward compatibility
if options.template then options.theme = options.template
if fs.existsSync options.theme
console.log "Setting theme to olio and layout to #{options.theme}"
options.themeLayout = options.theme
options.theme = 'olio'
else if options.theme isnt 'default' and options.theme in LEGACY_TEMPLATES
variables = options.theme.split('-')[0]
console.log "Setting theme to olio and variables to #{variables}"
options.themeVariables = variables
options.theme = 'olio'
# Handle custom directive(s)
input = includeDirective options.includePath, input
# Protagonist does not support \r ot \t in the input, so
# try to intelligently massage the input so that it works.
# This is required to process files created on Windows.
filteredInput = if not options.filterInput then input else
input
.replace(/\r\n?/g, '\n')
.replace(/\t/g, ' ')
benchmark.start 'parse'
drafter = new Drafter()
drafter.make filteredInput, (err, res) ->
benchmark.end 'parse'
if err
err.input = input
return done(err)
try
theme = exports.getTheme options.theme
catch err
return done(err)
# Setup default options if needed
for option in theme.getConfig().options or []
# Convert `foo-bar` into `themeFooBar`
words = (f[0].toUpperCase() + f.slice(1) for f in option.name.split('-'))
name = "theme#{words.join('')}"
options[name] ?= option.default
benchmark.start 'render-total'
theme.render res.ast, options, (err, html) ->
benchmark.end 'render-total'
if err then return done(err)
# Add filtered input to warnings since we have no
# error to return
res.warnings.input = filteredInput
done null, html, res.warnings
# Render from/to files
exports.renderFile = (inputFile, outputFile, options, done) ->
render = (input) ->
exports.render input, options, (err, html, warnings) ->
if err then return done(err)
if outputFile isnt '-'
fs.writeFile outputFile, html, (err) ->
done err, warnings
else
console.log html
done null, warnings
if inputFile isnt '-'
options.includePath ?= path.dirname inputFile
fs.readFile inputFile, encoding: 'utf-8', (err, input) ->
if err then return done(err)
render input.toString()
else
process.stdin.setEncoding 'utf-8'
process.stdin.on 'readable', ->
chunk = process.stdin.read()
if chunk?
render chunk
# Compile markdown from/to files
exports.compileFile = (inputFile, outputFile, done) ->
compile = (input) ->
compiled = includeDirective path.dirname(inputFile), input
if outputFile isnt '-'
fs.writeFile outputFile, compiled, (err) ->
done err
else
console.log compiled
done null
if inputFile isnt '-'
fs.readFile inputFile, encoding: 'utf-8', (err, input) ->
if err then return done(err)
compile input.toString()
else
process.stdin.setEncoding 'utf-8'
process.stdin.on 'readable', ->
chunk = process.stdin.read()
if chunk?
compile chunk