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