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 +171 -81
- data/bin/redom +1 -1
- data/lib/redom.rb +4 -3
- data/lib/redom/connection.rb +58 -9
- data/lib/redom/dispatcher.rb +7 -13
- data/lib/redom/parser.rb +235 -0
- data/lib/redom/proxy.rb +3 -3
- data/lib/redom/runtime.rb +82 -8
- data/lib/redom/task.rb +2 -2
- data/lib/redom/version.rb +1 -1
- data/redom.gemspec +1 -0
- metadata +16 -4
data/README.md
CHANGED
@@ -1,126 +1,216 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
-
|
31
|
+
[EM-WebSocket]:https://github.com/igrigorik/em-websocket
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
DJS.start(MyConnection, {:host => 127.0.0.1, :port => 8080})
|
35
|
-
#
|
33
|
+
### Example: Hello World! ###
|
34
|
+
hello.rb
|
36
35
|
|
37
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
101
|
+
- __sync{}__
|
61
102
|
|
62
|
-
|
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
|
-
|
122
|
+
Example:
|
65
123
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
124
|
+
def foo
|
125
|
+
sleep 5
|
126
|
+
"bar"
|
127
|
+
end
|
128
|
+
p sync.foo # will print "bar" after 5 seconds
|
70
129
|
|
71
|
-
|
130
|
+
- __async { ... }__
|
72
131
|
|
73
|
-
|
132
|
+
Evaluate the code inside the block asynchronously which means current process will not be blocked.
|
74
133
|
|
75
|
-
|
134
|
+
Return:
|
135
|
+
nil
|
136
|
+
|
137
|
+
Example:
|
76
138
|
|
77
|
-
|
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
|
-
|
80
|
-
(DJS::Connections) - An DJS::Connections instance that contains all DJS::Connection instances the DJS server is holding currently.
|
147
|
+
- __async__
|
81
148
|
|
82
|
-
|
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
|
-
|
151
|
+
Return:
|
152
|
+
(Redom::Connection::Sender) - A asynchronous method caller
|
87
153
|
|
88
|
-
|
154
|
+
Example:
|
89
155
|
|
90
|
-
|
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
|
-
|
162
|
+
- __window, document__
|
93
163
|
|
94
|
-
|
164
|
+
A reference for browser-side object 'window' and 'document'.
|
95
165
|
|
96
|
-
|
97
|
-
|
166
|
+
Returns:
|
167
|
+
(Redom::Proxy) - A Redom::Proxy that references to browser-side object 'window' and 'document'.
|
98
168
|
|
99
|
-
|
100
|
-
|
169
|
+
Examples:
|
170
|
+
`window.alert "Hello world."`
|
171
|
+
`alert "Hello World." (window can be omitted)`
|
101
172
|
|
102
|
-
|
173
|
+
- __parse(src)__
|
103
174
|
|
104
|
-
|
175
|
+
Parse Ruby code into JavaScript code.
|
105
176
|
|
106
|
-
|
177
|
+
Parameter:
|
178
|
+
src (String) - Ruby code
|
107
179
|
|
108
|
-
|
109
|
-
|
180
|
+
Return:
|
181
|
+
(String) - JavaScript code.
|
110
182
|
|
111
|
-
|
112
|
-
|
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 © 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
|
-
|
30
|
-
|
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
|
data/lib/redom/connection.rb
CHANGED
@@ -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(
|
44
|
-
|
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 =
|
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
|
data/lib/redom/dispatcher.rb
CHANGED
@@ -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
|
-
|
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[
|
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
|
-
|
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='#{
|
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
|
-
|
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
|
data/lib/redom/parser.rb
ADDED
@@ -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
|
-
|
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
|
-
|
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
data/redom.gemspec
CHANGED
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
|
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-
|
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: &
|
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: *
|
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
|