macgyver 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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
+ };