macgyver 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +20 -0
  5. data/README.md +45 -0
  6. data/Rakefile +1 -0
  7. data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Growl +0 -0
  8. data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Growl +0 -0
  9. data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/Growl.h +5 -0
  10. data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlApplicationBridge.h +551 -0
  11. data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlDefines.h +341 -0
  12. data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Resources/Info.plist +40 -0
  13. data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/_CodeSignature/CodeResources +34 -0
  14. data/assets/MacGap.app/Contents/Info.plist +48 -0
  15. data/assets/MacGap.app/Contents/MacOS/MacGap +0 -0
  16. data/assets/MacGap.app/Contents/PkgInfo +1 -0
  17. data/assets/MacGap.app/Contents/Resources/_debugger.js +1718 -0
  18. data/assets/MacGap.app/Contents/Resources/_http_agent.js +310 -0
  19. data/assets/MacGap.app/Contents/Resources/_http_client.js +533 -0
  20. data/assets/MacGap.app/Contents/Resources/_http_common.js +222 -0
  21. data/assets/MacGap.app/Contents/Resources/_http_incoming.js +194 -0
  22. data/assets/MacGap.app/Contents/Resources/_http_outgoing.js +597 -0
  23. data/assets/MacGap.app/Contents/Resources/_http_server.js +510 -0
  24. data/assets/MacGap.app/Contents/Resources/_linklist.js +76 -0
  25. data/assets/MacGap.app/Contents/Resources/_stream_duplex.js +69 -0
  26. data/assets/MacGap.app/Contents/Resources/_stream_passthrough.js +41 -0
  27. data/assets/MacGap.app/Contents/Resources/_stream_readable.js +900 -0
  28. data/assets/MacGap.app/Contents/Resources/_stream_transform.js +204 -0
  29. data/assets/MacGap.app/Contents/Resources/_stream_writable.js +456 -0
  30. data/assets/MacGap.app/Contents/Resources/_tls_legacy.js +887 -0
  31. data/assets/MacGap.app/Contents/Resources/_tls_wrap.js +831 -0
  32. data/assets/MacGap.app/Contents/Resources/application.icns +0 -0
  33. data/assets/MacGap.app/Contents/Resources/assert.js +326 -0
  34. data/assets/MacGap.app/Contents/Resources/buffer.js +724 -0
  35. data/assets/MacGap.app/Contents/Resources/child_process.js +1107 -0
  36. data/assets/MacGap.app/Contents/Resources/cluster.js +613 -0
  37. data/assets/MacGap.app/Contents/Resources/console.js +108 -0
  38. data/assets/MacGap.app/Contents/Resources/constants.js +22 -0
  39. data/assets/MacGap.app/Contents/Resources/crypto.js +691 -0
  40. data/assets/MacGap.app/Contents/Resources/dgram.js +459 -0
  41. data/assets/MacGap.app/Contents/Resources/dns.js +274 -0
  42. data/assets/MacGap.app/Contents/Resources/domain.js +292 -0
  43. data/assets/MacGap.app/Contents/Resources/en.lproj/Credits.rtf +29 -0
  44. data/assets/MacGap.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
  45. data/assets/MacGap.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
  46. data/assets/MacGap.app/Contents/Resources/en.lproj/Window.nib +0 -0
  47. data/assets/MacGap.app/Contents/Resources/events.js +312 -0
  48. data/assets/MacGap.app/Contents/Resources/freelist.js +43 -0
  49. data/assets/MacGap.app/Contents/Resources/fs.js +1732 -0
  50. data/assets/MacGap.app/Contents/Resources/http.js +119 -0
  51. data/assets/MacGap.app/Contents/Resources/https.js +134 -0
  52. data/assets/MacGap.app/Contents/Resources/module.js +529 -0
  53. data/assets/MacGap.app/Contents/Resources/net.js +1378 -0
  54. data/assets/MacGap.app/Contents/Resources/nodelike.js +195 -0
  55. data/assets/MacGap.app/Contents/Resources/os.js +64 -0
  56. data/assets/MacGap.app/Contents/Resources/path.js +517 -0
  57. data/assets/MacGap.app/Contents/Resources/public/index.html +38 -0
  58. data/assets/MacGap.app/Contents/Resources/punycode.js +507 -0
  59. data/assets/MacGap.app/Contents/Resources/querystring.js +206 -0
  60. data/assets/MacGap.app/Contents/Resources/readline.js +1311 -0
  61. data/assets/MacGap.app/Contents/Resources/repl.js +945 -0
  62. data/assets/MacGap.app/Contents/Resources/smalloc.js +90 -0
  63. data/assets/MacGap.app/Contents/Resources/stream.js +127 -0
  64. data/assets/MacGap.app/Contents/Resources/string_decoder.js +189 -0
  65. data/assets/MacGap.app/Contents/Resources/sys.js +24 -0
  66. data/assets/MacGap.app/Contents/Resources/timers.js +568 -0
  67. data/assets/MacGap.app/Contents/Resources/tls.js +220 -0
  68. data/assets/MacGap.app/Contents/Resources/tty.js +129 -0
  69. data/assets/MacGap.app/Contents/Resources/url.js +693 -0
  70. data/assets/MacGap.app/Contents/Resources/util.js +688 -0
  71. data/assets/MacGap.app/Contents/Resources/vm.js +73 -0
  72. data/assets/MacGap.app/Contents/Resources/zlib.js +524 -0
  73. data/assets/index.html +38 -0
  74. data/bin/macgyver +104 -0
  75. data/macgyver.gemspec +19 -0
  76. data/test/public/index.html +27 -0
  77. metadata +121 -0
@@ -0,0 +1 @@
1
+ APPL????
@@ -0,0 +1,1718 @@
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
+ var util = require('util'),
23
+ path = require('path'),
24
+ net = require('net'),
25
+ vm = require('vm'),
26
+ repl = require('repl'),
27
+ inherits = util.inherits,
28
+ assert = require('assert'),
29
+ spawn = require('child_process').spawn;
30
+
31
+ exports.start = function(argv, stdin, stdout) {
32
+ argv || (argv = process.argv.slice(2));
33
+
34
+ if (argv.length < 1) {
35
+ console.error('Usage: node debug script.js');
36
+ process.exit(1);
37
+ }
38
+
39
+ // Setup input/output streams
40
+ stdin = stdin || process.stdin;
41
+ stdout = stdout || process.stdout;
42
+
43
+ var args = ['--debug-brk'].concat(argv),
44
+ interface_ = new Interface(stdin, stdout, args);
45
+
46
+ stdin.resume();
47
+
48
+ process.on('uncaughtException', function(e) {
49
+ console.error("There was an internal error in Node's debugger. " +
50
+ 'Please report this bug.');
51
+ console.error(e.message);
52
+ console.error(e.stack);
53
+ if (interface_.child) interface_.child.kill();
54
+ process.exit(1);
55
+ });
56
+ };
57
+
58
+ exports.port = 5858;
59
+
60
+
61
+ //
62
+ // Parser/Serializer for V8 debugger protocol
63
+ // http://code.google.com/p/v8/wiki/DebuggerProtocol
64
+ //
65
+ // Usage:
66
+ // p = new Protocol();
67
+ //
68
+ // p.onResponse = function(res) {
69
+ // // do stuff with response from V8
70
+ // };
71
+ //
72
+ // socket.setEncoding('utf8');
73
+ // socket.on('data', function(s) {
74
+ // // Pass strings into the protocol
75
+ // p.execute(s);
76
+ // });
77
+ //
78
+ //
79
+ function Protocol() {
80
+ this._newRes();
81
+ }
82
+ exports.Protocol = Protocol;
83
+
84
+
85
+ Protocol.prototype._newRes = function(raw) {
86
+ this.res = { raw: raw || '', headers: {} };
87
+ this.state = 'headers';
88
+ this.reqSeq = 1;
89
+ this.execute('');
90
+ };
91
+
92
+
93
+ Protocol.prototype.execute = function(d) {
94
+ var res = this.res;
95
+ res.raw += d;
96
+
97
+ switch (this.state) {
98
+ case 'headers':
99
+ var endHeaderIndex = res.raw.indexOf('\r\n\r\n');
100
+
101
+ if (endHeaderIndex < 0) break;
102
+
103
+ var rawHeader = res.raw.slice(0, endHeaderIndex);
104
+ var endHeaderByteIndex = Buffer.byteLength(rawHeader, 'utf8');
105
+ var lines = rawHeader.split('\r\n');
106
+ for (var i = 0; i < lines.length; i++) {
107
+ var kv = lines[i].split(/: +/);
108
+ res.headers[kv[0]] = kv[1];
109
+ }
110
+
111
+ this.contentLength = +res.headers['Content-Length'];
112
+ this.bodyStartByteIndex = endHeaderByteIndex + 4;
113
+
114
+ this.state = 'body';
115
+
116
+ var len = Buffer.byteLength(res.raw, 'utf8');
117
+ if (len - this.bodyStartByteIndex < this.contentLength) {
118
+ break;
119
+ }
120
+ // pass thru
121
+ case 'body':
122
+ var resRawByteLength = Buffer.byteLength(res.raw, 'utf8');
123
+
124
+ if (resRawByteLength - this.bodyStartByteIndex >= this.contentLength) {
125
+ var buf = new Buffer(resRawByteLength);
126
+ buf.write(res.raw, 0, resRawByteLength, 'utf8');
127
+ res.body =
128
+ buf.slice(this.bodyStartByteIndex,
129
+ this.bodyStartByteIndex +
130
+ this.contentLength).toString('utf8');
131
+ // JSON parse body?
132
+ res.body = res.body.length ? JSON.parse(res.body) : {};
133
+
134
+ // Done!
135
+ this.onResponse(res);
136
+
137
+ this._newRes(buf.slice(this.bodyStartByteIndex +
138
+ this.contentLength).toString('utf8'));
139
+ }
140
+ break;
141
+
142
+ default:
143
+ throw new Error('Unknown state');
144
+ break;
145
+ }
146
+ };
147
+
148
+
149
+ Protocol.prototype.serialize = function(req) {
150
+ req.type = 'request';
151
+ req.seq = this.reqSeq++;
152
+ var json = JSON.stringify(req);
153
+ return 'Content-Length: ' + Buffer.byteLength(json, 'utf8') +
154
+ '\r\n\r\n' + json;
155
+ };
156
+
157
+
158
+ var NO_FRAME = -1;
159
+
160
+ function Client() {
161
+ net.Stream.call(this);
162
+ var protocol = this.protocol = new Protocol(this);
163
+ this._reqCallbacks = [];
164
+ var socket = this;
165
+
166
+ this.currentFrame = NO_FRAME;
167
+ this.currentSourceLine = -1;
168
+ this.currentSource = null;
169
+ this.handles = {};
170
+ this.scripts = {};
171
+ this.breakpoints = [];
172
+
173
+ // Note that 'Protocol' requires strings instead of Buffers.
174
+ socket.setEncoding('utf8');
175
+ socket.on('data', function(d) {
176
+ protocol.execute(d);
177
+ });
178
+
179
+ protocol.onResponse = this._onResponse.bind(this);
180
+ }
181
+ inherits(Client, net.Stream);
182
+ exports.Client = Client;
183
+
184
+
185
+ Client.prototype._addHandle = function(desc) {
186
+ if (!util.isObject(desc) || !util.isNumber(desc.handle)) {
187
+ return;
188
+ }
189
+
190
+ this.handles[desc.handle] = desc;
191
+
192
+ if (desc.type == 'script') {
193
+ this._addScript(desc);
194
+ }
195
+ };
196
+
197
+
198
+ var natives = process.binding('natives');
199
+
200
+
201
+ Client.prototype._addScript = function(desc) {
202
+ this.scripts[desc.id] = desc;
203
+ if (desc.name) {
204
+ desc.isNative = (desc.name.replace('.js', '') in natives) ||
205
+ desc.name == 'node.js';
206
+ }
207
+ };
208
+
209
+
210
+ Client.prototype._removeScript = function(desc) {
211
+ this.scripts[desc.id] = undefined;
212
+ };
213
+
214
+
215
+ Client.prototype._onResponse = function(res) {
216
+ var cb,
217
+ index = -1;
218
+
219
+ this._reqCallbacks.some(function(fn, i) {
220
+ if (fn.request_seq == res.body.request_seq) {
221
+ cb = fn;
222
+ index = i;
223
+ return true;
224
+ }
225
+ });
226
+
227
+ var self = this;
228
+ var handled = false;
229
+
230
+ if (res.headers.Type == 'connect') {
231
+ // Request a list of scripts for our own storage.
232
+ self.reqScripts();
233
+ self.emit('ready');
234
+ handled = true;
235
+
236
+ } else if (res.body && res.body.event == 'break') {
237
+ this.emit('break', res.body);
238
+ handled = true;
239
+
240
+ } else if (res.body && res.body.event == 'exception') {
241
+ this.emit('exception', res.body);
242
+ handled = true;
243
+
244
+ } else if (res.body && res.body.event == 'afterCompile') {
245
+ this._addHandle(res.body.body.script);
246
+ handled = true;
247
+
248
+ } else if (res.body && res.body.event == 'scriptCollected') {
249
+ // ???
250
+ this._removeScript(res.body.body.script);
251
+ handled = true;
252
+
253
+ }
254
+
255
+ if (cb) {
256
+ this._reqCallbacks.splice(index, 1);
257
+ handled = true;
258
+
259
+ var err = res.success === false && (res.message || true) ||
260
+ res.body.success === false && (res.body.message || true);
261
+ cb(err, res.body && res.body.body || res.body, res);
262
+ }
263
+
264
+ if (!handled) this.emit('unhandledResponse', res.body);
265
+ };
266
+
267
+
268
+ Client.prototype.req = function(req, cb) {
269
+ this.write(this.protocol.serialize(req));
270
+ cb.request_seq = req.seq;
271
+ this._reqCallbacks.push(cb);
272
+ };
273
+
274
+
275
+ Client.prototype.reqVersion = function(cb) {
276
+ cb = cb || function() {};
277
+ this.req({ command: 'version' } , function(err, body, res) {
278
+ if (err) return cb(err);
279
+ cb(null, res.body.body.V8Version, res.body.running);
280
+ });
281
+ };
282
+
283
+
284
+ Client.prototype.reqLookup = function(refs, cb) {
285
+ var self = this;
286
+
287
+ // TODO: We have a cache of handle's we've already seen in this.handles
288
+ // This can be used if we're careful.
289
+ var req = {
290
+ command: 'lookup',
291
+ arguments: {
292
+ handles: refs
293
+ }
294
+ };
295
+
296
+ cb = cb || function() {};
297
+ this.req(req, function(err, res) {
298
+ if (err) return cb(err);
299
+ for (var ref in res) {
300
+ if (util.isObject(res[ref])) {
301
+ self._addHandle(res[ref]);
302
+ }
303
+ }
304
+
305
+ cb(null, res);
306
+ });
307
+ };
308
+
309
+ Client.prototype.reqScopes = function(cb) {
310
+ var self = this,
311
+ req = {
312
+ command: 'scopes',
313
+ arguments: {}
314
+ };
315
+
316
+ cb = cb || function() {};
317
+ this.req(req, function(err, res) {
318
+ if (err) return cb(err);
319
+ var refs = res.scopes.map(function(scope) {
320
+ return scope.object.ref;
321
+ });
322
+
323
+ self.reqLookup(refs, function(err, res) {
324
+ if (err) return cb(err);
325
+
326
+ var globals = Object.keys(res).map(function(key) {
327
+ return res[key].properties.map(function(prop) {
328
+ return prop.name;
329
+ });
330
+ });
331
+
332
+ cb(null, globals.reverse());
333
+ });
334
+ });
335
+ };
336
+
337
+ // This is like reqEval, except it will look up the expression in each of the
338
+ // scopes associated with the current frame.
339
+ Client.prototype.reqEval = function(expression, cb) {
340
+ var self = this;
341
+
342
+ if (this.currentFrame == NO_FRAME) {
343
+ // Only need to eval in global scope.
344
+ this.reqFrameEval(expression, NO_FRAME, cb);
345
+ return;
346
+ }
347
+
348
+ cb = cb || function() {};
349
+ // Otherwise we need to get the current frame to see which scopes it has.
350
+ this.reqBacktrace(function(err, bt) {
351
+ if (err || !bt.frames) {
352
+ // ??
353
+ return cb(null, {});
354
+ }
355
+
356
+ var frame = bt.frames[self.currentFrame];
357
+
358
+ var evalFrames = frame.scopes.map(function(s) {
359
+ if (!s) return;
360
+ var x = bt.frames[s.index];
361
+ if (!x) return;
362
+ return x.index;
363
+ });
364
+
365
+ self._reqFramesEval(expression, evalFrames, cb);
366
+ });
367
+ };
368
+
369
+
370
+ // Finds the first scope in the array in which the expression evals.
371
+ Client.prototype._reqFramesEval = function(expression, evalFrames, cb) {
372
+ if (evalFrames.length == 0) {
373
+ // Just eval in global scope.
374
+ this.reqFrameEval(expression, NO_FRAME, cb);
375
+ return;
376
+ }
377
+
378
+ var self = this;
379
+ var i = evalFrames.shift();
380
+
381
+ cb = cb || function() {};
382
+ this.reqFrameEval(expression, i, function(err, res) {
383
+ if (!err) return cb(null, res);
384
+ self._reqFramesEval(expression, evalFrames, cb);
385
+ });
386
+ };
387
+
388
+
389
+ Client.prototype.reqFrameEval = function(expression, frame, cb) {
390
+ var self = this;
391
+ var req = {
392
+ command: 'evaluate',
393
+ arguments: { expression: expression }
394
+ };
395
+
396
+ if (frame == NO_FRAME) {
397
+ req.arguments.global = true;
398
+ } else {
399
+ req.arguments.frame = frame;
400
+ }
401
+
402
+ cb = cb || function() {};
403
+ this.req(req, function(err, res) {
404
+ if (!err) self._addHandle(res);
405
+ cb(err, res);
406
+ });
407
+ };
408
+
409
+
410
+ // reqBacktrace(cb)
411
+ // TODO: from, to, bottom
412
+ Client.prototype.reqBacktrace = function(cb) {
413
+ this.req({ command: 'backtrace', arguments: { inlineRefs: true } } , cb);
414
+ };
415
+
416
+
417
+ // reqSetExceptionBreak(type, cb)
418
+ // TODO: from, to, bottom
419
+ Client.prototype.reqSetExceptionBreak = function(type, cb) {
420
+ this.req({
421
+ command: 'setexceptionbreak',
422
+ arguments: { type: type, enabled: true }
423
+ }, cb);
424
+ };
425
+
426
+
427
+ // Returns an array of objects like this:
428
+ //
429
+ // { handle: 11,
430
+ // type: 'script',
431
+ // name: 'node.js',
432
+ // id: 14,
433
+ // lineOffset: 0,
434
+ // columnOffset: 0,
435
+ // lineCount: 562,
436
+ // sourceStart: '(function(process) {\n\n ',
437
+ // sourceLength: 15939,
438
+ // scriptType: 2,
439
+ // compilationType: 0,
440
+ // context: { ref: 10 },
441
+ // text: 'node.js (lines: 562)' }
442
+ //
443
+ Client.prototype.reqScripts = function(cb) {
444
+ var self = this;
445
+ cb = cb || function() {};
446
+
447
+ this.req({ command: 'scripts' } , function(err, res) {
448
+ if (err) return cb(err);
449
+
450
+ for (var i = 0; i < res.length; i++) {
451
+ self._addHandle(res[i]);
452
+ }
453
+ cb(null);
454
+ });
455
+ };
456
+
457
+
458
+ Client.prototype.reqContinue = function(cb) {
459
+ this.currentFrame = NO_FRAME;
460
+ this.req({ command: 'continue' }, cb);
461
+ };
462
+
463
+ Client.prototype.listbreakpoints = function(cb) {
464
+ this.req({ command: 'listbreakpoints' }, cb);
465
+ };
466
+
467
+ Client.prototype.setBreakpoint = function(req, cb) {
468
+ req = {
469
+ command: 'setbreakpoint',
470
+ arguments: req
471
+ };
472
+
473
+ this.req(req, cb);
474
+ };
475
+
476
+ Client.prototype.clearBreakpoint = function(req, cb) {
477
+ var req = {
478
+ command: 'clearbreakpoint',
479
+ arguments: req
480
+ };
481
+
482
+ this.req(req, cb);
483
+ };
484
+
485
+ Client.prototype.reqSource = function(from, to, cb) {
486
+ var req = {
487
+ command: 'source',
488
+ fromLine: from,
489
+ toLine: to
490
+ };
491
+
492
+ this.req(req, cb);
493
+ };
494
+
495
+
496
+ // client.next(1, cb);
497
+ Client.prototype.step = function(action, count, cb) {
498
+ var req = {
499
+ command: 'continue',
500
+ arguments: { stepaction: action, stepcount: count }
501
+ };
502
+
503
+ this.currentFrame = NO_FRAME;
504
+ this.req(req, cb);
505
+ };
506
+
507
+
508
+ Client.prototype.mirrorObject = function(handle, depth, cb) {
509
+ var self = this;
510
+
511
+ var val;
512
+
513
+ if (handle.type === 'object') {
514
+ // The handle looks something like this:
515
+ // { handle: 8,
516
+ // type: 'object',
517
+ // className: 'Object',
518
+ // constructorFunction: { ref: 9 },
519
+ // protoObject: { ref: 4 },
520
+ // prototypeObject: { ref: 2 },
521
+ // properties: [ { name: 'hello', propertyType: 1, ref: 10 } ],
522
+ // text: '#<an Object>' }
523
+
524
+ // For now ignore the className and constructor and prototype.
525
+ // TJ's method of object inspection would probably be good for this:
526
+ // https://groups.google.com/forum/?pli=1#!topic/nodejs-dev/4gkWBOimiOg
527
+
528
+ var propertyRefs = handle.properties.map(function(p) {
529
+ return p.ref;
530
+ });
531
+
532
+ cb = cb || function() {};
533
+ this.reqLookup(propertyRefs, function(err, res) {
534
+ if (err) {
535
+ console.error('problem with reqLookup');
536
+ cb(null, handle);
537
+ return;
538
+ }
539
+
540
+ var mirror,
541
+ waiting = 1;
542
+
543
+ if (handle.className == 'Array') {
544
+ mirror = [];
545
+ } else if (handle.className == 'Date') {
546
+ mirror = new Date(handle.value);
547
+ } else {
548
+ mirror = {};
549
+ }
550
+
551
+
552
+ var keyValues = [];
553
+ handle.properties.forEach(function(prop, i) {
554
+ var value = res[prop.ref];
555
+ var mirrorValue;
556
+ if (value) {
557
+ mirrorValue = value.value ? value.value : value.text;
558
+ } else {
559
+ mirrorValue = '[?]';
560
+ }
561
+
562
+
563
+ if (util.isArray(mirror) && !util.isNumber(prop.name)) {
564
+ // Skip the 'length' property.
565
+ return;
566
+ }
567
+
568
+ keyValues[i] = {
569
+ name: prop.name,
570
+ value: mirrorValue
571
+ };
572
+ if (value && value.handle && depth > 0) {
573
+ waiting++;
574
+ self.mirrorObject(value, depth - 1, function(err, result) {
575
+ if (!err) keyValues[i].value = result;
576
+ waitForOthers();
577
+ });
578
+ }
579
+ });
580
+
581
+ waitForOthers();
582
+ function waitForOthers() {
583
+ if (--waiting === 0 && cb) {
584
+ keyValues.forEach(function(kv) {
585
+ mirror[kv.name] = kv.value;
586
+ });
587
+ cb(null, mirror);
588
+ }
589
+ };
590
+ });
591
+ return;
592
+ } else if (handle.type === 'function') {
593
+ val = function() {};
594
+ } else if (handle.type === 'null') {
595
+ val = null;
596
+ } else if (!util.isUndefined(handle.value)) {
597
+ val = handle.value;
598
+ } else if (handle.type === 'undefined') {
599
+ val = undefined;
600
+ } else {
601
+ val = handle;
602
+ }
603
+ process.nextTick(function() {
604
+ cb(null, val);
605
+ });
606
+ };
607
+
608
+
609
+ Client.prototype.fullTrace = function(cb) {
610
+ var self = this;
611
+
612
+ cb = cb || function() {};
613
+ this.reqBacktrace(function(err, trace) {
614
+ if (err) return cb(err);
615
+ if (trace.totalFrames <= 0) return cb(Error('No frames'));
616
+
617
+ var refs = [];
618
+
619
+ for (var i = 0; i < trace.frames.length; i++) {
620
+ var frame = trace.frames[i];
621
+ // looks like this:
622
+ // { type: 'frame',
623
+ // index: 0,
624
+ // receiver: { ref: 1 },
625
+ // func: { ref: 0 },
626
+ // script: { ref: 7 },
627
+ // constructCall: false,
628
+ // atReturn: false,
629
+ // debuggerFrame: false,
630
+ // arguments: [],
631
+ // locals: [],
632
+ // position: 160,
633
+ // line: 7,
634
+ // column: 2,
635
+ // sourceLineText: ' debugger;',
636
+ // scopes: [ { type: 1, index: 0 }, { type: 0, index: 1 } ],
637
+ // text: '#00 blah() /home/ryan/projects/node/test-debug.js l...' }
638
+ refs.push(frame.script.ref);
639
+ refs.push(frame.func.ref);
640
+ refs.push(frame.receiver.ref);
641
+ }
642
+
643
+ self.reqLookup(refs, function(err, res) {
644
+ if (err) return cb(err);
645
+
646
+ for (var i = 0; i < trace.frames.length; i++) {
647
+ var frame = trace.frames[i];
648
+ frame.script = res[frame.script.ref];
649
+ frame.func = res[frame.func.ref];
650
+ frame.receiver = res[frame.receiver.ref];
651
+ }
652
+
653
+ cb(null, trace);
654
+ });
655
+ });
656
+ };
657
+
658
+
659
+
660
+
661
+
662
+
663
+ var commands = [
664
+ [
665
+ 'run (r)',
666
+ 'cont (c)',
667
+ 'next (n)',
668
+ 'step (s)',
669
+ 'out (o)',
670
+ 'backtrace (bt)',
671
+ 'setBreakpoint (sb)',
672
+ 'clearBreakpoint (cb)'
673
+ ],
674
+ [
675
+ 'watch',
676
+ 'unwatch',
677
+ 'watchers',
678
+ 'repl',
679
+ 'restart',
680
+ 'kill',
681
+ 'list',
682
+ 'scripts',
683
+ 'breakOnException',
684
+ 'breakpoints',
685
+ 'version'
686
+ ]
687
+ ];
688
+
689
+
690
+ var helpMessage = 'Commands: ' + commands.map(function(group) {
691
+ return group.join(', ');
692
+ }).join(',\n');
693
+
694
+
695
+ function SourceUnderline(sourceText, position, repl) {
696
+ if (!sourceText) return '';
697
+
698
+ var head = sourceText.slice(0, position),
699
+ tail = sourceText.slice(position);
700
+
701
+ // Colourize char if stdout supports colours
702
+ if (repl.useColors) {
703
+ tail = tail.replace(/(.+?)([^\w]|$)/, '\u001b[32m$1\u001b[39m$2');
704
+ }
705
+
706
+ // Return source line with coloured char at `position`
707
+ return [
708
+ head,
709
+ tail
710
+ ].join('');
711
+ }
712
+
713
+
714
+ function SourceInfo(body) {
715
+ var result = body.exception ? 'exception in ' : 'break in ';
716
+
717
+ if (body.script) {
718
+ if (body.script.name) {
719
+ var name = body.script.name,
720
+ dir = path.resolve() + '/';
721
+
722
+ // Change path to relative, if possible
723
+ if (name.indexOf(dir) === 0) {
724
+ name = name.slice(dir.length);
725
+ }
726
+
727
+ result += name;
728
+ } else {
729
+ result += '[unnamed]';
730
+ }
731
+ }
732
+ result += ':';
733
+ result += body.sourceLine + 1;
734
+
735
+ if (body.exception) result += '\n' + body.exception.text;
736
+
737
+ return result;
738
+ }
739
+
740
+ // This class is the repl-enabled debugger interface which is invoked on
741
+ // "node debug"
742
+ function Interface(stdin, stdout, args) {
743
+ var self = this;
744
+
745
+ this.stdin = stdin;
746
+ this.stdout = stdout;
747
+ this.args = args;
748
+
749
+ // Two eval modes are available: controlEval and debugEval
750
+ // But controlEval is used by default
751
+ var opts = {
752
+ prompt: 'debug> ',
753
+ input: this.stdin,
754
+ output: this.stdout,
755
+ eval: this.controlEval.bind(this),
756
+ useGlobal: false,
757
+ ignoreUndefined: true
758
+ };
759
+ if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
760
+ opts.terminal = false;
761
+ } else if (parseInt(process.env['NODE_FORCE_READLINE'], 10)) {
762
+ opts.terminal = true;
763
+
764
+ // Emulate Ctrl+C if we're emulating terminal
765
+ if (!this.stdout.isTTY) {
766
+ process.on('SIGINT', function() {
767
+ self.repl.rli.emit('SIGINT');
768
+ });
769
+ }
770
+ }
771
+ if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
772
+ opts.useColors = false;
773
+ }
774
+ this.repl = repl.start(opts);
775
+
776
+ // Do not print useless warning
777
+ repl._builtinLibs.splice(repl._builtinLibs.indexOf('repl'), 1);
778
+
779
+ // Kill child process when main process dies
780
+ this.repl.on('exit', function() {
781
+ process.exit(0);
782
+ });
783
+
784
+ // Handle all possible exits
785
+ process.on('exit', this.killChild.bind(this));
786
+ process.once('SIGTERM', process.exit.bind(process, 0));
787
+ process.once('SIGHUP', process.exit.bind(process, 0));
788
+
789
+ var proto = Interface.prototype,
790
+ ignored = ['pause', 'resume', 'exitRepl', 'handleBreak',
791
+ 'requireConnection', 'killChild', 'trySpawn',
792
+ 'controlEval', 'debugEval', 'print', 'childPrint',
793
+ 'clearline'],
794
+ shortcut = {
795
+ 'run': 'r',
796
+ 'cont': 'c',
797
+ 'next': 'n',
798
+ 'step': 's',
799
+ 'out': 'o',
800
+ 'backtrace': 'bt',
801
+ 'setBreakpoint': 'sb',
802
+ 'clearBreakpoint': 'cb',
803
+ 'pause_': 'pause'
804
+ };
805
+
806
+ function defineProperty(key, protoKey) {
807
+ // Check arity
808
+ var fn = proto[protoKey].bind(self);
809
+
810
+ if (proto[protoKey].length === 0) {
811
+ Object.defineProperty(self.repl.context, key, {
812
+ get: fn,
813
+ enumerable: true,
814
+ configurable: false
815
+ });
816
+ } else {
817
+ self.repl.context[key] = fn;
818
+ }
819
+ };
820
+
821
+ // Copy all prototype methods in repl context
822
+ // Setup them as getters if possible
823
+ for (var i in proto) {
824
+ if (Object.prototype.hasOwnProperty.call(proto, i) &&
825
+ ignored.indexOf(i) === -1) {
826
+ defineProperty(i, i);
827
+ if (shortcut[i]) defineProperty(shortcut[i], i);
828
+ }
829
+ }
830
+
831
+ this.killed = false;
832
+ this.waiting = null;
833
+ this.paused = 0;
834
+ this.context = this.repl.context;
835
+ this.history = {
836
+ debug: [],
837
+ control: []
838
+ };
839
+ this.breakpoints = [];
840
+ this._watchers = [];
841
+
842
+ // Run script automatically
843
+ this.pause();
844
+
845
+ // XXX Need to figure out why we need this delay
846
+ setTimeout(function() {
847
+
848
+ self.run(function() {
849
+ self.resume();
850
+ });
851
+ }, 10);
852
+ }
853
+
854
+
855
+ // Stream control
856
+
857
+
858
+ Interface.prototype.pause = function() {
859
+ if (this.killed || this.paused++ > 0) return this;
860
+ this.repl.rli.pause();
861
+ this.stdin.pause();
862
+ return this;
863
+ };
864
+
865
+ Interface.prototype.resume = function(silent) {
866
+ if (this.killed || this.paused === 0 || --this.paused !== 0) return this;
867
+ this.repl.rli.resume();
868
+ if (silent !== true) {
869
+ this.repl.displayPrompt();
870
+ }
871
+ this.stdin.resume();
872
+
873
+ if (this.waiting) {
874
+ this.waiting();
875
+ this.waiting = null;
876
+ }
877
+ return this;
878
+ };
879
+
880
+
881
+ // Clear current line
882
+ Interface.prototype.clearline = function() {
883
+ if (this.stdout.isTTY) {
884
+ this.stdout.cursorTo(0);
885
+ this.stdout.clearLine(1);
886
+ } else {
887
+ this.stdout.write('\b');
888
+ }
889
+ };
890
+
891
+ // Print text to output stream
892
+ Interface.prototype.print = function(text, oneline) {
893
+ if (this.killed) return;
894
+ this.clearline();
895
+
896
+ this.stdout.write(util.isString(text) ? text : util.inspect(text));
897
+
898
+ if (oneline !== true) {
899
+ this.stdout.write('\n');
900
+ }
901
+ };
902
+
903
+ // Format and print text from child process
904
+ Interface.prototype.childPrint = function(text) {
905
+ this.print(text.toString().split(/\r\n|\r|\n/g).filter(function(chunk) {
906
+ return chunk;
907
+ }).map(function(chunk) {
908
+ return '< ' + chunk;
909
+ }).join('\n'));
910
+ this.repl.displayPrompt(true);
911
+ };
912
+
913
+ // Errors formatting
914
+ Interface.prototype.error = function(text) {
915
+ this.print(text);
916
+ this.resume();
917
+ };
918
+
919
+
920
+ // Debugger's `break` event handler
921
+ Interface.prototype.handleBreak = function(r) {
922
+ var self = this;
923
+
924
+ this.pause();
925
+
926
+ // Save execution context's data
927
+ this.client.currentSourceLine = r.sourceLine;
928
+ this.client.currentSourceLineText = r.sourceLineText;
929
+ this.client.currentSourceColumn = r.sourceColumn;
930
+ this.client.currentFrame = 0;
931
+ this.client.currentScript = r.script && r.script.name;
932
+
933
+ // Print break data
934
+ this.print(SourceInfo(r));
935
+
936
+ // Show watchers' values
937
+ this.watchers(true, function(err) {
938
+ if (err) return self.error(err);
939
+
940
+ // And list source
941
+ self.list(2);
942
+
943
+ self.resume(true);
944
+ });
945
+ };
946
+
947
+
948
+ // Internal method for checking connection state
949
+ Interface.prototype.requireConnection = function() {
950
+ if (!this.client) {
951
+ this.error('App isn\'t running... Try `run` instead');
952
+ return false;
953
+ }
954
+ return true;
955
+ };
956
+
957
+
958
+ // Evals
959
+
960
+ // Used for debugger's commands evaluation and execution
961
+ Interface.prototype.controlEval = function(code, context, filename, callback) {
962
+ try {
963
+ // Repeat last command if empty line are going to be evaluated
964
+ if (this.repl.rli.history && this.repl.rli.history.length > 0) {
965
+ if (code === '\n') {
966
+ code = this.repl.rli.history[0] + '\n';
967
+ }
968
+ }
969
+
970
+ var result = vm.runInContext(code, context, filename);
971
+
972
+ // Repl should not ask for next command
973
+ // if current one was asynchronous.
974
+ if (this.paused === 0) return callback(null, result);
975
+
976
+ // Add a callback for asynchronous command
977
+ // (it will be automatically invoked by .resume() method
978
+ this.waiting = function() {
979
+ callback(null, result);
980
+ };
981
+ } catch (e) {
982
+ callback(e);
983
+ }
984
+ };
985
+
986
+ // Used for debugger's remote evaluation (`repl`) commands
987
+ Interface.prototype.debugEval = function(code, context, filename, callback) {
988
+ if (!this.requireConnection()) return;
989
+
990
+ var self = this,
991
+ client = this.client;
992
+
993
+ // Repl asked for scope variables
994
+ if (code === '.scope') {
995
+ client.reqScopes(callback);
996
+ return;
997
+ }
998
+
999
+ var frame = client.currentFrame === NO_FRAME ? frame : undefined;
1000
+
1001
+ self.pause();
1002
+
1003
+ // Request remote evaluation globally or in current frame
1004
+ client.reqFrameEval(code, frame, function(err, res) {
1005
+ if (err) {
1006
+ callback(err);
1007
+ self.resume(true);
1008
+ return;
1009
+ }
1010
+
1011
+ // Request object by handles (and it's sub-properties)
1012
+ client.mirrorObject(res, 3, function(err, mirror) {
1013
+ callback(null, mirror);
1014
+ self.resume(true);
1015
+ });
1016
+ });
1017
+ };
1018
+
1019
+
1020
+ // Utils
1021
+
1022
+ // Adds spaces and prefix to number
1023
+ // maxN is a maximum number we should have space for
1024
+ function leftPad(n, prefix, maxN) {
1025
+ var s = n.toString(),
1026
+ nchars = Math.max(2, String(maxN).length) + 1,
1027
+ nspaces = nchars - s.length - 1;
1028
+
1029
+ for (var i = 0; i < nspaces; i++) {
1030
+ prefix += ' ';
1031
+ }
1032
+
1033
+ return prefix + s;
1034
+ }
1035
+
1036
+
1037
+ // Commands
1038
+
1039
+
1040
+ // Print help message
1041
+ Interface.prototype.help = function() {
1042
+ this.print(helpMessage);
1043
+ };
1044
+
1045
+
1046
+ // Run script
1047
+ Interface.prototype.run = function() {
1048
+ var callback = arguments[0];
1049
+
1050
+ if (this.child) {
1051
+ this.error('App is already running... Try `restart` instead');
1052
+ callback && callback(true);
1053
+ } else {
1054
+ this.trySpawn(callback);
1055
+ }
1056
+ };
1057
+
1058
+
1059
+ // Restart script
1060
+ Interface.prototype.restart = function() {
1061
+ if (!this.requireConnection()) return;
1062
+
1063
+ var self = this;
1064
+
1065
+ self.pause();
1066
+ self.killChild();
1067
+
1068
+ // XXX need to wait a little bit for the restart to work?
1069
+ setTimeout(function() {
1070
+ self.trySpawn();
1071
+ self.resume();
1072
+ }, 1000);
1073
+ };
1074
+
1075
+
1076
+ // Print version
1077
+ Interface.prototype.version = function() {
1078
+ if (!this.requireConnection()) return;
1079
+
1080
+ var self = this;
1081
+
1082
+ this.pause();
1083
+ this.client.reqVersion(function(err, v) {
1084
+ if (err) {
1085
+ self.error(err);
1086
+ } else {
1087
+ self.print(v);
1088
+ }
1089
+ self.resume();
1090
+ });
1091
+ };
1092
+
1093
+ // List source code
1094
+ Interface.prototype.list = function(delta) {
1095
+ if (!this.requireConnection()) return;
1096
+
1097
+ delta || (delta = 5);
1098
+
1099
+ var self = this,
1100
+ client = this.client,
1101
+ from = client.currentSourceLine - delta + 1,
1102
+ to = client.currentSourceLine + delta + 1;
1103
+
1104
+ self.pause();
1105
+ client.reqSource(from, to, function(err, res) {
1106
+ if (err || !res) {
1107
+ self.error('You can\'t list source code right now');
1108
+ self.resume();
1109
+ return;
1110
+ }
1111
+
1112
+ var lines = res.source.split('\n');
1113
+ for (var i = 0; i < lines.length; i++) {
1114
+ var lineno = res.fromLine + i + 1;
1115
+ if (lineno < from || lineno > to) continue;
1116
+
1117
+ var current = lineno == 1 + client.currentSourceLine,
1118
+ breakpoint = client.breakpoints.some(function(bp) {
1119
+ return bp.script === client.currentScript &&
1120
+ bp.line == lineno;
1121
+ });
1122
+
1123
+ if (lineno == 1) {
1124
+ // The first line needs to have the module wrapper filtered out of
1125
+ // it.
1126
+ var wrapper = require('module').wrapper[0];
1127
+ lines[i] = lines[i].slice(wrapper.length);
1128
+
1129
+ client.currentSourceColumn -= wrapper.length;
1130
+ }
1131
+
1132
+ // Highlight executing statement
1133
+ var line;
1134
+ if (current) {
1135
+ line = SourceUnderline(lines[i],
1136
+ client.currentSourceColumn,
1137
+ self.repl);
1138
+ } else {
1139
+ line = lines[i];
1140
+ }
1141
+
1142
+ var prefixChar = ' ';
1143
+ if (current) {
1144
+ prefixChar = '>';
1145
+ } else if (breakpoint) {
1146
+ prefixChar = '*';
1147
+ }
1148
+
1149
+ self.print(leftPad(lineno, prefixChar, to) + ' ' + line);
1150
+ }
1151
+ self.resume();
1152
+ });
1153
+ };
1154
+
1155
+ // Print backtrace
1156
+ Interface.prototype.backtrace = function() {
1157
+ if (!this.requireConnection()) return;
1158
+
1159
+ var self = this,
1160
+ client = this.client;
1161
+
1162
+ self.pause();
1163
+ client.fullTrace(function(err, bt) {
1164
+ if (err) {
1165
+ self.error('Can\'t request backtrace now');
1166
+ self.resume();
1167
+ return;
1168
+ }
1169
+
1170
+ if (bt.totalFrames == 0) {
1171
+ self.print('(empty stack)');
1172
+ } else {
1173
+ var trace = [],
1174
+ firstFrameNative = bt.frames[0].script.isNative;
1175
+
1176
+ for (var i = 0; i < bt.frames.length; i++) {
1177
+ var frame = bt.frames[i];
1178
+ if (!firstFrameNative && frame.script.isNative) break;
1179
+
1180
+ var text = '#' + i + ' ';
1181
+ if (frame.func.inferredName && frame.func.inferredName.length > 0) {
1182
+ text += frame.func.inferredName + ' ';
1183
+ }
1184
+ text += path.basename(frame.script.name) + ':';
1185
+ text += (frame.line + 1) + ':' + (frame.column + 1);
1186
+
1187
+ trace.push(text);
1188
+ }
1189
+
1190
+ self.print(trace.join('\n'));
1191
+ }
1192
+
1193
+ self.resume();
1194
+ });
1195
+ };
1196
+
1197
+
1198
+ // First argument tells if it should display internal node scripts or not
1199
+ // (available only for internal debugger's functions)
1200
+ Interface.prototype.scripts = function() {
1201
+ if (!this.requireConnection()) return;
1202
+
1203
+ var client = this.client,
1204
+ displayNatives = arguments[0] || false,
1205
+ scripts = [];
1206
+
1207
+ this.pause();
1208
+ for (var id in client.scripts) {
1209
+ var script = client.scripts[id];
1210
+ if (util.isObject(script) && script.name) {
1211
+ if (displayNatives ||
1212
+ script.name == client.currentScript ||
1213
+ !script.isNative) {
1214
+ scripts.push(
1215
+ (script.name == client.currentScript ? '* ' : ' ') +
1216
+ id + ': ' +
1217
+ path.basename(script.name)
1218
+ );
1219
+ }
1220
+ }
1221
+ }
1222
+ this.print(scripts.join('\n'));
1223
+ this.resume();
1224
+ };
1225
+
1226
+
1227
+ // Continue execution of script
1228
+ Interface.prototype.cont = function() {
1229
+ if (!this.requireConnection()) return;
1230
+ this.pause();
1231
+
1232
+ var self = this;
1233
+ this.client.reqContinue(function(err) {
1234
+ if (err) self.error(err);
1235
+ self.resume();
1236
+ });
1237
+ };
1238
+
1239
+
1240
+ // Step commands generator
1241
+ Interface.stepGenerator = function(type, count) {
1242
+ return function() {
1243
+ if (!this.requireConnection()) return;
1244
+
1245
+ var self = this;
1246
+
1247
+ self.pause();
1248
+ self.client.step(type, count, function(err, res) {
1249
+ if (err) self.error(err);
1250
+ self.resume();
1251
+ });
1252
+ };
1253
+ };
1254
+
1255
+
1256
+ // Jump to next command
1257
+ Interface.prototype.next = Interface.stepGenerator('next', 1);
1258
+
1259
+
1260
+ // Step in
1261
+ Interface.prototype.step = Interface.stepGenerator('in', 1);
1262
+
1263
+
1264
+ // Step out
1265
+ Interface.prototype.out = Interface.stepGenerator('out', 1);
1266
+
1267
+
1268
+ // Watch
1269
+ Interface.prototype.watch = function(expr) {
1270
+ this._watchers.push(expr);
1271
+ };
1272
+
1273
+ // Unwatch
1274
+ Interface.prototype.unwatch = function(expr) {
1275
+ var index = this._watchers.indexOf(expr);
1276
+
1277
+ // Unwatch by expression
1278
+ // or
1279
+ // Unwatch by watcher number
1280
+ this._watchers.splice(index !== -1 ? index : +expr, 1);
1281
+ };
1282
+
1283
+ // List watchers
1284
+ Interface.prototype.watchers = function() {
1285
+ var self = this,
1286
+ verbose = arguments[0] || false,
1287
+ callback = arguments[1] || function() {},
1288
+ waiting = this._watchers.length,
1289
+ values = [];
1290
+
1291
+ this.pause();
1292
+
1293
+ if (!waiting) {
1294
+ this.resume();
1295
+
1296
+ return callback();
1297
+ }
1298
+
1299
+ this._watchers.forEach(function(watcher, i) {
1300
+ self.debugEval(watcher, null, null, function(err, value) {
1301
+ values[i] = err ? '<error>' : value;
1302
+ wait();
1303
+ });
1304
+ });
1305
+
1306
+ function wait() {
1307
+ if (--waiting === 0) {
1308
+ if (verbose) self.print('Watchers:');
1309
+
1310
+ self._watchers.forEach(function(watcher, i) {
1311
+ self.print(leftPad(i, ' ', self._watchers.length - 1) +
1312
+ ': ' + watcher + ' = ' +
1313
+ JSON.stringify(values[i]));
1314
+ });
1315
+
1316
+ if (verbose) self.print('');
1317
+
1318
+ self.resume();
1319
+
1320
+ callback(null);
1321
+ }
1322
+ }
1323
+ };
1324
+
1325
+ // Break on exception
1326
+ Interface.prototype.breakOnException = function breakOnException() {
1327
+ if (!this.requireConnection()) return;
1328
+
1329
+ var self = this;
1330
+
1331
+ // Break on exceptions
1332
+ this.pause();
1333
+ this.client.reqSetExceptionBreak('all', function(err, res) {
1334
+ self.resume();
1335
+ });
1336
+ };
1337
+
1338
+ // Add breakpoint
1339
+ Interface.prototype.setBreakpoint = function(script, line,
1340
+ condition, silent) {
1341
+ if (!this.requireConnection()) return;
1342
+
1343
+ var self = this,
1344
+ scriptId,
1345
+ ambiguous;
1346
+
1347
+ // setBreakpoint() should insert breakpoint on current line
1348
+ if (util.isUndefined(script)) {
1349
+ script = this.client.currentScript;
1350
+ line = this.client.currentSourceLine + 1;
1351
+ }
1352
+
1353
+ // setBreakpoint(line-number) should insert breakpoint in current script
1354
+ if (util.isUndefined(line) && util.isNumber(script)) {
1355
+ line = script;
1356
+ script = this.client.currentScript;
1357
+ }
1358
+
1359
+ if (/\(\)$/.test(script)) {
1360
+ // setBreakpoint('functionname()');
1361
+ var req = {
1362
+ type: 'function',
1363
+ target: script.replace(/\(\)$/, ''),
1364
+ condition: condition
1365
+ };
1366
+ } else {
1367
+ // setBreakpoint('scriptname')
1368
+ if (script != +script && !this.client.scripts[script]) {
1369
+ var scripts = this.client.scripts;
1370
+ Object.keys(scripts).forEach(function(id) {
1371
+ if (scripts[id] &&
1372
+ scripts[id].name &&
1373
+ scripts[id].name.indexOf(script) !== -1) {
1374
+ if (scriptId) {
1375
+ ambiguous = true;
1376
+ }
1377
+ scriptId = id;
1378
+ }
1379
+ });
1380
+ } else {
1381
+ scriptId = script;
1382
+ }
1383
+
1384
+ if (ambiguous) return this.error('Script name is ambiguous');
1385
+ if (line <= 0) return this.error('Line should be a positive value');
1386
+
1387
+ var req;
1388
+ if (scriptId) {
1389
+ req = {
1390
+ type: 'scriptId',
1391
+ target: scriptId,
1392
+ line: line - 1,
1393
+ condition: condition
1394
+ };
1395
+ } else {
1396
+ this.print('Warning: script \'' + script + '\' was not loaded yet.');
1397
+ var escapedPath = script.replace(/([/\\.?*()^${}|[\]])/g, '\\$1');
1398
+ var scriptPathRegex = '^(.*[\\/\\\\])?' + escapedPath + '$';
1399
+ req = {
1400
+ type: 'scriptRegExp',
1401
+ target: scriptPathRegex,
1402
+ line: line - 1,
1403
+ condition: condition
1404
+ };
1405
+ }
1406
+ }
1407
+
1408
+ self.pause();
1409
+ self.client.setBreakpoint(req, function(err, res) {
1410
+ if (err) {
1411
+ if (!silent) {
1412
+ self.error(err);
1413
+ }
1414
+ } else {
1415
+ if (!silent) {
1416
+ self.list(5);
1417
+ }
1418
+
1419
+ // Try load scriptId and line from response
1420
+ if (!scriptId) {
1421
+ scriptId = res.script_id;
1422
+ line = res.line + 1;
1423
+ }
1424
+
1425
+ // Remember this breakpoint even if scriptId is not resolved yet
1426
+ self.client.breakpoints.push({
1427
+ id: res.breakpoint,
1428
+ scriptId: scriptId,
1429
+ script: (self.client.scripts[scriptId] || {}).name,
1430
+ line: line,
1431
+ condition: condition,
1432
+ scriptReq: script
1433
+ });
1434
+ }
1435
+ self.resume();
1436
+ });
1437
+ };
1438
+
1439
+ // Clear breakpoint
1440
+ Interface.prototype.clearBreakpoint = function(script, line) {
1441
+ if (!this.requireConnection()) return;
1442
+
1443
+ var ambiguous,
1444
+ breakpoint,
1445
+ index;
1446
+
1447
+ this.client.breakpoints.some(function(bp, i) {
1448
+ if (bp.scriptId === script ||
1449
+ bp.scriptReq === script ||
1450
+ (bp.script && bp.script.indexOf(script) !== -1)) {
1451
+ if (!util.isUndefined(index)) {
1452
+ ambiguous = true;
1453
+ }
1454
+ if (bp.line === line) {
1455
+ index = i;
1456
+ breakpoint = bp.id;
1457
+ return true;
1458
+ }
1459
+ }
1460
+ });
1461
+
1462
+ if (ambiguous) return this.error('Script name is ambiguous');
1463
+
1464
+ if (util.isUndefined(breakpoint)) {
1465
+ return this.error('Script : ' + script + ' not found');
1466
+ }
1467
+
1468
+ var self = this,
1469
+ req = {
1470
+ breakpoint: breakpoint
1471
+ };
1472
+
1473
+ self.pause();
1474
+ self.client.clearBreakpoint(req, function(err, res) {
1475
+ if (err) {
1476
+ self.error(err);
1477
+ } else {
1478
+ self.client.breakpoints.splice(index, 1);
1479
+ self.list(5);
1480
+ }
1481
+ self.resume();
1482
+ });
1483
+ };
1484
+
1485
+
1486
+ // Show breakpoints
1487
+ Interface.prototype.breakpoints = function() {
1488
+ if (!this.requireConnection()) return;
1489
+
1490
+ this.pause();
1491
+ var self = this;
1492
+ this.client.listbreakpoints(function(err, res) {
1493
+ if (err) {
1494
+ self.error(err);
1495
+ } else {
1496
+ self.print(res);
1497
+ self.resume();
1498
+ }
1499
+ });
1500
+ };
1501
+
1502
+
1503
+ // Pause child process
1504
+ Interface.prototype.pause_ = function() {
1505
+ if (!this.requireConnection()) return;
1506
+
1507
+ var self = this,
1508
+ cmd = 'process._debugPause();';
1509
+
1510
+ this.pause();
1511
+ this.client.reqFrameEval(cmd, NO_FRAME, function(err, res) {
1512
+ if (err) {
1513
+ self.error(err);
1514
+ } else {
1515
+ self.resume();
1516
+ }
1517
+ });
1518
+ };
1519
+
1520
+
1521
+ // Kill child process
1522
+ Interface.prototype.kill = function() {
1523
+ if (!this.child) return;
1524
+ this.killChild();
1525
+ };
1526
+
1527
+
1528
+ // Activate debug repl
1529
+ Interface.prototype.repl = function() {
1530
+ if (!this.requireConnection()) return;
1531
+
1532
+ var self = this;
1533
+
1534
+ self.print('Press Ctrl + C to leave debug repl');
1535
+
1536
+ // Don't display any default messages
1537
+ var listeners = this.repl.rli.listeners('SIGINT').slice(0);
1538
+ this.repl.rli.removeAllListeners('SIGINT');
1539
+
1540
+ // Exit debug repl on Ctrl + C
1541
+ this.repl.rli.once('SIGINT', function() {
1542
+ // Restore all listeners
1543
+ process.nextTick(function() {
1544
+ listeners.forEach(function(listener) {
1545
+ self.repl.rli.on('SIGINT', listener);
1546
+ });
1547
+ });
1548
+
1549
+ // Exit debug repl
1550
+ self.exitRepl();
1551
+ });
1552
+
1553
+ // Set new
1554
+ this.repl.eval = this.debugEval.bind(this);
1555
+ this.repl.context = {};
1556
+
1557
+ // Swap history
1558
+ this.history.control = this.repl.rli.history;
1559
+ this.repl.rli.history = this.history.debug;
1560
+
1561
+ this.repl.prompt = '> ';
1562
+ this.repl.rli.setPrompt('> ');
1563
+ this.repl.displayPrompt();
1564
+ };
1565
+
1566
+
1567
+ // Exit debug repl
1568
+ Interface.prototype.exitRepl = function() {
1569
+ // Restore eval
1570
+ this.repl.eval = this.controlEval.bind(this);
1571
+
1572
+ // Swap history
1573
+ this.history.debug = this.repl.rli.history;
1574
+ this.repl.rli.history = this.history.control;
1575
+
1576
+ this.repl.context = this.context;
1577
+ this.repl.prompt = 'debug> ';
1578
+ this.repl.rli.setPrompt('debug> ');
1579
+ this.repl.displayPrompt();
1580
+ };
1581
+
1582
+
1583
+ // Quit
1584
+ Interface.prototype.quit = function() {
1585
+ this.killChild();
1586
+ process.exit(0);
1587
+ };
1588
+
1589
+
1590
+ // Kills child process
1591
+ Interface.prototype.killChild = function() {
1592
+ if (this.child) {
1593
+ this.child.kill();
1594
+ this.child = null;
1595
+ }
1596
+
1597
+ if (this.client) {
1598
+ // Save breakpoints
1599
+ this.breakpoints = this.client.breakpoints;
1600
+
1601
+ this.client.destroy();
1602
+ this.client = null;
1603
+ }
1604
+ };
1605
+
1606
+
1607
+ // Spawns child process (and restores breakpoints)
1608
+ Interface.prototype.trySpawn = function(cb) {
1609
+ var self = this,
1610
+ breakpoints = this.breakpoints || [],
1611
+ port = exports.port,
1612
+ host = 'localhost',
1613
+ childArgs = this.args;
1614
+
1615
+ this.killChild();
1616
+ assert(!this.child);
1617
+
1618
+ if (this.args.length === 2) {
1619
+ var match = this.args[1].match(/^([^:]+):(\d+)$/);
1620
+
1621
+ if (match) {
1622
+ // Connecting to remote debugger
1623
+ // `node debug localhost:5858`
1624
+ host = match[1];
1625
+ port = parseInt(match[2], 10);
1626
+ this.child = {
1627
+ kill: function() {
1628
+ // TODO Do we really need to handle it?
1629
+ }
1630
+ };
1631
+ }
1632
+ } else if (this.args.length === 3) {
1633
+ // `node debug -p pid`
1634
+ if (this.args[1] === '-p' && /^\d+$/.test(this.args[2])) {
1635
+ this.child = {
1636
+ kill: function() {
1637
+ // TODO Do we really need to handle it?
1638
+ }
1639
+ };
1640
+ process._debugProcess(parseInt(this.args[2], 10));
1641
+ } else {
1642
+ var match = this.args[1].match(/^--port=(\d+)$/);
1643
+ if (match) {
1644
+ // Start debugger on custom port
1645
+ // `node debug --port=5858 app.js`
1646
+ port = parseInt(match[1], 10);
1647
+ childArgs = ['--debug-brk=' + port].concat(this.args.slice(2));
1648
+ }
1649
+ }
1650
+ }
1651
+
1652
+ this.child = spawn(process.execPath, childArgs);
1653
+
1654
+ this.child.stdout.on('data', this.childPrint.bind(this));
1655
+ this.child.stderr.on('data', this.childPrint.bind(this));
1656
+
1657
+ this.pause();
1658
+
1659
+ var client = self.client = new Client(),
1660
+ connectionAttempts = 0;
1661
+
1662
+ client.once('ready', function() {
1663
+ self.stdout.write(' ok\n');
1664
+
1665
+ // Restore breakpoints
1666
+ breakpoints.forEach(function(bp) {
1667
+ self.print('Restoring breakpoint ' + bp.scriptReq + ':' + bp.line);
1668
+ self.setBreakpoint(bp.scriptReq, bp.line, bp.condition, true);
1669
+ });
1670
+
1671
+ client.on('close', function() {
1672
+ self.pause();
1673
+ self.print('program terminated');
1674
+ self.resume();
1675
+ self.client = null;
1676
+ self.killChild();
1677
+ });
1678
+
1679
+ if (cb) cb();
1680
+ self.resume();
1681
+ });
1682
+
1683
+ client.on('unhandledResponse', function(res) {
1684
+ self.pause();
1685
+ self.print('\nunhandled res:' + JSON.stringify(res));
1686
+ self.resume();
1687
+ });
1688
+
1689
+ client.on('break', function(res) {
1690
+ self.handleBreak(res.body);
1691
+ });
1692
+
1693
+ client.on('exception', function(res) {
1694
+ self.handleBreak(res.body);
1695
+ });
1696
+
1697
+ client.on('error', connectError);
1698
+ function connectError() {
1699
+ // If it's failed to connect 4 times then don't catch the next error
1700
+ if (connectionAttempts >= 10) {
1701
+ client.removeListener('error', connectError);
1702
+ }
1703
+ setTimeout(attemptConnect, 500);
1704
+ }
1705
+
1706
+ function attemptConnect() {
1707
+ ++connectionAttempts;
1708
+ self.stdout.write('.');
1709
+ client.connect(port, host);
1710
+ }
1711
+
1712
+ this.child.stderr.once('data', function() {
1713
+ setImmediate(function() {
1714
+ self.print('connecting to port ' + port + '..', true);
1715
+ attemptConnect();
1716
+ });
1717
+ });
1718
+ };