goat 0.3.45 → 0.3.46

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.
@@ -39,7 +39,7 @@ class Object
39
39
  while c
40
40
  cs << c
41
41
  return cs if c == upto
42
- raise "#{upto} isn't in the hierachy of #{start.inspect}" if c == Object && upto != Object
42
+ raise "#{start.inspect} isn't in the hierachy of #{upto}" if c == Object && upto != Object
43
43
 
44
44
  c = c.superclass
45
45
  end
@@ -53,34 +53,40 @@ var Goat = {}
53
53
  Goat.RT = {
54
54
  version: 0,
55
55
  pendingTxns: {},
56
+ completedTxns: {},
56
57
  ops: {},
57
58
  consecutiveFailures: 0
58
59
  };
59
60
 
60
61
  (function(rt) {
61
62
  function updateFailed(msg) {
62
- console.error(msg);
63
+ Goat.error('updateFailed', msg);
63
64
  }
64
65
 
65
66
  function node(par, pos) {
66
- if(pos === null)
67
+ Goat.debug('node', par, pos, pos === null)
68
+ if(pos === null || pos === undefined)
67
69
  return $('#' + par);
68
70
  else
69
71
  return $("#" + par + " > :nth-child(" + (pos + 1) + ")");
70
72
  }
71
73
 
72
74
  function removeNode(par, pos, html) {
75
+ Goat.debug('removeNode', node(par, pos));
73
76
  node(par, pos).remove();
74
77
  }
75
78
 
76
79
  function addNode(par, pos, html) {
77
- node(par, pos).before(html);
80
+ Goat.debug('addNode', node(par, pos), html);
81
+ if(pos == 0)
82
+ node(par).prepend(html)
83
+ else
84
+ node(par, pos - 1).after(html);
78
85
  }
79
86
 
80
87
  function replaceNode(par, pos, html) {
81
- console.log("rep " + par + " " + pos);
82
- console.log(node(par, pos));
83
- node(par, pos).html(html);
88
+ Goat.warn('Replace fault', node(par, pos));
89
+ node(par, pos).replaceWith(html);
84
90
  if(pos === null)
85
91
  delete Goat.components[par];
86
92
  }
@@ -92,9 +98,10 @@ Goat.RT = {
92
98
  var html = up.html;
93
99
  var js = up.js;
94
100
  var css = up.css;
101
+ var id = up.id; // only for rem
95
102
 
96
103
  if(t == "rem") {
97
- removeNode(par, pos, html);
104
+ removeNode(par, pos, id);
98
105
  } else if(t == "add") {
99
106
  addNode(par, pos, html);
100
107
  } else if(t == "rep") {
@@ -121,8 +128,6 @@ Goat.RT = {
121
128
  script.innerHTML = js;
122
129
  addToHead(script);
123
130
  Goat.loadComponents();
124
- /*console.log("Eval " + js);
125
- eval(js);*/
126
131
  }
127
132
 
128
133
  function updateReceived(m) {
@@ -164,31 +169,30 @@ Goat.RT = {
164
169
  return pending;
165
170
  }
166
171
 
167
- function completeTxn(txn) {
168
- if(rt.pendingTxns[txn]) {
169
- delete rt.pendingTxns[txn];
172
+ function completeTxn(txnid) {
173
+ if(rt.pendingTxns[txnid]) {
174
+ var txn = rt.ops[txnid];
175
+
176
+ delete rt.pendingTxns[txnid];
177
+ rt.completedTxns[txnid] = true
170
178
 
171
179
  if(pendingCount() == 0) {
172
180
  Goat.LoadingIndicator.hide();
173
181
  }
174
182
 
175
183
  if(txn.complete) txn.complete();
184
+ } else if(rt.completedTxns[txnid]) {
185
+ Goat.warn('Duplicate txn_complete message for txn ' + txnid);
176
186
  } else {
177
- console.error("Can't complete txn " + txn + ": no such txn was started");
187
+ Goat.error('Can\'t complete txn ' + txn + ': no such txn was started');
178
188
  }
179
189
  }
180
190
 
181
- function txnCompleteMsg(msg) {
182
- console.log('txnCompleteMsg');
183
- console.log(msg);
184
- completeTxn(msg['txn']);
185
- }
186
-
187
191
  rt.updateReceived = updateReceived;
188
192
  rt.node = node;
189
193
  rt.newTxn = newTxn;
190
194
  rt.beginTxn = beginTxn;
191
- rt.txnCompleteMsg = txnCompleteMsg;
195
+ rt.completeTxn = completeTxn;
192
196
 
193
197
  return rt;
194
198
  })(Goat.RT);
@@ -203,7 +207,7 @@ Goat.LoadingIndicator = {
203
207
  (function(ld) {
204
208
  function initIndicator() {
205
209
  if(!ld.url || !ld.where) {
206
- console.error("Loading indicator not configured");
210
+ Goat.error("Loading indicator not configured");
207
211
  return;
208
212
  }
209
213
  ld.indicator = $("<img id=\"loading_indicator\" src=\"" + ld.url + "\">");
@@ -278,7 +282,7 @@ Goat.RPC = Class.extend({
278
282
  },
279
283
 
280
284
  rpcFailure: function() {
281
- console.error("RPC error: couldn't load " + this.name);
285
+ Goat.error("RPC error: couldn't load " + this.name);
282
286
  alert("An error was encountered connecting to the server. This could be because of a problem with your internet connection, or with the server itself. You should try refreshing this page.");
283
287
  if(this.rpcError) this.rpcError(this);
284
288
  },
@@ -351,14 +355,28 @@ $.extend(Goat, {
351
355
  activeChannel: null,
352
356
  pageDead: false,
353
357
  components: {},
354
- page_id: null
358
+ page_id: null,
355
359
  });
356
360
 
357
361
  $.extend(Goat, {
362
+ isLocal: function() {
363
+ return window.location.host.match(/127\.0\.0\.1/) || window.location.host.match(/localhost/)
364
+ },
365
+
366
+ console_apply: function(f, a) {
367
+ if(this.isLocal() || f == 'warn' || f == 'error')
368
+ console[f].apply(console, a);
369
+ },
370
+
371
+ debug: function() { this.console_apply('debug', arguments); },
372
+ warn: function() { this.console_apply('warn', arguments); },
373
+ error: function() { this.console_apply('error', arguments); },
374
+ log: function() { this.console_apply('log', arguments); },
375
+
358
376
  closure: function(fn) { return closure(this, fn); },
359
377
 
360
378
  channelURL: function() {
361
- if(window.location.host.match(/127\.0\.0\.1/) || window.location.host.match(/localhost/))
379
+ if(this.isLocal())
362
380
  return 'http://127.0.0.1:8050/channel';
363
381
  else
364
382
  return '/channel';
@@ -378,34 +396,34 @@ $.extend(Goat, {
378
396
  },
379
397
 
380
398
  messageReceived: function(m) {
381
- console.log(m);
399
+ Goat.log('messageReceived', m.type, m);
382
400
 
383
401
  var t = m['type'];
384
402
  if(t == 'component_updated') {
385
403
  Goat.RT.updateReceived(m);
386
404
  } else if(t == 'redirect') {
387
405
  if(m["location"]) {
388
- console.log("Redirecting to " + m['location']);
406
+ Goat.log('Redirecting to ' + m['location']);
389
407
  sleep(2);
390
408
  window.location = m['location'];
391
409
  } else {
392
- console.error("Tried to redirect to null. Buggy message: " + m);
410
+ Goat.error('Tried to redirect to null. Buggy message: ', m);
393
411
  }
394
412
  } else if(t == 'page_expired') {
395
413
  alert("Due to inactivity, you'll need to refresh this page.");
396
414
  Goat.setPageDead();
397
415
  } else if(t == 'txn_complete') {
398
- Goat.RT.txnCompleteMsg(m);
416
+ Goat.RT.completeTxn(m['txn']);
399
417
  } else if(t == 'alert') {
400
418
  this.showAlert(m);
401
419
  } else {
402
- console.log("Unknown message type: " + m);
420
+ Goat.error('Unknown message type: ', m);
403
421
  }
404
422
  },
405
423
 
406
424
  channelDataReceived: function(data) {
407
425
  if(!data) {
408
- console.log("Null data received in channelDataReceived")
426
+ Goat.error('Null data received in channelDataReceived')
409
427
  this.reopenChannel();
410
428
  return;
411
429
  }
@@ -415,12 +433,11 @@ $.extend(Goat, {
415
433
  if(data['messages']) {
416
434
  $(data['messages']).each(this.closure(function(i, m) { this.messageReceived(m) }));
417
435
  } else {
418
- console.error("Bad channel data: " + data)
436
+ Goat.error('Bad channel data: ', data)
419
437
  }
420
438
  },
421
439
 
422
440
  reopenChannel: function() {
423
- console.log("reopenChannel()");
424
441
  var fails = this.channelOpenFails;
425
442
  setTimeout(function() { Goat.openChannel(); }, Math.min(30000, Math.max(1000, Math.pow(1.5, fails))));
426
443
  this.channelOpenFails++;
@@ -428,14 +445,14 @@ $.extend(Goat, {
428
445
 
429
446
  openChannel: function() {
430
447
  if(Goat.pageDead) {
431
- console.log("not opening channel: page dead");
448
+ Goat.log("not opening channel: page dead");
432
449
  return;
433
450
  }
434
451
 
435
- console.log("opening channel");
452
+ Goat.debug("opening channel");
436
453
 
437
454
  if(this.activeChannel) {
438
- console.error("can't open channel: channel already open");
455
+ Goat.error('can\'t open channel: channel already open');
439
456
  return;
440
457
  }
441
458
 
@@ -456,7 +473,7 @@ $.extend(Goat, {
456
473
  function injectScriptTag(tag) {
457
474
 
458
475
  var logError = closure(this, function() {
459
- console.error("Goat: error loading " + src);
476
+ Goat.error('Goat: error loading ', src);
460
477
  });
461
478
 
462
479
  var base = Goat.channelURL();
@@ -487,8 +504,7 @@ $.extend(Goat, {
487
504
  cache: false,
488
505
  success: function(data) {},
489
506
  error: function(req, stat, err) {
490
- console.log("submit form error");
491
- console.log({req: req, status: stat, err: err});
507
+ Goat.error('submit form error', req, stat, err);
492
508
  }
493
509
  });
494
510
 
@@ -499,6 +515,12 @@ $.extend(Goat, {
499
515
  this.components[id] = obj;
500
516
  },
501
517
 
518
+ init: function() {
519
+ if(Goat.isLocal())
520
+ Goat.log('Running in debug mode');
521
+ this.loadComponents();
522
+ },
523
+
502
524
  loadComponents: function() {
503
525
  $.each(Goat.components, function(_, c) {
504
526
  if(!c.isLoaded())
@@ -508,7 +530,7 @@ $.extend(Goat, {
508
530
  });
509
531
 
510
532
  $(document).ready(function() {
511
- Goat.loadComponents();
533
+ Goat.init();
512
534
  setTimeout(function() { if(Goat.page_id) Goat.openChannel(); }, 1000);
513
535
  });
514
536
 
@@ -22,6 +22,14 @@ module Goat
22
22
  send_message('register_page', 'pgid' => pgid, 'user' => user, 'components' => cs.map(&:to_hash))
23
23
  end
24
24
 
25
+ def self.update_page(pgid, txn, updates)
26
+ send_message('update_page',
27
+ 'pgid' => pgid,
28
+ 'txn' => txn,
29
+ 'updates' => updates.map(&:to_hash)
30
+ )
31
+ end
32
+
25
33
  def self.live_components(cls, spec)
26
34
  resp = send_message('live_components', {'class' => cls, 'spec' => spec}, true)
27
35
  JSON.load(resp)['response'].map{|h| ComponentSkeleton.from_hash(h)}
@@ -36,17 +44,23 @@ module Goat
36
44
  components_updated(txn, pgid, [update])
37
45
  end
38
46
 
47
+ def self.components_update_completed(cs)
48
+ puts "Ack for components_updated: #{cs.inspect}"
49
+ end
50
+
39
51
  def self.components_updated(txn, pgid, updates)
40
- send_message('components_updated',
52
+ send_message('components_updated', {
41
53
  'txn' => txn,
42
54
  'pgid' => pgid,
43
- 'updates' => updates.map(&:to_hash)
55
+ 'updates' => updates.map(&:to_hash)},
56
+ true
44
57
  )
45
58
  end
46
59
  end
47
60
 
48
61
  class NoStateSrvConnectionError < RuntimeError; end
49
62
 
63
+ # this is an ugly mix of sync and async stuff
50
64
  class StateSrvConnection < EM::Connection
51
65
  include EM::P::LineText2
52
66
 
@@ -66,13 +80,29 @@ module Goat
66
80
  self.connect(@host, @port)
67
81
  end
68
82
 
69
- def self.send_message_sync(msg)
70
- s = TCPSocket.open(@host, @port)
71
- s.write(msg.to_json + "\n")
72
- resp = s.readline
83
+ def self.reconnect_sync
84
+ @sock = TCPSocket.open(@host, @port)
85
+ end
86
+
87
+ def self.sync_connection_active?
88
+ @sock && !@sock.closed?
89
+ end
90
+
91
+ def self.send_message_sync(msg, failed_last_time=false)
92
+ reconnect_sync unless sync_connection_active?
93
+ @sock.write(msg.to_json + "\n")
94
+ resp = @sock.readline
73
95
  Goat.logd("=> #{resp.inspect}") if $verbose
74
- s.close
75
96
  resp
97
+ rescue Errno::ECONNRESET, EOFError => e
98
+ # almost certainly connection was closed and we didn't notice
99
+ if failed_last_time
100
+ raise e
101
+ else
102
+ Goat.logw "Reinitializing sync connection to state-srv (#{e.inspect})"
103
+ reconnect_sync
104
+ send_message_sync(msg, true)
105
+ end
76
106
  end
77
107
 
78
108
  def self.send_message(*args)
@@ -103,6 +133,16 @@ module Goat
103
133
  end
104
134
  end
105
135
 
136
+ def message_received(msg)
137
+ msg = msg['response']
138
+
139
+ if msg['type'] = 'update_ack'
140
+ StateSrvClient.components_update_completed(msg['components'])
141
+ else
142
+ raise "Unknown message type: #{msg['type']}"
143
+ end
144
+ end
145
+
106
146
  def send_message(t, msg, sync=false)
107
147
  msg = msg.merge('type' => t)
108
148
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goat
3
3
  version: !ruby/object:Gem::Version
4
- hash: 73
4
+ hash: 79
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 45
10
- version: 0.3.45
9
+ - 46
10
+ version: 0.3.46
11
11
  platform: ruby
12
12
  authors:
13
13
  - Patrick Collison
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-01 00:00:00 +00:00
18
+ date: 2011-03-09 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -151,7 +151,7 @@ files:
151
151
  - lib/goat/dynamic.rb
152
152
  - lib/goat/extn.rb
153
153
  - lib/goat/goat.js
154
- - lib/goat/html.rb
154
+ - lib/goat/dom.rb
155
155
  - lib/goat/js/component.js
156
156
  - lib/goat/mongo.rb
157
157
  - lib/goat/net-common.rb
@@ -1,301 +0,0 @@
1
- require 'rack/utils'
2
-
3
- module Goat
4
- module DOMTools
5
- class Traverser
6
- def initialize(tree, dlg, transpose)
7
- @tree = tree
8
- @dlg = dlg
9
- @transpose = transpose
10
- end
11
-
12
- def dom_node?(node)
13
- node.is_a?(Array) && node.first.is_a?(Symbol)
14
- end
15
-
16
- def tag(node); node[0]; end
17
- def attrs(node); node[1] if node[1].is_a?(Hash); end
18
- def body(node); node[1].is_a?(Hash) ? node[2..-1] : node[1..-1]; end
19
- def domid(node); attrs(node) ? attrs(node)[:id] : nil; end
20
-
21
- def to_node(tag, attrs, body)
22
- tag = [tag]
23
- tag << attrs if attrs
24
- tag << body
25
- end
26
-
27
- def replacement_block
28
- @rep = nil
29
- @replacement_block ||= lambda {|new| @rep = new}
30
- end
31
-
32
- def traverse(node)
33
- if node.is_a?(String)
34
- @dlg.string(node, &replacement_block)
35
- @rep || node
36
- elsif node == nil
37
- @dlg.string('', &replacement_block)
38
- @rep || nil
39
- elsif dom_node?(node)
40
- @dlg.node(node, &replacement_block)
41
- rep = @rep || node
42
-
43
- if @transpose
44
- if rep != node
45
- rep
46
- else
47
- to_node(tag(node), attrs(node), traverse(body(node)))
48
- end
49
- else
50
- traverse(body(node))
51
- end
52
- elsif node.is_a?(Array)
53
- if node.size == 1
54
- traverse(node.first)
55
- else
56
- if @transpose
57
- node.map{|x| traverse(x)}
58
- else
59
- node.each{|x| traverse(x)}
60
- end
61
- end
62
- elsif node.kind_of?(Component)
63
- @dlg.component(node, &replacement_block)
64
- @rep || node
65
- else
66
- raise "Unknown object in the dom: #{node.inspect}"
67
- end
68
- end
69
-
70
- def traverse!
71
- traverse(@tree)
72
- end
73
-
74
- class BlockTraverser
75
- # would be infinitely nicer if you could just yield from a block
76
- def initialize(blk, transpose)
77
- @blk = blk
78
- @transpose = transpose
79
- end
80
-
81
- def node(node, &blk)
82
- if @transpose
83
- @blk.call(node, blk)
84
- else
85
- @blk.call(node)
86
- end
87
- end
88
-
89
- def string(str, &blk)
90
- if @transpose
91
- @blk.call(str, blk)
92
- else
93
- @blk.call(str)
94
- end
95
- end
96
-
97
- def component(c, &blk)
98
- if @transpose
99
- @blk.call(c, blk)
100
- else
101
- @blk.call(c)
102
- end
103
- end
104
- end
105
-
106
- def self.traverse(tree, dlg=nil, transpose=false, &blk)
107
- d = nil
108
-
109
- if dlg
110
- d = dlg
111
- elsif blk
112
- d = BlockTraverser.new(blk, transpose)
113
- else
114
- raise "Need a delegate"
115
- end
116
-
117
- self.new(tree, d, transpose).traverse!
118
- end
119
-
120
- def self.transpose(tree, dlg=nil, &blk)
121
- traverse(tree, dlg, true, &blk)
122
- end
123
- end
124
-
125
- def self.traverse(tree, dlg=nil, &blk); Traverser.traverse(tree, dlg, &blk); end
126
- def self.transpose(tree, dlg=nil, &blk); Traverser.transpose(tree, dlg, &blk); end
127
-
128
- def self.expanded_dom(dom)
129
- DOMTools.transpose(dom) do |elt, update|
130
- if elt.kind_of?(Component)
131
- raise "Component #{elt} has no ID: was super's initialize called?" unless elt.id
132
- Dynamic[:expander].component_used(elt)
133
- update.call(elt.component(elt.expanded_dom))
134
- end
135
- end
136
- end
137
-
138
- def self.inject_prefixes(id, dom)
139
- DOMTools.traverse(dom) do |elt|
140
- if elt.kind_of?(Array) && elt.first.is_a?(Symbol) && elt[1].is_a?(Hash)
141
- attrs = elt[1]
142
- elt[1] = attrs.map_to_hash do |k, v|
143
- if v.kind_of?(String)
144
- [k, v.prefix_ns(id)]
145
- elsif v.kind_of?(Array) && HTMLBuilder::ARRAY_ATTRS.include?(k)
146
- [k, v]
147
- elsif v.kind_of?(Integer) && HTMLBuilder::INTEGER_ATTRS.include?(k)
148
- [k, v]
149
- else
150
- raise "Invalid object #{v.inspect} to get a prefix in dom:\n#{dom.inspect}"
151
- end
152
- end
153
- end
154
- end
155
- dom
156
- end
157
-
158
- class ::String
159
- def to_html(builder)
160
- Rack::Utils.escape_html(self)
161
- end
162
- end
163
-
164
- class ::NilClass
165
- def to_html(builder)
166
- ''
167
- end
168
- end
169
-
170
- class ::Goat::HTMLString < String
171
- def to_html(builder)
172
- self
173
- end
174
- end
175
-
176
- class ::Array
177
- def to_html(builder)
178
- builder.array_to_html(self)
179
- end
180
- end
181
-
182
- class InvalidBodyError < RuntimeError
183
- attr_reader :body
184
-
185
- def initialize(body)
186
- super("Invalid body: #{body.inspect}")
187
- @body = body
188
- end
189
- end
190
-
191
- class HTMLBuilder
192
- ARRAY_ATTRS = [:class]
193
- INTEGER_ATTRS = [:colspan, :rowspan]
194
-
195
- class TagBuilder
196
- # TODO: gmail trick of only a single onclick() handler
197
-
198
- def self.build(tag, attrs, body)
199
- self.new(tag, attrs, body).dispatch
200
- end
201
-
202
- def initialize(tag, attrs, body)
203
- @tag = tag
204
- @attrs = attrs
205
- @body = body
206
-
207
- rewrite_attrs
208
- end
209
-
210
- def rewrite_attrs
211
- new = {}
212
-
213
- @attrs.map_to_hash do |k, v|
214
- if k == :class && v.kind_of?(Array)
215
- new[:class] = @attrs[:class].join(' ')
216
- else
217
- new[k] = v
218
- end
219
- end
220
-
221
- @attrs = new
222
- end
223
-
224
- def build_node
225
- [@tag, @attrs, @body]
226
- end
227
-
228
- def a_tag
229
- unless @attrs.include?(:href)
230
- @attrs[:href] = 'javascript:void(0)'
231
- end
232
-
233
- build_node
234
- end
235
-
236
- def input_tag
237
- unless @attrs.include?(:name)
238
- $stderr.puts "Warning: no name for <#{@tag} #{@attrs.inspect}>#{@body.inspect}</#{@tag}>"
239
-
240
- # this is somewhat ungainly: we generate a name automatically by hashing the values of the
241
- # other attrs. this may conflict - ideally would track per-page state for these.
242
- # purpose is to have name-less inputs preserve their values when user goes back to page.
243
- # webkit in safari 5 gets confused when inputs are nameless.
244
- unless (vals = @attrs.values{|k, v| v.kind_of?(String)}).empty?
245
- $stderr.puts "Generating a name automatically..."
246
- @attrs[:name] = vals.join.md5[0..10]
247
- end
248
- end
249
-
250
- build_node
251
- end
252
-
253
- def dispatch
254
- meth = "#{@tag}_tag".to_sym
255
-
256
- if self.respond_to?(meth)
257
- self.send(meth)
258
- else
259
- build_node
260
- end
261
- end
262
- end
263
-
264
- def standalone_tags
265
- %w{br img input}
266
- end
267
-
268
- def attrs_to_html(attrs)
269
- attrs.map {|k, v| "#{k}=\"#{v}\""}.join(' ')
270
- end
271
-
272
- def array_to_html(ar)
273
- if ar.first.kind_of?(Symbol)
274
- tag = ar[0]
275
- have_attrs = ar[1].is_a?(Hash)
276
- attrs = have_attrs ? ar[1] : {}
277
- body = ar[(have_attrs ? 2 : 1)..-1]
278
-
279
- tag, attrs, body = TagBuilder.build(tag, attrs, body)
280
- lonely = standalone_tags.include?(tag.to_s)
281
-
282
- open_tag = "<#{tag}#{attrs.empty? ? '' : (' ' + attrs_to_html(attrs))}>"
283
- body_html = body.empty? ? '' : body.map{|x| x.to_html(self)}.join
284
- close_tag = (lonely ? '' : "</#{tag}>")
285
-
286
- "#{open_tag}#{body_html}#{close_tag}"
287
- else
288
- ar.map{|x| x.to_html(self)}.join
289
- end
290
- end
291
-
292
- def initialize(input)
293
- @input = input
294
- end
295
-
296
- def html
297
- @input.to_html(self)
298
- end
299
- end
300
- end
301
- end