curlybars 1.15.0 → 1.16.0.pre
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/exe/curlybars-indent-lint +181 -0
- data/lib/curlybars/configuration.rb +3 -1
- data/lib/curlybars/node/block_helper_else.rb +5 -5
- data/lib/curlybars/node/boolean.rb +1 -1
- data/lib/curlybars/node/each_else.rb +3 -3
- data/lib/curlybars/node/if_else.rb +3 -3
- data/lib/curlybars/node/item.rb +2 -2
- data/lib/curlybars/node/literal.rb +1 -1
- data/lib/curlybars/node/output.rb +2 -2
- data/lib/curlybars/node/partial.rb +56 -7
- data/lib/curlybars/node/path.rb +1 -1
- data/lib/curlybars/node/root.rb +7 -3
- data/lib/curlybars/node/string.rb +1 -1
- data/lib/curlybars/node/sub_expression.rb +1 -1
- data/lib/curlybars/node/template.rb +2 -2
- data/lib/curlybars/node/text.rb +1 -1
- data/lib/curlybars/node/unless_else.rb +3 -3
- data/lib/curlybars/node/variable.rb +1 -1
- data/lib/curlybars/node/with_else.rb +3 -3
- data/lib/curlybars/parser.rb +3 -3
- data/lib/curlybars/partial_presenter.rb +26 -0
- data/lib/curlybars/rendering_support.rb +39 -3
- data/lib/curlybars/template_handler.rb +3 -0
- data/lib/curlybars/validation_context.rb +18 -0
- data/lib/curlybars/version.rb +1 -1
- data/lib/curlybars/visitor.rb +1 -0
- data/lib/curlybars.rb +6 -2
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e288b8260b5607e12658628a38b544a4b1afc6affebe60d52072a8161a9935c9
|
|
4
|
+
data.tar.gz: fb9df5a81e5e78bfb8d6445f69e619be0dc32d9a21ad47c788a386a2340caf8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ece4a9322875eff75fd72399bf2baac620074535f913f5a70873b29234fa6f93d251be05d33f9384f07fe659e5e801c4d54761f00dbe690875a8c77deaa83cf
|
|
7
|
+
data.tar.gz: 02b7cc876acb0ceb519ac519b6792261c06e68eb7ee53fac11bbabcea11c6cd10f4ce599727e5c117aed9b9edb60464228ac15f3fc5a6d983bf4e5f5b430d4ca
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# A standalone indentation linter for Curlybars / Handlebars (.hbs) files.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ruby bin/curlybars-indent-lint [--indent N] file1.hbs [file2.hbs ...]
|
|
8
|
+
#
|
|
9
|
+
# Exit codes:
|
|
10
|
+
# 0 - no indentation issues found
|
|
11
|
+
# 1 - indentation issues found
|
|
12
|
+
# 2 - usage error
|
|
13
|
+
|
|
14
|
+
require "optparse"
|
|
15
|
+
|
|
16
|
+
LintWarning = Data.define(:file, :line, :message, :content)
|
|
17
|
+
|
|
18
|
+
INDENT_SIZE_DEFAULT = 2
|
|
19
|
+
|
|
20
|
+
def parse_args(argv)
|
|
21
|
+
indent_size = INDENT_SIZE_DEFAULT
|
|
22
|
+
|
|
23
|
+
parser = OptionParser.new do |opts|
|
|
24
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [--indent N] file1.hbs [file2.hbs ...]"
|
|
25
|
+
|
|
26
|
+
opts.on("--indent N", Integer, "Number of spaces per indentation level (default: #{INDENT_SIZE_DEFAULT})") do |n|
|
|
27
|
+
indent_size = n
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
opts.on("-h", "--help", "Show this help message") do
|
|
31
|
+
warn opts
|
|
32
|
+
exit 0
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
files = parser.parse(argv)
|
|
37
|
+
|
|
38
|
+
if files.empty?
|
|
39
|
+
warn "Error: no files given. Run with --help for usage."
|
|
40
|
+
exit 2
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
[indent_size, files]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Matches a standalone block-opening tag on a line, e.g. {{#if condition}}
|
|
47
|
+
BLOCK_OPEN = /\{\{~?\s*#\s*\S+.*?\}\}/
|
|
48
|
+
# Matches a standalone block-closing tag on a line, e.g. {{/if}}
|
|
49
|
+
BLOCK_CLOSE = %r{\{\{~?\s*/\s*\S+\s*~?\}\}}
|
|
50
|
+
# Matches an {{else}} tag
|
|
51
|
+
BLOCK_ELSE = /\{\{~?\s*else\b.*?~?\}\}/
|
|
52
|
+
|
|
53
|
+
def classify_line(stripped)
|
|
54
|
+
has_open = stripped.match?(BLOCK_OPEN)
|
|
55
|
+
has_close = stripped.match?(BLOCK_CLOSE)
|
|
56
|
+
has_else = stripped.match?(BLOCK_ELSE)
|
|
57
|
+
|
|
58
|
+
# A line with both open and close (e.g. {{#if x}}...{{/if}}) is inline — treat as plain.
|
|
59
|
+
return :plain if has_open && has_close
|
|
60
|
+
|
|
61
|
+
return :else if has_else
|
|
62
|
+
return :close if has_close
|
|
63
|
+
return :open if has_open
|
|
64
|
+
|
|
65
|
+
:plain
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def lint_file(path, indent_size)
|
|
69
|
+
source = File.read(path)
|
|
70
|
+
lines = source.lines
|
|
71
|
+
warnings = []
|
|
72
|
+
|
|
73
|
+
# Stack of [block_open_indent, block_content_indent] pairs.
|
|
74
|
+
# We track indentation relative to each block opener rather than
|
|
75
|
+
# enforcing absolute column positions, so the linter works correctly
|
|
76
|
+
# when Handlebars blocks are nested inside HTML.
|
|
77
|
+
indent_stack = []
|
|
78
|
+
|
|
79
|
+
lines.each_with_index do |line, index|
|
|
80
|
+
line_number = index + 1
|
|
81
|
+
raw = line.chomp
|
|
82
|
+
|
|
83
|
+
next if raw.strip.empty?
|
|
84
|
+
|
|
85
|
+
stripped = raw.lstrip
|
|
86
|
+
actual_indent = raw.length - stripped.length
|
|
87
|
+
kind = classify_line(stripped)
|
|
88
|
+
|
|
89
|
+
case kind
|
|
90
|
+
when :close
|
|
91
|
+
if indent_stack.empty?
|
|
92
|
+
warnings << LintWarning.new(
|
|
93
|
+
file: path, line: line_number, content: raw,
|
|
94
|
+
message: "unexpected closing tag (no matching opener)"
|
|
95
|
+
)
|
|
96
|
+
else
|
|
97
|
+
open_indent, _content_indent = indent_stack.pop
|
|
98
|
+
if actual_indent != open_indent
|
|
99
|
+
warnings << LintWarning.new(
|
|
100
|
+
file: path, line: line_number, content: raw,
|
|
101
|
+
message: "expected #{open_indent} spaces (to match opening tag), got #{actual_indent}"
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
when :else
|
|
107
|
+
if indent_stack.empty?
|
|
108
|
+
warnings << LintWarning.new(
|
|
109
|
+
file: path, line: line_number, content: raw,
|
|
110
|
+
message: "unexpected {{else}} (no matching opener)"
|
|
111
|
+
)
|
|
112
|
+
else
|
|
113
|
+
open_indent, _content_indent = indent_stack.pop
|
|
114
|
+
if actual_indent != open_indent
|
|
115
|
+
warnings << LintWarning.new(
|
|
116
|
+
file: path, line: line_number, content: raw,
|
|
117
|
+
message: "expected #{open_indent} spaces (to match opening tag), got #{actual_indent}"
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
# Reset the content indent for the else branch.
|
|
121
|
+
indent_stack.push([open_indent, open_indent + indent_size])
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
when :open
|
|
125
|
+
# The opening tag establishes the expected content indentation.
|
|
126
|
+
indent_stack.push([actual_indent, actual_indent + indent_size])
|
|
127
|
+
|
|
128
|
+
when :plain
|
|
129
|
+
unless indent_stack.empty?
|
|
130
|
+
open_indent, _content_indent = indent_stack.last
|
|
131
|
+
if actual_indent < open_indent
|
|
132
|
+
warnings << LintWarning.new(
|
|
133
|
+
file: path, line: line_number, content: raw,
|
|
134
|
+
message: "expected at least #{open_indent} spaces (inside block), got #{actual_indent}"
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
indent_stack.each do |open_indent, _| # rubocop:disable Style/HashEachMethods
|
|
142
|
+
warnings << LintWarning.new(file: path, line: nil,
|
|
143
|
+
message: "unclosed block (opened at indent #{open_indent})",
|
|
144
|
+
content: nil)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
warnings
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def main
|
|
151
|
+
indent_size, files = parse_args(ARGV)
|
|
152
|
+
warnings = []
|
|
153
|
+
|
|
154
|
+
files.each do |file|
|
|
155
|
+
unless File.exist?(file)
|
|
156
|
+
warn "#{file}: no such file"
|
|
157
|
+
next
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
warnings.concat(lint_file(file, indent_size))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
warnings.each do |warning|
|
|
164
|
+
if warning.line
|
|
165
|
+
warn "#{warning.file}:#{warning.line}: #{warning.message}"
|
|
166
|
+
warn " #{warning.content}\n"
|
|
167
|
+
else
|
|
168
|
+
warn "#{warning.file}: #{warning.message}\n"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
if warnings.empty?
|
|
173
|
+
puts "No indentation issues found."
|
|
174
|
+
exit 0
|
|
175
|
+
else
|
|
176
|
+
puts "#{warnings.length} indentation issue(s) found."
|
|
177
|
+
exit 1
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
main if __FILE__ == $PROGRAM_NAME
|
|
@@ -16,7 +16,7 @@ module Curlybars
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
class Configuration
|
|
19
|
-
attr_accessor :presenters_namespace, :nesting_limit, :traversing_limit, :output_limit, :rendering_timeout, :custom_processors, :compiler_transformers, :global_helpers_provider_classes, :cache
|
|
19
|
+
attr_accessor :presenters_namespace, :nesting_limit, :traversing_limit, :output_limit, :rendering_timeout, :custom_processors, :compiler_transformers, :global_helpers_provider_classes, :cache, :partial_nesting_limit, :partial_provider_class
|
|
20
20
|
|
|
21
21
|
def initialize
|
|
22
22
|
@presenters_namespace = ''
|
|
@@ -28,6 +28,8 @@ module Curlybars
|
|
|
28
28
|
@compiler_transformers = []
|
|
29
29
|
@global_helpers_provider_classes = []
|
|
30
30
|
@cache = ->(key, &block) { block.call }
|
|
31
|
+
@partial_nesting_limit = 3
|
|
32
|
+
@partial_provider_class = nil
|
|
31
33
|
end
|
|
32
34
|
end
|
|
33
35
|
end
|
|
@@ -64,7 +64,7 @@ module Curlybars
|
|
|
64
64
|
RUBY
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
def validate(branches)
|
|
67
|
+
def validate(branches, context: nil)
|
|
68
68
|
check_open_and_close_elements(helper, helperclose, Curlybars::Error::Validate)
|
|
69
69
|
|
|
70
70
|
if helper.leaf?(branches)
|
|
@@ -74,8 +74,8 @@ module Curlybars
|
|
|
74
74
|
end
|
|
75
75
|
elsif helper.helper?(branches)
|
|
76
76
|
[
|
|
77
|
-
helper_template.validate(branches),
|
|
78
|
-
else_template.validate(branches),
|
|
77
|
+
helper_template.validate(branches, context: context),
|
|
78
|
+
else_template.validate(branches, context: context),
|
|
79
79
|
arguments.map { |argument| argument.validate_as_value(branches) },
|
|
80
80
|
options.map { |option| option.validate(branches) }
|
|
81
81
|
]
|
|
@@ -85,8 +85,8 @@ module Curlybars
|
|
|
85
85
|
Curlybars::Error::Validate.new('invalid_signature', message, helper.position)
|
|
86
86
|
else
|
|
87
87
|
[
|
|
88
|
-
helper_template.validate(branches),
|
|
89
|
-
else_template.validate(branches),
|
|
88
|
+
helper_template.validate(branches, context: context),
|
|
89
|
+
else_template.validate(branches, context: context),
|
|
90
90
|
arguments.map { |argument| argument.validate(branches, check_type: :anything) },
|
|
91
91
|
options.map { |option| option.validate(branches) }
|
|
92
92
|
]
|
|
@@ -36,18 +36,18 @@ module Curlybars
|
|
|
36
36
|
RUBY
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def validate(branches)
|
|
39
|
+
def validate(branches, context: nil)
|
|
40
40
|
resolved = path.resolve_and_check!(branches, check_type: :collectionlike)
|
|
41
41
|
sub_tree = resolved.first
|
|
42
42
|
|
|
43
43
|
each_template_errors = begin
|
|
44
44
|
branches.push(sub_tree)
|
|
45
|
-
each_template.validate(branches)
|
|
45
|
+
each_template.validate(branches, context: context)
|
|
46
46
|
ensure
|
|
47
47
|
branches.pop
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
else_template_errors = else_template.validate(branches)
|
|
50
|
+
else_template_errors = else_template.validate(branches, context: context)
|
|
51
51
|
|
|
52
52
|
[
|
|
53
53
|
each_template_errors,
|
|
@@ -13,11 +13,11 @@ module Curlybars
|
|
|
13
13
|
RUBY
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def validate(branches)
|
|
16
|
+
def validate(branches, context: nil)
|
|
17
17
|
[
|
|
18
18
|
expression.validate(branches),
|
|
19
|
-
if_template.validate(branches),
|
|
20
|
-
else_template.validate(branches)
|
|
19
|
+
if_template.validate(branches, context: context),
|
|
20
|
+
else_template.validate(branches, context: context)
|
|
21
21
|
]
|
|
22
22
|
end
|
|
23
23
|
|
data/lib/curlybars/node/item.rb
CHANGED
|
@@ -1,23 +1,72 @@
|
|
|
1
1
|
module Curlybars
|
|
2
2
|
module Node
|
|
3
|
-
Partial = Struct.new(:path) do
|
|
3
|
+
Partial = Struct.new(:path, :options, :position) do
|
|
4
4
|
def compile
|
|
5
|
+
compiled_options = options.map do |option|
|
|
6
|
+
"options.merge!(#{option.compile})"
|
|
7
|
+
end.join("\n")
|
|
8
|
+
|
|
5
9
|
# NOTE: the following is a heredoc string, representing the ruby code fragment
|
|
6
10
|
# outputted by this node.
|
|
7
11
|
<<-RUBY
|
|
8
|
-
|
|
12
|
+
options = ::ActiveSupport::HashWithIndifferentAccess.new
|
|
13
|
+
#{compiled_options}
|
|
14
|
+
|
|
15
|
+
partial_source = rendering.resolve_partial(#{path.path.inspect})
|
|
16
|
+
if partial_source
|
|
17
|
+
buffer.concat(rendering.render_partial(partial_source, #{path.path.inspect}, options).to_s)
|
|
18
|
+
else
|
|
19
|
+
buffer.concat(rendering.cached_call(#{path.compile}).to_s)
|
|
20
|
+
end
|
|
9
21
|
RUBY
|
|
10
22
|
end
|
|
11
23
|
|
|
12
|
-
def validate(branches)
|
|
13
|
-
|
|
24
|
+
def validate(branches, context: nil)
|
|
25
|
+
# Validate option expressions in current scope
|
|
26
|
+
errors = options.flat_map { |option| option.expression.validate(branches) }
|
|
27
|
+
|
|
28
|
+
return errors unless context&.partial_resolver
|
|
29
|
+
|
|
30
|
+
partial_source = begin
|
|
31
|
+
context.partial_resolver.call(path.path)
|
|
32
|
+
rescue StandardError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if partial_source
|
|
37
|
+
options_tree = options.each_with_object({}) do |option, tree|
|
|
38
|
+
expr = option.expression
|
|
39
|
+
tree[option.key.to_sym] = if expr.respond_to?(:resolve)
|
|
40
|
+
begin
|
|
41
|
+
expr.resolve(branches)
|
|
42
|
+
rescue Curlybars::Error::Validate
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if context.valid?
|
|
49
|
+
partial_errors = Curlybars.validate(
|
|
50
|
+
options_tree,
|
|
51
|
+
partial_source,
|
|
52
|
+
:"partials/#{path.path}",
|
|
53
|
+
validation_context: context.increment_depth,
|
|
54
|
+
run_processors: false
|
|
55
|
+
)
|
|
56
|
+
errors.concat(partial_errors)
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
errors.concat(Array(path.validate(branches, check_type: :partial)))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
errors
|
|
14
63
|
end
|
|
15
64
|
|
|
16
65
|
def cache_key
|
|
17
66
|
[
|
|
18
|
-
path
|
|
19
|
-
|
|
20
|
-
].join("/")
|
|
67
|
+
path,
|
|
68
|
+
options
|
|
69
|
+
].flatten.map(&:cache_key).push(position&.file_name, self.class.name).join("/")
|
|
21
70
|
end
|
|
22
71
|
end
|
|
23
72
|
end
|
data/lib/curlybars/node/path.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Curlybars
|
|
|
16
16
|
validate(branches, check_type: :leaf)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def validate(branches, check_type: :anything)
|
|
19
|
+
def validate(branches, check_type: :anything, context: nil)
|
|
20
20
|
resolve_and_check!(branches, check_type: check_type)
|
|
21
21
|
[]
|
|
22
22
|
rescue Curlybars::Error::Validate => path_error
|
data/lib/curlybars/node/root.rb
CHANGED
|
@@ -7,13 +7,17 @@ module Curlybars
|
|
|
7
7
|
<<-RUBY
|
|
8
8
|
contexts = [presenter]
|
|
9
9
|
variables = [{}]
|
|
10
|
+
has_rendering_context = defined?(rendering_context)
|
|
10
11
|
rendering = ::Curlybars::RenderingSupport.new(
|
|
11
12
|
::Curlybars.configuration.rendering_timeout,
|
|
12
13
|
contexts,
|
|
13
14
|
variables,
|
|
14
15
|
#{position.file_name.inspect},
|
|
15
16
|
global_helpers_providers,
|
|
16
|
-
::Curlybars.configuration.cache
|
|
17
|
+
::Curlybars.configuration.cache,
|
|
18
|
+
start_time: has_rendering_context ? rendering_context[:start_time] : nil,
|
|
19
|
+
depth: has_rendering_context ? rendering_context[:depth] : 0,
|
|
20
|
+
partial_provider: partial_provider
|
|
17
21
|
)
|
|
18
22
|
buffer = ::Curlybars::SafeBuffer.new
|
|
19
23
|
#{template.compile}
|
|
@@ -21,8 +25,8 @@ module Curlybars
|
|
|
21
25
|
RUBY
|
|
22
26
|
end
|
|
23
27
|
|
|
24
|
-
def validate(branches)
|
|
25
|
-
template.validate(branches)
|
|
28
|
+
def validate(branches, context: nil)
|
|
29
|
+
template.validate(branches, context: context)
|
|
26
30
|
end
|
|
27
31
|
end
|
|
28
32
|
end
|
|
@@ -46,7 +46,7 @@ module Curlybars
|
|
|
46
46
|
validate(branches, check_type: check_type)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
def validate(branches, check_type: :anything)
|
|
49
|
+
def validate(branches, check_type: :anything, context: nil)
|
|
50
50
|
[
|
|
51
51
|
helper.validate(branches, check_type: :helper),
|
|
52
52
|
arguments.map { |argument| argument.validate_as_value(branches) },
|
data/lib/curlybars/node/text.rb
CHANGED
|
@@ -13,11 +13,11 @@ module Curlybars
|
|
|
13
13
|
RUBY
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def validate(branches)
|
|
16
|
+
def validate(branches, context: nil)
|
|
17
17
|
[
|
|
18
18
|
expression.validate(branches),
|
|
19
|
-
unless_template.validate(branches),
|
|
20
|
-
else_template.validate(branches)
|
|
19
|
+
unless_template.validate(branches, context: context),
|
|
20
|
+
else_template.validate(branches, context: context)
|
|
21
21
|
]
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -23,16 +23,16 @@ module Curlybars
|
|
|
23
23
|
RUBY
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def validate(branches)
|
|
26
|
+
def validate(branches, context: nil)
|
|
27
27
|
sub_tree = path.resolve_and_check!(branches, check_type: :presenterlike)
|
|
28
28
|
with_template_errors = begin
|
|
29
29
|
branches.push(sub_tree)
|
|
30
|
-
with_template.validate(branches)
|
|
30
|
+
with_template.validate(branches, context: context)
|
|
31
31
|
ensure
|
|
32
32
|
branches.pop
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
else_template_errors = else_template.validate(branches)
|
|
35
|
+
else_template_errors = else_template.validate(branches, context: context)
|
|
36
36
|
|
|
37
37
|
[
|
|
38
38
|
with_template_errors,
|
data/lib/curlybars/parser.rb
CHANGED
|
@@ -162,8 +162,8 @@ module Curlybars
|
|
|
162
162
|
Node::WithElse.new(subexpression, with_template || VOID, else_template || VOID, pos(0))
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
-
clause('START GT .path END') do |path|
|
|
166
|
-
Node::Partial.new(path)
|
|
165
|
+
clause('START GT .path .options? END') do |path, options|
|
|
166
|
+
Node::Partial.new(path, options || [], pos(0))
|
|
167
167
|
end
|
|
168
168
|
end
|
|
169
169
|
|
|
@@ -210,7 +210,7 @@ module Curlybars
|
|
|
210
210
|
# Nothing to compile.
|
|
211
211
|
end
|
|
212
212
|
|
|
213
|
-
def validate(branches)
|
|
213
|
+
def validate(branches, context: nil)
|
|
214
214
|
[] # Nothing to validate.
|
|
215
215
|
end
|
|
216
216
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Curlybars
|
|
2
|
+
class PartialPresenter
|
|
3
|
+
extend MethodWhitelist
|
|
4
|
+
|
|
5
|
+
def initialize(_context, data = {})
|
|
6
|
+
@_safe_keys = Set.new
|
|
7
|
+
data.symbolize_keys.each do |key, value|
|
|
8
|
+
next if respond_to?(key, true)
|
|
9
|
+
|
|
10
|
+
define_singleton_method(key) { value }
|
|
11
|
+
@_safe_keys.add(key)
|
|
12
|
+
end
|
|
13
|
+
@_safe_keys.freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Subclasses that call allow_methods get their allowed_methods via super,
|
|
17
|
+
# which includes these dynamic data keys.
|
|
18
|
+
def allowed_methods
|
|
19
|
+
@_safe_keys.to_a
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def allows_method?(method)
|
|
23
|
+
@_safe_keys.include?(method.to_sym)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
module Curlybars
|
|
2
2
|
class RenderingSupport
|
|
3
|
-
def initialize(timeout, contexts, variables, file_name, global_helpers_providers = [], cache = ->(key, &block) { block.call })
|
|
3
|
+
def initialize(timeout, contexts, variables, file_name, global_helpers_providers = [], cache = ->(key, &block) { block.call }, start_time: nil, depth: 0, partial_provider: nil)
|
|
4
4
|
@timeout = timeout
|
|
5
|
-
@start_time = Time.now
|
|
5
|
+
@start_time = start_time || Time.now
|
|
6
|
+
@depth = depth
|
|
6
7
|
|
|
7
8
|
@contexts = contexts
|
|
8
9
|
@variables = variables
|
|
9
10
|
@file_name = file_name
|
|
10
11
|
@cached_calls = {}
|
|
11
12
|
@cache = cache
|
|
13
|
+
@global_helpers_providers = global_helpers_providers
|
|
14
|
+
@partial_provider = partial_provider
|
|
12
15
|
|
|
13
16
|
@global_helpers = {}
|
|
14
17
|
|
|
@@ -176,9 +179,42 @@ module Curlybars
|
|
|
176
179
|
end
|
|
177
180
|
end
|
|
178
181
|
|
|
182
|
+
def resolve_partial(name)
|
|
183
|
+
return nil unless partial_provider
|
|
184
|
+
|
|
185
|
+
source = partial_provider.resolve_partial(name)
|
|
186
|
+
return nil unless source
|
|
187
|
+
|
|
188
|
+
raise TypeError, "resolve_partial must return a String, got #{source.class}" unless source.is_a?(String)
|
|
189
|
+
|
|
190
|
+
source
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def render_partial(source, name, options)
|
|
194
|
+
return "" if depth >= ::Curlybars.configuration.partial_nesting_limit
|
|
195
|
+
|
|
196
|
+
compiled = ::Curlybars.compile(source, "partial:#{name}")
|
|
197
|
+
|
|
198
|
+
eval_source = <<-RUBY
|
|
199
|
+
rendering_context = { start_time: @start_time, depth: @depth + 1 }
|
|
200
|
+
presenter = ::Curlybars::PartialPresenter.new(nil, options)
|
|
201
|
+
global_helpers_providers = @global_helpers_providers
|
|
202
|
+
partial_provider = @partial_provider
|
|
203
|
+
#{compiled}
|
|
204
|
+
RUBY
|
|
205
|
+
|
|
206
|
+
eval(eval_source) # rubocop:disable Security/Eval -- eval is the established compilation pattern for the whole engine
|
|
207
|
+
rescue Curlybars::Error::Render => e
|
|
208
|
+
raise if e.id == 'render.timeout' || e.id == 'render.output_too_long'
|
|
209
|
+
|
|
210
|
+
""
|
|
211
|
+
rescue StandardError
|
|
212
|
+
""
|
|
213
|
+
end
|
|
214
|
+
|
|
179
215
|
private
|
|
180
216
|
|
|
181
|
-
attr_reader :contexts, :variables, :cached_calls, :file_name, :global_helpers, :start_time, :timeout, :cache
|
|
217
|
+
attr_reader :contexts, :variables, :cached_calls, :file_name, :global_helpers, :start_time, :timeout, :cache, :depth, :partial_provider
|
|
182
218
|
|
|
183
219
|
def instrument(meth, &)
|
|
184
220
|
# Instruments only callables that give enough details (eg. methods)
|
|
@@ -72,6 +72,9 @@ module Curlybars
|
|
|
72
72
|
provider_classes = ::Curlybars.configuration.global_helpers_provider_classes
|
|
73
73
|
global_helpers_providers = provider_classes.map { |klass| klass.new(self) }
|
|
74
74
|
|
|
75
|
+
partial_provider_class = ::Curlybars.configuration.partial_provider_class
|
|
76
|
+
partial_provider = partial_provider_class&.new(self)
|
|
77
|
+
|
|
75
78
|
presenter = ::#{presenter_class}.new(self, options)
|
|
76
79
|
presenter.setup!
|
|
77
80
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Curlybars
|
|
2
|
+
class ValidationContext
|
|
3
|
+
attr_reader :partial_resolver, :depth
|
|
4
|
+
|
|
5
|
+
def initialize(partial_resolver: nil, depth: 0)
|
|
6
|
+
@partial_resolver = partial_resolver
|
|
7
|
+
@depth = depth
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def valid?
|
|
11
|
+
depth < Curlybars.configuration.partial_nesting_limit
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def increment_depth
|
|
15
|
+
self.class.new(partial_resolver: partial_resolver, depth: depth + 1)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/curlybars/version.rb
CHANGED
data/lib/curlybars/visitor.rb
CHANGED
data/lib/curlybars.rb
CHANGED
|
@@ -40,14 +40,16 @@ module Curlybars
|
|
|
40
40
|
# identifier - The the file name of the template being validated (defaults to `nil`).
|
|
41
41
|
#
|
|
42
42
|
# Returns an array of Curlybars::Error::Validation
|
|
43
|
-
def validate(dependency_tree, source, identifier = nil, **options)
|
|
43
|
+
def validate(dependency_tree, source, identifier = nil, partial_resolver: nil, validation_context: nil, **options)
|
|
44
44
|
options.reverse_merge!(
|
|
45
45
|
run_processors: true
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
+
validation_context ||= ValidationContext.new(partial_resolver: partial_resolver) if partial_resolver
|
|
49
|
+
|
|
48
50
|
errors = begin
|
|
49
51
|
branches = [dependency_tree]
|
|
50
|
-
ast(source, identifier, run_processors: options[:run_processors]).validate(branches)
|
|
52
|
+
ast(source, identifier, run_processors: options[:run_processors]).validate(branches, context: validation_context)
|
|
51
53
|
rescue Curlybars::Error::Base => ast_error
|
|
52
54
|
[ast_error]
|
|
53
55
|
end
|
|
@@ -159,8 +161,10 @@ require 'curlybars/error/compile'
|
|
|
159
161
|
require 'curlybars/error/validate'
|
|
160
162
|
require 'curlybars/error/render'
|
|
161
163
|
require 'curlybars/template_handler'
|
|
164
|
+
require 'curlybars/validation_context'
|
|
162
165
|
require 'curlybars/railtie' if defined?(Rails)
|
|
163
166
|
require 'curlybars/presenter'
|
|
164
167
|
require 'curlybars/method_whitelist'
|
|
168
|
+
require 'curlybars/partial_presenter'
|
|
165
169
|
require 'curlybars/visitor'
|
|
166
170
|
require 'curlybars/path_finder'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: curlybars
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.16.0.pre
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Libo Cannici
|
|
@@ -12,7 +12,7 @@ authors:
|
|
|
12
12
|
- Andreas Garnæs
|
|
13
13
|
- Augusto Silva
|
|
14
14
|
- Attila Večerek
|
|
15
|
-
bindir:
|
|
15
|
+
bindir: exe
|
|
16
16
|
cert_chain: []
|
|
17
17
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
18
18
|
dependencies:
|
|
@@ -76,10 +76,12 @@ description: |-
|
|
|
76
76
|
A view layer for your Rails apps that separates structure and logic, using Handlebars templates.
|
|
77
77
|
Strongly inspired by Curly Template gem by Daniel Schierbeck.
|
|
78
78
|
email: vikings@zendesk.com
|
|
79
|
-
executables:
|
|
79
|
+
executables:
|
|
80
|
+
- curlybars-indent-lint
|
|
80
81
|
extensions: []
|
|
81
82
|
extra_rdoc_files: []
|
|
82
83
|
files:
|
|
84
|
+
- exe/curlybars-indent-lint
|
|
83
85
|
- lib/curlybars.rb
|
|
84
86
|
- lib/curlybars/configuration.rb
|
|
85
87
|
- lib/curlybars/dependency_tracker.rb
|
|
@@ -112,6 +114,7 @@ files:
|
|
|
112
114
|
- lib/curlybars/node/variable.rb
|
|
113
115
|
- lib/curlybars/node/with_else.rb
|
|
114
116
|
- lib/curlybars/parser.rb
|
|
117
|
+
- lib/curlybars/partial_presenter.rb
|
|
115
118
|
- lib/curlybars/path_finder.rb
|
|
116
119
|
- lib/curlybars/position.rb
|
|
117
120
|
- lib/curlybars/presenter.rb
|
|
@@ -121,6 +124,7 @@ files:
|
|
|
121
124
|
- lib/curlybars/rendering_support.rb
|
|
122
125
|
- lib/curlybars/safe_buffer.rb
|
|
123
126
|
- lib/curlybars/template_handler.rb
|
|
127
|
+
- lib/curlybars/validation_context.rb
|
|
124
128
|
- lib/curlybars/version.rb
|
|
125
129
|
- lib/curlybars/visitor.rb
|
|
126
130
|
homepage: https://github.com/zendesk/curlybars
|