curlybars 1.3.1 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/curlybars.rb +8 -1
- data/lib/curlybars/configuration.rb +1 -9
- data/lib/curlybars/error/base.rb +2 -0
- data/lib/curlybars/generic.rb +36 -0
- data/lib/curlybars/lexer.rb +3 -0
- data/lib/curlybars/method_whitelist.rb +25 -3
- data/lib/curlybars/node/block_helper_else.rb +1 -0
- data/lib/curlybars/node/each_else.rb +6 -2
- data/lib/curlybars/node/if_else.rb +1 -1
- data/lib/curlybars/node/path.rb +58 -11
- data/lib/curlybars/node/sub_expression.rb +108 -0
- data/lib/curlybars/node/unless_else.rb +1 -1
- data/lib/curlybars/node/with_else.rb +6 -2
- data/lib/curlybars/parser.rb +39 -0
- data/lib/curlybars/processor/tilde.rb +3 -0
- data/lib/curlybars/rendering_support.rb +9 -1
- data/lib/curlybars/template_handler.rb +18 -6
- data/lib/curlybars/version.rb +1 -1
- data/lib/curlybars/visitor.rb +6 -0
- data/spec/acceptance/application_layout_spec.rb +2 -2
- data/spec/acceptance/collection_blocks_spec.rb +1 -1
- data/spec/acceptance/global_helper_spec.rb +1 -1
- data/spec/curlybars/lexer_spec.rb +25 -2
- data/spec/curlybars/method_whitelist_spec.rb +8 -4
- data/spec/curlybars/rendering_support_spec.rb +4 -9
- data/spec/curlybars/template_handler_spec.rb +33 -30
- data/spec/integration/cache_spec.rb +20 -18
- data/spec/integration/node/block_helper_else_spec.rb +0 -2
- data/spec/integration/node/each_else_spec.rb +208 -4
- data/spec/integration/node/each_spec.rb +0 -2
- data/spec/integration/node/helper_spec.rb +12 -2
- data/spec/integration/node/if_else_spec.rb +0 -2
- data/spec/integration/node/if_spec.rb +2 -4
- data/spec/integration/node/output_spec.rb +0 -2
- data/spec/integration/node/partial_spec.rb +0 -2
- data/spec/integration/node/path_spec.rb +0 -2
- data/spec/integration/node/root_spec.rb +0 -2
- data/spec/integration/node/sub_expression_spec.rb +426 -0
- data/spec/integration/node/template_spec.rb +0 -2
- data/spec/integration/node/unless_else_spec.rb +2 -4
- data/spec/integration/node/unless_spec.rb +0 -2
- data/spec/integration/node/with_spec.rb +66 -4
- data/spec/integration/processor/tilde_spec.rb +1 -1
- data/spec/integration/processors_spec.rb +4 -5
- data/spec/integration/visitor_spec.rb +13 -5
- metadata +49 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba4b2799fad2886edfd0bc1c4873d8da9ed80d8885a1868f4b0fa5797fb9e49c
|
4
|
+
data.tar.gz: ee1bfc5d500f2ceee63675c5188d915227f507de7895c6ed2f08e89ddcae122e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4233c4cd5ed833892d17603776a6db2abae2061cf72dfebe1add84bb7464e99b563cacee499fbcd447f59dd54a1ea3bf3b4aa18010526249aba2ed39a2310b96
|
7
|
+
data.tar.gz: f78754a1adc25de66302116338dd2abf9b4e9f8c2ae3439d1f74beda9065bb634c8fae64c19f65767be4d7389f1362559c0537f36bb438969e151bb93a47612f
|
data/lib/curlybars.rb
CHANGED
@@ -77,6 +77,13 @@ module Curlybars
|
|
77
77
|
visitor.accept(tree)
|
78
78
|
end
|
79
79
|
|
80
|
+
def global_helpers_dependency_tree
|
81
|
+
@global_helpers_dependency_tree ||= begin
|
82
|
+
classes = Curlybars.configuration.global_helpers_provider_classes
|
83
|
+
classes.map(&:dependency_tree).inject({}, :merge)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
80
87
|
def cache
|
81
88
|
@cache ||= ActiveSupport::Cache::MemoryStore.new
|
82
89
|
end
|
@@ -117,8 +124,8 @@ require 'curlybars/configuration'
|
|
117
124
|
require 'curlybars/rendering_support'
|
118
125
|
require 'curlybars/parser'
|
119
126
|
require 'curlybars/position'
|
127
|
+
require 'curlybars/generic'
|
120
128
|
require 'curlybars/lexer'
|
121
|
-
require 'curlybars/parser'
|
122
129
|
require 'curlybars/processor/token_factory'
|
123
130
|
require 'curlybars/processor/tilde'
|
124
131
|
require 'curlybars/error/lex'
|
@@ -16,15 +16,7 @@ module Curlybars
|
|
16
16
|
end
|
17
17
|
|
18
18
|
class Configuration
|
19
|
-
attr_accessor :presenters_namespace
|
20
|
-
attr_accessor :nesting_limit
|
21
|
-
attr_accessor :traversing_limit
|
22
|
-
attr_accessor :output_limit
|
23
|
-
attr_accessor :rendering_timeout
|
24
|
-
attr_accessor :custom_processors
|
25
|
-
attr_accessor :compiler_transformers
|
26
|
-
attr_accessor :global_helpers_provider_classes
|
27
|
-
attr_accessor :cache
|
19
|
+
attr_accessor :presenters_namespace, :nesting_limit, :traversing_limit, :output_limit, :rendering_timeout, :custom_processors, :compiler_transformers, :global_helpers_provider_classes, :cache
|
28
20
|
|
29
21
|
def initialize
|
30
22
|
@presenters_namespace = ''
|
data/lib/curlybars/error/base.rb
CHANGED
@@ -8,8 +8,10 @@ module Curlybars
|
|
8
8
|
@id = id
|
9
9
|
@position = position
|
10
10
|
@metadata = metadata
|
11
|
+
|
11
12
|
return if position.nil?
|
12
13
|
return if position.file_name.nil?
|
14
|
+
|
13
15
|
location = "%s:%d:%d" % [position.file_name, position.line_number, position.line_offset]
|
14
16
|
set_backtrace([location])
|
15
17
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'curlybars/method_whitelist'
|
2
|
+
|
3
|
+
module Curlybars
|
4
|
+
# A base class that can be used to signify that a helper's return type is a sort a generic.
|
5
|
+
#
|
6
|
+
# Examples
|
7
|
+
#
|
8
|
+
# class GlobalHelperProvider
|
9
|
+
# extend Curlybars::MethodWhitelist
|
10
|
+
#
|
11
|
+
# allow_methods slice: [:helper, [Curlybars::Generic]],
|
12
|
+
# translate: [:helper, Curlybars::Generic]
|
13
|
+
#
|
14
|
+
# def slice(collection, start, length, _)
|
15
|
+
# collection[start, length]
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def translate(object, locale)
|
19
|
+
# object.translate(locale)
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# {{#each (slice articles, 0, 5)}}
|
24
|
+
# Title: {{title}}
|
25
|
+
# Body: {{body}}
|
26
|
+
# {{/each}}
|
27
|
+
#
|
28
|
+
# {{#with (translate article "en-us")}}
|
29
|
+
# Title: {{title}}
|
30
|
+
# Body: {{body}}
|
31
|
+
# {{/with}}
|
32
|
+
#
|
33
|
+
class Generic
|
34
|
+
extend Curlybars::MethodWhitelist
|
35
|
+
end
|
36
|
+
end
|
data/lib/curlybars/lexer.rb
CHANGED
@@ -32,6 +32,9 @@ module Curlybars
|
|
32
32
|
r(/{{/) { push_state :curly; :START }
|
33
33
|
r(/}}/, :curly) { pop_state; :END }
|
34
34
|
|
35
|
+
r(/\(/, :curly) { push_state :curly; :LPAREN }
|
36
|
+
r(/\)/, :curly) { pop_state; :RPAREN }
|
37
|
+
|
35
38
|
r(/#/, :curly) { :HASH }
|
36
39
|
r(/\//, :curly) { :SLASH }
|
37
40
|
r(/>/, :curly) { :GT }
|
@@ -4,6 +4,8 @@ module Curlybars
|
|
4
4
|
methods_with_type_validator = lambda do |methods_to_validate|
|
5
5
|
methods_to_validate.each do |(method_name, type)|
|
6
6
|
if type.is_a?(Array)
|
7
|
+
next if generic_or_collection_helper?(type)
|
8
|
+
|
7
9
|
if type.size != 1 || !type.first.respond_to?(:dependency_tree)
|
8
10
|
raise "Invalid allowed method syntax for `#{method_name}`. Collections must be of one presenter class"
|
9
11
|
end
|
@@ -49,8 +51,8 @@ module Curlybars
|
|
49
51
|
memo[method] = nil
|
50
52
|
end
|
51
53
|
|
52
|
-
methods_with_type_resolved = all_methods_with_type.
|
53
|
-
|
54
|
+
methods_with_type_resolved = all_methods_with_type.transform_values do |type|
|
55
|
+
if type.respond_to?(:call)
|
54
56
|
type.call(context)
|
55
57
|
else
|
56
58
|
type
|
@@ -65,6 +67,7 @@ module Curlybars
|
|
65
67
|
# Included modules
|
66
68
|
included_modules.each do |mod|
|
67
69
|
next unless mod.respond_to?(:methods_schema)
|
70
|
+
|
68
71
|
schema.merge!(mod.methods_schema(context))
|
69
72
|
end
|
70
73
|
|
@@ -79,7 +82,15 @@ module Curlybars
|
|
79
82
|
memo[method_name] = if type.respond_to?(:dependency_tree)
|
80
83
|
type.dependency_tree(context)
|
81
84
|
elsif type.is_a?(Array)
|
82
|
-
|
85
|
+
if type.first == :helper
|
86
|
+
if type.last.is_a?(Array)
|
87
|
+
[:helper, [type.last.first.dependency_tree(context)]]
|
88
|
+
else
|
89
|
+
[:helper, type.last.dependency_tree(context)]
|
90
|
+
end
|
91
|
+
else
|
92
|
+
[type.first.dependency_tree(context)]
|
93
|
+
end
|
83
94
|
else
|
84
95
|
type
|
85
96
|
end
|
@@ -95,5 +106,16 @@ module Curlybars
|
|
95
106
|
# define a default of no method allowed
|
96
107
|
base.allow_methods
|
97
108
|
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def generic_or_collection_helper?(type)
|
113
|
+
return false unless type.size == 2
|
114
|
+
return false unless type.first == :helper
|
115
|
+
return true if type.last.respond_to?(:dependency_tree)
|
116
|
+
return false unless type.last.is_a?(Array) && type.last.size == 1
|
117
|
+
|
118
|
+
type.last.first.respond_to?(:dependency_tree)
|
119
|
+
end
|
98
120
|
end
|
99
121
|
end
|
@@ -100,6 +100,7 @@ module Curlybars
|
|
100
100
|
|
101
101
|
def check_open_and_close_elements(helper, helperclose, error_class)
|
102
102
|
return unless helper.path != helperclose.path
|
103
|
+
|
103
104
|
message = "block `#{helper.path}` cannot be closed by `#{helperclose.path}`"
|
104
105
|
raise error_class.new('closing_tag_mismatch', message, helperclose.position)
|
105
106
|
end
|
@@ -11,7 +11,7 @@ module Curlybars
|
|
11
11
|
position = rendering.position(#{position.line_number}, #{position.line_offset})
|
12
12
|
template_cache_key = '#{each_template.cache_key}'
|
13
13
|
|
14
|
-
collection = rendering.coerce_to_hash!(collection, #{
|
14
|
+
collection = rendering.coerce_to_hash!(collection, #{collection_path.path.inspect}, position)
|
15
15
|
collection.each.with_index.map do |key_and_presenter, index|
|
16
16
|
rendering.check_timeout!
|
17
17
|
rendering.optional_presenter_cache(key_and_presenter[1], template_cache_key, buffer) do |buffer|
|
@@ -37,7 +37,7 @@ module Curlybars
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def validate(branches)
|
40
|
-
resolved = path.resolve_and_check!(branches, check_type: :
|
40
|
+
resolved = path.resolve_and_check!(branches, check_type: :collectionlike)
|
41
41
|
sub_tree = resolved.first
|
42
42
|
|
43
43
|
each_template_errors = begin
|
@@ -57,6 +57,10 @@ module Curlybars
|
|
57
57
|
path_error
|
58
58
|
end
|
59
59
|
|
60
|
+
def collection_path
|
61
|
+
path.subexpression? ? path.helper : path
|
62
|
+
end
|
63
|
+
|
60
64
|
def cache_key
|
61
65
|
[
|
62
66
|
path,
|
data/lib/curlybars/node/path.rb
CHANGED
@@ -35,11 +35,18 @@ module Curlybars
|
|
35
35
|
resolve(branches).is_a?(Hash)
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
39
|
-
value
|
38
|
+
def presenter_value?(value)
|
39
|
+
value.is_a?(Hash)
|
40
|
+
end
|
41
|
+
|
42
|
+
def collection_value?(value)
|
40
43
|
value.is_a?(Array) && value.first.is_a?(Hash)
|
41
44
|
end
|
42
45
|
|
46
|
+
def presenter_collection?(branches)
|
47
|
+
collection_value?(resolve(branches))
|
48
|
+
end
|
49
|
+
|
43
50
|
def leaf?(branches)
|
44
51
|
value = resolve(branches)
|
45
52
|
value.nil?
|
@@ -53,9 +60,41 @@ module Curlybars
|
|
53
60
|
resolve(branches) == :helper
|
54
61
|
end
|
55
62
|
|
63
|
+
def generic_helper?(branches)
|
64
|
+
value = resolve(branches)
|
65
|
+
value.is_a?(Array) &&
|
66
|
+
value.first == :helper &&
|
67
|
+
presenter_value?(value.last)
|
68
|
+
end
|
69
|
+
|
70
|
+
def generic_collection_helper?(branches)
|
71
|
+
value = resolve(branches)
|
72
|
+
value.is_a?(Array) &&
|
73
|
+
value.first == :helper &&
|
74
|
+
collection_value?(value.last)
|
75
|
+
end
|
76
|
+
|
77
|
+
def collectionlike?(branches)
|
78
|
+
presenter_collection?(branches) || generic_collection_helper?(branches)
|
79
|
+
end
|
80
|
+
|
81
|
+
def presenterlike?(branches)
|
82
|
+
presenter?(branches) || generic_helper?(branches)
|
83
|
+
end
|
84
|
+
|
85
|
+
def subexpression?
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
56
89
|
def resolve(branches)
|
57
90
|
@value ||= begin
|
58
|
-
|
91
|
+
if Curlybars.global_helpers_dependency_tree.key?(path.to_sym)
|
92
|
+
dep_node = Curlybars.global_helpers_dependency_tree[path.to_sym]
|
93
|
+
|
94
|
+
return :helper if dep_node.nil?
|
95
|
+
|
96
|
+
return [:helper, dep_node]
|
97
|
+
end
|
59
98
|
|
60
99
|
path_split_by_slashes = path.split('/')
|
61
100
|
backward_steps_on_branches = path_split_by_slashes.count - 1
|
@@ -97,38 +136,46 @@ module Curlybars
|
|
97
136
|
|
98
137
|
private
|
99
138
|
|
100
|
-
# TODO: extract me away
|
101
|
-
def global_helpers_dependency_tree
|
102
|
-
@global_helpers_dependency_tree ||= begin
|
103
|
-
classes = Curlybars.configuration.global_helpers_provider_classes
|
104
|
-
classes.map(&:dependency_tree).inject({}, :merge)
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
139
|
def check_type_of(branches, check_type)
|
109
140
|
case check_type
|
110
141
|
when :presenter
|
111
142
|
return if presenter?(branches)
|
143
|
+
|
112
144
|
message = "`#{path}` must resolve to a presenter"
|
113
145
|
raise Curlybars::Error::Validate.new('not_a_presenter', message, position)
|
146
|
+
when :presenterlike
|
147
|
+
return if presenterlike?(branches)
|
148
|
+
|
149
|
+
message = "`#{path}` must resolve to a presenter"
|
150
|
+
raise Curlybars::Error::Validate.new('not_presenterlike', message, position)
|
151
|
+
when :collectionlike
|
152
|
+
return if collectionlike?(branches)
|
153
|
+
|
154
|
+
message = "`#{path}` must resolve to a collection of presenters"
|
155
|
+
raise Curlybars::Error::Validate.new('not_collectionlike', message, position)
|
114
156
|
when :presenter_collection
|
115
157
|
return if presenter_collection?(branches)
|
158
|
+
|
116
159
|
message = "`#{path}` must resolve to a collection of presenters"
|
117
160
|
raise Curlybars::Error::Validate.new('not_a_presenter_collection', message, position)
|
118
161
|
when :leaf
|
119
162
|
return if leaf?(branches)
|
163
|
+
|
120
164
|
message = "`#{path}` cannot resolve to a component"
|
121
165
|
raise Curlybars::Error::Validate.new('not_a_leaf', message, position)
|
122
166
|
when :partial
|
123
167
|
return if partial?(branches)
|
168
|
+
|
124
169
|
message = "`#{path}` cannot resolve to a partial"
|
125
170
|
raise Curlybars::Error::Validate.new('not_a_partial', message, position)
|
126
171
|
when :helper
|
127
172
|
return if helper?(branches)
|
173
|
+
|
128
174
|
message = "`#{path}` cannot resolve to a helper"
|
129
175
|
raise Curlybars::Error::Validate.new('not_a_helper', message, position)
|
130
176
|
when :not_helper
|
131
177
|
return unless helper?(branches)
|
178
|
+
|
132
179
|
message = "`#{path}` resolves to a helper"
|
133
180
|
raise Curlybars::Error::Validate.new('is_a_helper', message, position)
|
134
181
|
when :anything
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Curlybars
|
2
|
+
module Node
|
3
|
+
SubExpression = Struct.new(:helper, :arguments, :options, :position) do
|
4
|
+
def subexpression?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
|
8
|
+
def compile
|
9
|
+
compiled_arguments = arguments.map do |argument|
|
10
|
+
"arguments.push(rendering.cached_call(#{argument.compile}))"
|
11
|
+
end.join("\n")
|
12
|
+
|
13
|
+
compiled_options = options.map do |option|
|
14
|
+
"options.merge!(#{option.compile})"
|
15
|
+
end.join("\n")
|
16
|
+
|
17
|
+
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
18
|
+
# outputted by this node.
|
19
|
+
<<-RUBY
|
20
|
+
::Module.new do
|
21
|
+
def self.exec(contexts, rendering)
|
22
|
+
rendering.check_timeout!
|
23
|
+
|
24
|
+
-> {
|
25
|
+
options = ::ActiveSupport::HashWithIndifferentAccess.new
|
26
|
+
#{compiled_options}
|
27
|
+
|
28
|
+
arguments = []
|
29
|
+
#{compiled_arguments}
|
30
|
+
|
31
|
+
helper = #{helper.compile}
|
32
|
+
helper_position = rendering.position(#{helper.position.line_number},
|
33
|
+
#{helper.position.line_offset})
|
34
|
+
|
35
|
+
options[:this] = contexts.last
|
36
|
+
|
37
|
+
rendering.call(helper, #{helper.path.inspect}, helper_position,
|
38
|
+
arguments, options)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end.exec(contexts, rendering)
|
42
|
+
RUBY
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_as_value(branches, check_type: :anything)
|
46
|
+
validate(branches, check_type: check_type)
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate(branches, check_type: :anything)
|
50
|
+
[
|
51
|
+
helper.validate(branches, check_type: :helper),
|
52
|
+
arguments.map { |argument| argument.validate_as_value(branches) },
|
53
|
+
options.map { |option| option.validate(branches) }
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve_and_check!(branches, check_type: :collectionlike)
|
58
|
+
node = if arguments.first.is_a?(Curlybars::Node::SubExpression)
|
59
|
+
arguments.first
|
60
|
+
else
|
61
|
+
helper
|
62
|
+
end
|
63
|
+
|
64
|
+
type = node.resolve_and_check!(branches, check_type: check_type)
|
65
|
+
|
66
|
+
if helper?(type)
|
67
|
+
if generic_helper?(type)
|
68
|
+
is_collection = type.last.is_a?(Array)
|
69
|
+
return infer_generic_helper_type!(branches, is_collection: is_collection)
|
70
|
+
end
|
71
|
+
|
72
|
+
return type.last
|
73
|
+
end
|
74
|
+
|
75
|
+
type
|
76
|
+
end
|
77
|
+
|
78
|
+
def generic_helper?(type)
|
79
|
+
return false unless type.is_a?(Array)
|
80
|
+
return false unless type.size == 2
|
81
|
+
return false unless type.first == :helper
|
82
|
+
|
83
|
+
type.last == [{}] || type.last == {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def helper?(type)
|
87
|
+
type.first == :helper
|
88
|
+
end
|
89
|
+
|
90
|
+
def infer_generic_helper_type!(branches, is_collection:)
|
91
|
+
if arguments.empty?
|
92
|
+
raise Curlybars::Error::Validate.new('missing_path', "'#{helper.path}' requires a collection as its first argument", helper.position)
|
93
|
+
end
|
94
|
+
|
95
|
+
check_type = is_collection ? :presenter_collection : :presenter
|
96
|
+
arguments.first.resolve_and_check!(branches, check_type: check_type)
|
97
|
+
end
|
98
|
+
|
99
|
+
def cache_key
|
100
|
+
[
|
101
|
+
helper,
|
102
|
+
arguments,
|
103
|
+
options
|
104
|
+
].flatten.map(&:cache_key).push(self.class.name).join("/")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|