volt 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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