wunderbar 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -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 = object.map do |item|
342
- args.inject({}) {|hash, arg| hash[arg.to_s] = item.send arg; hash}
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
@@ -54,6 +54,11 @@ if self.to_s == 'main'
54
54
  Wunderbar.text(*args, &block)
55
55
  end
56
56
 
57
+ def _websocket(*args, &block)
58
+ args.last[:sync]=args.last.fetch(:sync,true) if Hash === args.last
59
+ Wunderbar.websocket(*args, &block)
60
+ end
61
+
57
62
  def env
58
63
  ENV
59
64
  end
@@ -15,6 +15,7 @@ end
15
15
  port = ARGV.find {|arg| arg =~ /--port=(.*)/}
16
16
  if port and ARGV.delete(port)
17
17
  port = $1.to_i
18
+ ENV['SERVER_PORT'] = port.to_s
18
19
 
19
20
  # Evaluate optional data from the script (after __END__)
20
21
  eval Wunderbar.data if Object.const_defined? :DATA
@@ -1,7 +1,7 @@
1
1
  module Wunderbar
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 13
4
+ MINOR = 14
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -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 < EM::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 ||= Thread.new do
38
- EM::WebSocket.start(:host => '0.0.0.0', :port => @port) do |ws|
39
- ws.onopen {@memory.each {|msg| ws.send msg }}
40
-
41
- sid = subscribe do |msg|
42
- if msg == :shutdown
43
- ws.close_websocket
44
- else
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
- ws.onclose {unsubscribe sid}
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 _(msg=nil, &block)
55
- return self if msg==nil
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(port=nil, &block)
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
- submit do
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.push :shutdown
105
- sleep 5
106
- channel.close if channel
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
- sleep 1
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.13.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-28"
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: 43
4
+ hash: 39
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 13
8
+ - 14
9
9
  - 0
10
- version: 0.13.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-28 00:00:00 Z
18
+ date: 2012-04-30 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: builder