wunderbar 0.13.0 → 0.14.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 +35 -0
- data/lib/wunderbar/builder.rb +4 -2
- data/lib/wunderbar/environment.rb +5 -0
- data/lib/wunderbar/server.rb +1 -0
- data/lib/wunderbar/version.rb +1 -1
- data/lib/wunderbar/websocket.rb +112 -39
- data/wunderbar.gemspec +2 -2
- metadata +4 -4
data/README.md
CHANGED
|
@@ -156,6 +156,10 @@ output. This is accomplished by providing one or more of the following:
|
|
|
156
156
|
code
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
+
_websocket do
|
|
160
|
+
code
|
|
161
|
+
end
|
|
162
|
+
|
|
159
163
|
Arbitrary Ruby code can be placed in each. Form parameters are made available
|
|
160
164
|
as instance variables (e.g., `@name`). Host environment (CGI, Rack, Sinatra)
|
|
161
165
|
values are accessible as methods of the `_` object: for example `_.headers`
|
|
@@ -286,6 +290,37 @@ output stream, which provides access to other useful methods, for example:
|
|
|
286
290
|
_.print 'foo'
|
|
287
291
|
_.printf "Hello %s!\n", 'world'
|
|
288
292
|
|
|
293
|
+
Methods provided to Wunderbar.websocket
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
WebSocket support requires `em-websocket` to be installed.
|
|
297
|
+
|
|
298
|
+
A web socket is a bidrectional channel. `_.send` or `_.push` can be used to
|
|
299
|
+
send arbitrary strings. More commonly, the JSON array methods described above
|
|
300
|
+
can be all be used, the important difference is that the individual entries
|
|
301
|
+
are sent individually and as they are produced.
|
|
302
|
+
|
|
303
|
+
`_.recv` or `_.pop` can be used to receive arbitrary strings. More commonly,
|
|
304
|
+
`_.subscribe` is used to register a block that is used as a callback.
|
|
305
|
+
|
|
306
|
+
`_.system` will run an aritrary command. Lines of output are sent across the
|
|
307
|
+
websocket as they are received as JSON encoded hashes with two values: `type`
|
|
308
|
+
is one of `stdin`, `stdout` or `stderr`; and `line` which contains the line
|
|
309
|
+
itself.
|
|
310
|
+
|
|
311
|
+
Options to `_websocket` are provided as a hash:
|
|
312
|
+
|
|
313
|
+
* `:port` will chose a port number, with the default being that an
|
|
314
|
+
available one is picked for you.
|
|
315
|
+
* `:sync` set to `false` will cause the WebSocket server to be run as a
|
|
316
|
+
daemon process. This defaults to `true` when run from the command line and
|
|
317
|
+
to `false` when run as CGI.
|
|
318
|
+
* `buffer_limit` will limit the amount of entries retained and sent to
|
|
319
|
+
new clients on open requests. Default is `1`. A value of zero will disable
|
|
320
|
+
buffering. A value of `nil` will result in unlimited buffering. Note:
|
|
321
|
+
buffering is effectively unlimited until the first client connects.
|
|
322
|
+
|
|
323
|
+
|
|
289
324
|
Secure by default
|
|
290
325
|
---
|
|
291
326
|
|
data/lib/wunderbar/builder.rb
CHANGED
|
@@ -338,8 +338,10 @@ module Wunderbar
|
|
|
338
338
|
result = {}
|
|
339
339
|
args.each {|arg| result[arg.to_s] = object.send arg}
|
|
340
340
|
else
|
|
341
|
-
result =
|
|
342
|
-
|
|
341
|
+
result = []
|
|
342
|
+
result = @_target if name.empty? and @_target.respond_to? :<<
|
|
343
|
+
object.each do |item|
|
|
344
|
+
result << Hash[args.map {|arg| [arg.to_s, item.send(arg)]}]
|
|
343
345
|
end
|
|
344
346
|
end
|
|
345
347
|
end
|
data/lib/wunderbar/server.rb
CHANGED
data/lib/wunderbar/version.rb
CHANGED
data/lib/wunderbar/websocket.rb
CHANGED
|
@@ -6,54 +6,104 @@ require 'socket'
|
|
|
6
6
|
begin
|
|
7
7
|
require 'em-websocket'
|
|
8
8
|
rescue LoadError
|
|
9
|
-
module EM
|
|
10
|
-
class Channel
|
|
11
|
-
def initialize
|
|
12
|
-
require 'em-websocket'
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
9
|
end
|
|
17
10
|
|
|
18
11
|
module Wunderbar
|
|
19
|
-
class Channel
|
|
20
|
-
attr_reader :port
|
|
12
|
+
class Channel
|
|
13
|
+
attr_reader :port, :connected, :complete
|
|
14
|
+
|
|
15
|
+
def initialize(port, limit)
|
|
16
|
+
# verify that the port is available
|
|
17
|
+
TCPServer.new('0.0.0.0', port).close
|
|
21
18
|
|
|
22
|
-
def initialize(port, limit=nil)
|
|
23
|
-
TCPSocket.new('localhost', port).close
|
|
24
|
-
raise ArgumentError.new "Socket #{port} is not available"
|
|
25
|
-
rescue Errno::ECONNREFUSED
|
|
26
19
|
super()
|
|
27
20
|
@port = port
|
|
21
|
+
@connected = @complete = false
|
|
22
|
+
@channel1 = EM::Channel.new
|
|
23
|
+
@channel2 = EM::Channel.new
|
|
28
24
|
@memory = []
|
|
29
|
-
@memory_channel = subscribe do |msg|
|
|
25
|
+
@memory_channel = @channel1.subscribe do |msg|
|
|
30
26
|
@memory << msg.chomp unless Symbol === msg
|
|
31
|
-
@memory.shift while limit and @memory.length > limit
|
|
27
|
+
@memory.shift while @connected and limit and @memory.length > limit
|
|
32
28
|
end
|
|
33
29
|
websocket.run
|
|
34
30
|
end
|
|
35
31
|
|
|
36
32
|
def websocket
|
|
37
|
-
@websocket
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
ws.send msg
|
|
33
|
+
return @websocket if @websocket
|
|
34
|
+
ready = false
|
|
35
|
+
@websocket = Thread.new do
|
|
36
|
+
EM.epoll
|
|
37
|
+
EM.run do
|
|
38
|
+
connection = EventMachine::WebSocket::Connection
|
|
39
|
+
EM.start_server('0.0.0.0', @port, connection, {}) do |ws|
|
|
40
|
+
ws.onopen do
|
|
41
|
+
@memory.each {|msg| ws.send msg }
|
|
42
|
+
@connected = true
|
|
43
|
+
ws.close_websocket if complete
|
|
46
44
|
end
|
|
45
|
+
|
|
46
|
+
sid = @channel1.subscribe do |msg|
|
|
47
|
+
if msg == :shutdown
|
|
48
|
+
ws.close_websocket
|
|
49
|
+
else
|
|
50
|
+
ws.send msg
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
ws.onmessage {|msg| @channel2.push msg}
|
|
55
|
+
|
|
56
|
+
ws.onclose {@channel1.unsubscribe sid}
|
|
47
57
|
end
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
EM.add_timer(0.1) {ready = true}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
sleep 0.2 until ready
|
|
62
|
+
@websocket
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def subscribe(*args, &block)
|
|
66
|
+
@channel2.subscribe(*args, &block)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def unsubscribe(*args, &block)
|
|
70
|
+
@channel2.unsubscribe(*args, &block)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def push(*args)
|
|
74
|
+
@channel1.push(*args)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def send(*args)
|
|
78
|
+
@channel1.push(*args)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def pop(*args)
|
|
82
|
+
@channel2.pop(*args)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def recv(*args)
|
|
86
|
+
@channel2.pop(*args)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _(*args, &block)
|
|
90
|
+
if block or args.length > 1
|
|
91
|
+
begin
|
|
92
|
+
builder = Wunderbar::JsonBuilder.new(Struct.new(:params).new({}))
|
|
93
|
+
builder._! self
|
|
94
|
+
builder._(*args, &block)
|
|
95
|
+
rescue Exception => e
|
|
96
|
+
self << {:type=>'stderr', :line=>e.inspect}
|
|
50
97
|
end
|
|
98
|
+
elsif args.length == 1
|
|
99
|
+
@channel1.push(args.first.to_json)
|
|
100
|
+
else
|
|
101
|
+
self
|
|
51
102
|
end
|
|
52
103
|
end
|
|
53
104
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
push(msg.to_json)
|
|
105
|
+
def <<(value)
|
|
106
|
+
@channel1.push(value.to_json)
|
|
57
107
|
end
|
|
58
108
|
|
|
59
109
|
def system(command)
|
|
@@ -73,26 +123,39 @@ module Wunderbar
|
|
|
73
123
|
end
|
|
74
124
|
end
|
|
75
125
|
|
|
126
|
+
def complete=(value)
|
|
127
|
+
@channel1.push :shutdown if value
|
|
128
|
+
@complete = value
|
|
129
|
+
end
|
|
130
|
+
|
|
76
131
|
def close
|
|
77
|
-
unsubscribe @memory_channel if @memory_channel
|
|
78
|
-
push :shutdown
|
|
79
|
-
sleep 1
|
|
132
|
+
@channel1.unsubscribe @memory_channel if @memory_channel
|
|
80
133
|
EM::WebSocket.stop
|
|
81
134
|
websocket.join
|
|
82
135
|
end
|
|
83
136
|
end
|
|
84
137
|
|
|
85
138
|
if defined? EventMachine::WebSocket
|
|
86
|
-
def self.websocket(
|
|
139
|
+
def self.websocket(opts={}, &block)
|
|
140
|
+
opts = {:port => opts} if Fixnum === opts
|
|
141
|
+
port = opts[:port]
|
|
142
|
+
buffer = opts.fetch(:buffer,1)
|
|
143
|
+
|
|
87
144
|
if not port
|
|
88
145
|
socket = TCPServer.new(0)
|
|
89
146
|
port = Socket.unpack_sockaddr_in(socket.getsockname).first
|
|
90
147
|
socket.close
|
|
91
148
|
end
|
|
92
149
|
|
|
93
|
-
|
|
150
|
+
sock1 = nil
|
|
151
|
+
|
|
152
|
+
proc = Proc.new do
|
|
94
153
|
begin
|
|
95
|
-
channel = Wunderbar::Channel.new(port)
|
|
154
|
+
channel = Wunderbar::Channel.new(port, buffer)
|
|
155
|
+
if sock1
|
|
156
|
+
sock1.send('x',0)
|
|
157
|
+
sock1.close
|
|
158
|
+
end
|
|
96
159
|
channel.instance_eval &block
|
|
97
160
|
rescue Exception => exception
|
|
98
161
|
channel._ :type=>:stderr, :line=>exception.inspect
|
|
@@ -101,13 +164,23 @@ module Wunderbar
|
|
|
101
164
|
channel._ :type=>:stderr, :line=>" #{frame}"
|
|
102
165
|
end
|
|
103
166
|
ensure
|
|
104
|
-
channel
|
|
105
|
-
|
|
106
|
-
|
|
167
|
+
if channel
|
|
168
|
+
channel.complete = true
|
|
169
|
+
sleep 5
|
|
170
|
+
sleep 60 unless channel.connected
|
|
171
|
+
channel.close
|
|
172
|
+
end
|
|
107
173
|
end
|
|
108
174
|
end
|
|
109
175
|
|
|
110
|
-
|
|
176
|
+
if opts[:sync]
|
|
177
|
+
instance_eval &proc
|
|
178
|
+
else
|
|
179
|
+
sock1, sock2 = UNIXSocket.pair
|
|
180
|
+
submit &proc
|
|
181
|
+
sleep 0.3 while sock2.recv(1) != 'x'
|
|
182
|
+
sock2.close
|
|
183
|
+
end
|
|
111
184
|
|
|
112
185
|
port
|
|
113
186
|
end
|
data/wunderbar.gemspec
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = "wunderbar"
|
|
5
|
-
s.version = "0.
|
|
5
|
+
s.version = "0.14.0"
|
|
6
6
|
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
8
8
|
s.authors = ["Sam Ruby"]
|
|
9
|
-
s.date = "2012-04-
|
|
9
|
+
s.date = "2012-04-30"
|
|
10
10
|
s.description = " Wunderbar makes it easy to produce valid HTML5, wellformed XHTML, Unicode\n (utf-8), consistently indented, readable applications. This includes\n output that conforms to the Polyglot specification and the emerging\n results from the XML Error Recovery Community Group.\n"
|
|
11
11
|
s.email = "rubys@intertwingly.net"
|
|
12
12
|
s.files = ["wunderbar.gemspec", "README.md", "COPYING", "lib/wunderbar.rb", "lib/wunderbar", "lib/wunderbar/installation.rb", "lib/wunderbar/html-methods.rb", "lib/wunderbar/job-control.rb", "lib/wunderbar/server.rb", "lib/wunderbar/logger.rb", "lib/wunderbar/rack.rb", "lib/wunderbar/builder.rb", "lib/wunderbar/websocket.rb", "lib/wunderbar/sinatra.rb", "lib/wunderbar/environment.rb", "lib/wunderbar/rails.rb", "lib/wunderbar/cgi-methods.rb", "lib/wunderbar/cssproxy.rb", "lib/wunderbar/version.rb"]
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wunderbar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 39
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
8
|
+
- 14
|
|
9
9
|
- 0
|
|
10
|
-
version: 0.
|
|
10
|
+
version: 0.14.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Sam Ruby
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2012-04-
|
|
18
|
+
date: 2012-04-30 00:00:00 Z
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
21
21
|
name: builder
|