volt 0.8.14 → 0.8.15
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/.gitignore +1 -1
- data/Readme.md +8 -2
- data/VERSION +1 -1
- data/app/volt/controllers/notices_controller.rb +1 -1
- data/app/volt/models/user.rb +2 -2
- data/app/volt/tasks/live_query/live_query_pool.rb +1 -1
- data/app/volt/tasks/query_tasks.rb +1 -1
- data/app/volt/tasks/store_tasks.rb +1 -1
- data/app/volt/tasks/user_tasks.rb +2 -2
- data/lib/volt/boot.rb +2 -2
- data/lib/volt/cli/asset_compile.rb +31 -27
- data/lib/volt/cli.rb +64 -65
- data/lib/volt/config.rb +25 -23
- data/lib/volt/console.rb +17 -16
- data/lib/volt/controllers/model_controller.rb +82 -80
- data/lib/volt/data_stores/data_store.rb +2 -2
- data/lib/volt/data_stores/mongo_driver.rb +2 -2
- data/lib/volt/extra_core/inflections.rb +2 -2
- data/lib/volt/extra_core/inflector/inflections.rb +185 -183
- data/lib/volt/extra_core/inflector/methods.rb +50 -48
- data/lib/volt/extra_core/string.rb +2 -2
- data/lib/volt/models/array_model.rb +93 -92
- data/lib/volt/models/cursor.rb +3 -2
- data/lib/volt/models/model.rb +248 -251
- data/lib/volt/models/model_hash_behaviour.rb +44 -44
- data/lib/volt/models/model_helpers.rb +38 -36
- data/lib/volt/models/model_state.rb +16 -17
- data/lib/volt/models/model_wrapper.rb +25 -24
- data/lib/volt/models/persistors/array_store.rb +145 -143
- data/lib/volt/models/persistors/base.rb +18 -16
- data/lib/volt/models/persistors/flash.rb +24 -22
- data/lib/volt/models/persistors/local_store.rb +46 -44
- data/lib/volt/models/persistors/model_identity_map.rb +10 -8
- data/lib/volt/models/persistors/model_store.rb +76 -76
- data/lib/volt/models/persistors/params.rb +19 -17
- data/lib/volt/models/persistors/query/query_listener.rb +65 -63
- data/lib/volt/models/persistors/query/query_listener_pool.rb +12 -10
- data/lib/volt/models/persistors/store.rb +28 -28
- data/lib/volt/models/persistors/store_factory.rb +12 -10
- data/lib/volt/models/persistors/store_state.rb +33 -31
- data/lib/volt/models/url.rb +96 -104
- data/lib/volt/models/validations.rb +56 -54
- data/lib/volt/models/validators/length_validator.rb +24 -22
- data/lib/volt/models/validators/presence_validator.rb +14 -12
- data/lib/volt/page/bindings/attribute_binding.rb +106 -106
- data/lib/volt/page/bindings/base_binding.rb +23 -21
- data/lib/volt/page/bindings/component_binding.rb +3 -1
- data/lib/volt/page/bindings/content_binding.rb +34 -34
- data/lib/volt/page/bindings/each_binding.rb +113 -113
- data/lib/volt/page/bindings/event_binding.rb +38 -34
- data/lib/volt/page/bindings/if_binding.rb +56 -54
- data/lib/volt/page/bindings/template_binding/grouped_controllers.rb +24 -22
- data/lib/volt/page/bindings/template_binding.rb +182 -185
- data/lib/volt/page/channel.rb +79 -77
- data/lib/volt/page/channel_stub.rb +29 -27
- data/lib/volt/page/document.rb +6 -5
- data/lib/volt/page/document_events.rb +54 -52
- data/lib/volt/page/page.rb +139 -138
- data/lib/volt/page/string_template_renderer.rb +36 -36
- data/lib/volt/page/sub_context.rb +26 -25
- data/lib/volt/page/targets/attribute_section.rb +27 -25
- data/lib/volt/page/targets/attribute_target.rb +7 -6
- data/lib/volt/page/targets/base_section.rb +27 -26
- data/lib/volt/page/targets/binding_document/base_node.rb +3 -1
- data/lib/volt/page/targets/binding_document/component_node.rb +85 -82
- data/lib/volt/page/targets/binding_document/html_node.rb +11 -9
- data/lib/volt/page/targets/dom_section.rb +78 -77
- data/lib/volt/page/targets/dom_target.rb +8 -6
- data/lib/volt/page/targets/dom_template.rb +90 -88
- data/lib/volt/page/targets/helpers/comment_searchers.rb +51 -49
- data/lib/volt/page/tasks.rb +59 -57
- data/lib/volt/page/template_renderer.rb +17 -14
- data/lib/volt/page/url_tracker.rb +26 -24
- data/lib/volt/reactive/computation.rb +87 -88
- data/lib/volt/reactive/dependency.rb +30 -28
- data/lib/volt/reactive/eventable.rb +64 -62
- data/lib/volt/reactive/hash_dependency.rb +25 -23
- data/lib/volt/reactive/reactive_accessors.rb +34 -32
- data/lib/volt/reactive/reactive_array.rb +162 -162
- data/lib/volt/reactive/reactive_hash.rb +37 -35
- data/lib/volt/router/routes.rb +99 -101
- data/lib/volt/server/component_handler.rb +20 -21
- data/lib/volt/server/component_templates.rb +72 -70
- data/lib/volt/server/html_parser/attribute_scope.rb +109 -99
- data/lib/volt/server/html_parser/each_scope.rb +17 -16
- data/lib/volt/server/html_parser/if_view_scope.rb +51 -49
- data/lib/volt/server/html_parser/sandlebars_parser.rb +184 -177
- data/lib/volt/server/html_parser/textarea_scope.rb +24 -22
- data/lib/volt/server/html_parser/view_handler.rb +66 -65
- data/lib/volt/server/html_parser/view_parser.rb +23 -21
- data/lib/volt/server/html_parser/view_scope.rb +142 -141
- data/lib/volt/server/rack/asset_files.rb +81 -79
- data/lib/volt/server/rack/component_code.rb +17 -15
- data/lib/volt/server/rack/component_html_renderer.rb +14 -12
- data/lib/volt/server/rack/component_paths.rb +72 -71
- data/lib/volt/server/rack/index_files.rb +36 -39
- data/lib/volt/server/rack/opal_files.rb +43 -41
- data/lib/volt/server/rack/source_map_server.rb +23 -21
- data/lib/volt/server/socket_connection_handler.rb +46 -45
- data/lib/volt/server/socket_connection_handler_stub.rb +21 -19
- data/lib/volt/server.rb +60 -58
- data/lib/volt/spec/setup.rb +3 -3
- data/lib/volt/tasks/dispatcher.rb +24 -23
- data/lib/volt/tasks/task_handler.rb +35 -33
- data/lib/volt/utils/ejson.rb +8 -6
- data/lib/volt/utils/generic_counting_pool.rb +33 -31
- data/lib/volt/utils/generic_pool.rb +73 -70
- data/lib/volt/utils/local_storage.rb +42 -38
- data/lib/volt/volt/environment.rb +1 -1
- data/lib/volt.rb +44 -42
- data/spec/apps/kitchen_sink/app/main/assets/css/todos.css +28 -0
- data/spec/apps/kitchen_sink/app/main/config/routes.rb +1 -0
- data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +2 -2
- data/spec/apps/kitchen_sink/app/main/controllers/todos_controller.rb +17 -0
- data/spec/apps/kitchen_sink/app/main/views/main/main.html +1 -0
- data/spec/apps/kitchen_sink/app/main/views/todos/index.html +24 -0
- data/spec/apps/kitchen_sink/config.ru +1 -1
- data/spec/controllers/reactive_accessors_spec.rb +5 -5
- data/spec/extra_core/inflector_spec.rb +2 -2
- data/spec/integration/list_spec.rb +68 -0
- data/spec/models/model_spec.rb +57 -57
- data/spec/models/persistors/params_spec.rb +6 -6
- data/spec/models/persistors/store_spec.rb +7 -7
- data/spec/models/validations_spec.rb +3 -3
- data/spec/page/bindings/content_binding_spec.rb +7 -7
- data/spec/page/bindings/template_binding_spec.rb +4 -5
- data/spec/page/sub_context_spec.rb +2 -2
- data/spec/reactive/computation_spec.rb +10 -10
- data/spec/reactive/dependency_spec.rb +2 -2
- data/spec/reactive/eventable_spec.rb +4 -4
- data/spec/reactive/reactive_array_spec.rb +13 -13
- data/spec/router/routes_spec.rb +5 -5
- data/spec/server/html_parser/sandlebars_parser_spec.rb +9 -9
- data/spec/server/html_parser/view_parser_spec.rb +27 -27
- data/spec/server/rack/asset_files_spec.rb +5 -5
- data/spec/server/rack/component_paths_spec.rb +2 -2
- data/spec/tasks/live_query_spec.rb +2 -2
- data/spec/tasks/query_tasks.rb +1 -1
- data/spec/tasks/query_tracker_spec.rb +1 -1
- data/spec/templates/targets/binding_document/component_node_spec.rb +2 -2
- data/spec/utils/generic_counting_pool_spec.rb +2 -2
- data/spec/utils/generic_pool_spec.rb +2 -2
- data/templates/component/controllers/main_controller.rb +1 -1
- data/templates/model/model.rb.tt +2 -2
- data/templates/newgem/app/newgem/controllers/main_controller.rb.tt +2 -2
- data/templates/project/app/main/controllers/main_controller.rb +1 -1
- data/templates/project/config.ru +1 -1
- metadata +10 -3
- data/app/volt/assets/js/vertxbus.js +0 -216
|
@@ -1,222 +1,229 @@
|
|
|
1
1
|
require 'strscan'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
# Parses html and bindings
|
|
6
|
-
# based on http://ejohn.org/files/htmlparser.js
|
|
7
|
-
#
|
|
8
|
-
# takes the html and a handler object that will have the following methods
|
|
9
|
-
# called as each is seen: comment, text, binding, start_tag, end_tag
|
|
10
|
-
#
|
|
11
|
-
# This is not a full html parser, but should cover most common cases.
|
|
12
|
-
class SandlebarsParser
|
|
13
|
-
def self.truth_hash(array)
|
|
14
|
-
hash = {}
|
|
15
|
-
array.each {|v| hash[v] = true }
|
|
16
|
-
|
|
17
|
-
return hash
|
|
3
|
+
module Volt
|
|
4
|
+
class HTMLParseError < RuntimeError
|
|
18
5
|
end
|
|
19
6
|
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
7
|
+
# Parses html and bindings
|
|
8
|
+
# based on http://ejohn.org/files/htmlparser.js
|
|
9
|
+
#
|
|
10
|
+
# takes the html and a handler object that will have the following methods
|
|
11
|
+
# called as each is seen: comment, text, binding, start_tag, end_tag
|
|
12
|
+
#
|
|
13
|
+
# This is not a full html parser, but should cover most common cases.
|
|
14
|
+
class SandlebarsParser
|
|
15
|
+
def self.truth_hash(array)
|
|
16
|
+
hash = {}
|
|
17
|
+
array.each { |v| hash[v] = true }
|
|
18
|
+
|
|
19
|
+
return hash
|
|
20
|
+
end
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
CLOSE_SELF = truth_hash(%w{colgroup dd dt li options p td tfoot th thead tr})
|
|
30
|
-
SPECIAL = truth_hash(%w{script style})
|
|
22
|
+
# regex matchers
|
|
23
|
+
START_TAG = /^<([-!\:A-Za-z0-9_]+)((?:\s+[\w\-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/
|
|
24
|
+
END_TAG = /^<\/([-!\:A-Za-z0-9_]+)[^>]*>/
|
|
25
|
+
ATTRIBUTES = /([-\:A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
# Types of elements
|
|
28
|
+
BLOCK = truth_hash(%w{a address applet blockquote button center dd del dir div dl dt fieldset form frameset hr iframe ins isindex li map menu noframes noscript object ol p pre script table tbody td tfoot th thead tr ul})
|
|
29
|
+
EMPTY = truth_hash(%w{area base basefont br col frame hr img input isindex link meta param embed})
|
|
30
|
+
INLINE = truth_hash(%w{abbr acronym applet b basefont bdo big br button cite code del dfn em font i iframe img input ins kbd label map object q s samp script select small span strike strong sub sup textarea tt u var})
|
|
31
|
+
CLOSE_SELF = truth_hash(%w{colgroup dd dt li options p td tfoot th thead tr})
|
|
32
|
+
SPECIAL = truth_hash(%w{script style})
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
@html = StringScanner.new(html)
|
|
36
|
-
@handler = handler
|
|
37
|
-
@file_path = file_path
|
|
34
|
+
FILL_IN_ATTRIBUTES = truth_hash(%w{checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected})
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
def initialize(html, handler, file_path=nil)
|
|
37
|
+
@html = StringScanner.new(html)
|
|
38
|
+
@handler = handler
|
|
39
|
+
@file_path = file_path
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
end
|
|
41
|
+
@stack = []
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
43
|
+
parse
|
|
44
|
+
end
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# In a script or style tag, just look for the first end
|
|
52
|
-
close_tag = "</#{last}>"
|
|
53
|
-
body = @html.scan_until(/#{close_tag}/)
|
|
54
|
-
body = body[0..((-1 * close_tag.size)-1)]
|
|
55
|
-
|
|
56
|
-
body = body.gsub(/\<\!--(.*?)--\>/, "\\1").gsub(/\<\!\[CDATA\[(.*?)\]\]\>/, "\\1")
|
|
57
|
-
|
|
58
|
-
text(body)
|
|
59
|
-
|
|
60
|
-
end_tag(last, last)
|
|
61
|
-
elsif @html.scan(/\<\!--/)
|
|
62
|
-
# start comment
|
|
63
|
-
comment = @html.scan_until(/--\>/)
|
|
64
|
-
comment = comment[0..-4]
|
|
65
|
-
|
|
66
|
-
@handler.comment(comment) if @handler.respond_to?(:comment)
|
|
67
|
-
elsif (tag = @html.scan(START_TAG))
|
|
68
|
-
tag_name = @html[1]
|
|
69
|
-
rest = @html[2]
|
|
70
|
-
unary = @html[3]
|
|
71
|
-
|
|
72
|
-
start_tag(tag, tag_name, rest, unary)
|
|
73
|
-
elsif @html.scan(END_TAG)
|
|
74
|
-
tag_name = @html[1]
|
|
75
|
-
|
|
76
|
-
end_tag(tag_name, tag_name)
|
|
77
|
-
elsif (escaped = @html.scan(/\{\{\{(.*?)\}\}\}([^\}]|$)/))
|
|
78
|
-
# Anything between {{{ and }}} is escaped and not processed (treaded as text)
|
|
79
|
-
if escaped[-1] != '}'
|
|
80
|
-
# Move back if we matched a new non } for close, skip if we hit the end
|
|
81
|
-
@html.pos = @html.pos - 1
|
|
82
|
-
end
|
|
46
|
+
def last
|
|
47
|
+
@stack.last
|
|
48
|
+
end
|
|
83
49
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
50
|
+
def parse
|
|
51
|
+
loop do
|
|
52
|
+
if last && SPECIAL[last]
|
|
53
|
+
# In a script or style tag, just look for the first end
|
|
54
|
+
close_tag = "</#{last}>"
|
|
55
|
+
body = @html.scan_until(/#{close_tag}/)
|
|
56
|
+
special_tag(close_tag, body)
|
|
57
|
+
elsif @html.scan(/\<\!--/)
|
|
58
|
+
# start comment
|
|
59
|
+
comment = @html.scan_until(/--\>/)
|
|
60
|
+
comment = comment[0..-4]
|
|
61
|
+
|
|
62
|
+
@handler.comment(comment) if @handler.respond_to?(:comment)
|
|
63
|
+
elsif (tag = @html.scan(START_TAG))
|
|
64
|
+
tag_name = @html[1]
|
|
65
|
+
rest = @html[2]
|
|
66
|
+
unary = @html[3]
|
|
67
|
+
|
|
68
|
+
start_tag(tag, tag_name, rest, unary)
|
|
69
|
+
elsif @html.scan(END_TAG)
|
|
70
|
+
tag_name = @html[1]
|
|
71
|
+
|
|
72
|
+
end_tag(tag_name, tag_name)
|
|
73
|
+
elsif (escaped = @html.scan(/\{\{\{(.*?)\}\}\}([^\}]|$)/))
|
|
74
|
+
# Anything between {{{ and }}} is escaped and not processed (treaded as text)
|
|
75
|
+
if escaped[-1] != '}'
|
|
76
|
+
# Move back if we matched a new non } for close, skip if we hit the end
|
|
77
|
+
@html.pos = @html.pos - 1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
text(@html[1])
|
|
81
|
+
elsif (binding = @html.scan(/\{\{/))
|
|
82
|
+
# We are in text mode and matched the start of a binding
|
|
83
|
+
start_binding
|
|
84
|
+
elsif (text = @html.scan(/\{/))
|
|
85
|
+
# A single { outside of a binding
|
|
86
|
+
text(text)
|
|
87
|
+
elsif (text = @html.scan(/(?:[^\<\{]+)/))
|
|
88
|
+
# matched text up until the next html tag
|
|
89
|
+
text(text)
|
|
90
|
+
else
|
|
91
|
+
# Nothing left
|
|
92
|
+
break
|
|
93
|
+
end
|
|
97
94
|
end
|
|
98
|
-
end
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
end_tag(nil, nil)
|
|
97
|
+
end
|
|
102
98
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
def text(text)
|
|
100
|
+
@handler.text(text) if @handler.respond_to?(:text)
|
|
101
|
+
end
|
|
106
102
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
103
|
+
# Findings the end of a binding
|
|
104
|
+
def start_binding
|
|
105
|
+
binding = ''
|
|
106
|
+
open_count = 1
|
|
107
|
+
|
|
108
|
+
# scan until we reach a {{ or }}
|
|
109
|
+
loop do
|
|
110
|
+
binding << @html.scan_until(/(\{\{|\}\}|\n|\Z)/)
|
|
111
|
+
|
|
112
|
+
match = @html[1]
|
|
113
|
+
if match == '}}'
|
|
114
|
+
# close
|
|
115
|
+
open_count -= 1
|
|
116
|
+
break if open_count == 0
|
|
117
|
+
elsif match == '{{'
|
|
118
|
+
# open more
|
|
119
|
+
open_count += 1
|
|
120
|
+
elsif match == "\n" || @html.eos?
|
|
121
|
+
# Starting new tag, should be closed before this
|
|
122
|
+
# or end of doc before closed binding
|
|
123
|
+
raise_parse_error("unclosed binding: {#{binding.strip}")
|
|
124
|
+
else
|
|
125
|
+
raise "should not reach here"
|
|
126
|
+
end
|
|
130
127
|
end
|
|
131
|
-
end
|
|
132
128
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
binding = binding[0..-3]
|
|
130
|
+
@handler.binding(binding) if @handler.respond_to?(:binding)
|
|
131
|
+
end
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
def raise_parse_error(error)
|
|
134
|
+
line_number = @html.pre_match.count("\n") + 1
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
error_str = error + " on line: #{line_number}"
|
|
137
|
+
error_str += " of #{@file_path}" if @file_path
|
|
142
138
|
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
raise HTMLParseError, error_str
|
|
140
|
+
end
|
|
145
141
|
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
def start_tag(tag, tag_name, rest, unary)
|
|
143
|
+
section_tag = tag_name[0] == ':' && tag_name[1] =~ /[A-Z]/
|
|
148
144
|
|
|
149
|
-
|
|
145
|
+
tag_name = tag_name.downcase
|
|
150
146
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
147
|
+
# handle doctype so we get it output exactly the same way
|
|
148
|
+
if tag_name == '!doctype'
|
|
149
|
+
@handler.text(tag) if @handler.respond_to?(:start_tag)
|
|
150
|
+
return
|
|
151
|
+
end
|
|
156
152
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
161
158
|
end
|
|
162
|
-
end
|
|
163
159
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
160
|
+
# Some tags close themselves when a new one of themselves is reached.
|
|
161
|
+
# ex, a tr will close the previous tr
|
|
162
|
+
if CLOSE_SELF[tag_name] && last == tag_name
|
|
163
|
+
end_tag(nil, tag_name)
|
|
164
|
+
end
|
|
169
165
|
|
|
170
|
-
|
|
166
|
+
unary = EMPTY[tag_name] || !unary.blank?
|
|
171
167
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
168
|
+
# Section tag's are also unary
|
|
169
|
+
unless unary || section_tag
|
|
170
|
+
@stack.push(tag_name)
|
|
171
|
+
end
|
|
176
172
|
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
if @handler.respond_to?(:start_tag)
|
|
174
|
+
attributes = {}
|
|
179
175
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
176
|
+
# Take the rest string and extract the attributes, filling in any
|
|
177
|
+
# "fill in" attribute values if not provided.
|
|
178
|
+
rest.scan(ATTRIBUTES).each do |match|
|
|
179
|
+
name = match[0]
|
|
184
180
|
|
|
185
|
-
|
|
181
|
+
value = match[1] || match[2] || match[3] || FILL_IN_ATTRIBUTES[name] || ''
|
|
186
182
|
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
attributes[name] = value
|
|
184
|
+
end
|
|
189
185
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
186
|
+
if section_tag
|
|
187
|
+
@handler.start_section(tag_name, attributes, unary)
|
|
188
|
+
else
|
|
189
|
+
@handler.start_tag(tag_name, attributes, unary)
|
|
190
|
+
end
|
|
194
191
|
end
|
|
195
192
|
end
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def end_tag(tag, tag_name)
|
|
199
|
-
# If no tag name is provided, close all the way up
|
|
200
|
-
new_size = 0
|
|
201
193
|
|
|
202
|
-
|
|
203
|
-
#
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
194
|
+
def end_tag(tag, tag_name)
|
|
195
|
+
# If no tag name is provided, close all the way up
|
|
196
|
+
new_size = 0
|
|
197
|
+
|
|
198
|
+
if tag
|
|
199
|
+
# Find the closest tag that closes.
|
|
200
|
+
(@stack.size-1).downto(0) do |index|
|
|
201
|
+
if @stack[index] == tag_name
|
|
202
|
+
new_size = index
|
|
203
|
+
break
|
|
204
|
+
end
|
|
208
205
|
end
|
|
209
206
|
end
|
|
210
|
-
end
|
|
211
207
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
208
|
+
if new_size >= 0
|
|
209
|
+
if @handler.respond_to?(:end_tag)
|
|
210
|
+
(@stack.size-1).downto(new_size) do |index|
|
|
211
|
+
@handler.end_tag(@stack[index])
|
|
212
|
+
end
|
|
216
213
|
end
|
|
214
|
+
|
|
215
|
+
@stack = @stack[0...new_size]
|
|
217
216
|
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def special_tag(close_tag, body)
|
|
220
|
+
body = body[0..((-1 * close_tag.size)-1)]
|
|
221
|
+
|
|
222
|
+
body = body.gsub(/\<\!--(.*?)--\>/, "\\1").gsub(/\<\!\[CDATA\[(.*?)\]\]\>/, "\\1")
|
|
218
223
|
|
|
219
|
-
|
|
224
|
+
text(body)
|
|
225
|
+
|
|
226
|
+
end_tag(last, last)
|
|
220
227
|
end
|
|
221
228
|
end
|
|
222
|
-
end
|
|
229
|
+
end
|
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
module Volt
|
|
2
|
+
class TextareaScope < ViewScope
|
|
3
|
+
def initialize(handler, path, attributes)
|
|
4
|
+
super(handler, path)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
@attributes = attributes
|
|
7
|
+
end
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
def add_binding(content)
|
|
10
|
+
@html << "{{#{content}}}"
|
|
11
|
+
end
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
def close_scope(pop=true)
|
|
14
|
+
# Remove from the scope
|
|
15
|
+
@handler.scope.pop
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
attributes = @attributes
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
if @html[/\{\{[^\}]+\}\}/]
|
|
20
|
+
# If the html inside the textarea has a binding, process it as
|
|
21
|
+
# a value attribute.
|
|
22
|
+
attributes['value'] = @html
|
|
23
|
+
@html = ''
|
|
24
|
+
end
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
# Normal tag
|
|
27
|
+
attributes = @handler.last.process_attributes('textarea', attributes)
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
@handler.last.html << "<textarea#{attribute_string(attributes)}>#{@html}</textarea>"
|
|
29
30
|
|
|
31
|
+
end
|
|
30
32
|
end
|
|
31
|
-
end
|
|
33
|
+
end
|
|
@@ -1,82 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
module Volt
|
|
2
|
+
class ViewHandler
|
|
3
|
+
attr_reader :templates, :scope
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def last
|
|
9
|
-
@scope.last
|
|
10
|
-
end
|
|
5
|
+
def html
|
|
6
|
+
last.html
|
|
7
|
+
end
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
def last
|
|
10
|
+
@scope.last
|
|
11
|
+
end
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
def initialize(initial_path, allow_sections=true)
|
|
14
|
+
@original_path = initial_path
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
16
|
+
# Default to the body section
|
|
17
|
+
initial_path += '/body' if allow_sections
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def text(text)
|
|
27
|
-
last << text
|
|
28
|
-
end
|
|
19
|
+
@scope = [ViewScope.new(self, initial_path)]
|
|
20
|
+
@templates = {}
|
|
21
|
+
end
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
def comment(comment)
|
|
24
|
+
last << "<!--#{comment}-->"
|
|
25
|
+
end
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Component
|
|
38
|
-
last.add_component(tag_name, attributes, unary)
|
|
39
|
-
else
|
|
40
|
-
if tag_name == 'textarea'
|
|
41
|
-
@in_textarea = true
|
|
42
|
-
last.add_textarea(tag_name, attributes, unary)
|
|
43
|
-
else
|
|
27
|
+
def text(text)
|
|
28
|
+
last << text
|
|
29
|
+
end
|
|
44
30
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
def binding(binding)
|
|
32
|
+
@scope.last.add_binding(binding)
|
|
33
|
+
end
|
|
48
34
|
|
|
49
|
-
|
|
35
|
+
def start_tag(tag_name, attributes, unary)
|
|
36
|
+
case tag_name[0]
|
|
37
|
+
when ':'
|
|
38
|
+
# Component
|
|
39
|
+
last.add_component(tag_name, attributes, unary)
|
|
40
|
+
else
|
|
41
|
+
if tag_name == 'textarea'
|
|
42
|
+
@in_textarea = true
|
|
43
|
+
last.add_textarea(tag_name, attributes, unary)
|
|
44
|
+
else
|
|
45
|
+
|
|
46
|
+
# Normal tag
|
|
47
|
+
attributes = last.process_attributes(tag_name, attributes)
|
|
48
|
+
attr_str = last.attribute_string(attributes)
|
|
49
|
+
|
|
50
|
+
last << "<#{tag_name}#{attr_str}#{unary ? ' /' : ''}>"
|
|
51
|
+
end
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
|
-
end
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
def end_tag(tag_name)
|
|
56
|
+
if @in_textarea && tag_name == 'textarea'
|
|
57
|
+
last.close_scope
|
|
58
|
+
@in_textarea = nil
|
|
59
|
+
else
|
|
60
|
+
last << "</#{tag_name}>"
|
|
61
|
+
end
|
|
60
62
|
end
|
|
61
|
-
end
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
def start_section(tag_name, attributes, unary)
|
|
65
|
+
path = last.path
|
|
66
|
+
# Start of section
|
|
67
|
+
if @in_section
|
|
68
|
+
# Close any previous sections
|
|
69
|
+
last.close_scope
|
|
70
|
+
else
|
|
71
|
+
# This is the first time we've hit a section header, everything
|
|
72
|
+
# outside of the headers should be removed
|
|
73
|
+
@templates = {}
|
|
74
|
+
end
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
@in_section = tag_name[1..-1]
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
# Set the new path to include the section
|
|
79
|
+
new_path = @original_path + '/' + @in_section
|
|
80
|
+
@scope = [ViewScope.new(self, new_path)]
|
|
81
|
+
end
|
|
80
82
|
end
|
|
81
|
-
|
|
82
|
-
end
|
|
83
|
+
end
|
|
@@ -5,35 +5,37 @@ require 'volt/server/html_parser/view_handler'
|
|
|
5
5
|
require 'volt/server/html_parser/each_scope'
|
|
6
6
|
require 'volt/server/html_parser/textarea_scope'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
module Volt
|
|
9
|
+
class ViewParser
|
|
10
|
+
attr_reader :templates
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
def initialize(html, template_path)
|
|
13
|
+
@template_path = template_path
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
handler = ViewHandler.new(template_path)
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
SandlebarsParser.new(html, handler)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
# Close out the last scope
|
|
20
|
+
handler.scope.last.close_scope
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
@templates = handler.templates
|
|
23
|
+
end
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
# Returns a parsed version of the data (useful for backend rendering
|
|
26
|
+
# and testing)
|
|
27
|
+
def data
|
|
28
|
+
templates = @templates.deep_clone
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
templates.each_pair do |name, value|
|
|
31
|
+
if value['bindings']
|
|
32
|
+
value['bindings'].each_pair do |number, binding|
|
|
33
|
+
value['bindings'][number] = binding.map { |code| eval(code) }
|
|
34
|
+
end
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
|
-
end
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
return templates
|
|
39
|
+
end
|
|
38
40
|
end
|
|
39
|
-
end
|
|
41
|
+
end
|