1 | module.exports = promzard
|
2 | promzard.PromZard = PromZard
|
3 |
|
4 | var fs = require('fs')
|
5 | var vm = require('vm')
|
6 | var util = require('util')
|
7 | var files = {}
|
8 | var crypto = require('crypto')
|
9 | var EventEmitter = require('events').EventEmitter
|
10 | var read = require('read')
|
11 |
|
12 | var Module = require('module').Module
|
13 | var path = require('path')
|
14 |
|
15 | function promzard (file, ctx, cb) {
|
16 | if (typeof ctx === 'function') cb = ctx, ctx = null;
|
17 | if (!ctx) ctx = {};
|
18 | var pz = new PromZard(file, ctx)
|
19 | pz.on('error', cb)
|
20 | pz.on('data', function (data) {
|
21 | cb(null, data)
|
22 | })
|
23 | }
|
24 | promzard.fromBuffer = function (buf, ctx, cb) {
|
25 | var filename = 0
|
26 | do {
|
27 | filename = '\0' + Math.random();
|
28 | } while (files[filename])
|
29 | files[filename] = buf
|
30 | var ret = promzard(filename, ctx, cb)
|
31 | delete files[filename]
|
32 | return ret
|
33 | }
|
34 |
|
35 | function PromZard (file, ctx) {
|
36 | if (!(this instanceof PromZard))
|
37 | return new PromZard(file, ctx)
|
38 | EventEmitter.call(this)
|
39 | this.file = file
|
40 | this.ctx = ctx
|
41 | this.unique = crypto.randomBytes(8).toString('hex')
|
42 | this.load()
|
43 | }
|
44 |
|
45 | PromZard.prototype = Object.create(
|
46 | EventEmitter.prototype,
|
47 | { constructor: {
|
48 | value: PromZard,
|
49 | readable: true,
|
50 | configurable: true,
|
51 | writable: true,
|
52 | enumerable: false } } )
|
53 |
|
54 | PromZard.prototype.load = function () {
|
55 | if (files[this.file])
|
56 | return this.loaded()
|
57 |
|
58 | fs.readFile(this.file, 'utf8', function (er, d) {
|
59 | if (er && this.backupFile) {
|
60 | this.file = this.backupFile
|
61 | delete this.backupFile
|
62 | return this.load()
|
63 | }
|
64 | if (er)
|
65 | return this.emit('error', this.error = er)
|
66 | files[this.file] = d
|
67 | this.loaded()
|
68 | }.bind(this))
|
69 | }
|
70 |
|
71 | PromZard.prototype.loaded = function () {
|
72 | this.ctx.prompt = this.makePrompt()
|
73 | this.ctx.__filename = this.file
|
74 | this.ctx.__dirname = path.dirname(this.file)
|
75 | this.ctx.__basename = path.basename(this.file)
|
76 | var mod = this.ctx.module = this.makeModule()
|
77 | this.ctx.require = function (path) {
|
78 | return mod.require(path)
|
79 | }
|
80 | this.ctx.require.resolve = function(path) {
|
81 | return Module._resolveFilename(path, mod);
|
82 | }
|
83 | this.ctx.exports = mod.exports
|
84 |
|
85 | this.script = this.wrap(files[this.file])
|
86 | var fn = vm.runInThisContext(this.script, this.file)
|
87 | var args = Object.keys(this.ctx).map(function (k) {
|
88 | return this.ctx[k]
|
89 | }.bind(this))
|
90 | try { var res = fn.apply(this.ctx, args) }
|
91 | catch (er) { this.emit('error', er) }
|
92 | if (res &&
|
93 | typeof res === 'object' &&
|
94 | exports === mod.exports &&
|
95 | Object.keys(exports).length === 1) {
|
96 | this.result = res
|
97 | } else {
|
98 | this.result = mod.exports
|
99 | }
|
100 | this.walk()
|
101 | }
|
102 |
|
103 | PromZard.prototype.makeModule = function () {
|
104 | var mod = new Module(this.file, module)
|
105 | mod.loaded = true
|
106 | mod.filename = this.file
|
107 | mod.id = this.file
|
108 | mod.paths = Module._nodeModulePaths(path.dirname(this.file))
|
109 | return mod
|
110 | }
|
111 |
|
112 | PromZard.prototype.wrap = function (body) {
|
113 | var s = '(function( %s ) { %s\n })'
|
114 | var args = Object.keys(this.ctx).join(', ')
|
115 | return util.format(s, args, body)
|
116 | }
|
117 |
|
118 | PromZard.prototype.makePrompt = function () {
|
119 | this.prompts = []
|
120 | return prompt.bind(this)
|
121 | function prompt () {
|
122 | var p, d, t
|
123 | for (var i = 0; i < arguments.length; i++) {
|
124 | var a = arguments[i]
|
125 | if (typeof a === 'string' && p)
|
126 | d = a
|
127 | else if (typeof a === 'string')
|
128 | p = a
|
129 | else if (typeof a === 'function')
|
130 | t = a
|
131 | else if (a && typeof a === 'object') {
|
132 | p = a.prompt || p
|
133 | d = a.default || d
|
134 | t = a.transform || t
|
135 | }
|
136 | }
|
137 |
|
138 | try { return this.unique + '-' + this.prompts.length }
|
139 | finally { this.prompts.push([p, d, t]) }
|
140 | }
|
141 | }
|
142 |
|
143 | PromZard.prototype.walk = function (o, cb) {
|
144 | o = o || this.result
|
145 | cb = cb || function (er, res) {
|
146 | if (er)
|
147 | return this.emit('error', this.error = er)
|
148 | this.result = res
|
149 | return this.emit('data', res)
|
150 | }
|
151 | cb = cb.bind(this)
|
152 | var keys = Object.keys(o)
|
153 | var i = 0
|
154 | var len = keys.length
|
155 |
|
156 | L.call(this)
|
157 | function L () {
|
158 | if (this.error)
|
159 | return
|
160 | while (i < len) {
|
161 | var k = keys[i]
|
162 | var v = o[k]
|
163 | i++
|
164 |
|
165 | if (v && typeof v === 'object') {
|
166 | return this.walk(v, function (er, res) {
|
167 | if (er) return cb(er)
|
168 | o[k] = res
|
169 | L.call(this)
|
170 | }.bind(this))
|
171 | } else if (v &&
|
172 | typeof v === 'string' &&
|
173 | v.indexOf(this.unique) === 0) {
|
174 | var n = +v.substr(this.unique.length + 1)
|
175 | var prompt = this.prompts[n]
|
176 | if (isNaN(n) || !prompt)
|
177 | continue
|
178 |
|
179 |
|
180 | if (undefined === prompt[0])
|
181 | prompt[0] = k
|
182 |
|
183 |
|
184 | if (undefined === prompt[1])
|
185 | prompt[1] = this.ctx[k]
|
186 |
|
187 | return this.prompt(prompt, function (er, res) {
|
188 | if (er) {
|
189 | if (!er.notValid) {
|
190 | return this.emit('error', this.error = er);
|
191 | }
|
192 | console.log(er.message)
|
193 | i --
|
194 | return L.call(this)
|
195 | }
|
196 | o[k] = res
|
197 | L.call(this)
|
198 | }.bind(this))
|
199 | } else if (typeof v === 'function') {
|
200 | try { return v.call(this.ctx, function (er, res) {
|
201 | if (er)
|
202 | return this.emit('error', this.error = er)
|
203 | o[k] = res
|
204 |
|
205 |
|
206 | i --
|
207 | L.call(this)
|
208 | }.bind(this)) }
|
209 | catch (er) { this.emit('error', er) }
|
210 | }
|
211 | }
|
212 |
|
213 | if (i >= len)
|
214 | return cb(null, o)
|
215 | }
|
216 | }
|
217 |
|
218 | PromZard.prototype.prompt = function (pdt, cb) {
|
219 | var prompt = pdt[0]
|
220 | var def = pdt[1]
|
221 | var tx = pdt[2]
|
222 |
|
223 | if (tx) {
|
224 | cb = function (cb) { return function (er, data) {
|
225 | try {
|
226 | var res = tx(data)
|
227 | if (!er && res instanceof Error && !!res.notValid) {
|
228 | return cb(res, null)
|
229 | }
|
230 | return cb(er, res)
|
231 | }
|
232 | catch (er) { this.emit('error', er) }
|
233 | }}(cb).bind(this)
|
234 | }
|
235 |
|
236 | read({ prompt: prompt + ':' , default: def }, cb)
|
237 | }
|
238 |
|