UNPKG

6.06 kBJavaScriptView Raw
1module.exports = promzard
2promzard.PromZard = PromZard
3
4var fs = require('fs')
5var vm = require('vm')
6var util = require('util')
7var files = {}
8var crypto = require('crypto')
9var EventEmitter = require('events').EventEmitter
10var read = require('read')
11
12var Module = require('module').Module
13var path = require('path')
14
15function 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}
24promzard.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
35function 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
45PromZard.prototype = Object.create(
46 EventEmitter.prototype,
47 { constructor: {
48 value: PromZard,
49 readable: true,
50 configurable: true,
51 writable: true,
52 enumerable: false } } )
53
54PromZard.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
71PromZard.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
103PromZard.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
112PromZard.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
118PromZard.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
143PromZard.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 // default to the key
180 if (undefined === prompt[0])
181 prompt[0] = k
182
183 // default to the ctx value, if there is one
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 // back up so that we process this one again.
205 // this is because it might return a prompt() call in the cb.
206 i --
207 L.call(this)
208 }.bind(this)) }
209 catch (er) { this.emit('error', er) }
210 }
211 }
212 // made it to the end of the loop, maybe
213 if (i >= len)
214 return cb(null, o)
215 }
216}
217
218PromZard.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