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