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 239