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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ceff8a95cec7c183aa997401411044bcaf297b09b2793bc27163b8e39f703202
4
- data.tar.gz: 4cd7908efca8ecd3e152e78a1f746529bf3d26e0959ea9a3889b3c405c58f5e7
3
+ metadata.gz: e288b8260b5607e12658628a38b544a4b1afc6affebe60d52072a8161a9935c9
4
+ data.tar.gz: fb9df5a81e5e78bfb8d6445f69e619be0dc32d9a21ad47c788a386a2340caf8f
5
5
  SHA512:
6
- metadata.gz: 6617f8bfbe1377e9934dd8ae998ba63b1c5aad2f5014f1cffaaa90c366f16103bb10c8f68bdad142c738ff2dc1a5a205eccaf7d9f3bc62c64bff3120400e14b6
7
- data.tar.gz: c71d2069e11a7b6f825e391f63cd6729051e09f55e80da0adeaeb62360b3b62fb0280f8e90c6fef496bc799ee2eb96d627d1d2fd8ee75fdc330ad74a37e5cf61
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
  ]
@@ -9,7 +9,7 @@ module Curlybars
9
9
  RUBY
10
10
  end
11
11
 
12
- def validate(branches)
12
+ def validate(branches, context: nil)
13
13
  # Nothing to validate here.
14
14
  end
15
15
 
@@ -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
 
@@ -14,8 +14,8 @@ module Curlybars
14
14
  RUBY
15
15
  end
16
16
 
17
- def validate(branches)
18
- item.validate(branches)
17
+ def validate(branches, context: nil)
18
+ item.validate(branches, context: context)
19
19
  rescue Curlybars::Error::Validate => path_error
20
20
  path_error
21
21
  end
@@ -9,7 +9,7 @@ module Curlybars
9
9
  RUBY
10
10
  end
11
11
 
12
- def validate(branches, check_type: :anything)
12
+ def validate(branches, check_type: :anything, context: nil)
13
13
  # Nothing to validate here.
14
14
  end
15
15
 
@@ -9,8 +9,8 @@ module Curlybars
9
9
  RUBY
10
10
  end
11
11
 
12
- def validate(branches)
13
- value.validate(branches)
12
+ def validate(branches, context: nil)
13
+ value.validate(branches, context: context)
14
14
  end
15
15
 
16
16
  def cache_key
@@ -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
- buffer.concat(rendering.cached_call(#{path.compile}).to_s)
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
- path.validate(branches, check_type: :partial)
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.cache_key,
19
- self.class.name
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
@@ -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
@@ -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
@@ -9,7 +9,7 @@ module Curlybars
9
9
  RUBY
10
10
  end
11
11
 
12
- def validate(branches)
12
+ def validate(branches, context: nil)
13
13
  # Nothing to validate here.
14
14
  end
15
15
 
@@ -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) },
@@ -20,8 +20,8 @@ module Curlybars
20
20
  RUBY
21
21
  end
22
22
 
23
- def validate(branches)
24
- items.map { |item| item.validate(branches) }
23
+ def validate(branches, context: nil)
24
+ items.map { |item| item.validate(branches, context: context) }
25
25
  end
26
26
 
27
27
  def cache_key
@@ -9,7 +9,7 @@ module Curlybars
9
9
  RUBY
10
10
  end
11
11
 
12
- def validate(branches)
12
+ def validate(branches, context: nil)
13
13
  # Nothing to validate here.
14
14
  end
15
15
 
@@ -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
 
@@ -15,7 +15,7 @@ module Curlybars
15
15
  RUBY
16
16
  end
17
17
 
18
- def validate(branches, check_type: :anything)
18
+ def validate(branches, check_type: :anything, context: nil)
19
19
  # Nothing to validate here.
20
20
  end
21
21
 
@@ -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,
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Curlybars
4
- VERSION = '1.15.0'
4
+ VERSION = '1.16.0.pre'
5
5
  end
@@ -63,6 +63,7 @@ module Curlybars
63
63
 
64
64
  def visit_partial(node)
65
65
  visit(node.path)
66
+ node.options.each { |opt| visit(opt) }
66
67
  end
67
68
 
68
69
  def visit_path(_node)
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.15.0
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: bin
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