macgyver 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Growl +0 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Growl +0 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/Growl.h +5 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlApplicationBridge.h +551 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlDefines.h +341 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Resources/Info.plist +40 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/_CodeSignature/CodeResources +34 -0
- data/assets/MacGap.app/Contents/Info.plist +48 -0
- data/assets/MacGap.app/Contents/MacOS/MacGap +0 -0
- data/assets/MacGap.app/Contents/PkgInfo +1 -0
- data/assets/MacGap.app/Contents/Resources/_debugger.js +1718 -0
- data/assets/MacGap.app/Contents/Resources/_http_agent.js +310 -0
- data/assets/MacGap.app/Contents/Resources/_http_client.js +533 -0
- data/assets/MacGap.app/Contents/Resources/_http_common.js +222 -0
- data/assets/MacGap.app/Contents/Resources/_http_incoming.js +194 -0
- data/assets/MacGap.app/Contents/Resources/_http_outgoing.js +597 -0
- data/assets/MacGap.app/Contents/Resources/_http_server.js +510 -0
- data/assets/MacGap.app/Contents/Resources/_linklist.js +76 -0
- data/assets/MacGap.app/Contents/Resources/_stream_duplex.js +69 -0
- data/assets/MacGap.app/Contents/Resources/_stream_passthrough.js +41 -0
- data/assets/MacGap.app/Contents/Resources/_stream_readable.js +900 -0
- data/assets/MacGap.app/Contents/Resources/_stream_transform.js +204 -0
- data/assets/MacGap.app/Contents/Resources/_stream_writable.js +456 -0
- data/assets/MacGap.app/Contents/Resources/_tls_legacy.js +887 -0
- data/assets/MacGap.app/Contents/Resources/_tls_wrap.js +831 -0
- data/assets/MacGap.app/Contents/Resources/application.icns +0 -0
- data/assets/MacGap.app/Contents/Resources/assert.js +326 -0
- data/assets/MacGap.app/Contents/Resources/buffer.js +724 -0
- data/assets/MacGap.app/Contents/Resources/child_process.js +1107 -0
- data/assets/MacGap.app/Contents/Resources/cluster.js +613 -0
- data/assets/MacGap.app/Contents/Resources/console.js +108 -0
- data/assets/MacGap.app/Contents/Resources/constants.js +22 -0
- data/assets/MacGap.app/Contents/Resources/crypto.js +691 -0
- data/assets/MacGap.app/Contents/Resources/dgram.js +459 -0
- data/assets/MacGap.app/Contents/Resources/dns.js +274 -0
- data/assets/MacGap.app/Contents/Resources/domain.js +292 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/Credits.rtf +29 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/Window.nib +0 -0
- data/assets/MacGap.app/Contents/Resources/events.js +312 -0
- data/assets/MacGap.app/Contents/Resources/freelist.js +43 -0
- data/assets/MacGap.app/Contents/Resources/fs.js +1732 -0
- data/assets/MacGap.app/Contents/Resources/http.js +119 -0
- data/assets/MacGap.app/Contents/Resources/https.js +134 -0
- data/assets/MacGap.app/Contents/Resources/module.js +529 -0
- data/assets/MacGap.app/Contents/Resources/net.js +1378 -0
- data/assets/MacGap.app/Contents/Resources/nodelike.js +195 -0
- data/assets/MacGap.app/Contents/Resources/os.js +64 -0
- data/assets/MacGap.app/Contents/Resources/path.js +517 -0
- data/assets/MacGap.app/Contents/Resources/public/index.html +38 -0
- data/assets/MacGap.app/Contents/Resources/punycode.js +507 -0
- data/assets/MacGap.app/Contents/Resources/querystring.js +206 -0
- data/assets/MacGap.app/Contents/Resources/readline.js +1311 -0
- data/assets/MacGap.app/Contents/Resources/repl.js +945 -0
- data/assets/MacGap.app/Contents/Resources/smalloc.js +90 -0
- data/assets/MacGap.app/Contents/Resources/stream.js +127 -0
- data/assets/MacGap.app/Contents/Resources/string_decoder.js +189 -0
- data/assets/MacGap.app/Contents/Resources/sys.js +24 -0
- data/assets/MacGap.app/Contents/Resources/timers.js +568 -0
- data/assets/MacGap.app/Contents/Resources/tls.js +220 -0
- data/assets/MacGap.app/Contents/Resources/tty.js +129 -0
- data/assets/MacGap.app/Contents/Resources/url.js +693 -0
- data/assets/MacGap.app/Contents/Resources/util.js +688 -0
- data/assets/MacGap.app/Contents/Resources/vm.js +73 -0
- data/assets/MacGap.app/Contents/Resources/zlib.js +524 -0
- data/assets/index.html +38 -0
- data/bin/macgyver +104 -0
- data/macgyver.gemspec +19 -0
- data/test/public/index.html +27 -0
- metadata +121 -0
@@ -0,0 +1,945 @@
|
|
1
|
+
// Copyright Joyent, Inc. and other Node contributors.
|
2
|
+
//
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
// copy of this software and associated documentation files (the
|
5
|
+
// "Software"), to deal in the Software without restriction, including
|
6
|
+
// without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
8
|
+
// persons to whom the Software is furnished to do so, subject to the
|
9
|
+
// following conditions:
|
10
|
+
//
|
11
|
+
// The above copyright notice and this permission notice shall be included
|
12
|
+
// in all copies or substantial portions of the Software.
|
13
|
+
//
|
14
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
15
|
+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
17
|
+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
18
|
+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
19
|
+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
20
|
+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
/* A repl library that you can include in your own code to get a runtime
|
23
|
+
* interface to your program.
|
24
|
+
*
|
25
|
+
* var repl = require("repl");
|
26
|
+
* // start repl on stdin
|
27
|
+
* repl.start("prompt> ");
|
28
|
+
*
|
29
|
+
* // listen for unix socket connections and start repl on them
|
30
|
+
* net.createServer(function(socket) {
|
31
|
+
* repl.start("node via Unix socket> ", socket);
|
32
|
+
* }).listen("/tmp/node-repl-sock");
|
33
|
+
*
|
34
|
+
* // listen for TCP socket connections and start repl on them
|
35
|
+
* net.createServer(function(socket) {
|
36
|
+
* repl.start("node via TCP socket> ", socket);
|
37
|
+
* }).listen(5001);
|
38
|
+
*
|
39
|
+
* // expose foo to repl context
|
40
|
+
* repl.start("node > ").context.foo = "stdin is fun";
|
41
|
+
*/
|
42
|
+
|
43
|
+
var util = require('util');
|
44
|
+
var inherits = require('util').inherits;
|
45
|
+
var Stream = require('stream');
|
46
|
+
var vm = require('vm');
|
47
|
+
var path = require('path');
|
48
|
+
var fs = require('fs');
|
49
|
+
var rl = require('readline');
|
50
|
+
var Console = require('console').Console;
|
51
|
+
var EventEmitter = require('events').EventEmitter;
|
52
|
+
var domain = require('domain');
|
53
|
+
var debug = util.debuglog('repl');
|
54
|
+
|
55
|
+
// If obj.hasOwnProperty has been overridden, then calling
|
56
|
+
// obj.hasOwnProperty(prop) will break.
|
57
|
+
// See: https://github.com/joyent/node/issues/1707
|
58
|
+
function hasOwnProperty(obj, prop) {
|
59
|
+
return Object.prototype.hasOwnProperty.call(obj, prop);
|
60
|
+
}
|
61
|
+
|
62
|
+
|
63
|
+
// hack for require.resolve("./relative") to work properly.
|
64
|
+
module.filename = path.resolve('repl');
|
65
|
+
|
66
|
+
// hack for repl require to work properly with node_modules folders
|
67
|
+
module.paths = require('module')._nodeModulePaths(module.filename);
|
68
|
+
|
69
|
+
// Can overridden with custom print functions, such as `probe` or `eyes.js`.
|
70
|
+
// This is the default "writer" value if none is passed in the REPL options.
|
71
|
+
exports.writer = util.inspect;
|
72
|
+
|
73
|
+
exports._builtinLibs = ['assert', 'buffer', 'child_process', 'cluster',
|
74
|
+
'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net',
|
75
|
+
'os', 'path', 'punycode', 'querystring', 'readline', 'stream',
|
76
|
+
'string_decoder', 'tls', 'tty', 'url', 'util', 'vm', 'zlib', 'smalloc'];
|
77
|
+
|
78
|
+
|
79
|
+
function REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined) {
|
80
|
+
if (!(this instanceof REPLServer)) {
|
81
|
+
return new REPLServer(prompt, stream, eval_, useGlobal, ignoreUndefined);
|
82
|
+
}
|
83
|
+
|
84
|
+
EventEmitter.call(this);
|
85
|
+
|
86
|
+
var options, input, output, dom;
|
87
|
+
if (util.isObject(prompt)) {
|
88
|
+
// an options object was given
|
89
|
+
options = prompt;
|
90
|
+
stream = options.stream || options.socket;
|
91
|
+
input = options.input;
|
92
|
+
output = options.output;
|
93
|
+
eval_ = options.eval;
|
94
|
+
useGlobal = options.useGlobal;
|
95
|
+
ignoreUndefined = options.ignoreUndefined;
|
96
|
+
prompt = options.prompt;
|
97
|
+
dom = options.domain;
|
98
|
+
} else if (!util.isString(prompt)) {
|
99
|
+
throw new Error('An options Object, or a prompt String are required');
|
100
|
+
} else {
|
101
|
+
options = {};
|
102
|
+
}
|
103
|
+
|
104
|
+
var self = this;
|
105
|
+
|
106
|
+
self._domain = dom || domain.create();
|
107
|
+
|
108
|
+
self.useGlobal = !!useGlobal;
|
109
|
+
self.ignoreUndefined = !!ignoreUndefined;
|
110
|
+
|
111
|
+
eval_ = eval_ || defaultEval;
|
112
|
+
|
113
|
+
function defaultEval(code, context, file, cb) {
|
114
|
+
var err, result;
|
115
|
+
// first, create the Script object to check the syntax
|
116
|
+
try {
|
117
|
+
var script = vm.createScript(code, {
|
118
|
+
filename: file,
|
119
|
+
displayErrors: false
|
120
|
+
});
|
121
|
+
} catch (e) {
|
122
|
+
err = e;
|
123
|
+
debug('parse error %j', code, e);
|
124
|
+
}
|
125
|
+
|
126
|
+
if (!err) {
|
127
|
+
try {
|
128
|
+
if (self.useGlobal) {
|
129
|
+
result = script.runInThisContext({ displayErrors: false });
|
130
|
+
} else {
|
131
|
+
result = script.runInContext(context, { displayErrors: false });
|
132
|
+
}
|
133
|
+
} catch (e) {
|
134
|
+
err = e;
|
135
|
+
if (err && process.domain) {
|
136
|
+
debug('not recoverable, send to domain');
|
137
|
+
process.domain.emit('error', err);
|
138
|
+
process.domain.exit();
|
139
|
+
return;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
cb(err, result);
|
145
|
+
}
|
146
|
+
|
147
|
+
self.eval = self._domain.bind(eval_);
|
148
|
+
|
149
|
+
self._domain.on('error', function(e) {
|
150
|
+
debug('domain error');
|
151
|
+
self.outputStream.write((e.stack || e) + '\n');
|
152
|
+
self.bufferedCommand = '';
|
153
|
+
self.lines.level = [];
|
154
|
+
self.displayPrompt();
|
155
|
+
});
|
156
|
+
|
157
|
+
if (!input && !output) {
|
158
|
+
// legacy API, passing a 'stream'/'socket' option
|
159
|
+
if (!stream) {
|
160
|
+
// use stdin and stdout as the default streams if none were given
|
161
|
+
stream = process;
|
162
|
+
}
|
163
|
+
if (stream.stdin && stream.stdout) {
|
164
|
+
// We're given custom object with 2 streams, or the `process` object
|
165
|
+
input = stream.stdin;
|
166
|
+
output = stream.stdout;
|
167
|
+
} else {
|
168
|
+
// We're given a duplex readable/writable Stream, like a `net.Socket`
|
169
|
+
input = stream;
|
170
|
+
output = stream;
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
self.inputStream = input;
|
175
|
+
self.outputStream = output;
|
176
|
+
|
177
|
+
self.resetContext();
|
178
|
+
self.bufferedCommand = '';
|
179
|
+
self.lines.level = [];
|
180
|
+
|
181
|
+
self.prompt = !util.isUndefined(prompt) ? prompt : '> ';
|
182
|
+
|
183
|
+
function complete(text, callback) {
|
184
|
+
self.complete(text, callback);
|
185
|
+
}
|
186
|
+
|
187
|
+
var rli = rl.createInterface({
|
188
|
+
input: self.inputStream,
|
189
|
+
output: self.outputStream,
|
190
|
+
completer: complete,
|
191
|
+
terminal: options.terminal
|
192
|
+
});
|
193
|
+
self.rli = rli;
|
194
|
+
|
195
|
+
this.commands = {};
|
196
|
+
defineDefaultCommands(this);
|
197
|
+
|
198
|
+
// figure out which "writer" function to use
|
199
|
+
self.writer = options.writer || exports.writer;
|
200
|
+
|
201
|
+
if (util.isUndefined(options.useColors)) {
|
202
|
+
options.useColors = rli.terminal;
|
203
|
+
}
|
204
|
+
self.useColors = !!options.useColors;
|
205
|
+
|
206
|
+
if (self.useColors && self.writer === util.inspect) {
|
207
|
+
// Turn on ANSI coloring.
|
208
|
+
self.writer = function(obj, showHidden, depth) {
|
209
|
+
return util.inspect(obj, showHidden, depth, true);
|
210
|
+
};
|
211
|
+
}
|
212
|
+
|
213
|
+
rli.setPrompt(self.prompt);
|
214
|
+
|
215
|
+
rli.on('close', function() {
|
216
|
+
self.emit('exit');
|
217
|
+
});
|
218
|
+
|
219
|
+
var sawSIGINT = false;
|
220
|
+
rli.on('SIGINT', function() {
|
221
|
+
var empty = rli.line.length === 0;
|
222
|
+
rli.clearLine();
|
223
|
+
|
224
|
+
if (!(self.bufferedCommand && self.bufferedCommand.length > 0) && empty) {
|
225
|
+
if (sawSIGINT) {
|
226
|
+
rli.close();
|
227
|
+
sawSIGINT = false;
|
228
|
+
return;
|
229
|
+
}
|
230
|
+
rli.output.write('(^C again to quit)\n');
|
231
|
+
sawSIGINT = true;
|
232
|
+
} else {
|
233
|
+
sawSIGINT = false;
|
234
|
+
}
|
235
|
+
|
236
|
+
self.bufferedCommand = '';
|
237
|
+
self.lines.level = [];
|
238
|
+
self.displayPrompt();
|
239
|
+
});
|
240
|
+
|
241
|
+
rli.on('line', function(cmd) {
|
242
|
+
debug('line %j', cmd);
|
243
|
+
sawSIGINT = false;
|
244
|
+
var skipCatchall = false;
|
245
|
+
cmd = trimWhitespace(cmd);
|
246
|
+
|
247
|
+
// Check to see if a REPL keyword was used. If it returns true,
|
248
|
+
// display next prompt and return.
|
249
|
+
if (cmd && cmd.charAt(0) === '.' && isNaN(parseFloat(cmd))) {
|
250
|
+
var matches = cmd.match(/^(\.[^\s]+)\s*(.*)$/);
|
251
|
+
var keyword = matches && matches[1];
|
252
|
+
var rest = matches && matches[2];
|
253
|
+
if (self.parseREPLKeyword(keyword, rest) === true) {
|
254
|
+
return;
|
255
|
+
} else {
|
256
|
+
self.outputStream.write('Invalid REPL keyword\n');
|
257
|
+
skipCatchall = true;
|
258
|
+
}
|
259
|
+
}
|
260
|
+
|
261
|
+
if (!skipCatchall) {
|
262
|
+
var evalCmd = self.bufferedCommand + cmd;
|
263
|
+
if (/^\s*\{/.test(evalCmd) && /\}\s*$/.test(evalCmd)) {
|
264
|
+
// It's confusing for `{ a : 1 }` to be interpreted as a block
|
265
|
+
// statement rather than an object literal. So, we first try
|
266
|
+
// to wrap it in parentheses, so that it will be interpreted as
|
267
|
+
// an expression.
|
268
|
+
evalCmd = '(' + evalCmd + ')\n';
|
269
|
+
} else {
|
270
|
+
// otherwise we just append a \n so that it will be either
|
271
|
+
// terminated, or continued onto the next expression if it's an
|
272
|
+
// unexpected end of input.
|
273
|
+
evalCmd = evalCmd + '\n';
|
274
|
+
}
|
275
|
+
|
276
|
+
debug('eval %j', evalCmd);
|
277
|
+
self.eval(evalCmd, self.context, 'repl', finish);
|
278
|
+
} else {
|
279
|
+
finish(null);
|
280
|
+
}
|
281
|
+
|
282
|
+
function finish(e, ret) {
|
283
|
+
debug('finish', e, ret);
|
284
|
+
self.memory(cmd);
|
285
|
+
|
286
|
+
if (e && !self.bufferedCommand && cmd.trim().match(/^npm /)) {
|
287
|
+
self.outputStream.write('npm should be run outside of the ' +
|
288
|
+
'node repl, in your normal shell.\n' +
|
289
|
+
'(Press Control-D to exit.)\n');
|
290
|
+
self.bufferedCommand = '';
|
291
|
+
self.displayPrompt();
|
292
|
+
return;
|
293
|
+
}
|
294
|
+
|
295
|
+
// If error was SyntaxError and not JSON.parse error
|
296
|
+
if (e) {
|
297
|
+
if (isRecoverableError(e)) {
|
298
|
+
// Start buffering data like that:
|
299
|
+
// {
|
300
|
+
// ... x: 1
|
301
|
+
// ... }
|
302
|
+
self.bufferedCommand += cmd + '\n';
|
303
|
+
self.displayPrompt();
|
304
|
+
return;
|
305
|
+
} else {
|
306
|
+
self._domain.emit('error', e);
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
// Clear buffer if no SyntaxErrors
|
311
|
+
self.bufferedCommand = '';
|
312
|
+
|
313
|
+
// If we got any output - print it (if no error)
|
314
|
+
if (!e && (!self.ignoreUndefined || !util.isUndefined(ret))) {
|
315
|
+
self.context._ = ret;
|
316
|
+
self.outputStream.write(self.writer(ret) + '\n');
|
317
|
+
}
|
318
|
+
|
319
|
+
// Display prompt again
|
320
|
+
self.displayPrompt();
|
321
|
+
};
|
322
|
+
});
|
323
|
+
|
324
|
+
rli.on('SIGCONT', function() {
|
325
|
+
self.displayPrompt(true);
|
326
|
+
});
|
327
|
+
|
328
|
+
self.displayPrompt();
|
329
|
+
}
|
330
|
+
inherits(REPLServer, EventEmitter);
|
331
|
+
exports.REPLServer = REPLServer;
|
332
|
+
|
333
|
+
|
334
|
+
// prompt is a string to print on each line for the prompt,
|
335
|
+
// source is a stream to use for I/O, defaulting to stdin/stdout.
|
336
|
+
exports.start = function(prompt, source, eval_, useGlobal, ignoreUndefined) {
|
337
|
+
var repl = new REPLServer(prompt, source, eval_, useGlobal, ignoreUndefined);
|
338
|
+
if (!exports.repl) exports.repl = repl;
|
339
|
+
return repl;
|
340
|
+
};
|
341
|
+
|
342
|
+
|
343
|
+
REPLServer.prototype.createContext = function() {
|
344
|
+
var context;
|
345
|
+
if (this.useGlobal) {
|
346
|
+
context = global;
|
347
|
+
} else {
|
348
|
+
context = vm.createContext();
|
349
|
+
for (var i in global) context[i] = global[i];
|
350
|
+
context.console = new Console(this.outputStream);
|
351
|
+
context.global = context;
|
352
|
+
context.global.global = context;
|
353
|
+
}
|
354
|
+
|
355
|
+
context.module = module;
|
356
|
+
context.require = require;
|
357
|
+
|
358
|
+
this.lines = [];
|
359
|
+
this.lines.level = [];
|
360
|
+
|
361
|
+
// make built-in modules available directly
|
362
|
+
// (loaded lazily)
|
363
|
+
exports._builtinLibs.forEach(function(name) {
|
364
|
+
Object.defineProperty(context, name, {
|
365
|
+
get: function() {
|
366
|
+
var lib = require(name);
|
367
|
+
context._ = context[name] = lib;
|
368
|
+
return lib;
|
369
|
+
},
|
370
|
+
// allow the creation of other globals with this name
|
371
|
+
set: function(val) {
|
372
|
+
delete context[name];
|
373
|
+
context[name] = val;
|
374
|
+
},
|
375
|
+
configurable: true
|
376
|
+
});
|
377
|
+
});
|
378
|
+
|
379
|
+
return context;
|
380
|
+
};
|
381
|
+
|
382
|
+
REPLServer.prototype.resetContext = function() {
|
383
|
+
this.context = this.createContext();
|
384
|
+
|
385
|
+
// Allow REPL extensions to extend the new context
|
386
|
+
this.emit('reset', this.context);
|
387
|
+
};
|
388
|
+
|
389
|
+
REPLServer.prototype.displayPrompt = function(preserveCursor) {
|
390
|
+
var prompt = this.prompt;
|
391
|
+
if (this.bufferedCommand.length) {
|
392
|
+
prompt = '...';
|
393
|
+
var levelInd = new Array(this.lines.level.length).join('..');
|
394
|
+
prompt += levelInd + ' ';
|
395
|
+
}
|
396
|
+
this.rli.setPrompt(prompt);
|
397
|
+
this.rli.prompt(preserveCursor);
|
398
|
+
};
|
399
|
+
|
400
|
+
|
401
|
+
// A stream to push an array into a REPL
|
402
|
+
// used in REPLServer.complete
|
403
|
+
function ArrayStream() {
|
404
|
+
Stream.call(this);
|
405
|
+
|
406
|
+
this.run = function(data) {
|
407
|
+
var self = this;
|
408
|
+
data.forEach(function(line) {
|
409
|
+
self.emit('data', line + '\n');
|
410
|
+
});
|
411
|
+
}
|
412
|
+
}
|
413
|
+
util.inherits(ArrayStream, Stream);
|
414
|
+
ArrayStream.prototype.readable = true;
|
415
|
+
ArrayStream.prototype.writable = true;
|
416
|
+
ArrayStream.prototype.resume = function() {};
|
417
|
+
ArrayStream.prototype.write = function() {};
|
418
|
+
|
419
|
+
var requireRE = /\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/;
|
420
|
+
var simpleExpressionRE =
|
421
|
+
/(([a-zA-Z_$](?:\w|\$)*)\.)*([a-zA-Z_$](?:\w|\$)*)\.?$/;
|
422
|
+
|
423
|
+
|
424
|
+
// Provide a list of completions for the given leading text. This is
|
425
|
+
// given to the readline interface for handling tab completion.
|
426
|
+
//
|
427
|
+
// Example:
|
428
|
+
// complete('var foo = util.')
|
429
|
+
// -> [['util.print', 'util.debug', 'util.log', 'util.inspect', 'util.pump'],
|
430
|
+
// 'util.' ]
|
431
|
+
//
|
432
|
+
// Warning: This eval's code like "foo.bar.baz", so it will run property
|
433
|
+
// getter code.
|
434
|
+
REPLServer.prototype.complete = function(line, callback) {
|
435
|
+
// There may be local variables to evaluate, try a nested REPL
|
436
|
+
if (!util.isUndefined(this.bufferedCommand) && this.bufferedCommand.length) {
|
437
|
+
// Get a new array of inputed lines
|
438
|
+
var tmp = this.lines.slice();
|
439
|
+
// Kill off all function declarations to push all local variables into
|
440
|
+
// global scope
|
441
|
+
this.lines.level.forEach(function(kill) {
|
442
|
+
if (kill.isFunction) {
|
443
|
+
tmp[kill.line] = '';
|
444
|
+
}
|
445
|
+
});
|
446
|
+
var flat = new ArrayStream(); // make a new "input" stream
|
447
|
+
var magic = new REPLServer('', flat); // make a nested REPL
|
448
|
+
magic.context = magic.createContext();
|
449
|
+
flat.run(tmp); // eval the flattened code
|
450
|
+
// all this is only profitable if the nested REPL
|
451
|
+
// does not have a bufferedCommand
|
452
|
+
if (!magic.bufferedCommand) {
|
453
|
+
return magic.complete(line, callback);
|
454
|
+
}
|
455
|
+
}
|
456
|
+
|
457
|
+
var completions;
|
458
|
+
|
459
|
+
// list of completion lists, one for each inheritance "level"
|
460
|
+
var completionGroups = [];
|
461
|
+
|
462
|
+
var completeOn, match, filter, i, group, c;
|
463
|
+
|
464
|
+
// REPL commands (e.g. ".break").
|
465
|
+
var match = null;
|
466
|
+
match = line.match(/^\s*(\.\w*)$/);
|
467
|
+
if (match) {
|
468
|
+
completionGroups.push(Object.keys(this.commands));
|
469
|
+
completeOn = match[1];
|
470
|
+
if (match[1].length > 1) {
|
471
|
+
filter = match[1];
|
472
|
+
}
|
473
|
+
|
474
|
+
completionGroupsLoaded();
|
475
|
+
} else if (match = line.match(requireRE)) {
|
476
|
+
// require('...<Tab>')
|
477
|
+
var exts = Object.keys(require.extensions);
|
478
|
+
var indexRe = new RegExp('^index(' + exts.map(regexpEscape).join('|') +
|
479
|
+
')$');
|
480
|
+
|
481
|
+
completeOn = match[1];
|
482
|
+
var subdir = match[2] || '';
|
483
|
+
var filter = match[1];
|
484
|
+
var dir, files, f, name, base, ext, abs, subfiles, s;
|
485
|
+
group = [];
|
486
|
+
var paths = module.paths.concat(require('module').globalPaths);
|
487
|
+
for (i = 0; i < paths.length; i++) {
|
488
|
+
dir = path.resolve(paths[i], subdir);
|
489
|
+
try {
|
490
|
+
files = fs.readdirSync(dir);
|
491
|
+
} catch (e) {
|
492
|
+
continue;
|
493
|
+
}
|
494
|
+
for (f = 0; f < files.length; f++) {
|
495
|
+
name = files[f];
|
496
|
+
ext = path.extname(name);
|
497
|
+
base = name.slice(0, -ext.length);
|
498
|
+
if (base.match(/-\d+\.\d+(\.\d+)?/) || name === '.npm') {
|
499
|
+
// Exclude versioned names that 'npm' installs.
|
500
|
+
continue;
|
501
|
+
}
|
502
|
+
if (exts.indexOf(ext) !== -1) {
|
503
|
+
if (!subdir || base !== 'index') {
|
504
|
+
group.push(subdir + base);
|
505
|
+
}
|
506
|
+
} else {
|
507
|
+
abs = path.resolve(dir, name);
|
508
|
+
try {
|
509
|
+
if (fs.statSync(abs).isDirectory()) {
|
510
|
+
group.push(subdir + name + '/');
|
511
|
+
subfiles = fs.readdirSync(abs);
|
512
|
+
for (s = 0; s < subfiles.length; s++) {
|
513
|
+
if (indexRe.test(subfiles[s])) {
|
514
|
+
group.push(subdir + name);
|
515
|
+
}
|
516
|
+
}
|
517
|
+
}
|
518
|
+
} catch (e) {}
|
519
|
+
}
|
520
|
+
}
|
521
|
+
}
|
522
|
+
if (group.length) {
|
523
|
+
completionGroups.push(group);
|
524
|
+
}
|
525
|
+
|
526
|
+
if (!subdir) {
|
527
|
+
completionGroups.push(exports._builtinLibs);
|
528
|
+
}
|
529
|
+
|
530
|
+
completionGroupsLoaded();
|
531
|
+
|
532
|
+
// Handle variable member lookup.
|
533
|
+
// We support simple chained expressions like the following (no function
|
534
|
+
// calls, etc.). That is for simplicity and also because we *eval* that
|
535
|
+
// leading expression so for safety (see WARNING above) don't want to
|
536
|
+
// eval function calls.
|
537
|
+
//
|
538
|
+
// foo.bar<|> # completions for 'foo' with filter 'bar'
|
539
|
+
// spam.eggs.<|> # completions for 'spam.eggs' with filter ''
|
540
|
+
// foo<|> # all scope vars with filter 'foo'
|
541
|
+
// foo.<|> # completions for 'foo' with filter ''
|
542
|
+
} else if (line.length === 0 || line[line.length - 1].match(/\w|\.|\$/)) {
|
543
|
+
match = simpleExpressionRE.exec(line);
|
544
|
+
if (line.length === 0 || match) {
|
545
|
+
var expr;
|
546
|
+
completeOn = (match ? match[0] : '');
|
547
|
+
if (line.length === 0) {
|
548
|
+
filter = '';
|
549
|
+
expr = '';
|
550
|
+
} else if (line[line.length - 1] === '.') {
|
551
|
+
filter = '';
|
552
|
+
expr = match[0].slice(0, match[0].length - 1);
|
553
|
+
} else {
|
554
|
+
var bits = match[0].split('.');
|
555
|
+
filter = bits.pop();
|
556
|
+
expr = bits.join('.');
|
557
|
+
}
|
558
|
+
|
559
|
+
// Resolve expr and get its completions.
|
560
|
+
var obj, memberGroups = [];
|
561
|
+
if (!expr) {
|
562
|
+
// If context is instance of vm.ScriptContext
|
563
|
+
// Get global vars synchronously
|
564
|
+
if (this.useGlobal ||
|
565
|
+
this.context.constructor &&
|
566
|
+
this.context.constructor.name === 'Context') {
|
567
|
+
var contextProto = this.context;
|
568
|
+
while (contextProto = Object.getPrototypeOf(contextProto)) {
|
569
|
+
completionGroups.push(Object.getOwnPropertyNames(contextProto));
|
570
|
+
}
|
571
|
+
completionGroups.push(Object.getOwnPropertyNames(this.context));
|
572
|
+
addStandardGlobals(completionGroups, filter);
|
573
|
+
completionGroupsLoaded();
|
574
|
+
} else {
|
575
|
+
this.eval('.scope', this.context, 'repl', function(err, globals) {
|
576
|
+
if (err || !globals) {
|
577
|
+
addStandardGlobals(completionGroups, filter);
|
578
|
+
} else if (util.isArray(globals[0])) {
|
579
|
+
// Add grouped globals
|
580
|
+
globals.forEach(function(group) {
|
581
|
+
completionGroups.push(group);
|
582
|
+
});
|
583
|
+
} else {
|
584
|
+
completionGroups.push(globals);
|
585
|
+
addStandardGlobals(completionGroups, filter);
|
586
|
+
}
|
587
|
+
completionGroupsLoaded();
|
588
|
+
});
|
589
|
+
}
|
590
|
+
} else {
|
591
|
+
this.eval(expr, this.context, 'repl', function(e, obj) {
|
592
|
+
// if (e) console.log(e);
|
593
|
+
|
594
|
+
if (obj != null) {
|
595
|
+
if (util.isObject(obj) || util.isFunction(obj)) {
|
596
|
+
memberGroups.push(Object.getOwnPropertyNames(obj));
|
597
|
+
}
|
598
|
+
// works for non-objects
|
599
|
+
try {
|
600
|
+
var sentinel = 5;
|
601
|
+
var p;
|
602
|
+
if (util.isObject(obj) || util.isFunction(obj)) {
|
603
|
+
p = Object.getPrototypeOf(obj);
|
604
|
+
} else {
|
605
|
+
p = obj.constructor ? obj.constructor.prototype : null;
|
606
|
+
}
|
607
|
+
while (!util.isNull(p)) {
|
608
|
+
memberGroups.push(Object.getOwnPropertyNames(p));
|
609
|
+
p = Object.getPrototypeOf(p);
|
610
|
+
// Circular refs possible? Let's guard against that.
|
611
|
+
sentinel--;
|
612
|
+
if (sentinel <= 0) {
|
613
|
+
break;
|
614
|
+
}
|
615
|
+
}
|
616
|
+
} catch (e) {
|
617
|
+
//console.log("completion error walking prototype chain:" + e);
|
618
|
+
}
|
619
|
+
}
|
620
|
+
|
621
|
+
if (memberGroups.length) {
|
622
|
+
for (i = 0; i < memberGroups.length; i++) {
|
623
|
+
completionGroups.push(memberGroups[i].map(function(member) {
|
624
|
+
return expr + '.' + member;
|
625
|
+
}));
|
626
|
+
}
|
627
|
+
if (filter) {
|
628
|
+
filter = expr + '.' + filter;
|
629
|
+
}
|
630
|
+
}
|
631
|
+
|
632
|
+
completionGroupsLoaded();
|
633
|
+
});
|
634
|
+
}
|
635
|
+
} else {
|
636
|
+
completionGroupsLoaded();
|
637
|
+
}
|
638
|
+
} else {
|
639
|
+
completionGroupsLoaded();
|
640
|
+
}
|
641
|
+
|
642
|
+
// Will be called when all completionGroups are in place
|
643
|
+
// Useful for async autocompletion
|
644
|
+
function completionGroupsLoaded(err) {
|
645
|
+
if (err) throw err;
|
646
|
+
|
647
|
+
// Filter, sort (within each group), uniq and merge the completion groups.
|
648
|
+
if (completionGroups.length && filter) {
|
649
|
+
var newCompletionGroups = [];
|
650
|
+
for (i = 0; i < completionGroups.length; i++) {
|
651
|
+
group = completionGroups[i].filter(function(elem) {
|
652
|
+
return elem.indexOf(filter) == 0;
|
653
|
+
});
|
654
|
+
if (group.length) {
|
655
|
+
newCompletionGroups.push(group);
|
656
|
+
}
|
657
|
+
}
|
658
|
+
completionGroups = newCompletionGroups;
|
659
|
+
}
|
660
|
+
|
661
|
+
if (completionGroups.length) {
|
662
|
+
var uniq = {}; // unique completions across all groups
|
663
|
+
completions = [];
|
664
|
+
// Completion group 0 is the "closest"
|
665
|
+
// (least far up the inheritance chain)
|
666
|
+
// so we put its completions last: to be closest in the REPL.
|
667
|
+
for (i = completionGroups.length - 1; i >= 0; i--) {
|
668
|
+
group = completionGroups[i];
|
669
|
+
group.sort();
|
670
|
+
for (var j = 0; j < group.length; j++) {
|
671
|
+
c = group[j];
|
672
|
+
if (!hasOwnProperty(uniq, c)) {
|
673
|
+
completions.push(c);
|
674
|
+
uniq[c] = true;
|
675
|
+
}
|
676
|
+
}
|
677
|
+
completions.push(''); // separator btwn groups
|
678
|
+
}
|
679
|
+
while (completions.length && completions[completions.length - 1] === '') {
|
680
|
+
completions.pop();
|
681
|
+
}
|
682
|
+
}
|
683
|
+
|
684
|
+
callback(null, [completions || [], completeOn]);
|
685
|
+
}
|
686
|
+
};
|
687
|
+
|
688
|
+
|
689
|
+
/**
|
690
|
+
* Used to parse and execute the Node REPL commands.
|
691
|
+
*
|
692
|
+
* @param {keyword} keyword The command entered to check.
|
693
|
+
* @return {Boolean} If true it means don't continue parsing the command.
|
694
|
+
*/
|
695
|
+
REPLServer.prototype.parseREPLKeyword = function(keyword, rest) {
|
696
|
+
var cmd = this.commands[keyword];
|
697
|
+
if (cmd) {
|
698
|
+
cmd.action.call(this, rest);
|
699
|
+
return true;
|
700
|
+
}
|
701
|
+
return false;
|
702
|
+
};
|
703
|
+
|
704
|
+
|
705
|
+
REPLServer.prototype.defineCommand = function(keyword, cmd) {
|
706
|
+
if (util.isFunction(cmd)) {
|
707
|
+
cmd = {action: cmd};
|
708
|
+
} else if (!util.isFunction(cmd.action)) {
|
709
|
+
throw new Error('bad argument, action must be a function');
|
710
|
+
}
|
711
|
+
this.commands['.' + keyword] = cmd;
|
712
|
+
};
|
713
|
+
|
714
|
+
REPLServer.prototype.memory = function memory(cmd) {
|
715
|
+
var self = this;
|
716
|
+
|
717
|
+
self.lines = self.lines || [];
|
718
|
+
self.lines.level = self.lines.level || [];
|
719
|
+
|
720
|
+
// save the line so I can do magic later
|
721
|
+
if (cmd) {
|
722
|
+
// TODO should I tab the level?
|
723
|
+
self.lines.push(new Array(self.lines.level.length).join(' ') + cmd);
|
724
|
+
} else {
|
725
|
+
// I don't want to not change the format too much...
|
726
|
+
self.lines.push('');
|
727
|
+
}
|
728
|
+
|
729
|
+
// I need to know "depth."
|
730
|
+
// Because I can not tell the difference between a } that
|
731
|
+
// closes an object literal and a } that closes a function
|
732
|
+
if (cmd) {
|
733
|
+
// going down is { and ( e.g. function() {
|
734
|
+
// going up is } and )
|
735
|
+
var dw = cmd.match(/{|\(/g);
|
736
|
+
var up = cmd.match(/}|\)/g);
|
737
|
+
up = up ? up.length : 0;
|
738
|
+
dw = dw ? dw.length : 0;
|
739
|
+
var depth = dw - up;
|
740
|
+
|
741
|
+
if (depth) {
|
742
|
+
(function workIt() {
|
743
|
+
if (depth > 0) {
|
744
|
+
// going... down.
|
745
|
+
// push the line#, depth count, and if the line is a function.
|
746
|
+
// Since JS only has functional scope I only need to remove
|
747
|
+
// "function() {" lines, clearly this will not work for
|
748
|
+
// "function()
|
749
|
+
// {" but nothing should break, only tab completion for local
|
750
|
+
// scope will not work for this function.
|
751
|
+
self.lines.level.push({
|
752
|
+
line: self.lines.length - 1,
|
753
|
+
depth: depth,
|
754
|
+
isFunction: /\s*function\s*/.test(cmd)
|
755
|
+
});
|
756
|
+
} else if (depth < 0) {
|
757
|
+
// going... up.
|
758
|
+
var curr = self.lines.level.pop();
|
759
|
+
if (curr) {
|
760
|
+
var tmp = curr.depth + depth;
|
761
|
+
if (tmp < 0) {
|
762
|
+
//more to go, recurse
|
763
|
+
depth += curr.depth;
|
764
|
+
workIt();
|
765
|
+
} else if (tmp > 0) {
|
766
|
+
//remove and push back
|
767
|
+
curr.depth += depth;
|
768
|
+
self.lines.level.push(curr);
|
769
|
+
}
|
770
|
+
}
|
771
|
+
}
|
772
|
+
}());
|
773
|
+
}
|
774
|
+
|
775
|
+
// it is possible to determine a syntax error at this point.
|
776
|
+
// if the REPL still has a bufferedCommand and
|
777
|
+
// self.lines.level.length === 0
|
778
|
+
// TODO? keep a log of level so that any syntax breaking lines can
|
779
|
+
// be cleared on .break and in the case of a syntax error?
|
780
|
+
// TODO? if a log was kept, then I could clear the bufferedComand and
|
781
|
+
// eval these lines and throw the syntax error
|
782
|
+
} else {
|
783
|
+
self.lines.level = [];
|
784
|
+
}
|
785
|
+
};
|
786
|
+
|
787
|
+
function addStandardGlobals(completionGroups, filter) {
|
788
|
+
// Global object properties
|
789
|
+
// (http://www.ecma-international.org/publications/standards/Ecma-262.htm)
|
790
|
+
completionGroups.push(['NaN', 'Infinity', 'undefined',
|
791
|
+
'eval', 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'decodeURI',
|
792
|
+
'decodeURIComponent', 'encodeURI', 'encodeURIComponent',
|
793
|
+
'Object', 'Function', 'Array', 'String', 'Boolean', 'Number',
|
794
|
+
'Date', 'RegExp', 'Error', 'EvalError', 'RangeError',
|
795
|
+
'ReferenceError', 'SyntaxError', 'TypeError', 'URIError',
|
796
|
+
'Math', 'JSON']);
|
797
|
+
// Common keywords. Exclude for completion on the empty string, b/c
|
798
|
+
// they just get in the way.
|
799
|
+
if (filter) {
|
800
|
+
completionGroups.push(['break', 'case', 'catch', 'const',
|
801
|
+
'continue', 'debugger', 'default', 'delete', 'do', 'else',
|
802
|
+
'export', 'false', 'finally', 'for', 'function', 'if',
|
803
|
+
'import', 'in', 'instanceof', 'let', 'new', 'null', 'return',
|
804
|
+
'switch', 'this', 'throw', 'true', 'try', 'typeof', 'undefined',
|
805
|
+
'var', 'void', 'while', 'with', 'yield']);
|
806
|
+
}
|
807
|
+
}
|
808
|
+
|
809
|
+
function defineDefaultCommands(repl) {
|
810
|
+
// TODO remove me after 0.3.x
|
811
|
+
repl.defineCommand('break', {
|
812
|
+
help: 'Sometimes you get stuck, this gets you out',
|
813
|
+
action: function() {
|
814
|
+
this.bufferedCommand = '';
|
815
|
+
this.displayPrompt();
|
816
|
+
}
|
817
|
+
});
|
818
|
+
|
819
|
+
var clearMessage;
|
820
|
+
if (repl.useGlobal) {
|
821
|
+
clearMessage = 'Alias for .break';
|
822
|
+
} else {
|
823
|
+
clearMessage = 'Break, and also clear the local context';
|
824
|
+
}
|
825
|
+
repl.defineCommand('clear', {
|
826
|
+
help: clearMessage,
|
827
|
+
action: function() {
|
828
|
+
this.bufferedCommand = '';
|
829
|
+
if (!this.useGlobal) {
|
830
|
+
this.outputStream.write('Clearing context...\n');
|
831
|
+
this.resetContext();
|
832
|
+
}
|
833
|
+
this.displayPrompt();
|
834
|
+
}
|
835
|
+
});
|
836
|
+
|
837
|
+
repl.defineCommand('exit', {
|
838
|
+
help: 'Exit the repl',
|
839
|
+
action: function() {
|
840
|
+
this.rli.close();
|
841
|
+
}
|
842
|
+
});
|
843
|
+
|
844
|
+
repl.defineCommand('help', {
|
845
|
+
help: 'Show repl options',
|
846
|
+
action: function() {
|
847
|
+
var self = this;
|
848
|
+
Object.keys(this.commands).sort().forEach(function(name) {
|
849
|
+
var cmd = self.commands[name];
|
850
|
+
self.outputStream.write(name + '\t' + (cmd.help || '') + '\n');
|
851
|
+
});
|
852
|
+
this.displayPrompt();
|
853
|
+
}
|
854
|
+
});
|
855
|
+
|
856
|
+
repl.defineCommand('save', {
|
857
|
+
help: 'Save all evaluated commands in this REPL session to a file',
|
858
|
+
action: function(file) {
|
859
|
+
try {
|
860
|
+
fs.writeFileSync(file, this.lines.join('\n') + '\n');
|
861
|
+
this.outputStream.write('Session saved to:' + file + '\n');
|
862
|
+
} catch (e) {
|
863
|
+
this.outputStream.write('Failed to save:' + file + '\n');
|
864
|
+
}
|
865
|
+
this.displayPrompt();
|
866
|
+
}
|
867
|
+
});
|
868
|
+
|
869
|
+
repl.defineCommand('load', {
|
870
|
+
help: 'Load JS from a file into the REPL session',
|
871
|
+
action: function(file) {
|
872
|
+
try {
|
873
|
+
var stats = fs.statSync(file);
|
874
|
+
if (stats && stats.isFile()) {
|
875
|
+
var self = this;
|
876
|
+
var data = fs.readFileSync(file, 'utf8');
|
877
|
+
var lines = data.split('\n');
|
878
|
+
this.displayPrompt();
|
879
|
+
lines.forEach(function(line) {
|
880
|
+
if (line) {
|
881
|
+
self.rli.write(line + '\n');
|
882
|
+
}
|
883
|
+
});
|
884
|
+
}
|
885
|
+
} catch (e) {
|
886
|
+
this.outputStream.write('Failed to load:' + file + '\n');
|
887
|
+
}
|
888
|
+
this.displayPrompt();
|
889
|
+
}
|
890
|
+
});
|
891
|
+
}
|
892
|
+
|
893
|
+
|
894
|
+
function trimWhitespace(cmd) {
|
895
|
+
var trimmer = /^\s*(.+)\s*$/m,
|
896
|
+
matches = trimmer.exec(cmd);
|
897
|
+
|
898
|
+
if (matches && matches.length === 2) {
|
899
|
+
return matches[1];
|
900
|
+
}
|
901
|
+
return '';
|
902
|
+
}
|
903
|
+
|
904
|
+
|
905
|
+
function regexpEscape(s) {
|
906
|
+
return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
907
|
+
}
|
908
|
+
|
909
|
+
|
910
|
+
/**
|
911
|
+
* Converts commands that use var and function <name>() to use the
|
912
|
+
* local exports.context when evaled. This provides a local context
|
913
|
+
* on the REPL.
|
914
|
+
*
|
915
|
+
* @param {String} cmd The cmd to convert.
|
916
|
+
* @return {String} The converted command.
|
917
|
+
*/
|
918
|
+
REPLServer.prototype.convertToContext = function(cmd) {
|
919
|
+
var self = this, matches,
|
920
|
+
scopeVar = /^\s*var\s*([_\w\$]+)(.*)$/m,
|
921
|
+
scopeFunc = /^\s*function\s*([_\w\$]+)/;
|
922
|
+
|
923
|
+
// Replaces: var foo = "bar"; with: self.context.foo = bar;
|
924
|
+
matches = scopeVar.exec(cmd);
|
925
|
+
if (matches && matches.length === 3) {
|
926
|
+
return 'self.context.' + matches[1] + matches[2];
|
927
|
+
}
|
928
|
+
|
929
|
+
// Replaces: function foo() {}; with: foo = function foo() {};
|
930
|
+
matches = scopeFunc.exec(self.bufferedCommand);
|
931
|
+
if (matches && matches.length === 2) {
|
932
|
+
return matches[1] + ' = ' + self.bufferedCommand;
|
933
|
+
}
|
934
|
+
|
935
|
+
return cmd;
|
936
|
+
};
|
937
|
+
|
938
|
+
|
939
|
+
// If the error is that we've unexpectedly ended the input,
|
940
|
+
// then let the user try to recover by adding more input.
|
941
|
+
function isRecoverableError(e) {
|
942
|
+
return e &&
|
943
|
+
e.name === 'SyntaxError' &&
|
944
|
+
/^Unexpected end of input/.test(e.message);
|
945
|
+
}
|