volt 0.9.1.pre1 → 0.9.1.pre2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile +8 -0
- data/VERSION +1 -1
- data/app/volt/tasks/live_query/live_query.rb +8 -2
- data/app/volt/tasks/query_tasks.rb +2 -1
- data/lib/volt/boot.rb +6 -6
- data/lib/volt/cli.rb +22 -17
- data/lib/volt/cli/asset_compile.rb +8 -4
- data/lib/volt/cli/console.rb +1 -0
- data/lib/volt/controllers/model_controller.rb +1 -1
- data/lib/volt/extra_core/logger.rb +4 -0
- data/lib/volt/models/validators/unique_validator.rb +8 -6
- data/lib/volt/page/bindings/attribute_binding.rb +1 -1
- data/lib/volt/page/channel.rb +10 -4
- data/lib/volt/page/page.rb +1 -1
- data/lib/volt/page/string_template_renderer.rb +3 -3
- data/lib/volt/server.rb +55 -81
- data/lib/volt/server/component_handler.rb +16 -8
- data/lib/volt/server/forking_server.rb +176 -0
- data/lib/volt/server/html_parser/attribute_scope.rb +1 -1
- data/lib/volt/server/html_parser/sandlebars_parser.rb +5 -8
- data/lib/volt/server/rack/http_request.rb +3 -1
- data/lib/volt/server/rack/http_resource.rb +3 -1
- data/lib/volt/server/rack/opal_files.rb +14 -0
- data/lib/volt/server/socket_connection_handler.rb +12 -16
- data/lib/volt/server/websocket/rack_server_adaptor.rb +19 -0
- data/lib/volt/server/websocket/websocket_handler.rb +42 -0
- data/lib/volt/spec/capybara.rb +18 -7
- data/lib/volt/spec/setup.rb +7 -2
- data/lib/volt/tasks/dispatcher.rb +4 -0
- data/lib/volt/utils/generic_pool.rb +6 -0
- data/lib/volt/utils/read_write_lock.rb +173 -0
- data/lib/volt/volt/app.rb +46 -0
- data/lib/volt/volt/core.rb +3 -0
- data/spec/apps/kitchen_sink/Gemfile +0 -4
- data/spec/integration/flash_spec.rb +1 -0
- data/spec/integration/user_spec.rb +0 -2
- data/spec/server/html_parser/view_parser_spec.rb +1 -1
- data/spec/server/rack/asset_files_spec.rb +1 -1
- data/spec/spec_helper.rb +12 -0
- data/templates/project/Gemfile.tt +2 -0
- data/templates/project/config/app.rb.tt +2 -2
- data/templates/project/config/base/index.html +1 -0
- data/volt.gemspec +5 -1
- metadata +53 -7
- data/app/volt/assets/js/sockjs-0.3.4.min.js +0 -27
- data/lib/volt/server/rack/component_html_renderer.rb +0 -22
@@ -11,24 +11,32 @@ module Volt
|
|
11
11
|
def call(env)
|
12
12
|
req = Rack::Request.new(env)
|
13
13
|
|
14
|
+
path = req.path.strip
|
15
|
+
|
16
|
+
request_source_map = (File.extname(path) == '.map')
|
17
|
+
|
14
18
|
# TODO: Sanatize template path
|
15
|
-
component_name =
|
19
|
+
component_name = path.gsub(/^\/components\//, '').gsub(/[.](js|map)$/, '')
|
16
20
|
|
17
|
-
javascript_code = compile_for_component(component_name)
|
21
|
+
javascript_code = compile_for_component(component_name, request_source_map)
|
18
22
|
|
19
23
|
[200, { 'Content-Type' => 'application/javascript; charset=utf-8' }, StringIO.new(javascript_code)]
|
20
24
|
end
|
21
25
|
|
22
|
-
def compile_for_component(component_name)
|
26
|
+
def compile_for_component(component_name, map=false)
|
23
27
|
code = ComponentCode.new(component_name, @component_paths).code
|
24
28
|
|
25
|
-
# Add the lib directory to the load path
|
26
|
-
Opal.append_path(Volt.root + '/lib')
|
27
|
-
|
28
29
|
# Compile the code
|
29
|
-
javascript_code = Opal.compile(code)
|
30
|
+
# javascript_code = Opal.compile(code)
|
31
|
+
builder = Opal::Builder.new.build_str(code, 'app.rb')
|
32
|
+
|
33
|
+
if map
|
34
|
+
js_code = builder.source_map
|
35
|
+
else
|
36
|
+
js_code = builder.to_s + "\n//# sourceMappingURL=#{component_name}.map"
|
37
|
+
end
|
30
38
|
|
31
|
-
|
39
|
+
js_code
|
32
40
|
end
|
33
41
|
end
|
34
42
|
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
#
|
2
|
+
|
3
|
+
require 'drb'
|
4
|
+
require 'stringio'
|
5
|
+
require 'listen'
|
6
|
+
|
7
|
+
module Volt
|
8
|
+
class ForkingServer
|
9
|
+
def initialize(server)
|
10
|
+
# A read write lock for accessing and creating the lock
|
11
|
+
@child_lock = ReadWriteLock.new
|
12
|
+
|
13
|
+
# Trap exit
|
14
|
+
at_exit do
|
15
|
+
# Only run on parent
|
16
|
+
if @child_id
|
17
|
+
puts "Exiting..."
|
18
|
+
@exiting = true
|
19
|
+
stop_child
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@server = server
|
24
|
+
|
25
|
+
start_child
|
26
|
+
end
|
27
|
+
|
28
|
+
# Start child forks off a child process and sets up a DRb connection to the
|
29
|
+
# child. #start_child should be called from within the write lock.
|
30
|
+
def start_child
|
31
|
+
# Aquire the write lock, so we prevent anyone from using the child until
|
32
|
+
# its setup or recreated.
|
33
|
+
unless @drb_object
|
34
|
+
# Get the id of the parent process, so we can wait for exit in the child
|
35
|
+
# so the child can exit if the parent closes.
|
36
|
+
@parent_id = Process.pid
|
37
|
+
|
38
|
+
@reader, @writer = IO.pipe
|
39
|
+
|
40
|
+
if @child_id = fork
|
41
|
+
# running as parent
|
42
|
+
@writer.close
|
43
|
+
|
44
|
+
# Read the url from the child
|
45
|
+
uri = @reader.gets.strip
|
46
|
+
|
47
|
+
# Setup a drb object to the child
|
48
|
+
DRb.start_service
|
49
|
+
|
50
|
+
@drb_object = DRbObject.new_with_uri(uri)
|
51
|
+
@server_proxy = @drb_object[0]
|
52
|
+
@dispatcher_proxy = @drb_object[1]
|
53
|
+
|
54
|
+
SocketConnectionHandler.dispatcher = @dispatcher_proxy
|
55
|
+
|
56
|
+
start_change_listener
|
57
|
+
else
|
58
|
+
# Running as child
|
59
|
+
@reader.close
|
60
|
+
|
61
|
+
@server.boot_volt
|
62
|
+
@rack_app = @server.new_server
|
63
|
+
|
64
|
+
# Set the drb object locally
|
65
|
+
@dispatcher = Dispatcher.new
|
66
|
+
drb_object = DRb.start_service(nil, [self, @dispatcher])
|
67
|
+
|
68
|
+
@writer.puts(drb_object.uri)
|
69
|
+
|
70
|
+
watch_for_parent_exit
|
71
|
+
|
72
|
+
begin
|
73
|
+
DRb.thread.join
|
74
|
+
rescue Interrupt => e
|
75
|
+
# Ignore interrupt
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# In the even the parent gets killed without at_exit running,
|
83
|
+
# we watch the pipe and close if the pipe gets closed.
|
84
|
+
def watch_for_parent_exit
|
85
|
+
Thread.new do
|
86
|
+
loop do
|
87
|
+
if @writer.closed?
|
88
|
+
puts "Parent process died"
|
89
|
+
exit
|
90
|
+
end
|
91
|
+
|
92
|
+
sleep 3
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def call_on_child(env)
|
98
|
+
status, headers, body = @rack_app.call(env)
|
99
|
+
|
100
|
+
# Extract the body to pass as a string. We need to do this
|
101
|
+
# because after the call, the objects will be GC'ed, so we want
|
102
|
+
# them to be able to be marshaled to be send over DRb.
|
103
|
+
if body.respond_to?(:to_str)
|
104
|
+
body_str = body
|
105
|
+
else
|
106
|
+
extracted_body = []
|
107
|
+
|
108
|
+
# Read the
|
109
|
+
body.each do |str|
|
110
|
+
extracted_body << str
|
111
|
+
end
|
112
|
+
|
113
|
+
body.close if body.respond_to?(:close)
|
114
|
+
body_str = extracted_body.join
|
115
|
+
end
|
116
|
+
|
117
|
+
[status, headers, body_str]
|
118
|
+
end
|
119
|
+
|
120
|
+
def call(env)
|
121
|
+
@child_lock.with_read_lock do
|
122
|
+
if @exiting
|
123
|
+
[500, {}, 'Server Exiting']
|
124
|
+
else
|
125
|
+
@server_proxy.call_on_child(env)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def stop_child
|
131
|
+
# clear the drb object and kill the child process.
|
132
|
+
if @drb_object
|
133
|
+
begin
|
134
|
+
@drb_object = nil
|
135
|
+
DRb.stop_service
|
136
|
+
@reader.close
|
137
|
+
stop_change_listener
|
138
|
+
Process.kill(9, @child_id)
|
139
|
+
rescue => e
|
140
|
+
puts "Stop Child Error: #{e.inspect}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def reload
|
146
|
+
Volt.logger.log_with_color('file changed, sending reload', :light_blue)
|
147
|
+
begin
|
148
|
+
SocketConnectionHandler.send_message_all(nil, 'reload')
|
149
|
+
rescue => e
|
150
|
+
Volt.logger.error("Reload dispatch error: ")
|
151
|
+
Volt.logger.error(e)
|
152
|
+
end
|
153
|
+
|
154
|
+
@child_lock.with_write_lock do
|
155
|
+
stop_child
|
156
|
+
start_child
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def start_change_listener
|
161
|
+
# Setup the listeners for file changes
|
162
|
+
@listener = Listen.to("#{@server.app_path}/") do |modified, added, removed|
|
163
|
+
Thread.new do
|
164
|
+
# Run the reload in a new thread
|
165
|
+
reload
|
166
|
+
end
|
167
|
+
end
|
168
|
+
@listener.start
|
169
|
+
end
|
170
|
+
|
171
|
+
def stop_change_listener
|
172
|
+
@listener.stop
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
@@ -113,7 +113,7 @@ module Volt
|
|
113
113
|
|
114
114
|
string_template_renderer_path = add_string_template_renderer(content)
|
115
115
|
|
116
|
-
save_binding(id, "lambda { |__p, __t, __c, __id| Volt::AttributeBinding.new(__p, __t, __c, __id, #{attribute_name.inspect}, Proc.new { Volt::
|
116
|
+
save_binding(id, "lambda { |__p, __t, __c, __id| Volt::AttributeBinding.new(__p, __t, __c, __id, #{attribute_name.inspect}, Proc.new { Volt::StringTemplateRenderer.new(__p, __c, #{string_template_renderer_path.inspect}) }) }")
|
117
117
|
end
|
118
118
|
|
119
119
|
def add_string_template_renderer(content)
|
@@ -142,7 +142,11 @@ module Volt
|
|
142
142
|
def start_tag(tag, tag_name, rest, unary)
|
143
143
|
section_tag = tag_name[0] == ':' && tag_name[1] =~ /[A-Z]/
|
144
144
|
|
145
|
-
|
145
|
+
if section_tag
|
146
|
+
tag_name = tag_name.underscore
|
147
|
+
else
|
148
|
+
tag_name = tag_name.downcase
|
149
|
+
end
|
146
150
|
|
147
151
|
# handle doctype so we get it output exactly the same way
|
148
152
|
if tag_name == '!doctype'
|
@@ -150,13 +154,6 @@ module Volt
|
|
150
154
|
return
|
151
155
|
end
|
152
156
|
|
153
|
-
# Auto-close the last inline tag if we started a new block
|
154
|
-
if BLOCK[tag_name]
|
155
|
-
if last && INLINE[last]
|
156
|
-
end_tag(nil, last)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
157
|
# Some tags close themselves when a new one of themselves is reached.
|
161
158
|
# ex, a tr will close the previous tr
|
162
159
|
if CLOSE_SELF[tag_name] && last == tag_name
|
@@ -13,6 +13,20 @@ module Volt
|
|
13
13
|
Opal::Processor.source_map_enabled = Volt.source_maps?
|
14
14
|
Opal::Processor.const_missing_enabled = true
|
15
15
|
|
16
|
+
# Setup Opal paths
|
17
|
+
|
18
|
+
# Add the lib directory to the load path
|
19
|
+
Opal.append_path(Volt.root + '/app')
|
20
|
+
Opal.append_path(Volt.root + '/lib')
|
21
|
+
|
22
|
+
Gem.loaded_specs.values.each do |gem|
|
23
|
+
path = gem.full_gem_path + '/app'
|
24
|
+
|
25
|
+
if Dir.exists?(path)
|
26
|
+
Opal.append_path(path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
16
30
|
# Don't run arity checks in production
|
17
31
|
# Opal::Processor.arity_check_enabled = !Volt.env.production?
|
18
32
|
# Opal::Processor.dynamic_require_severity = :raise
|
@@ -1,9 +1,8 @@
|
|
1
1
|
require 'json'
|
2
|
-
require 'sockjs/session'
|
3
2
|
require File.join(File.dirname(__FILE__), '../../../app/volt/tasks/query_tasks')
|
4
3
|
|
5
4
|
module Volt
|
6
|
-
class SocketConnectionHandler
|
5
|
+
class SocketConnectionHandler
|
7
6
|
# Create one instance of the dispatcher
|
8
7
|
|
9
8
|
# We track the connected user_id with the channel for use with permissions.
|
@@ -20,6 +19,7 @@ module Volt
|
|
20
19
|
|
21
20
|
# Sends a message to all, optionally skipping a users channel
|
22
21
|
def self.send_message_all(skip_channel = nil, *args)
|
22
|
+
return unless defined?(@@channels)
|
23
23
|
@@channels.each do |channel|
|
24
24
|
if skip_channel && channel == skip_channel
|
25
25
|
next
|
@@ -33,8 +33,6 @@ module Volt
|
|
33
33
|
|
34
34
|
@@channels ||= []
|
35
35
|
@@channels << self
|
36
|
-
|
37
|
-
super
|
38
36
|
end
|
39
37
|
|
40
38
|
def process_message(message)
|
@@ -48,21 +46,19 @@ module Volt
|
|
48
46
|
def send_message(*args)
|
49
47
|
str = JSON.dump([*args])
|
50
48
|
|
51
|
-
|
52
|
-
send(str)
|
53
|
-
rescue MetaState::WrongStateError => e
|
54
|
-
puts "Tried to send to closed connection: #{e.inspect}"
|
55
|
-
|
56
|
-
# Mark this channel as closed
|
57
|
-
closed
|
58
|
-
end
|
49
|
+
@session.send(str)
|
59
50
|
end
|
60
51
|
|
61
52
|
def closed
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
53
|
+
unless @closed
|
54
|
+
@closed = true
|
55
|
+
# Remove ourself from the available channels
|
56
|
+
@@channels.delete(self)
|
57
|
+
|
58
|
+
QueryTasks.new(self).close!
|
59
|
+
else
|
60
|
+
Volt.logger.error("Socket Error: Connection already closed\n#{inspect}")
|
61
|
+
end
|
66
62
|
end
|
67
63
|
|
68
64
|
def inspect
|
@@ -0,0 +1,19 @@
|
|
1
|
+
begin
|
2
|
+
require 'puma'
|
3
|
+
RUNNING_SERVER = 'puma'
|
4
|
+
rescue LoadError => e
|
5
|
+
begin
|
6
|
+
require 'thin'
|
7
|
+
RUNNING_SERVER = 'thin'
|
8
|
+
rescue LoadError => e
|
9
|
+
Volt.logger.error('Unable to find a compatible rack server, please make sure your Gemfile includes one of the following: thin or puma')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Volt
|
14
|
+
class RackServerAdaptor
|
15
|
+
def self.load
|
16
|
+
Faye::WebSocket.load_adapter(RUNNING_SERVER)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'faye/websocket'
|
2
|
+
require 'volt/server/socket_connection_handler'
|
3
|
+
require 'volt/server/websocket/rack_server_adaptor'
|
4
|
+
|
5
|
+
|
6
|
+
module Volt
|
7
|
+
# Setup the dispatcher for the socket connection handler.
|
8
|
+
# SocketConnectionHandler.dispatcher = Dispatcher.new
|
9
|
+
|
10
|
+
class WebsocketHandler
|
11
|
+
def initialize(app)
|
12
|
+
# Setup the rack server and adaptor
|
13
|
+
RackServerAdaptor.load
|
14
|
+
|
15
|
+
@app = app
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
if Faye::WebSocket.websocket?(env)
|
20
|
+
ws = Faye::WebSocket.new(env)
|
21
|
+
|
22
|
+
socket_connection_handler = SocketConnectionHandler.new(ws)
|
23
|
+
|
24
|
+
ws.on :message do |event|
|
25
|
+
socket_connection_handler.process_message(event.data)
|
26
|
+
end
|
27
|
+
|
28
|
+
ws.on :close do |event|
|
29
|
+
socket_connection_handler.closed
|
30
|
+
|
31
|
+
ws = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return async Rack response
|
35
|
+
ws.rack_response
|
36
|
+
else
|
37
|
+
# Call down to the app
|
38
|
+
@app.call(env)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/volt/spec/capybara.rb
CHANGED
@@ -2,11 +2,11 @@ require 'volt/spec/sauce_labs'
|
|
2
2
|
|
3
3
|
module Volt
|
4
4
|
class << self
|
5
|
-
def setup_capybara(app_path)
|
5
|
+
def setup_capybara(app_path, volt_app=nil)
|
6
6
|
browser = ENV['BROWSER']
|
7
7
|
|
8
8
|
if browser
|
9
|
-
setup_capybara_app(app_path)
|
9
|
+
setup_capybara_app(app_path, volt_app)
|
10
10
|
|
11
11
|
case browser
|
12
12
|
when 'phantom'
|
@@ -27,19 +27,30 @@ module Volt
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def setup_capybara_app(app_path)
|
30
|
+
def setup_capybara_app(app_path, volt_app)
|
31
31
|
require 'capybara'
|
32
32
|
require 'capybara/dsl'
|
33
33
|
require 'capybara/rspec'
|
34
34
|
require 'capybara/poltergeist'
|
35
|
+
require 'selenium-webdriver'
|
35
36
|
require 'volt/server'
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
case RUNNING_SERVER
|
39
|
+
when 'thin'
|
40
|
+
Capybara.server do |app, port|
|
41
|
+
require 'rack/handler/thin'
|
42
|
+
Rack::Handler::Thin.run(app, Port: port)
|
43
|
+
end
|
44
|
+
when 'puma'
|
45
|
+
Capybara.server do |app, port|
|
46
|
+
Puma::Server.new(app).tap do |s|
|
47
|
+
s.add_tcp_listener Capybara.server_host, port
|
48
|
+
end.run.join
|
49
|
+
end
|
40
50
|
end
|
41
51
|
|
42
|
-
|
52
|
+
# Setup server, use existing booted app
|
53
|
+
Capybara.app = Server.new(app_path, volt_app).app
|
43
54
|
end
|
44
55
|
end
|
45
56
|
end
|