curlybars 1.3.1 → 1.7.0
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/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
|