goat 0.2.12 → 0.3.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.
- data/README.md +45 -0
- data/bin/channel-srv +150 -0
- data/bin/press +29 -0
- data/bin/state-srv +390 -0
- data/bin/sync +53 -0
- data/bin/yodel +1 -1
- data/goat.gemspec +37 -0
- data/lib/goat.rb +774 -618
- data/lib/goat/common.rb +53 -0
- data/lib/goat/dynamic.rb +91 -0
- data/lib/goat/extn.rb +94 -5
- data/lib/goat/goat.js +348 -119
- data/lib/goat/html.rb +221 -103
- data/lib/goat/js/component.js +18 -15
- data/lib/goat/net-common.rb +38 -0
- data/lib/goat/notifications.rb +51 -46
- data/lib/goat/state-srv.rb +119 -0
- data/lib/goat/yodel.rb +3 -2
- data/lib/views/plain_layout.erb +7 -3
- metadata +18 -10
- data/lib/goat/logger.rb +0 -39
- data/lib/goat/sinatra.rb +0 -11
data/lib/goat/html.rb
CHANGED
@@ -1,147 +1,265 @@
|
|
1
1
|
module Goat
|
2
|
-
|
3
|
-
class
|
4
|
-
def
|
5
|
-
|
2
|
+
module DOMTools
|
3
|
+
class Traverser
|
4
|
+
def initialize(tree, dlg, transpose)
|
5
|
+
@tree = tree
|
6
|
+
@dlg = dlg
|
7
|
+
@transpose = transpose
|
6
8
|
end
|
7
|
-
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
raise InvalidBodyError.new(self) if self.include?(nil)
|
12
|
-
builder.array_to_html(self, comp)
|
10
|
+
def dom_node?(node)
|
11
|
+
node.is_a?(Array) && node.first.is_a?(Symbol)
|
13
12
|
end
|
14
|
-
end
|
15
13
|
|
16
|
-
|
17
|
-
|
14
|
+
def tag(node); node[0]; end
|
15
|
+
def attrs(node); node[1] if node[1].is_a?(Hash); end
|
16
|
+
def body(node); node[1].is_a?(Hash) ? node[2..-1] : node[1..-1]; end
|
17
|
+
def domid(node); attrs(node) ? attrs(node)[:id] : nil; end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
19
|
+
def to_node(tag, attrs, body)
|
20
|
+
tag = [tag]
|
21
|
+
tag << attrs if attrs
|
22
|
+
tag << body
|
22
23
|
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class TagBuilder
|
26
|
-
# TODO: gmail trick of only a single onclick() handler
|
27
24
|
|
28
|
-
def
|
29
|
-
|
25
|
+
def replacement_block
|
26
|
+
@rep = nil
|
27
|
+
@replacement_block ||= lambda {|new| @rep = new}
|
30
28
|
end
|
31
29
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
def traverse(node)
|
31
|
+
if node.is_a?(String)
|
32
|
+
@dlg.string(node, &replacement_block)
|
33
|
+
@rep || node
|
34
|
+
elsif dom_node?(node)
|
35
|
+
@dlg.node(node, &replacement_block)
|
36
|
+
rep = @rep || node
|
37
|
+
|
38
|
+
if @transpose
|
39
|
+
if rep != node
|
40
|
+
rep
|
41
|
+
else
|
42
|
+
to_node(tag(node), attrs(node), traverse(body(node)))
|
43
|
+
end
|
44
|
+
else
|
45
|
+
traverse(body(node))
|
46
|
+
end
|
47
|
+
elsif node.is_a?(Array)
|
48
|
+
if node.include?(nil)
|
49
|
+
raise "Invalid array: #{node.inspect}"
|
50
|
+
end
|
37
51
|
|
38
|
-
|
52
|
+
if node.size == 1
|
53
|
+
traverse(node.first)
|
54
|
+
else
|
55
|
+
if @transpose
|
56
|
+
node.map{|x| traverse(x)}
|
57
|
+
else
|
58
|
+
node.each{|x| traverse(x)}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
elsif node.kind_of?(Component)
|
62
|
+
@dlg.component(node, &replacement_block)
|
63
|
+
@rep || node
|
64
|
+
else
|
65
|
+
raise "Unknown object in the dom: #{node.inspect}"
|
66
|
+
end
|
39
67
|
end
|
40
68
|
|
41
|
-
def
|
42
|
-
|
69
|
+
def traverse!
|
70
|
+
traverse(@tree)
|
71
|
+
end
|
43
72
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
73
|
+
class BlockTraverser
|
74
|
+
# would be infinitely nicer if you could just yield from a block
|
75
|
+
def initialize(blk, transpose)
|
76
|
+
@blk = blk
|
77
|
+
@transpose = transpose
|
78
|
+
end
|
49
79
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
else
|
56
|
-
new[k] = v
|
80
|
+
def node(node, &blk)
|
81
|
+
if @transpose
|
82
|
+
@blk.call(node, blk)
|
83
|
+
else
|
84
|
+
@blk.call(node)
|
57
85
|
end
|
58
86
|
end
|
59
87
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
def a_tag
|
69
|
-
if action = @attrs.delete(:action)
|
70
|
-
# [:a, {}, 'dead']
|
71
|
-
# key = @comp.register_callback(action)
|
72
|
-
# @attrs[:onclick] = "Goat.remoteDispatch('#{@comp.id}', '#{key}')"
|
73
|
-
# @attrs[:href] = '#'
|
74
|
-
# [:a, @attrs, @body]
|
75
|
-
action[:target] ||= @comp
|
76
|
-
action[:args] ||= []
|
77
|
-
key = @comp.register_callback(ActionProc.new { action })
|
78
|
-
@attrs[:onclick] = "return Goat.remoteDispatch('#{@comp.id}', '#{key}')"
|
79
|
-
@attrs[:href] = '#'
|
80
|
-
[:a, @attrs, @body]
|
81
|
-
else
|
82
|
-
identity
|
88
|
+
def string(str, &blk)
|
89
|
+
if @transpose
|
90
|
+
@blk.call(str, blk)
|
91
|
+
else
|
92
|
+
@blk.call(str)
|
93
|
+
end
|
83
94
|
end
|
84
|
-
end
|
85
95
|
|
86
|
-
|
87
|
-
|
96
|
+
def component(c, &blk)
|
97
|
+
if @transpose
|
98
|
+
@blk.call(c, blk)
|
99
|
+
else
|
100
|
+
@blk.call(c)
|
101
|
+
end
|
102
|
+
end
|
88
103
|
end
|
89
|
-
|
90
|
-
def dispatch
|
91
|
-
meth = "#{@tag}_tag".to_sym
|
92
104
|
|
93
|
-
|
94
|
-
|
105
|
+
def self.traverse(tree, dlg=nil, transpose=false, &blk)
|
106
|
+
d = nil
|
107
|
+
|
108
|
+
if dlg
|
109
|
+
d = dlg
|
110
|
+
elsif blk
|
111
|
+
d = BlockTraverser.new(blk, transpose)
|
95
112
|
else
|
96
|
-
|
113
|
+
raise "Need a delegate"
|
97
114
|
end
|
115
|
+
|
116
|
+
self.new(tree, d, transpose).traverse!
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.transpose(tree, dlg=nil, &blk)
|
120
|
+
traverse(tree, dlg, true, &blk)
|
98
121
|
end
|
99
122
|
end
|
100
123
|
|
101
|
-
def
|
102
|
-
|
124
|
+
def self.traverse(tree, dlg=nil, &blk); Traverser.traverse(tree, dlg, &blk); end
|
125
|
+
def self.transpose(tree, dlg=nil, &blk); Traverser.transpose(tree, dlg, &blk); end
|
126
|
+
|
127
|
+
def self.expanded_dom(dom)
|
128
|
+
DOMTools.transpose(dom) do |elt, update|
|
129
|
+
if elt.kind_of?(Component)
|
130
|
+
raise "Component #{elt} has no ID: was super's initialize called?" unless elt.id
|
131
|
+
Dynamic[:expander].component_used(elt)
|
132
|
+
update.call(elt.component(elt.expanded_dom))
|
133
|
+
end
|
134
|
+
end
|
103
135
|
end
|
104
136
|
|
105
|
-
def
|
106
|
-
|
137
|
+
def self.inject_prefixes(id, dom)
|
138
|
+
DOMTools.traverse(dom) do |elt|
|
139
|
+
if elt.kind_of?(Array) && elt.first.is_a?(Symbol) && elt[1].is_a?(Hash)
|
140
|
+
attrs = elt[1]
|
141
|
+
elt[1] = attrs.map_to_hash do |k, v|
|
142
|
+
if v.kind_of?(String)
|
143
|
+
[k, v.prefix_ns(id)]
|
144
|
+
elsif v.kind_of?(Array) && HTMLBuilder::ARRAY_ATTRS.include?(k)
|
145
|
+
[k, v]
|
146
|
+
else
|
147
|
+
raise "Invalid object #{v.inspect} to get a prefix in dom:\n#{dom.inspect}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
dom
|
153
|
+
end
|
154
|
+
|
155
|
+
class ::String
|
156
|
+
def to_html(builder)
|
157
|
+
builder.string_to_html(self)
|
158
|
+
end
|
107
159
|
end
|
108
160
|
|
109
|
-
|
110
|
-
|
161
|
+
class ::Array
|
162
|
+
def to_html(builder)
|
163
|
+
raise InvalidBodyError.new(self) if self.include?(nil)
|
164
|
+
builder.array_to_html(self)
|
165
|
+
end
|
111
166
|
end
|
112
167
|
|
113
|
-
|
114
|
-
|
168
|
+
class InvalidBodyError < RuntimeError
|
169
|
+
attr_reader :body
|
170
|
+
|
171
|
+
def initialize(body)
|
172
|
+
super("Invalid body: #{body.inspect}")
|
173
|
+
@body = body
|
174
|
+
end
|
115
175
|
end
|
116
176
|
|
117
|
-
|
118
|
-
|
119
|
-
tag = ar[0]
|
120
|
-
have_attrs = ar[1].is_a?(Hash)
|
121
|
-
attrs = have_attrs ? ar[1] : {}
|
122
|
-
body = ar[(have_attrs ? 2 : 1)..-1]
|
177
|
+
class HTMLBuilder
|
178
|
+
ARRAY_ATTRS = [:class]
|
123
179
|
|
124
|
-
|
125
|
-
|
126
|
-
lonely = standalone_tags.include?(tag.to_s)
|
180
|
+
class TagBuilder
|
181
|
+
# TODO: gmail trick of only a single onclick() handler
|
127
182
|
|
128
|
-
|
129
|
-
|
130
|
-
|
183
|
+
def self.build(tag, attrs, body)
|
184
|
+
self.new(tag, attrs, body).dispatch
|
185
|
+
end
|
186
|
+
|
187
|
+
def initialize(tag, attrs, body)
|
188
|
+
@tag = tag
|
189
|
+
@attrs = attrs
|
190
|
+
@body = body
|
191
|
+
|
192
|
+
rewrite_attrs
|
193
|
+
end
|
194
|
+
|
195
|
+
def rewrite_attrs
|
196
|
+
new = {}
|
197
|
+
|
198
|
+
@attrs.map_to_hash do |k, v|
|
199
|
+
if k == :class && v.kind_of?(Array)
|
200
|
+
new[:class] = @attrs[:class].join(' ')
|
201
|
+
else
|
202
|
+
new[k] = v
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
@attrs = new
|
207
|
+
end
|
208
|
+
|
209
|
+
def identity
|
210
|
+
[@tag, @attrs, @body]
|
211
|
+
end
|
212
|
+
|
213
|
+
def dispatch
|
214
|
+
meth = "#{@tag}_tag".to_sym
|
131
215
|
|
132
|
-
|
133
|
-
|
134
|
-
|
216
|
+
if self.respond_to?(meth)
|
217
|
+
self.send(meth)
|
218
|
+
else
|
219
|
+
identity
|
220
|
+
end
|
221
|
+
end
|
135
222
|
end
|
136
|
-
end
|
137
223
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
224
|
+
def standalone_tags
|
225
|
+
%w{br img input}
|
226
|
+
end
|
227
|
+
|
228
|
+
def attrs_to_html(attrs)
|
229
|
+
attrs.map {|k, v| "#{k}=\"#{v}\""}.join(' ')
|
230
|
+
end
|
231
|
+
|
232
|
+
def string_to_html(str)
|
233
|
+
str
|
234
|
+
end
|
235
|
+
|
236
|
+
def array_to_html(ar)
|
237
|
+
if ar.first.kind_of?(Symbol)
|
238
|
+
tag = ar[0]
|
239
|
+
have_attrs = ar[1].is_a?(Hash)
|
240
|
+
attrs = have_attrs ? ar[1] : {}
|
241
|
+
body = ar[(have_attrs ? 2 : 1)..-1]
|
142
242
|
|
143
|
-
|
144
|
-
|
243
|
+
tag, attrs, body = TagBuilder.build(tag, attrs, body)
|
244
|
+
lonely = standalone_tags.include?(tag.to_s)
|
245
|
+
|
246
|
+
open_tag = "<#{tag}#{attrs.empty? ? '' : (' ' + attrs_to_html(attrs))}>"
|
247
|
+
body_html = body.empty? ? '' : body.map{|x| x.to_html(self)}.join
|
248
|
+
close_tag = (lonely ? '' : "</#{tag}>")
|
249
|
+
|
250
|
+
"#{open_tag}#{body_html}#{close_tag}"
|
251
|
+
else
|
252
|
+
ar.map{|x| x.to_html(self)}.join
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def initialize(input)
|
257
|
+
@input = input
|
258
|
+
end
|
259
|
+
|
260
|
+
def html
|
261
|
+
@input.to_html(self)
|
262
|
+
end
|
145
263
|
end
|
146
264
|
end
|
147
265
|
end
|
data/lib/goat/js/component.js
CHANGED
@@ -1,29 +1,32 @@
|
|
1
1
|
var Component = Class.extend({
|
2
|
-
init: function(cls, id) {
|
2
|
+
init: function(cls, id, args) {
|
3
3
|
this.id = id;
|
4
4
|
this.cls = cls;
|
5
|
+
this.loaded = false;
|
6
|
+
|
7
|
+
for(var k in args) {
|
8
|
+
this[k] = args[k]
|
9
|
+
}
|
5
10
|
},
|
6
11
|
|
7
12
|
onLoad: function() {
|
8
13
|
this.node = $("#" + this.id);
|
9
14
|
},
|
15
|
+
|
16
|
+
isLoaded: function() {
|
17
|
+
return this.loaded;
|
18
|
+
},
|
19
|
+
|
20
|
+
load: function() {
|
21
|
+
this.onLoad();
|
22
|
+
this.loaded = true;
|
23
|
+
},
|
10
24
|
|
11
|
-
rpc: function(name
|
12
|
-
|
13
|
-
args['_rpc'] = name;
|
14
|
-
args['_component'] = this.cls;
|
15
|
-
$.ajax({
|
16
|
-
type: 'POST',
|
17
|
-
url: '/rpc',
|
18
|
-
data: args,
|
19
|
-
success: callback,
|
20
|
-
error: er,
|
21
|
-
dataType: 'json',
|
22
|
-
async: true
|
23
|
-
});
|
25
|
+
rpc: function(name) {
|
26
|
+
return new GoatRPC(this, name);
|
24
27
|
},
|
25
28
|
|
26
29
|
$: function(sel) {
|
27
30
|
return $("#" + this.id + " " + sel);
|
28
31
|
}
|
29
|
-
});
|
32
|
+
});
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
module Log
|
4
|
+
def self.logname; File.basename($0); end
|
5
|
+
def self.logpath; "/tmp/#{logname}.log"; end
|
6
|
+
def self.lockpath; "/tmp/#{logname}.lock"; end
|
7
|
+
def self.logf; @log ||= File.open(logpath, 'a'); end
|
8
|
+
|
9
|
+
def self.log(lvl, msg, termmsg=nil)
|
10
|
+
logf.puts("#{Time.now.asctime} #{msg}")
|
11
|
+
logf.flush
|
12
|
+
|
13
|
+
termmsg ||= msg
|
14
|
+
$stderr.puts(termmsg[0..90])
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.log_message(msg)
|
18
|
+
log("#{msg['type']} #{msg.inspect}", "#{Term::ANSIColor.green(msg['type'])}: #{msg.inspect}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def log(lvl, msg, tmsg=nil); Log.log(lvl, msg, tmsg); end
|
23
|
+
def logd(msg); log(:debug, msg); end
|
24
|
+
def logw(msg); log(:warn, msg); end
|
25
|
+
def loge(msg); log(:error, msg); end
|
26
|
+
|
27
|
+
module Goat
|
28
|
+
# for use with EM::Connection
|
29
|
+
module JSONMessages
|
30
|
+
def send_message(type, msg)
|
31
|
+
send_data(msg.merge('type' => type).to_json + "\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_messages(msgs)
|
35
|
+
send_data(msgs.map{|msg| msg[1].merge('type' => msg[0])}.to_json + "\n")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|