rubrowser 2.6.0 → 2.7.0

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.
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