redom 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,126 +1,216 @@
1
- DJS Tutorial
2
-
3
- DJS is a distributed object library for Server-WebBrowser communication. This tutorial shows how to create a web application using DJS.
1
+ Redom
2
+ =====
3
+ Redom is a distributed object based server-centric user-friendly web application framework. Redom enables developers to write all application logics in Ruby at server side easily using both browser-side and server-side libraries. Redom provides distributed objects published by browser in natural Ruby syntax so that developers can access browser-side objects directly.
4
4
 
5
- ===============
5
+ Redom requires Ruby 1.9 or higher.
6
+
6
7
  Getting started
7
- ===============
8
+ ---------------
9
+ ### Installation ###
10
+ #### `$ gem install redom`
8
11
 
9
- - Creating a DJS connection class
12
+ ### Usage ###
13
+ 1. __Create a Redom connection class__
14
+ A Redom connection class is where you write the scripts to manipulate the browser-side objects from server. The Redom connection class must include module **Redom::Connection**. An instance of Redom connection class will be created when the connection between browser and Redom server is established. There are three methods in **Redom::Connection** that can be overridden to tell Redom what to do.
15
+ * __on_open__       - Called when the connection is established.
16
+ * __on_close__       - Called when the connection is closed.
17
+ * __on_error(err)__ - Called when an error occurs.
18
+  
10
19
 
11
- A DJS connection class is where you write the scripts to manipulate the browser-side objects from server. The DJS connection class must be subclass of 'DJS::DJSConnection'. An instance of DJS connection class will be created when the connection between browser and DJS server is established. There are four methods in 'DJS::DJSConnection' that can be overridden to tell DJS what to do.
20
+ 2. __Access to browser-side objects__
21
+ Every browser-side object is published as a ditributed object. Therefore, method invocation and property reference of these objects can be done as if they are Ruby objects.
22
+ Example: document.getElementById('text').value
12
23
 
13
- on_open() - Called when the connection is established.
14
- on_error(err) - Called when an browser-side error occurs.
15
- on_message(msg) - Called when an message is received from browser.(not implemented)
16
- on_close() - Called when the connection is shutdown.(not implemented)
24
+ 3. __Start Redom server__
25
+ Assuming you have created a Redom connection class and saved it into a file `app.rb`, you can start Redom server using command `redom app.rb`.
26
+ Redom uses WebSocket as the server/browser communication protocol. [EM-WebSocket] is used as the default WebSocket server in Redom.
17
27
 
18
- Here is an example:
19
- #
20
- class MyConnection < DJS::Connection
21
- def on_open
22
- window.alert "Hello world!"
23
- end
24
- def on_error(error)
25
- p error
26
- end
27
- end
28
- #
28
+ 4. __Connect to Redom server from a web page__
29
+ Once you have added Redom JavaScript runtime into the web page, you can use `Redom("ws://localhost:8080").open("RedomConnectionClassName")` to connect to a Redom server. The operations written in the specified Redom connection class will be processed.
29
30
 
30
- - Starting DJS server
31
+ [EM-WebSocket]:https://github.com/igrigorik/em-websocket
31
32
 
32
- After a DJS connection class is defined, you can start DJS server using 'DJS::start(klass)' method. Your DJS connection class must be passed as the argument.
33
- #
34
- DJS.start(MyConnection, {:host => 127.0.0.1, :port => 8080})
35
- #
33
+ ### Example: Hello World! ###
34
+ hello.rb
36
35
 
37
- - Adding DJS library to HTML
36
+ require 'redom'
37
+
38
+ class HelloConnection
39
+ inlcude Redom::Connection
40
+
41
+ def on_open
42
+ alert "Hello World!"
43
+ end
44
+
45
+ def on_close
46
+ puts "Browser is closed."
47
+ end
48
+ end
38
49
 
39
- Add DJS JavaScript library 'djs.js' in your HTML. And call 'djs.start(host)' to connect to a DJS server.
40
- #
41
- <script type="text/javascript" src="djs.js"></script>
42
- <script type="text/javascript">
43
- window.onload = function() {
44
- djs.start("ws://127.0.0.1:8080");
45
- }
46
- </script>
47
- #
50
+ hello.html
48
51
 
49
- - Stopping DJS server
52
+ <html>
53
+ <head>
54
+ <title>Hello World!</title>
55
+ <script type="text/javascript" src="redom.js"></script>
56
+ <script type="text/javascript">
57
+ window.onload = function() {
58
+ Redom("ws://localhost:8080").open("HelloConnection");
59
+ }
60
+ </script>
61
+ </head>
62
+ </html>
50
63
 
51
- Use 'DJS.stop' to stop a DJS server.
52
- #
53
- DJS.stop
54
- #
64
+ See more examples in `example`
55
65
 
56
- ========
57
66
  API Docs
58
- ========
67
+ --------
68
+
69
+ #### (Module) Redom
70
+
71
+ - __Redom.start(opts = {})__
72
+
73
+ Start Redom server.
74
+
75
+ Parameter:
76
+ opts (Hash) - Options. Default values are as below:
77
+ :log => STDOUT - Log file
78
+ :log_level => 'error' - Log level. [fatal|error|warn|info|debug]
79
+ :worker => 5 - Number of worker threads.
80
+ :buff_size => 200 - Size of bluk messages before synchronization with browser.
81
+
82
+ - __Redom.stop__
83
+
84
+ Stop Redom server.
85
+
86
+ #### (Module) Redom::Connection
87
+
88
+ - __connections__
89
+
90
+ Return all Redom connection instances in an array that the Redom server is holding currently.
91
+
92
+ Return:
93
+ (Array) - An array of Redom::Connection instances
94
+
95
+ Example:
96
+
97
+ connections.each { |conn|
98
+ conn.do_something
99
+ }
59
100
 
60
- * Module: DJS
101
+ - __sync{}__
61
102
 
62
- - DJS.start(klass, opts = {})
103
+ Synchronize with browser immediately. Notice that a null block is required.
104
+
105
+ Return:
106
+ nil
107
+
108
+ Example:
109
+
110
+ a = document.getElementById("a").value
111
+ b = document.getElementById("b").value
112
+ sync{}
113
+ puts a + b
114
+
115
+ - __sync__
116
+
117
+ Return a synchronous method caller of this connection. Any method of this connection can be called through this method caller. Current process will be blocked until the method invocation is done.
118
+
119
+ Return:
120
+ (Redom::Connection::Sender) - A synchronous method caller
63
121
 
64
- Start DJS server.
122
+ Example:
65
123
 
66
- Parameters:
67
- klass (Class) - A class that extends DJS::Connection
68
- opts (Hash) - Options. Default values are as below:
69
- :buff_size => 200 - Messages stored before synchronization with browser.
124
+ def foo
125
+ sleep 5
126
+ "bar"
127
+ end
128
+ p sync.foo # will print "bar" after 5 seconds
70
129
 
71
- - DJS.stop
130
+ - __async { ... }__
72
131
 
73
- Stop DJS server.
132
+ Evaluate the code inside the block asynchronously which means current process will not be blocked.
74
133
 
75
- - DJS.connections
134
+ Return:
135
+ nil
136
+
137
+ Example:
76
138
 
77
- Return all DJS connection instances that the DJS server is holding currently.
139
+ def foo
140
+ sleep 5
141
+ "bar"
142
+ end
143
+ p async {
144
+ p foo
145
+ } # print "nil" immediately and will print "bar" after 5 seconds
78
146
 
79
- Returns:
80
- (DJS::Connections) - An DJS::Connections instance that contains all DJS::Connection instances the DJS server is holding currently.
147
+ - __async__
81
148
 
82
- Examples:
83
- DJS.connections.do_something
84
-
149
+ Return a asynchronous method caller of this connection. Any method of this connection can be called through this method caller and current process will not be blocked. The return value of the method invocation can not be retrieved.
85
150
 
86
- * Class: DJS::Connection
151
+ Return:
152
+ (Redom::Connection::Sender) - A asynchronous method caller
87
153
 
88
- - sync
154
+ Example:
89
155
 
90
- Synchronize with browser immediately.
156
+ def foo
157
+ sleep 5
158
+ "bar"
159
+ end
160
+ p async.foo # print "nil" immediately and will print "bar" after 5 seconds
91
161
 
92
- - window
162
+ - __window, document__
93
163
 
94
- A reference for browser-side object 'window'.
164
+ A reference for browser-side object 'window' and 'document'.
95
165
 
96
- Returns:
97
- (DJS::ProxyObject) - A DJS::ProxyObject that references to browser-side object 'window'.
166
+ Returns:
167
+ (Redom::Proxy) - A Redom::Proxy that references to browser-side object 'window' and 'document'.
98
168
 
99
- Examples:
100
- window.alert "Hello world."
169
+ Examples:
170
+ `window.alert "Hello world."`
171
+ `alert "Hello World." (window can be omitted)`
101
172
 
102
- * Class: DJS:ProxyObject
173
+ - __parse(src)__
103
174
 
104
- - sync, to_s, to_f, to_i, to_a, to_h, to_b
175
+ Parse Ruby code into JavaScript code.
105
176
 
106
- Synchronize with browser immediately and return the true value of the referenced object.
177
+ Parameter:
178
+ src (String) - Ruby code
107
179
 
108
- Returns:
109
- (Object) - Primitive type if the referenced object is primitive, otherwise the DJS::ProxyObject itself.
180
+ Return:
181
+ (String) - JavaScript code.
110
182
 
111
- Examples:
112
- value = document.getElementById("text").value.sync
183
+ Example:
184
+ `window.eval parse("alert 'Hello World.'")`
185
+
186
+ #### (Class) Redom:Proxy
187
+
188
+ - __sync__
189
+
190
+ Synchronize with browser immediately and return the true value of the referenced object.
191
+
192
+ Returns:
193
+ (Object) - Primitive type if the referenced object is primitive, otherwise the Redom::Proxy itself.
194
+
195
+ Examples:
196
+ `value = document.getElementById("text").value.sync`
113
197
 
114
- =====
115
198
  Hints
116
- =====
199
+ -----
200
+
201
+ #### * Define a event handler as 'object.event_name = :event_handler_name'
202
+ Examples:
117
203
 
118
- - Define a event handler as 'object.event_name = :event_handler_name'
119
- Examples:
120
204
  def button_click_handler(event)
121
205
  button = event.srcElement
122
206
  end
123
207
  document.getElementById("button").onclick = :button_click_handler
124
208
 
209
+ License
210
+ ----------
211
+ The [MIT] License - Copyright &copy; 2012 Yi Hu
212
+ [MIT]: http://www.opensource.org/licenses/mit-license.php
125
213
 
126
-
214
+ Contact
215
+ -------
216
+ Email: future.azure@gmail.com
data/bin/redom CHANGED
@@ -42,7 +42,7 @@ parser = OptionParser.new { |opts|
42
42
  opts.separator "Common options:"
43
43
 
44
44
  opts.on( "--worker NUM", "number of worker thread (default: 5)") { |num| options[:worker] = num.to_i }
45
- opts.on( "--buff-size NUM", "size of packed RPC message before sync") { |num| options[:buff_size] = num.to_i }
45
+ opts.on( "--buff-size NUM", "size of packed RPC message before sync (default: 200)") { |num| options[:buff_size] = num.to_i }
46
46
  opts.on( "--log FILE", "path to log (default: STDOUT)") { |path| options[:log] = path }
47
47
  opts.on( "--log-level fatal|error|warn|info|debug", "log level (default: error)") { |lv| options[:log_level] = lv }
48
48
  opts.on_tail( "--help", "show this message") { puts opts; exit }
data/lib/redom.rb CHANGED
@@ -5,7 +5,7 @@ require 'thread'
5
5
 
6
6
  $LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
7
7
  %w[
8
- utils connection dispatcher proxy task worker thread_pool runtime version
8
+ utils connection dispatcher proxy task worker thread_pool runtime version parser
9
9
  ].each { |file|
10
10
  require "redom/#{file}"
11
11
  }
@@ -26,8 +26,9 @@ module Redom
26
26
  # REQ_HANDSHAKE
27
27
  IDX_CONNECTION_CLASS = 1
28
28
  # REQ_METHOD_INVOCATION
29
- IDX_METHOD_NAME = 1
30
- IDX_ARGUMENTS = 2
29
+ IDX_CONNECTION_ID = 1
30
+ IDX_METHOD_NAME = 2
31
+ IDX_ARGUMENTS = 3
31
32
  # REQ_PROXY_RESULT
32
33
  IDX_TASK_ID = 1
33
34
  IDX_PROXY_RESULT = 2
@@ -2,6 +2,24 @@ module Redom
2
2
  module Connection
3
3
  include Utils
4
4
 
5
+ class Sender
6
+ def initialize(conn, async = false)
7
+ @conn = conn
8
+ @async = async
9
+ end
10
+
11
+ def method_missing(name, *args, &blck)
12
+ if @async
13
+ @conn._dispatcher.run_task(@conn, name, args, blck)
14
+ nil
15
+ else
16
+ result = @conn.send(name, *args, &blck)
17
+ @conn.sync{}
18
+ result
19
+ end
20
+ end
21
+ end
22
+
5
23
  # Default event handlers
6
24
  def on_open; end
7
25
  def on_close; end
@@ -28,6 +46,8 @@ module Redom
28
46
  @ws = ws
29
47
  @proxies = Hash.new
30
48
  @buff_size = opts[:buff_size]
49
+ @sync = Sender.new(self, false)
50
+ @async = Sender.new(self, true)
31
51
  self
32
52
  end
33
53
 
@@ -40,14 +60,10 @@ module Redom
40
60
  end
41
61
  end
42
62
 
43
- def sync(obj = nil)
44
- obj = _fid if obj == self
45
- if Connection === obj
46
- _dispatcher.run_task(obj, :sync, [_fid], nil)
47
- return
48
- end
63
+ def sync(&blck)
64
+ return @sync unless blck
49
65
 
50
- fid = obj ? obj : _fid
66
+ fid = _fid
51
67
  stack = @proxies[fid]
52
68
  return if !stack || stack.empty?
53
69
 
@@ -56,7 +72,7 @@ module Redom
56
72
  msg << v._info
57
73
  }
58
74
 
59
- rsp = Fiber.yield(msg)
75
+ rsp = Fiber.yield([self, msg])
60
76
 
61
77
  error = nil
62
78
  while result = rsp.shift
@@ -75,13 +91,46 @@ module Redom
75
91
  end
76
92
  end
77
93
 
94
+ vars = (eval('local_variables', blck.binding) - [:_]).map { |v|
95
+ "#{v} = map[#{v}.__id__].origin if Redom::Proxy === #{v} && map.key?(#{v}.__id__)"
96
+ }.join("\n")
97
+ set_var = eval %Q{
98
+ lambda { |map|
99
+ #{vars}
100
+ }
101
+ }, blck.binding
102
+ set_var.call stack
103
+
78
104
  stack.clear
79
105
 
80
106
  on_error(error) if error
107
+
108
+ nil
109
+ end
110
+
111
+ def async(block = nil, &blck)
112
+ return @async unless block || blck
113
+
114
+ if block
115
+ block.call
116
+ else
117
+ _dispatcher.run_task(self, :async, blck)
118
+ end
119
+
120
+ nil
121
+ end
122
+
123
+ def parse(str, file='(file)')
124
+ Parser.new(self).parse str, file
125
+ end
126
+
127
+ def _on_open
128
+ _bulk Proxy.new(self, [nil, nil, nil, _cid])
129
+ on_open
81
130
  end
82
131
 
83
132
  def _cid
84
- @ws.__id__
133
+ "#{@ws.__id__}@#{__id__}"
85
134
  end
86
135
 
87
136
  def _fid
@@ -18,7 +18,7 @@ module Redom
18
18
  @pool.stop
19
19
  end
20
20
 
21
- def run_task(conn, name, args, blck)
21
+ def run_task(conn, name, args = [], blck = nil)
22
22
  task = Task.new(conn, @pool.worker, [name, args, blck])
23
23
  @tasks[task._id] = task
24
24
  task.run
@@ -35,15 +35,13 @@ module Redom
35
35
  when REQ_HANDSHAKE
36
36
  if cls = @conn_cls[req[IDX_CONNECTION_CLASS]]
37
37
  conn = cls.new._init(ws, @opts)
38
- @conns[ws.__id__] = conn
39
- task = Task.new(conn, @pool.worker, [:on_open, []])
40
- @tasks[task._id] = task
41
- task.run
38
+ @conns["#{ws.__id__}@#{conn.__id__}"] = conn
39
+ run_task(conn, :_on_open)
42
40
  else
43
41
  _logger.error "Undefined Redom::Connection class '#{req[IDX_CONNECTION_CLASS]}'."
44
42
  end
45
43
  when REQ_METHOD_INVOCATION
46
- if conn = @conns[ws.__id__]
44
+ if conn = @conns[req[IDX_CONNECTION_ID]]
47
45
  req[IDX_ARGUMENTS].map! { |arg|
48
46
  if Array === arg
49
47
  case arg[0]
@@ -55,11 +53,9 @@ module Redom
55
53
  end
56
54
  arg
57
55
  }
58
- task = Task.new(conn, @pool.worker, req[IDX_METHOD_NAME..-1])
59
- @tasks[task._id] = task
60
- task.run
56
+ run_task(conn, req[IDX_METHOD_NAME], req[IDX_ARGUMENTS])
61
57
  else
62
- _logger.error "Connection missing. ID='#{ws.__id__}'"
58
+ _logger.error "Connection missing. ID='#{req[IDX_CONNECTION_ID]}'"
63
59
  end
64
60
  when REQ_PROXY_RESULT
65
61
  if task = @tasks[req[IDX_TASK_ID]]
@@ -75,9 +71,7 @@ module Redom
75
71
  def on_close(ws)
76
72
  _logger.debug "Connection closed. ID='#{ws.__id__}'"
77
73
  if conn = @conns[ws.__id__]
78
- task = Task.new(conn, @pool.worker, [:on_close, []])
79
- @tasks[task._id] = task
80
- task.run
74
+ run_task(conn, :on_close)
81
75
  @conns.delete ws.__id__
82
76
  end
83
77
  end
@@ -0,0 +1,235 @@
1
+ require 'opal'
2
+
3
+ module Redom
4
+ class Parser < Opal::Parser
5
+ include Utils
6
+
7
+ def initialize(conn)
8
+ @conn = conn
9
+ end
10
+
11
+ # s(:lit, 1)
12
+ # s(:lit, :foo)
13
+ def process_lit(sexp, level)
14
+ val = sexp.shift
15
+ case val
16
+ when Numeric
17
+ level == :recv ? "(#{val.inspect})" : val.inspect
18
+ when Symbol
19
+ "Opal.$method(#{@conn._cid.inspect}, #{val.to_s.inspect})"
20
+ when Regexp
21
+ val == // ? /^/.inspect : val.inspect
22
+ when Range
23
+ @helpers[:range] = true
24
+ "__range(#{val.begin}, #{val.end}, #{val.exclude_end?})"
25
+ else
26
+ raise "Bad lit: #{val.inspect}"
27
+ end
28
+ end
29
+
30
+ # Converts a ruby method name into its javascript equivalent for
31
+ # a method/function call. All ruby method names get prefixed with
32
+ # a '$', and if the name is a valid javascript identifier, it will
33
+ # have a '.' prefix (for dot-calling), otherwise it will be
34
+ # wrapped in brackets to use reference notation calling.
35
+ #
36
+ # mid_to_jsid('foo') # => ".$foo"
37
+ # mid_to_jsid('class') # => ".$class"
38
+ # mid_to_jsid('==') # => "['$==']"
39
+ # mid_to_jsid('name=') # => "['$name=']"
40
+ #
41
+ # @param [String] mid ruby method id
42
+ # @return [String]
43
+ def mid_to_jsid(mid, djs_call = true)
44
+ if /^([a-zA-Z0-9$_]+)=$/ =~ mid.to_s
45
+ ".$djsAssign('#{$1}')"
46
+ elsif '[]' == mid.to_s
47
+ ".$djsCall('')"
48
+ elsif /\=|\+|\-|\*|\/|\!|\?|\<|\>|\&|\||\^|\%|\~|\[/ =~ mid.to_s
49
+ "['$#{mid}']"
50
+ else
51
+ if djs_call
52
+ ".$djsCall('$#{mid}')"
53
+ else
54
+ ".$#{mid}"
55
+ end
56
+ end
57
+ end
58
+
59
+ def js_def(recvr, mid, args, stmts, line, end_line)
60
+ jsid = mid_to_jsid(mid.to_s, false)
61
+
62
+ if recvr
63
+ @scope.defines_defs = true
64
+ smethod = true if @scope.class_scope? && recvr.first == :self
65
+ recv = process(recvr, :expr)
66
+ else
67
+ @scope.defines_defn = true
68
+ recv = current_self
69
+ end
70
+
71
+ code = ''
72
+ params = nil
73
+ scope_name = nil
74
+ uses_super = nil
75
+
76
+ # opt args if last arg is sexp
77
+ opt = args.pop if Array === args.last
78
+
79
+ # block name &block
80
+ if args.last.to_s.start_with? '&'
81
+ block_name = args.pop.to_s[1..-1].to_sym
82
+ end
83
+
84
+ # splat args *splat
85
+ if args.last.to_s.start_with? '*'
86
+ if args.last == :*
87
+ args.pop
88
+ else
89
+ splat = args[-1].to_s[1..-1].to_sym
90
+ args[-1] = splat
91
+ len = args.length - 2
92
+ end
93
+ end
94
+
95
+ indent do
96
+ in_scope(:def) do
97
+ @scope.mid = mid
98
+ @scope.defs = true if recvr
99
+
100
+ if block_name
101
+ @scope.uses_block!
102
+ end
103
+
104
+ yielder = block_name || '__yield'
105
+ @scope.block_name = yielder
106
+
107
+ params = process args, :expr
108
+
109
+ opt[1..-1].each do |o|
110
+ next if o[2][2] == :undefined
111
+ id = process s(:lvar, o[1]), :expr
112
+ code += ("if (%s == null) {\n%s%s\n%s}" %
113
+ [id, @indent + INDENT, process(o, :expre), @indent])
114
+ end if opt
115
+
116
+ code += "#{splat} = __slice.call(arguments, #{len});" if splat
117
+ code += "\n#@indent" + process(stmts, :stmt)
118
+
119
+ # Returns the identity name if identified, nil otherwise
120
+ scope_name = @scope.identity
121
+
122
+ if @scope.uses_block?
123
+ @scope.add_temp '__context'
124
+ @scope.add_temp yielder
125
+
126
+ blk = "\n%s%s = %s._p || nil, __context = %s._s, %s._p = null;\n%s" %
127
+ [@indent, yielder, scope_name, yielder, scope_name, @indent]
128
+
129
+ code = blk + code
130
+ end
131
+
132
+ uses_super = @scope.uses_super
133
+
134
+ code = "#@indent#{@scope.to_vars}" + code
135
+ end
136
+ end
137
+
138
+ defcode = "#{"#{scope_name} = " if scope_name}function(#{params}) {\n#{code}\n#@indent}"
139
+
140
+ if recvr
141
+ if smethod
142
+ @scope.smethods << "$#{mid}"
143
+ "#{ @scope.name }#{jsid} = #{defcode}"
144
+ else
145
+ "#{ recv }#{ jsid } = #{ defcode }"
146
+ end
147
+ elsif @scope.class_scope?
148
+ @scope.methods << "$#{mid}"
149
+ if uses_super
150
+ @scope.add_temp uses_super
151
+ uses_super = "#{uses_super} = #{@scope.proto}#{jsid};\n#@indent"
152
+ end
153
+ "#{uses_super}#{ @scope.proto }#{jsid} = #{defcode}"
154
+ elsif @scope.type == :iter
155
+ "def#{jsid} = #{defcode}"
156
+ elsif @scope.type == :top
157
+ "#{ current_self }#{ jsid } = #{ defcode }"
158
+ else
159
+ "def#{jsid} = #{defcode}"
160
+ end
161
+ end
162
+
163
+ # s(:const, :const)
164
+ def process_const(sexp, level)
165
+ "__scope.$djsCall('#{sexp.shift}')()"
166
+ end
167
+
168
+ # s(:call, recv, :mid, s(:arglist))
169
+ # s(:call, nil, :mid, s(:arglist))
170
+ def process_call(sexp, level)
171
+ recv, meth, arglist, iter = sexp
172
+ mid = mid_to_jsid meth.to_s
173
+
174
+ case meth
175
+ when :attr_reader, :attr_writer, :attr_accessor
176
+ return handle_attr_optimize(meth, arglist[1..-1])
177
+ when :block_given?
178
+ return js_block_given(sexp, level)
179
+ when :alias_native
180
+ return handle_alias_native(sexp) if @scope.class_scope?
181
+ when :require
182
+ path = arglist[1]
183
+
184
+ if path and path[0] == :str
185
+ @requires << path[1]
186
+ end
187
+
188
+ return "//= require #{path[1]}"
189
+ when :respond_to?
190
+ return handle_respond_to(sexp, level)
191
+ end
192
+
193
+ splat = arglist[1..-1].any? { |a| a.first == :splat }
194
+
195
+ if Array === arglist.last and arglist.last.first == :block_pass
196
+ block = process s(:js_tmp, process(arglist.pop, :expr)), :expr
197
+ elsif iter
198
+ block = iter
199
+ end
200
+
201
+ recv ||= s(:self)
202
+
203
+ if block
204
+ tmprecv = @scope.new_temp
205
+ elsif splat and recv != [:self] and recv[0] != :lvar
206
+ tmprecv = @scope.new_temp
207
+ end
208
+
209
+ args = ""
210
+
211
+ recv_code = process recv, :recv
212
+
213
+ args = process arglist, :expr
214
+
215
+ result = if block
216
+ dispatch = "(%s = %s, %s%s._p = %s, %s%s" %
217
+ [tmprecv, recv_code, tmprecv, mid_to_jsid(meth.to_s, false), block, tmprecv, mid]
218
+
219
+ if splat
220
+ "%s.apply(null, %s))" % [dispatch, args]
221
+ else
222
+ "%s(%s))" % [dispatch, args]
223
+ end
224
+ else
225
+ # m_missing = " || __mm(#{meth.to_s.inspect})"
226
+ # dispatch = "((#{tmprecv} = #{recv_code}).$m#{mid}#{ m_missing })"
227
+ # splat ? "#{dispatch}.apply(null, #{args})" : "#{dispatch}(#{args})"
228
+ dispatch = tmprecv ? "(#{tmprecv} = #{recv_code})#{mid}" : "#{recv_code}#{mid}"
229
+ splat ? "#{dispatch}.apply(#{tmprecv || recv_code}, #{args})" : "#{dispatch}(#{args})"
230
+ end
231
+
232
+ result
233
+ end
234
+ end
235
+ end
data/lib/redom/proxy.rb CHANGED
@@ -25,7 +25,7 @@ module Redom
25
25
 
26
26
  def sync
27
27
  unless @solved
28
- @conn.sync
28
+ @conn.sync{}
29
29
  end
30
30
  @origin
31
31
  end
@@ -56,9 +56,9 @@ module Redom
56
56
  }
57
57
  arg
58
58
  when Method
59
- [TYPE_METHOD, arg.name]
59
+ [TYPE_METHOD, @conn._cid, arg.name]
60
60
  when Symbol
61
- [TYPE_METHOD, arg.to_s]
61
+ [TYPE_METHOD, @conn._cid, arg.to_s]
62
62
  else
63
63
  arg
64
64
  end
data/lib/redom/runtime.rb CHANGED
@@ -19,9 +19,88 @@ module Redom
19
19
  P_INFO_NAME = 2,
20
20
  P_INFO_ARGS = 3;
21
21
 
22
+ if (window.Opal) {
23
+ Object.prototype.$djsCall = function(name) {
24
+ if (name == '') {
25
+ return (function(obj) {
26
+ return function(key) {
27
+ return obj[key];
28
+ };
29
+ })(this);
30
+ }
31
+
32
+ if (name == '$method') {
33
+ return function(methodName) {
34
+ if (Opal.top['$' + methodName]) {
35
+ return Opal.top['$' + methodName];
36
+ } else {
37
+ return function(e) {
38
+ window.djs.invoke(methodName, [e]);
39
+ };
40
+ }
41
+ };
42
+ }
43
+
44
+ if (Opal.top[name]) {
45
+ if (typeof Opal.top[name] == 'function') {
46
+ return function() {
47
+ return Opal.top[name].apply(Opal.top, arguments);
48
+ };
49
+ } else {
50
+ return function() {
51
+ return Opal.top[name];
52
+ };
53
+ }
54
+ }
55
+ if (name[0] == "$" && this[name] == null) {
56
+ name = name.substring(1);
57
+ };
58
+
59
+ if (this[name] != null) {
60
+ if (typeof this[name] == 'function') {
61
+ return (function(obj) {
62
+ return function() {
63
+ return obj[name].apply(obj, arguments);
64
+ };
65
+ })(this);
66
+ } else {
67
+ return (function(obj) {
68
+ return function() {
69
+ return obj[name];
70
+ };
71
+ })(this);
72
+ }
73
+ } else if (window[name]) {
74
+ if (typeof window[name] == 'function') {
75
+ return function() {
76
+ return window[name].apply(window, arguments);
77
+ };
78
+ } else {
79
+ return function() {
80
+ return window[name];
81
+ };
82
+ }
83
+ }
84
+
85
+ return function() {
86
+ return null;
87
+ };
88
+ };
89
+ Object.prototype.$djsAssign = function(name) {
90
+ return (function(obj) {
91
+ return function(value) {
92
+ return obj[name] = value;
93
+ };
94
+ })(this);
95
+ };
96
+ Opal.top.$window = window;
97
+ Opal.top.$document = window.document;
98
+ Opal.top.$navigator = window.navigator;
99
+ Opal.top.$location = window.location;
100
+ }
101
+
22
102
  var Redom = function(server) {
23
- var ws = new WebSocket(server);
24
- conn = "Connection",
103
+ var ws = new WebSocket(server),
25
104
  refs = {
26
105
  seq: 0,
27
106
  hash: {},
@@ -54,8 +133,7 @@ module Redom
54
133
  // Initialize WebSocket event handlers
55
134
  function open(connectionClassName) {
56
135
  ws.onopen = function() {
57
- conn = connectionClassName;
58
- ws.send(serialize([REQ_HANDSHAKE, conn]));
136
+ ws.send(serialize([REQ_HANDSHAKE, connectionClassName]));
59
137
  console.log("WebSocket opened.");
60
138
  };
61
139
  ws.onclose = function() {
@@ -230,10 +308,6 @@ module Redom
230
308
  return server;
231
309
  },
232
310
 
233
- getConnection: function() {
234
- return conn;
235
- },
236
-
237
311
  open: function(connectionClassName) {
238
312
  open(connectionClassName);
239
313
  },
data/lib/redom/task.rb CHANGED
@@ -32,14 +32,14 @@ module Redom
32
32
  _logger.error "Method '#{@info[T_INFO_METHOD_NAME]}' is not defined in class '#{@conn.class}'."
33
33
  @conn.method(:on_error).call("No such method '#{@info[T_INFO_METHOD_NAME]}'.")
34
34
  end
35
- @conn.sync
35
+ @conn.sync{}
36
36
  nil
37
37
  end
38
38
  result = @fiber.resume
39
39
  end
40
40
 
41
41
  if result
42
- @conn._send [_id, result].to_json
42
+ result[0]._send [_id, result[1]].to_json
43
43
  else
44
44
  _dispatcher.delete_task _id
45
45
  end
data/lib/redom/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Redom
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
data/redom.gemspec CHANGED
@@ -17,4 +17,5 @@ Gem::Specification.new do |s|
17
17
  s.executables = ['redom']
18
18
 
19
19
  s.add_dependency 'em-websocket', '>= 0.3.5'
20
+ s.add_dependency 'opal', '>= 0.3.27'
20
21
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-12-10 00:00:00.000000000Z
13
+ date: 2012-12-21 00:00:00.000000000Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: em-websocket
17
- requirement: &70124221424660 !ruby/object:Gem::Requirement
17
+ requirement: &70180056191480 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,7 +22,18 @@ dependencies:
22
22
  version: 0.3.5
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70124221424660
25
+ version_requirements: *70180056191480
26
+ - !ruby/object:Gem::Dependency
27
+ name: opal
28
+ requirement: &70180056190760 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.27
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70180056190760
26
37
  description: A distributed object based server-centric web framework.
27
38
  email: future.azure@gmail.com
28
39
  executables:
@@ -36,6 +47,7 @@ files:
36
47
  - lib/redom.rb
37
48
  - lib/redom/connection.rb
38
49
  - lib/redom/dispatcher.rb
50
+ - lib/redom/parser.rb
39
51
  - lib/redom/proxy.rb
40
52
  - lib/redom/runtime.rb
41
53
  - lib/redom/task.rb