volt 0.2.3

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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +37 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE.txt +22 -0
  8. data/Rakefile +23 -0
  9. data/Readme.md +34 -0
  10. data/VERSION +1 -0
  11. data/bin/volt +4 -0
  12. data/docs/GETTING_STARTED.md +7 -0
  13. data/docs/GUIDE.md +33 -0
  14. data/lib/volt.rb +15 -0
  15. data/lib/volt/benchmark/benchmark.rb +25 -0
  16. data/lib/volt/cli.rb +34 -0
  17. data/lib/volt/console.rb +19 -0
  18. data/lib/volt/controllers/model_controller.rb +29 -0
  19. data/lib/volt/extra_core/array.rb +10 -0
  20. data/lib/volt/extra_core/blank.rb +88 -0
  21. data/lib/volt/extra_core/extra_core.rb +7 -0
  22. data/lib/volt/extra_core/numeric.rb +9 -0
  23. data/lib/volt/extra_core/object.rb +36 -0
  24. data/lib/volt/extra_core/string.rb +29 -0
  25. data/lib/volt/extra_core/stringify_keys.rb +7 -0
  26. data/lib/volt/extra_core/true_false.rb +44 -0
  27. data/lib/volt/extra_core/try.rb +31 -0
  28. data/lib/volt/models.rb +5 -0
  29. data/lib/volt/models/array_model.rb +37 -0
  30. data/lib/volt/models/model.rb +210 -0
  31. data/lib/volt/models/model_wrapper.rb +23 -0
  32. data/lib/volt/models/params.rb +67 -0
  33. data/lib/volt/models/url.rb +192 -0
  34. data/lib/volt/page/url_tracker.rb +36 -0
  35. data/lib/volt/reactive/array_extensions.rb +13 -0
  36. data/lib/volt/reactive/event_chain.rb +126 -0
  37. data/lib/volt/reactive/events.rb +283 -0
  38. data/lib/volt/reactive/object_tracker.rb +99 -0
  39. data/lib/volt/reactive/object_tracking.rb +15 -0
  40. data/lib/volt/reactive/reactive_array.rb +222 -0
  41. data/lib/volt/reactive/reactive_tags.rb +64 -0
  42. data/lib/volt/reactive/reactive_value.rb +368 -0
  43. data/lib/volt/reactive/string_extensions.rb +34 -0
  44. data/lib/volt/router/routes.rb +83 -0
  45. data/lib/volt/server.rb +121 -0
  46. data/lib/volt/server/binding_setup.rb +2 -0
  47. data/lib/volt/server/channel_handler.rb +31 -0
  48. data/lib/volt/server/component_handler.rb +88 -0
  49. data/lib/volt/server/if_binding_setup.rb +29 -0
  50. data/lib/volt/server/request_handler.rb +16 -0
  51. data/lib/volt/server/scope.rb +43 -0
  52. data/lib/volt/server/source_map_server.rb +31 -0
  53. data/lib/volt/server/template_parser.rb +452 -0
  54. data/lib/volt/store/mongo.rb +5 -0
  55. data/lib/volt/templates/attribute_binding.rb +110 -0
  56. data/lib/volt/templates/base_binding.rb +37 -0
  57. data/lib/volt/templates/channel.rb +48 -0
  58. data/lib/volt/templates/content_binding.rb +35 -0
  59. data/lib/volt/templates/document_events.rb +80 -0
  60. data/lib/volt/templates/each_binding.rb +115 -0
  61. data/lib/volt/templates/event_binding.rb +51 -0
  62. data/lib/volt/templates/if_binding.rb +74 -0
  63. data/lib/volt/templates/memory_test.rb +26 -0
  64. data/lib/volt/templates/page.rb +146 -0
  65. data/lib/volt/templates/reactive_template.rb +38 -0
  66. data/lib/volt/templates/render_queue.rb +5 -0
  67. data/lib/volt/templates/sub_context.rb +23 -0
  68. data/lib/volt/templates/targets/attribute_section.rb +33 -0
  69. data/lib/volt/templates/targets/attribute_target.rb +18 -0
  70. data/lib/volt/templates/targets/base_section.rb +14 -0
  71. data/lib/volt/templates/targets/binding_document/base_node.rb +3 -0
  72. data/lib/volt/templates/targets/binding_document/component_node.rb +112 -0
  73. data/lib/volt/templates/targets/binding_document/html_node.rb +11 -0
  74. data/lib/volt/templates/targets/dom_section.rb +147 -0
  75. data/lib/volt/templates/targets/dom_target.rb +11 -0
  76. data/lib/volt/templates/template_binding.rb +159 -0
  77. data/lib/volt/templates/template_renderer.rb +50 -0
  78. data/spec/models/event_chain_spec.rb +129 -0
  79. data/spec/models/model_spec.rb +340 -0
  80. data/spec/models/old_model_spec.rb +109 -0
  81. data/spec/models/reactive_array_spec.rb +262 -0
  82. data/spec/models/reactive_tags_spec.rb +35 -0
  83. data/spec/models/reactive_value_spec.rb +336 -0
  84. data/spec/models/string_extensions_spec.rb +57 -0
  85. data/spec/router/routes_spec.rb +24 -0
  86. data/spec/server/template_parser_spec.rb +50 -0
  87. data/spec/spec_helper.rb +20 -0
  88. data/spec/store/mongo_spec.rb +4 -0
  89. data/spec/templates/targets/binding_document/component_node_spec.rb +18 -0
  90. data/spec/templates/template_binding_spec.rb +98 -0
  91. data/templates/.gitignore +12 -0
  92. data/templates/Gemfile.tt +8 -0
  93. data/templates/app/.empty_directory +0 -0
  94. data/templates/app/home/config/routes.rb +1 -0
  95. data/templates/app/home/controllers/index_controller.rb +5 -0
  96. data/templates/app/home/css/.empty_directory +0 -0
  97. data/templates/app/home/models/.empty_directory +0 -0
  98. data/templates/app/home/views/index/about.html +9 -0
  99. data/templates/app/home/views/index/home.html +7 -0
  100. data/templates/app/home/views/index/index.html +28 -0
  101. data/templates/config.ru +4 -0
  102. data/templates/public/css/ansi.css +0 -0
  103. data/templates/public/css/bootstrap-theme.css +459 -0
  104. data/templates/public/css/bootstrap.css +7098 -0
  105. data/templates/public/css/jumbotron.css +79 -0
  106. data/templates/public/fonts/glyphicons-halflings-regular.eot +0 -0
  107. data/templates/public/fonts/glyphicons-halflings-regular.svg +229 -0
  108. data/templates/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  109. data/templates/public/fonts/glyphicons-halflings-regular.woff +0 -0
  110. data/templates/public/index.html +25 -0
  111. data/templates/public/js/bootstrap.js +0 -0
  112. data/templates/public/js/jquery-2.0.3.js +8829 -0
  113. data/templates/public/js/sockjs-0.2.1.min.js +27 -0
  114. data/templates/spec/spec_helper.rb +20 -0
  115. data/volt.gemspec +41 -0
  116. metadata +412 -0
@@ -0,0 +1,34 @@
1
+ class String
2
+ include ReactiveTags
3
+
4
+ alias :__old_plus :+
5
+ if RUBY_PLATFORM != 'opal'
6
+ alias :__old_concat :<<
7
+ end
8
+ # alias :concat :__old_concat
9
+
10
+ # In volt, we want a value + reactive strings to return a reactive string. So we
11
+ # over-ride + to check for when we are adding a reactive string to a string.
12
+ def +(val)
13
+ result = __old_plus(val.cur)
14
+ if val.reactive? && !result.reactive?
15
+ result = ReactiveValue.new(result)
16
+ end
17
+
18
+ return result
19
+ end
20
+
21
+ if RUBY_PLATFORM != 'opal'
22
+ tag_method(:<<) do
23
+ destructive!
24
+ end
25
+ def <<(val)
26
+ if val.reactive?
27
+ raise "Cannot append a reactive string to non-reactive string. Use + instead"
28
+ end
29
+ result = __old_concat(val)
30
+
31
+ return result
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,83 @@
1
+ class Routes
2
+ attr_reader :routes
3
+
4
+ def initialize
5
+ @routes = []
6
+ end
7
+
8
+ def define(&block)
9
+ instance_eval(&block)
10
+
11
+ return self
12
+ end
13
+
14
+ def get(path, options)
15
+ if path.index(':')
16
+ sections = path.split(/([:][^:\/]+)/)
17
+
18
+ sections.each do |section|
19
+ if section[0] == ':'
20
+ options[section[1..-1]] = nil
21
+ end
22
+ end
23
+ path = Proc.new do |params|
24
+ # Create a path using the params in the path
25
+ sections.map do |section|
26
+ if section[0] == ':'
27
+ params[section[1..-1]]
28
+ else
29
+ section
30
+ end
31
+ end.join('')
32
+ end
33
+ end
34
+
35
+ @routes << [path, options]
36
+ end
37
+
38
+ # Takes in params and generates a path and the remaining params
39
+ # that should be shown in the url.
40
+ def url_for_params(params)
41
+ routes.each do |route|
42
+ if params_match_options?(params, route[1])
43
+ return path_and_params(params, route[0], route[1])
44
+ end
45
+ end
46
+
47
+ return '/', params
48
+ end
49
+
50
+ # Takes in a path and returns the matching params.
51
+ def params_for_path(path)
52
+ routes.each do |route|
53
+ if route[0] == path
54
+ # Found the matching route
55
+ return route[1]
56
+ end
57
+ end
58
+
59
+ return {}
60
+ end
61
+
62
+ private
63
+ def path_and_params(params, path, options)
64
+ params = params.attributes.dup
65
+ path = path.call(params) if path.class == Proc
66
+
67
+ options.keys.each do |key|
68
+ params.delete(key)
69
+ end
70
+
71
+ return path, params
72
+ end
73
+
74
+ def params_match_options?(params, options)
75
+ options.each_pair do |key, value|
76
+ if value == nil || value != params.send(key)
77
+ return false
78
+ end
79
+ end
80
+
81
+ return true
82
+ end
83
+ end
@@ -0,0 +1,121 @@
1
+ require 'opal'
2
+ require "rack"
3
+ if RUBY_PLATFORM != 'java'
4
+ require "rack/sockjs"
5
+ require "eventmachine"
6
+ end
7
+ require "sprockets-sass"
8
+ require "sass"
9
+
10
+ require 'volt/extra_core/extra_core'
11
+ require 'volt/server/request_handler'
12
+ require 'volt/server/component_handler'
13
+ if RUBY_PLATFORM != 'java'
14
+ require 'volt/server/channel_handler'
15
+ end
16
+ require 'volt/server/source_map_server'
17
+
18
+ class Index
19
+
20
+ def initialize(app, files)
21
+ @app = app
22
+ @files = files
23
+ end
24
+
25
+ def call(env)
26
+ if %w[/ /demo /blog /todos /page3 /page4].include?(env['PATH_INFO']) || env['PATH_INFO'][0..5] == '/todos'
27
+ [200, { 'Content-Type' => 'text/html' }, [html]]
28
+ else
29
+ @app.call env
30
+ end
31
+ end
32
+
33
+ def html
34
+ index_path = File.expand_path(File.join(Dir.pwd, "public/index.html"))
35
+ html = File.read(index_path)
36
+
37
+ ERB.new(html).result(binding)
38
+ end
39
+ end
40
+
41
+
42
+
43
+ SOURCE_MAPS = !!ENV['MAPS']
44
+
45
+ Opal::Processor.source_map_enabled = SOURCE_MAPS
46
+ # Opal::Processor.arity_check_enabled = true
47
+ # Opal::Processor.dynamic_require_severity = :raise
48
+
49
+
50
+
51
+ class Server
52
+ def self.app
53
+
54
+ builder = Rack::Builder.new do
55
+ use Rack::CommonLogger
56
+ # run RequestHandler.new
57
+
58
+ use Rack::ShowExceptions
59
+
60
+ map '/components' do
61
+ run ComponentHandler.new
62
+ end
63
+
64
+ environment = Opal::Environment.new
65
+
66
+ app_path = File.expand_path(File.join(Dir.pwd, "app"))
67
+ environment.append_path(app_path)
68
+
69
+ volt_gem_lib_path = File.expand_path(File.join(File.dirname(__FILE__), ".."))
70
+ environment.append_path(volt_gem_lib_path)
71
+
72
+ # Add the opal load paths
73
+ Opal.paths.each do |path|
74
+ environment.append_path(path)
75
+ end
76
+
77
+ # opal-jquery gem
78
+ spec = Gem::Specification.find_by_name("opal-jquery")
79
+ environment.append_path(spec.gem_dir + "/opal")
80
+
81
+
82
+ map '/assets' do
83
+ run environment
84
+ end
85
+
86
+ if SOURCE_MAPS
87
+ source_maps = SourceMapServer.new(environment)
88
+
89
+ map(source_maps.prefix) do
90
+ run source_maps
91
+ end
92
+ end
93
+
94
+ if RUBY_PLATFORM != 'java'
95
+ map "/channel" do
96
+ run Rack::SockJS.new(ChannelHandler)#, :websocket => false
97
+ end
98
+ end
99
+
100
+ if SOURCE_MAPS
101
+ files = environment['volt/templates/page'].to_a.map {|v| v.logical_path }
102
+ else
103
+ files = []
104
+ end
105
+
106
+ use Index, files
107
+
108
+ use Rack::Static,
109
+ :urls => ["/"],
110
+ :root => "public",
111
+ :index => "",
112
+ :header_rules => [
113
+ [:all, {'Cache-Control' => 'public, max-age=86400'}]
114
+ ]
115
+
116
+ run lambda{ |env| [ 404, { 'Content-Type' => 'text/html' }, ['404 - page not found'] ] }
117
+ end
118
+
119
+ return builder
120
+ end
121
+ end
@@ -0,0 +1,2 @@
1
+ class BindingSetup
2
+ end
@@ -0,0 +1,31 @@
1
+ require 'json'
2
+ require 'sockjs/session'
3
+
4
+ class ChannelHandler < SockJS::Session
5
+ def self.message_all
6
+ @@channels.each do |channel|
7
+ channel.send(message)
8
+ end
9
+
10
+ end
11
+
12
+ def initialize(session, *args)
13
+ @session = session
14
+
15
+ @@channels ||= []
16
+ @@channels << self
17
+
18
+ super
19
+ end
20
+
21
+ def process_message(message)
22
+ puts "Process: #{message}"
23
+ self.class.message_all(message)
24
+ end
25
+
26
+ def closed
27
+ # Remove ourself from the available channels
28
+ @@channels.delete(self)
29
+ end
30
+
31
+ end
@@ -0,0 +1,88 @@
1
+ require 'stringio'
2
+ require 'volt'
3
+ require 'volt/server/template_parser'
4
+
5
+ class ComponentHandler
6
+ def call(env)
7
+ req = Rack::Request.new(env)
8
+
9
+ # TODO: Sanatize template path
10
+ @component_path = req.path.strip.gsub(/^\/components\//, '').gsub(/[.]js$/, '')
11
+
12
+ code = generate_controller_code + generate_view_code + generate_model_code + generate_routes_code
13
+
14
+ javascript_code = Opal.compile(code)
15
+
16
+ # puts "ENV: #{env.inspect}"
17
+ [200, {"Content-Type" => "text/html"}, StringIO.new(javascript_code)]
18
+ end
19
+
20
+ def generate_view_code
21
+ code = ''
22
+ views_path = Volt.root + "/app/#{@component_path}/views/"
23
+
24
+ # Load all templates in the folder
25
+ Dir["#{views_path}*/*.html"].each do |view_path|
26
+ # Get the path for the template, supports templates in folders
27
+ template_path = view_path[views_path.size..((-1 * ('.html'.size + 1)))]
28
+ template_path = "#{@component_path}/#{template_path}"
29
+ # puts "Template Path: #{template_path.inspect}"
30
+
31
+ all_templates = TemplateParser.new(File.read(view_path), template_path)
32
+
33
+ binding_initializers = []
34
+ all_templates.templates.each_pair do |name, template|
35
+ binding_code = []
36
+
37
+ template['bindings'].each_pair do |key,value|
38
+ binding_code << "#{key.inspect} => [#{value.join(', ')}]"
39
+ end
40
+
41
+ binding_code = "{#{binding_code.join(', ')}}"
42
+
43
+ code << "$page.add_template(#{name.inspect}, #{template['html'].inspect}, #{binding_code})\n"
44
+ end
45
+ end
46
+ # puts "--------------"
47
+ # puts "CODE: #{code}"
48
+
49
+ return code
50
+ end
51
+
52
+ def generate_controller_code
53
+ code = ''
54
+ controllers_path = Volt.root + "/app/#{@component_path}/controllers/"
55
+
56
+ Dir["#{controllers_path}*_controller.rb"].each do |controller_path|
57
+ code << File.read(controller_path) + "\n\n"
58
+ end
59
+
60
+ return code
61
+ end
62
+
63
+ def generate_model_code
64
+ code = ''
65
+ models_path = Volt.root + "/app/#{@component_path}/models/"
66
+
67
+ Dir["#{models_path}*.rb"].each do |model_path|
68
+ code << File.read(model_path) + "\n\n"
69
+
70
+ model_name = model_path.match(/([^\/]+)[.]rb$/)[1]
71
+
72
+ code << "$page.add_model(#{model_name.inspect})\n\n"
73
+ end
74
+
75
+ return code
76
+ end
77
+
78
+ def generate_routes_code
79
+ code = ''
80
+ routes_path = Volt.root + "/app/#{@component_path}/config/routes.rb"
81
+
82
+ code << "$page.add_routes do\n"
83
+ code << "\n" + File.read(routes_path) + "\n"
84
+ code << "end\n\n"
85
+
86
+ return code
87
+ end
88
+ end
@@ -0,0 +1,29 @@
1
+ # With if bindings, we need to track each branch, which is on the
2
+ # same scope level as the original if statement. We use this class
3
+ # to track each branch.
4
+
5
+ require 'volt/server/binding_setup'
6
+ class IfBindingSetup < BindingSetup
7
+ def initialize
8
+ @branches = []
9
+ end
10
+
11
+ def add_branch(content, template_name)
12
+ @branches << [content, template_name]
13
+ end
14
+
15
+ def to_setup_code
16
+ branches = @branches.map do |branch|
17
+ content = branch[0]
18
+ if content == nil
19
+ content = nil.inspect
20
+ else
21
+ content = "Proc.new { #{branch[0]} }"
22
+ end
23
+
24
+ "[#{content}, #{branch[1].inspect}]"
25
+ end.join(', ')
26
+
27
+ "lambda { |target, context, id| IfBinding.new(target, context, id, [#{branches}]) }"
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ require 'stringio'
2
+
3
+ class RequestHandler
4
+ def call(env)
5
+ req = Rack::Request.new(env)
6
+ # puts env.inspect
7
+ # puts req.inspect
8
+
9
+ puts req.path
10
+ req.post?
11
+ puts req.params["data"]
12
+
13
+ # puts "ENV: #{env.inspect}"
14
+ [200, {"Content-Type" => "text/html"}, StringIO.new("Hello Rack!")]
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ # Template parsing is fairly simple at the moment. Basically we walk the dom and
2
+ # do two types of replacements.
3
+ # 1) replacement in text nodes
4
+ # 2) attribute replacements
5
+
6
+ class Scope
7
+ attr_accessor :bindings, :outer_binding_number, :closed_block_scopes, :last_if_binding
8
+
9
+ def initialize(outer_binding_number=nil)
10
+ # For block bindings, the outer binding number lets us know what the name
11
+ # of the comments are that go before/after this scope block.
12
+ @outer_binding_number = outer_binding_number
13
+ @bindings = {}
14
+ end
15
+
16
+ def add_closed_child_scope(scope)
17
+ @closed_block_scopes ||= []
18
+ @closed_block_scopes << scope
19
+ end
20
+
21
+ def add_binding(binding_name, setup_code)
22
+ @bindings[binding_name] ||= []
23
+ @bindings[binding_name] << setup_code
24
+ end
25
+
26
+ def start_if_binding(binding_name, if_binding_setup)
27
+ @last_if_binding = [binding_name, if_binding_setup]
28
+ end
29
+
30
+ def current_if_binding
31
+ @last_if_binding
32
+ end
33
+
34
+ def close_if_binding!
35
+ if @last_if_binding
36
+ binding_name, if_binding_setup = @last_if_binding
37
+ @last_if_binding = nil
38
+
39
+ add_binding(binding_name, if_binding_setup.to_setup_code)
40
+ end
41
+ end
42
+
43
+ end