rubrowser 2.6.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0105f708ea3b97b023bb2be41d2d86eb7144083e
4
- data.tar.gz: a44cc7253f7e810a2994ae801a8e2940f379bd38
2
+ SHA256:
3
+ metadata.gz: 16c4a3914f4bee3c490769aff15c24df034f2208a26e1c6d17a0da79f8c5be9b
4
+ data.tar.gz: 3410a5bb59bf64fe18aad3c0ac5d4f4a71f07ff5afe18d92dede8e1d7e0502a1
5
5
  SHA512:
6
- metadata.gz: baa24df653d21ed1c87ad867934fe0cf619adec9fec72dcbec1d12ea88b1d5163a5a6a985773f65d13f577f8149f441145da2e71f05c65b94e00f7ceb447f82f
7
- data.tar.gz: 4ee9e2d15638d6cb2c24f39673c5ff515f10a2d6d4da7f135592823d1c717af0cdb6d176f4a342760e01ec921ec5dda3e0b46f3a5b332fe8441024d39603550d
6
+ metadata.gz: d683deffdf39ea8edf19993871a5c7fb72b242a25f02b09b01c5b529e90eb208322d80fcf682fca8b844536788a31a8611be1d0713f925747ffc7a94119038cb
7
+ data.tar.gz: 3f75f1444103b98756147fe22ed8c36c195e23cb925795eaeb5f2d40731c031c0f14e5d4915d06b05d4c3505ba5bff4e655389cf4e47d98149d1cf3980c77931
@@ -1,19 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rubrowser (2.6.0)
4
+ rubrowser (2.7.0)
5
+ litecable
5
6
  parser (~> 2.3, >= 2.3.0)
7
+ puma
8
+ websocket
6
9
 
7
10
  GEM
8
11
  remote: https://rubygems.org/
9
12
  specs:
13
+ anyway_config (1.2.0)
10
14
  ast (2.2.0)
11
15
  byebug (9.0.6)
12
16
  diff-lcs (1.3)
17
+ litecable (0.5.0)
18
+ anyway_config (~> 1.0)
13
19
  parallel (1.12.0)
14
20
  parser (2.4.0.0)
15
21
  ast (~> 2.2)
16
22
  powerpack (0.1.1)
23
+ puma (3.11.2)
17
24
  rainbow (2.2.2)
18
25
  rake
19
26
  rake (10.4.2)
@@ -39,6 +46,7 @@ GEM
39
46
  unicode-display_width (~> 1.0, >= 1.0.1)
40
47
  ruby-progressbar (1.8.1)
41
48
  unicode-display_width (1.3.0)
49
+ websocket (1.2.5)
42
50
 
43
51
  PLATFORMS
44
52
  ruby
@@ -26,6 +26,12 @@ OptionParser.new do |opts|
26
26
  'layout file to apply on the resulting graph'
27
27
  ) { |layout| options[:layout] = layout }
28
28
 
29
+ opts.on(
30
+ '-sSERVER:PORT',
31
+ '--server=SERVER:PORT',
32
+ 'rubrowser server for execution monitoring'
33
+ ) { |server| options[:server] = server }
34
+
29
35
  opts.on('-T', '--no-toolbox', 'Don\'t display toolbox on the page') do
30
36
  options[:toolbox] = false
31
37
  end
@@ -1,4 +1,5 @@
1
1
  require 'rubrowser/version'
2
+ require 'rubrowser/monitor'
2
3
 
3
4
  module Rubrowser
4
5
  end
@@ -0,0 +1,9 @@
1
+ module Rubrowser
2
+ class Channel < LiteCable::Channel::Base
3
+ identifier :classes
4
+
5
+ def subscribed
6
+ stream_from "classes"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ require 'rubrowser/channel'
2
+
3
+ module Rubrowser
4
+ class Connection < LiteCable::Connection::Base
5
+ identified_by :id
6
+
7
+ def connect
8
+ @id = rand(100000)
9
+ self.class.initiate_reader
10
+ end
11
+
12
+ def self.initiate_reader
13
+ @reader ||= Thread.new do
14
+ $rd.each_line do |line|
15
+ LiteCable.broadcast('classes', message: line.strip)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'rubrowser/trace'
2
+ require 'rubrowser/server'
3
+
4
+
5
+ module Rubrowser
6
+ module Monitor
7
+ module_function
8
+
9
+ def run(path: "", port: 8080)
10
+ $rd, $wr = IO.pipe
11
+
12
+ if fork
13
+ $rd.close
14
+ Trace.new(path: path).run
15
+ else
16
+ $wr.close
17
+ Server.new(port: port).run
18
+ exit
19
+ end
20
+ end
21
+ end
22
+ end
@@ -19,11 +19,12 @@ module Rubrowser
19
19
 
20
20
  include ERB::Util
21
21
 
22
- attr_reader :files, :output
22
+ attr_reader :files, :output, :server
23
23
 
24
24
  def initialize(options)
25
25
  @output = output_file(options[:output])
26
26
  @layout = options[:layout]
27
+ @server = options[:server]
27
28
  @files = options[:files]
28
29
  @toolbox = options[:toolbox]
29
30
  end
@@ -0,0 +1,42 @@
1
+ require 'lite_cable'
2
+ require 'lite_cable/server'
3
+ require 'puma/configuration'
4
+ require 'rubrowser/connection'
5
+
6
+ module Rubrowser
7
+ class Server
8
+ def initialize(port: 8080)
9
+ @port = port
10
+ end
11
+
12
+ def run
13
+ launcher.run
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :port
19
+
20
+ def launcher
21
+ @launcher ||= Puma::Launcher.new(conf)
22
+ end
23
+
24
+ def conf
25
+ @conf ||= Puma::Configuration.new do |user_config|
26
+ user_config.threads(1, 1)
27
+ user_config.workers 1
28
+ user_config.port port
29
+ user_config.app(rack_app)
30
+ end
31
+ end
32
+
33
+ def rack_app
34
+ @app ||= Rack::Builder.new do
35
+ map '/' do
36
+ use LiteCable::Server::Middleware, connection_class: Rubrowser::Connection
37
+ run proc { |_| [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,21 @@
1
+ module Rubrowser
2
+ class Trace
3
+ def initialize(path: "")
4
+ @path = path
5
+ end
6
+
7
+ def run
8
+ trace_point.enable
9
+ end
10
+
11
+ private
12
+
13
+ attr_reader :path
14
+
15
+ def trace_point
16
+ @trace_point ||= TracePoint.new(:call) do |tp|
17
+ $wr.puts tp.defined_class if tp.path.start_with?(path)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Rubrowser
2
- VERSION = '2.6.0'.freeze
2
+ VERSION = '2.7.0'.freeze
3
3
  end
@@ -45,6 +45,11 @@ text.type{
45
45
  display: none;
46
46
  }
47
47
 
48
+ .running circle{
49
+ stroke: #18b738;
50
+ fill: #7dea93;
51
+ }
52
+
48
53
  .highlighted,
49
54
  .highlighted_by_namespace,
50
55
  .highlighted_by_path,
@@ -0,0 +1,613 @@
1
+ (function() {
2
+ var slice = [].slice;
3
+
4
+ this.ActionCable = {
5
+ INTERNAL: {
6
+ "message_types": {
7
+ "welcome": "welcome",
8
+ "ping": "ping",
9
+ "confirmation": "confirm_subscription",
10
+ "rejection": "reject_subscription"
11
+ },
12
+ "default_mount_path": "/cable",
13
+ "protocols": ["actioncable-v1-json", "actioncable-unsupported"]
14
+ },
15
+ WebSocket: window.WebSocket,
16
+ logger: window.console,
17
+ createConsumer: function(url) {
18
+ var ref;
19
+ if (url == null) {
20
+ url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
21
+ }
22
+ return new ActionCable.Consumer(this.createWebSocketURL(url));
23
+ },
24
+ getConfig: function(name) {
25
+ var element;
26
+ element = document.head.querySelector("meta[name='action-cable-" + name + "']");
27
+ return element != null ? element.getAttribute("content") : void 0;
28
+ },
29
+ createWebSocketURL: function(url) {
30
+ var a;
31
+ if (url && !/^wss?:/i.test(url)) {
32
+ a = document.createElement("a");
33
+ a.href = url;
34
+ a.href = a.href;
35
+ a.protocol = a.protocol.replace("http", "ws");
36
+ return a.href;
37
+ } else {
38
+ return url;
39
+ }
40
+ },
41
+ startDebugging: function() {
42
+ return this.debugging = true;
43
+ },
44
+ stopDebugging: function() {
45
+ return this.debugging = null;
46
+ },
47
+ log: function() {
48
+ var messages, ref;
49
+ messages = 1 <= arguments.length ? slice.call(arguments, 0) : [];
50
+ if (this.debugging) {
51
+ messages.push(Date.now());
52
+ return (ref = this.logger).log.apply(ref, ["[ActionCable]"].concat(slice.call(messages)));
53
+ }
54
+ }
55
+ };
56
+
57
+ }).call(this);
58
+ (function() {
59
+ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
60
+
61
+ ActionCable.ConnectionMonitor = (function() {
62
+ var clamp, now, secondsSince;
63
+
64
+ ConnectionMonitor.pollInterval = {
65
+ min: 3,
66
+ max: 30
67
+ };
68
+
69
+ ConnectionMonitor.staleThreshold = 6;
70
+
71
+ function ConnectionMonitor(connection) {
72
+ this.connection = connection;
73
+ this.visibilityDidChange = bind(this.visibilityDidChange, this);
74
+ this.reconnectAttempts = 0;
75
+ }
76
+
77
+ ConnectionMonitor.prototype.start = function() {
78
+ if (!this.isRunning()) {
79
+ this.startedAt = now();
80
+ delete this.stoppedAt;
81
+ this.startPolling();
82
+ document.addEventListener("visibilitychange", this.visibilityDidChange);
83
+ return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
84
+ }
85
+ };
86
+
87
+ ConnectionMonitor.prototype.stop = function() {
88
+ if (this.isRunning()) {
89
+ this.stoppedAt = now();
90
+ this.stopPolling();
91
+ document.removeEventListener("visibilitychange", this.visibilityDidChange);
92
+ return ActionCable.log("ConnectionMonitor stopped");
93
+ }
94
+ };
95
+
96
+ ConnectionMonitor.prototype.isRunning = function() {
97
+ return (this.startedAt != null) && (this.stoppedAt == null);
98
+ };
99
+
100
+ ConnectionMonitor.prototype.recordPing = function() {
101
+ return this.pingedAt = now();
102
+ };
103
+
104
+ ConnectionMonitor.prototype.recordConnect = function() {
105
+ this.reconnectAttempts = 0;
106
+ this.recordPing();
107
+ delete this.disconnectedAt;
108
+ return ActionCable.log("ConnectionMonitor recorded connect");
109
+ };
110
+
111
+ ConnectionMonitor.prototype.recordDisconnect = function() {
112
+ this.disconnectedAt = now();
113
+ return ActionCable.log("ConnectionMonitor recorded disconnect");
114
+ };
115
+
116
+ ConnectionMonitor.prototype.startPolling = function() {
117
+ this.stopPolling();
118
+ return this.poll();
119
+ };
120
+
121
+ ConnectionMonitor.prototype.stopPolling = function() {
122
+ return clearTimeout(this.pollTimeout);
123
+ };
124
+
125
+ ConnectionMonitor.prototype.poll = function() {
126
+ return this.pollTimeout = setTimeout((function(_this) {
127
+ return function() {
128
+ _this.reconnectIfStale();
129
+ return _this.poll();
130
+ };
131
+ })(this), this.getPollInterval());
132
+ };
133
+
134
+ ConnectionMonitor.prototype.getPollInterval = function() {
135
+ var interval, max, min, ref;
136
+ ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
137
+ interval = 5 * Math.log(this.reconnectAttempts + 1);
138
+ return Math.round(clamp(interval, min, max) * 1000);
139
+ };
140
+
141
+ ConnectionMonitor.prototype.reconnectIfStale = function() {
142
+ if (this.connectionIsStale()) {
143
+ ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
144
+ this.reconnectAttempts++;
145
+ if (this.disconnectedRecently()) {
146
+ return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
147
+ } else {
148
+ ActionCable.log("ConnectionMonitor reopening");
149
+ return this.connection.reopen();
150
+ }
151
+ }
152
+ };
153
+
154
+ ConnectionMonitor.prototype.connectionIsStale = function() {
155
+ var ref;
156
+ return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
157
+ };
158
+
159
+ ConnectionMonitor.prototype.disconnectedRecently = function() {
160
+ return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
161
+ };
162
+
163
+ ConnectionMonitor.prototype.visibilityDidChange = function() {
164
+ if (document.visibilityState === "visible") {
165
+ return setTimeout((function(_this) {
166
+ return function() {
167
+ if (_this.connectionIsStale() || !_this.connection.isOpen()) {
168
+ ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
169
+ return _this.connection.reopen();
170
+ }
171
+ };
172
+ })(this), 200);
173
+ }
174
+ };
175
+
176
+ now = function() {
177
+ return new Date().getTime();
178
+ };
179
+
180
+ secondsSince = function(time) {
181
+ return (now() - time) / 1000;
182
+ };
183
+
184
+ clamp = function(number, min, max) {
185
+ return Math.max(min, Math.min(max, number));
186
+ };
187
+
188
+ return ConnectionMonitor;
189
+
190
+ })();
191
+
192
+ }).call(this);
193
+ (function() {
194
+ var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
195
+ slice = [].slice,
196
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
197
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
198
+
199
+ ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;
200
+
201
+ supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];
202
+
203
+ ActionCable.Connection = (function() {
204
+ Connection.reopenDelay = 500;
205
+
206
+ function Connection(consumer) {
207
+ this.consumer = consumer;
208
+ this.open = bind(this.open, this);
209
+ this.subscriptions = this.consumer.subscriptions;
210
+ this.monitor = new ActionCable.ConnectionMonitor(this);
211
+ this.disconnected = true;
212
+ }
213
+
214
+ Connection.prototype.send = function(data) {
215
+ if (this.isOpen()) {
216
+ this.webSocket.send(JSON.stringify(data));
217
+ return true;
218
+ } else {
219
+ return false;
220
+ }
221
+ };
222
+
223
+ Connection.prototype.open = function() {
224
+ if (this.isActive()) {
225
+ ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
226
+ return false;
227
+ } else {
228
+ ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
229
+ if (this.webSocket != null) {
230
+ this.uninstallEventHandlers();
231
+ }
232
+ this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols);
233
+ this.installEventHandlers();
234
+ this.monitor.start();
235
+ return true;
236
+ }
237
+ };
238
+
239
+ Connection.prototype.close = function(arg) {
240
+ var allowReconnect, ref1;
241
+ allowReconnect = (arg != null ? arg : {
242
+ allowReconnect: true
243
+ }).allowReconnect;
244
+ if (!allowReconnect) {
245
+ this.monitor.stop();
246
+ }
247
+ if (this.isActive()) {
248
+ return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
249
+ }
250
+ };
251
+
252
+ Connection.prototype.reopen = function() {
253
+ var error;
254
+ ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
255
+ if (this.isActive()) {
256
+ try {
257
+ return this.close();
258
+ } catch (error1) {
259
+ error = error1;
260
+ return ActionCable.log("Failed to reopen WebSocket", error);
261
+ } finally {
262
+ ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
263
+ setTimeout(this.open, this.constructor.reopenDelay);
264
+ }
265
+ } else {
266
+ return this.open();
267
+ }
268
+ };
269
+
270
+ Connection.prototype.getProtocol = function() {
271
+ var ref1;
272
+ return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
273
+ };
274
+
275
+ Connection.prototype.isOpen = function() {
276
+ return this.isState("open");
277
+ };
278
+
279
+ Connection.prototype.isActive = function() {
280
+ return this.isState("open", "connecting");
281
+ };
282
+
283
+ Connection.prototype.isProtocolSupported = function() {
284
+ var ref1;
285
+ return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
286
+ };
287
+
288
+ Connection.prototype.isState = function() {
289
+ var ref1, states;
290
+ states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
291
+ return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
292
+ };
293
+
294
+ Connection.prototype.getState = function() {
295
+ var ref1, state, value;
296
+ for (state in WebSocket) {
297
+ value = WebSocket[state];
298
+ if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
299
+ return state.toLowerCase();
300
+ }
301
+ }
302
+ return null;
303
+ };
304
+
305
+ Connection.prototype.installEventHandlers = function() {
306
+ var eventName, handler;
307
+ for (eventName in this.events) {
308
+ handler = this.events[eventName].bind(this);
309
+ this.webSocket["on" + eventName] = handler;
310
+ }
311
+ };
312
+
313
+ Connection.prototype.uninstallEventHandlers = function() {
314
+ var eventName;
315
+ for (eventName in this.events) {
316
+ this.webSocket["on" + eventName] = function() {};
317
+ }
318
+ };
319
+
320
+ Connection.prototype.events = {
321
+ message: function(event) {
322
+ var identifier, message, ref1, type;
323
+ if (!this.isProtocolSupported()) {
324
+ return;
325
+ }
326
+ ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
327
+ switch (type) {
328
+ case message_types.welcome:
329
+ this.monitor.recordConnect();
330
+ return this.subscriptions.reload();
331
+ case message_types.ping:
332
+ return this.monitor.recordPing();
333
+ case message_types.confirmation:
334
+ return this.subscriptions.notify(identifier, "connected");
335
+ case message_types.rejection:
336
+ return this.subscriptions.reject(identifier);
337
+ default:
338
+ return this.subscriptions.notify(identifier, "received", message);
339
+ }
340
+ },
341
+ open: function() {
342
+ ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
343
+ this.disconnected = false;
344
+ if (!this.isProtocolSupported()) {
345
+ ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
346
+ return this.close({
347
+ allowReconnect: false
348
+ });
349
+ }
350
+ },
351
+ close: function(event) {
352
+ ActionCable.log("WebSocket onclose event");
353
+ if (this.disconnected) {
354
+ return;
355
+ }
356
+ this.disconnected = true;
357
+ this.monitor.recordDisconnect();
358
+ return this.subscriptions.notifyAll("disconnected", {
359
+ willAttemptReconnect: this.monitor.isRunning()
360
+ });
361
+ },
362
+ error: function() {
363
+ return ActionCable.log("WebSocket onerror event");
364
+ }
365
+ };
366
+
367
+ return Connection;
368
+
369
+ })();
370
+
371
+ }).call(this);
372
+ (function() {
373
+ var slice = [].slice;
374
+
375
+ ActionCable.Subscriptions = (function() {
376
+ function Subscriptions(consumer) {
377
+ this.consumer = consumer;
378
+ this.subscriptions = [];
379
+ }
380
+
381
+ Subscriptions.prototype.create = function(channelName, mixin) {
382
+ var channel, params, subscription;
383
+ channel = channelName;
384
+ params = typeof channel === "object" ? channel : {
385
+ channel: channel
386
+ };
387
+ subscription = new ActionCable.Subscription(this.consumer, params, mixin);
388
+ return this.add(subscription);
389
+ };
390
+
391
+ Subscriptions.prototype.add = function(subscription) {
392
+ this.subscriptions.push(subscription);
393
+ this.consumer.ensureActiveConnection();
394
+ this.notify(subscription, "initialized");
395
+ this.sendCommand(subscription, "subscribe");
396
+ return subscription;
397
+ };
398
+
399
+ Subscriptions.prototype.remove = function(subscription) {
400
+ this.forget(subscription);
401
+ if (!this.findAll(subscription.identifier).length) {
402
+ this.sendCommand(subscription, "unsubscribe");
403
+ }
404
+ return subscription;
405
+ };
406
+
407
+ Subscriptions.prototype.reject = function(identifier) {
408
+ var i, len, ref, results, subscription;
409
+ ref = this.findAll(identifier);
410
+ results = [];
411
+ for (i = 0, len = ref.length; i < len; i++) {
412
+ subscription = ref[i];
413
+ this.forget(subscription);
414
+ this.notify(subscription, "rejected");
415
+ results.push(subscription);
416
+ }
417
+ return results;
418
+ };
419
+
420
+ Subscriptions.prototype.forget = function(subscription) {
421
+ var s;
422
+ this.subscriptions = (function() {
423
+ var i, len, ref, results;
424
+ ref = this.subscriptions;
425
+ results = [];
426
+ for (i = 0, len = ref.length; i < len; i++) {
427
+ s = ref[i];
428
+ if (s !== subscription) {
429
+ results.push(s);
430
+ }
431
+ }
432
+ return results;
433
+ }).call(this);
434
+ return subscription;
435
+ };
436
+
437
+ Subscriptions.prototype.findAll = function(identifier) {
438
+ var i, len, ref, results, s;
439
+ ref = this.subscriptions;
440
+ results = [];
441
+ for (i = 0, len = ref.length; i < len; i++) {
442
+ s = ref[i];
443
+ if (s.identifier === identifier) {
444
+ results.push(s);
445
+ }
446
+ }
447
+ return results;
448
+ };
449
+
450
+ Subscriptions.prototype.reload = function() {
451
+ var i, len, ref, results, subscription;
452
+ ref = this.subscriptions;
453
+ results = [];
454
+ for (i = 0, len = ref.length; i < len; i++) {
455
+ subscription = ref[i];
456
+ results.push(this.sendCommand(subscription, "subscribe"));
457
+ }
458
+ return results;
459
+ };
460
+
461
+ Subscriptions.prototype.notifyAll = function() {
462
+ var args, callbackName, i, len, ref, results, subscription;
463
+ callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
464
+ ref = this.subscriptions;
465
+ results = [];
466
+ for (i = 0, len = ref.length; i < len; i++) {
467
+ subscription = ref[i];
468
+ results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
469
+ }
470
+ return results;
471
+ };
472
+
473
+ Subscriptions.prototype.notify = function() {
474
+ var args, callbackName, i, len, results, subscription, subscriptions;
475
+ subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
476
+ if (typeof subscription === "string") {
477
+ subscriptions = this.findAll(subscription);
478
+ } else {
479
+ subscriptions = [subscription];
480
+ }
481
+ results = [];
482
+ for (i = 0, len = subscriptions.length; i < len; i++) {
483
+ subscription = subscriptions[i];
484
+ results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
485
+ }
486
+ return results;
487
+ };
488
+
489
+ Subscriptions.prototype.sendCommand = function(subscription, command) {
490
+ var identifier;
491
+ identifier = subscription.identifier;
492
+ return this.consumer.send({
493
+ command: command,
494
+ identifier: identifier
495
+ });
496
+ };
497
+
498
+ return Subscriptions;
499
+
500
+ })();
501
+
502
+ }).call(this);
503
+ (function() {
504
+ ActionCable.Subscription = (function() {
505
+ var extend;
506
+
507
+ function Subscription(consumer, params, mixin) {
508
+ this.consumer = consumer;
509
+ if (params == null) {
510
+ params = {};
511
+ }
512
+ this.identifier = JSON.stringify(params);
513
+ extend(this, mixin);
514
+ }
515
+
516
+ Subscription.prototype.perform = function(action, data) {
517
+ if (data == null) {
518
+ data = {};
519
+ }
520
+ data.action = action;
521
+ return this.send(data);
522
+ };
523
+
524
+ Subscription.prototype.send = function(data) {
525
+ return this.consumer.send({
526
+ command: "message",
527
+ identifier: this.identifier,
528
+ data: JSON.stringify(data)
529
+ });
530
+ };
531
+
532
+ Subscription.prototype.unsubscribe = function() {
533
+ return this.consumer.subscriptions.remove(this);
534
+ };
535
+
536
+ extend = function(object, properties) {
537
+ var key, value;
538
+ if (properties != null) {
539
+ for (key in properties) {
540
+ value = properties[key];
541
+ object[key] = value;
542
+ }
543
+ }
544
+ return object;
545
+ };
546
+
547
+ return Subscription;
548
+
549
+ })();
550
+
551
+ }).call(this);
552
+ (function() {
553
+ ActionCable.Consumer = (function() {
554
+ function Consumer(url) {
555
+ this.url = url;
556
+ this.subscriptions = new ActionCable.Subscriptions(this);
557
+ this.connection = new ActionCable.Connection(this);
558
+ }
559
+
560
+ Consumer.prototype.send = function(data) {
561
+ return this.connection.send(data);
562
+ };
563
+
564
+ Consumer.prototype.connect = function() {
565
+ return this.connection.open();
566
+ };
567
+
568
+ Consumer.prototype.disconnect = function() {
569
+ return this.connection.close({
570
+ allowReconnect: false
571
+ });
572
+ };
573
+
574
+ Consumer.prototype.ensureActiveConnection = function() {
575
+ if (!this.connection.isActive()) {
576
+ return this.connection.open();
577
+ }
578
+ };
579
+
580
+ return Consumer;
581
+
582
+ })();
583
+
584
+ }).call(this);
585
+
586
+ var glowing = {}
587
+ function glow(cls) {
588
+ if (!glowing[cls]) {
589
+ var node = rubrowser.node.filter(function(d){
590
+ return d.id == cls;
591
+ });
592
+
593
+ if (node.size() > 0){
594
+ node.classed("running", true);
595
+ glowing[cls] = true;
596
+ setTimeout(function(){
597
+ glowing[cls] = false;
598
+ node.classed("running", false);
599
+ }, 2000)
600
+ }
601
+ }
602
+ }
603
+
604
+ var cable = ActionCable.createConsumer(server)
605
+ cable.ensureActiveConnection()
606
+ var channel = cable.subscriptions.create(
607
+ "classes",
608
+ {
609
+ received: function(data){
610
+ glow(data.message);
611
+ }
612
+ }
613
+ )
data/readme.md CHANGED
@@ -51,9 +51,10 @@ gem install rubrowser
51
51
 
52
52
 
53
53
  ```
54
- Usage: rubrowser [options] [file] ...
54
+ Usage: /home/emad/code/rubrowser/bin/rubrowser [options] [file] ...
55
55
  -o, --output=FILE output file page, if not specified output will be written to stdout
56
56
  -l, --layout=FILE layout file to apply on the resulting graph
57
+ -s, --server=SERVER:PORT rubrowser server for execution monitoring
57
58
  -T, --no-toolbox Don't display toolbox on the page
58
59
  -v, --version Print Rubrowser version
59
60
  -h, --help Prints this help
@@ -71,6 +72,30 @@ it to a file, and open it in your browser
71
72
  rubrowser > output.html
72
73
  ```
73
74
 
75
+ ## Monitoring your application execution
76
+
77
+ Add rubrowser to your ruby project `Gemfile`
78
+ ```
79
+ gem 'rubrowser'
80
+ ```
81
+
82
+ Add the following code before your application runs, if it's a rails project
83
+ this should be an initializer
84
+
85
+ ```
86
+ Rubrowser::Monitor.run(path: Rails.root.to_s, port: 8080)
87
+ ```
88
+
89
+ path is your code path, and port is the execution websocket server port, the
90
+ server will open that port and announce any execution happens to the code inside
91
+ your project directory.
92
+
93
+ now generate a rubrowser HTML output that connect to your server
94
+
95
+ ```
96
+ rubrowser -slocalhost:8080 > output.html
97
+ ```
98
+
74
99
  ## Using a saved layout
75
100
 
76
101
  When you move classes/modules in the graph to fix them in one place, then you
@@ -96,6 +121,7 @@ follow that naming, your project will be ready in that case.
96
121
 
97
122
  ## Features
98
123
 
124
+ * trace your execution in realtime on the graph
99
125
  * interactive graph, you can pull any node to fix it to some position
100
126
  * to release node double click on it
101
127
  * zoom and pan with mouse or touch pad
@@ -20,6 +20,10 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ['lib']
21
21
 
22
22
  s.add_runtime_dependency 'parser', '~> 2.3', '>= 2.3.0'
23
+ s.add_runtime_dependency 'puma'
24
+ s.add_runtime_dependency 'litecable'
25
+ s.add_runtime_dependency 'websocket'
26
+
23
27
  s.add_development_dependency 'bundler', '~> 1.14'
24
28
  s.add_development_dependency 'rake', '~> 10.0'
25
29
  s.add_development_dependency 'byebug'
@@ -26,5 +26,12 @@
26
26
  <%= erb :toolbox %>
27
27
  <script type='text/javascript'><%= file('javascript/toolbox.js') %></script>
28
28
  <% end %>
29
+
30
+ <% if server %>
31
+ <script type='text/javascript'>
32
+ server = "ws://<%= server %>";
33
+ <%= file('javascript/websocket.js') %>
34
+ </script>
35
+ <% end %>
29
36
  </body>
30
37
  </html>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubrowser
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.0
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emad Elsaid
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-15 00:00:00.000000000 Z
11
+ date: 2018-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -30,6 +30,48 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 2.3.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: puma
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: litecable
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: websocket
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
33
75
  - !ruby/object:Gem::Dependency
34
76
  name: bundler
35
77
  requirement: !ruby/object:Gem::Requirement
@@ -121,9 +163,12 @@ files:
121
163
  - _config.yml
122
164
  - bin/rubrowser
123
165
  - lib/rubrowser.rb
166
+ - lib/rubrowser/channel.rb
167
+ - lib/rubrowser/connection.rb
124
168
  - lib/rubrowser/data.rb
125
169
  - lib/rubrowser/formatter/json.rb
126
170
  - lib/rubrowser/graph.rb
171
+ - lib/rubrowser/monitor.rb
127
172
  - lib/rubrowser/parser/definition/base.rb
128
173
  - lib/rubrowser/parser/definition/class.rb
129
174
  - lib/rubrowser/parser/definition/module.rb
@@ -133,10 +178,13 @@ files:
133
178
  - lib/rubrowser/parser/file/builder.rb
134
179
  - lib/rubrowser/parser/relation/base.rb
135
180
  - lib/rubrowser/renderer.rb
181
+ - lib/rubrowser/server.rb
182
+ - lib/rubrowser/trace.rb
136
183
  - lib/rubrowser/version.rb
137
184
  - public/css/application.css
138
185
  - public/javascript/application.js
139
186
  - public/javascript/toolbox.js
187
+ - public/javascript/websocket.js
140
188
  - readme.md
141
189
  - rubrowser.gemspec
142
190
  - views/index.erb
@@ -161,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
209
  version: '0'
162
210
  requirements: []
163
211
  rubyforge_project:
164
- rubygems_version: 2.6.10
212
+ rubygems_version: 2.7.3
165
213
  signing_key:
166
214
  specification_version: 4
167
215
  summary: A ruby interactive dependency graph visualizer