volt 0.9.1.pre1 → 0.9.1.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/Gemfile +8 -0
  4. data/VERSION +1 -1
  5. data/app/volt/tasks/live_query/live_query.rb +8 -2
  6. data/app/volt/tasks/query_tasks.rb +2 -1
  7. data/lib/volt/boot.rb +6 -6
  8. data/lib/volt/cli.rb +22 -17
  9. data/lib/volt/cli/asset_compile.rb +8 -4
  10. data/lib/volt/cli/console.rb +1 -0
  11. data/lib/volt/controllers/model_controller.rb +1 -1
  12. data/lib/volt/extra_core/logger.rb +4 -0
  13. data/lib/volt/models/validators/unique_validator.rb +8 -6
  14. data/lib/volt/page/bindings/attribute_binding.rb +1 -1
  15. data/lib/volt/page/channel.rb +10 -4
  16. data/lib/volt/page/page.rb +1 -1
  17. data/lib/volt/page/string_template_renderer.rb +3 -3
  18. data/lib/volt/server.rb +55 -81
  19. data/lib/volt/server/component_handler.rb +16 -8
  20. data/lib/volt/server/forking_server.rb +176 -0
  21. data/lib/volt/server/html_parser/attribute_scope.rb +1 -1
  22. data/lib/volt/server/html_parser/sandlebars_parser.rb +5 -8
  23. data/lib/volt/server/rack/http_request.rb +3 -1
  24. data/lib/volt/server/rack/http_resource.rb +3 -1
  25. data/lib/volt/server/rack/opal_files.rb +14 -0
  26. data/lib/volt/server/socket_connection_handler.rb +12 -16
  27. data/lib/volt/server/websocket/rack_server_adaptor.rb +19 -0
  28. data/lib/volt/server/websocket/websocket_handler.rb +42 -0
  29. data/lib/volt/spec/capybara.rb +18 -7
  30. data/lib/volt/spec/setup.rb +7 -2
  31. data/lib/volt/tasks/dispatcher.rb +4 -0
  32. data/lib/volt/utils/generic_pool.rb +6 -0
  33. data/lib/volt/utils/read_write_lock.rb +173 -0
  34. data/lib/volt/volt/app.rb +46 -0
  35. data/lib/volt/volt/core.rb +3 -0
  36. data/spec/apps/kitchen_sink/Gemfile +0 -4
  37. data/spec/integration/flash_spec.rb +1 -0
  38. data/spec/integration/user_spec.rb +0 -2
  39. data/spec/server/html_parser/view_parser_spec.rb +1 -1
  40. data/spec/server/rack/asset_files_spec.rb +1 -1
  41. data/spec/spec_helper.rb +12 -0
  42. data/templates/project/Gemfile.tt +2 -0
  43. data/templates/project/config/app.rb.tt +2 -2
  44. data/templates/project/config/base/index.html +1 -0
  45. data/volt.gemspec +5 -1
  46. metadata +53 -7
  47. data/app/volt/assets/js/sockjs-0.3.4.min.js +0 -27
  48. 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 = req.path.strip.gsub(/^\/components\//, '').gsub(/[.]js$/, '')
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
- javascript_code
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::StringTemplateRender.new(__p, __c, #{string_template_renderer_path.inspect}) }) }")
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
- tag_name = tag_name.downcase
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
@@ -1,5 +1,7 @@
1
1
  require 'volt'
2
- require 'rack'
2
+ if RUBY_PLATFORM != 'opal'
3
+ require 'rack'
4
+ end
3
5
 
4
6
  module Volt
5
7
  # A request object for a HttpController. See Rack::Request for more details
@@ -1,4 +1,6 @@
1
- require 'rack'
1
+ if RUBY_PLATFORM != 'opal'
2
+ require 'rack'
3
+ end
2
4
  require 'volt'
3
5
  require 'volt/router/routes'
4
6
  require 'volt/server/rack/http_request'
@@ -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 < SockJS::Session
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
- begin
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
- # Remove ourself from the available channels
63
- @@channels.delete(self)
64
-
65
- QueryTasks.new(self).close!
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
@@ -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
- Capybara.server do |app, port|
38
- require 'rack/handler/thin'
39
- Rack::Handler::Thin.run(app, Port: port)
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
- Capybara.app = Server.new(app_path).app
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