luoma 0.1.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 +7 -0
- checksums.yaml.gz.sig +2 -0
- data/.rdoc_options +16 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/Rakefile +69 -0
- data/Steepfile +41 -0
- data/certs/jgrp.pem +27 -0
- data/docs/configuration.md +180 -0
- data/docs/custom_filters.md +3 -0
- data/docs/custom_tags.md +3 -0
- data/docs/expressions.md +3 -0
- data/docs/extension_types.md +41 -0
- data/docs/filter_reference.md +1702 -0
- data/docs/index.md +73 -0
- data/docs/luoma_for_template_authors.md +3 -0
- data/docs/markdown.md +111 -0
- data/docs/predicate_reference.md +3 -0
- data/docs/static_analysis.md +3 -0
- data/docs/tag_reference.md +352 -0
- data/docs/template_loaders.md +177 -0
- data/docs/undefined_variables.md +3 -0
- data/docs-requirements.txt +11 -0
- data/docs_/cycle.md +17 -0
- data/docs_/font_rendering_example.md +157 -0
- data/docs_/header_block_example.md +51 -0
- data/docs_/increment_and_decrement.md +49 -0
- data/docs_/logo_style_example.md +40 -0
- data/docs_/migration.md +11 -0
- data/docs_/notes.md +19 -0
- data/lib/luoma/cache.rb +80 -0
- data/lib/luoma/chain_hash.rb +54 -0
- data/lib/luoma/context.rb +227 -0
- data/lib/luoma/drop.rb +84 -0
- data/lib/luoma/drops/block.rb +33 -0
- data/lib/luoma/drops/expression.rb +29 -0
- data/lib/luoma/drops/html_safe.rb +36 -0
- data/lib/luoma/drops/range.rb +69 -0
- data/lib/luoma/drops/undefined.rb +119 -0
- data/lib/luoma/environment.rb +479 -0
- data/lib/luoma/errors.rb +79 -0
- data/lib/luoma/escape.rb +35 -0
- data/lib/luoma/expression.rb +1271 -0
- data/lib/luoma/filter.rb +97 -0
- data/lib/luoma/filters/array.rb +372 -0
- data/lib/luoma/filters/date.rb +19 -0
- data/lib/luoma/filters/default.rb +16 -0
- data/lib/luoma/filters/json.rb +14 -0
- data/lib/luoma/filters/math.rb +84 -0
- data/lib/luoma/filters/size.rb +13 -0
- data/lib/luoma/filters/slice.rb +52 -0
- data/lib/luoma/filters/sort.rb +113 -0
- data/lib/luoma/filters/string.rb +186 -0
- data/lib/luoma/fnv.rb +15 -0
- data/lib/luoma/lexer.rb +106 -0
- data/lib/luoma/lexer_legacy.rb +396 -0
- data/lib/luoma/lexer_unified.rb +279 -0
- data/lib/luoma/loader.rb +50 -0
- data/lib/luoma/loaders/choice_loader.rb +20 -0
- data/lib/luoma/loaders/file_system_loader.rb +75 -0
- data/lib/luoma/loaders/mixins.rb +52 -0
- data/lib/luoma/markup.rb +109 -0
- data/lib/luoma/parser.rb +252 -0
- data/lib/luoma/parser_unified.rb +979 -0
- data/lib/luoma/predicates/blank.rb +19 -0
- data/lib/luoma/predicates/defined.rb +10 -0
- data/lib/luoma/predicates/empty.rb +15 -0
- data/lib/luoma/predicates/type.rb +35 -0
- data/lib/luoma/static_analysis.rb +427 -0
- data/lib/luoma/tags/assign.rb +63 -0
- data/lib/luoma/tags/break.rb +23 -0
- data/lib/luoma/tags/capture.rb +43 -0
- data/lib/luoma/tags/case.rb +137 -0
- data/lib/luoma/tags/comment.rb +17 -0
- data/lib/luoma/tags/continue.rb +23 -0
- data/lib/luoma/tags/define.rb +42 -0
- data/lib/luoma/tags/else.rb +30 -0
- data/lib/luoma/tags/for.rb +108 -0
- data/lib/luoma/tags/if.rb +109 -0
- data/lib/luoma/tags/import.rb +91 -0
- data/lib/luoma/tags/include.rb +77 -0
- data/lib/luoma/tags/output.rb +20 -0
- data/lib/luoma/tags/raw.rb +26 -0
- data/lib/luoma/tags/render.rb +92 -0
- data/lib/luoma/tags/with.rb +55 -0
- data/lib/luoma/template.rb +178 -0
- data/lib/luoma/token.rb +84 -0
- data/lib/luoma/version.rb +5 -0
- data/lib/luoma.rb +75 -0
- data/sig/luoma/cache.rbs +44 -0
- data/sig/luoma/chain_hash.rbs +26 -0
- data/sig/luoma/context.rbs +115 -0
- data/sig/luoma/drop.rbs +44 -0
- data/sig/luoma/drops/block.rbs +10 -0
- data/sig/luoma/drops/expression.rbs +10 -0
- data/sig/luoma/drops/html_safe.rbs +14 -0
- data/sig/luoma/drops/range.rbs +15 -0
- data/sig/luoma/drops/undefined.rbs +27 -0
- data/sig/luoma/environment.rbs +212 -0
- data/sig/luoma/errors.rbs +67 -0
- data/sig/luoma/escape.rbs +15 -0
- data/sig/luoma/expression.rbs +514 -0
- data/sig/luoma/filter.rbs +66 -0
- data/sig/luoma/filters/array.rbs +74 -0
- data/sig/luoma/filters/date.rbs +9 -0
- data/sig/luoma/filters/default.rbs +8 -0
- data/sig/luoma/filters/json.rbs +7 -0
- data/sig/luoma/filters/math.rbs +37 -0
- data/sig/luoma/filters/size.rbs +6 -0
- data/sig/luoma/filters/slice.rbs +10 -0
- data/sig/luoma/filters/sort.rbs +17 -0
- data/sig/luoma/filters/string.rbs +103 -0
- data/sig/luoma/fnv.rbs +3 -0
- data/sig/luoma/lexer.rbs +42 -0
- data/sig/luoma/lexer_legacy.rbs +81 -0
- data/sig/luoma/lexer_unified.rbs +49 -0
- data/sig/luoma/loader.rbs +40 -0
- data/sig/luoma/loaders/choice_loader.rbs +9 -0
- data/sig/luoma/loaders/file_system_loader.rbs +19 -0
- data/sig/luoma/loaders/mixins.rbs +16 -0
- data/sig/luoma/markup.rbs +68 -0
- data/sig/luoma/parser.rbs +105 -0
- data/sig/luoma/parser_unified.rbs +150 -0
- data/sig/luoma/predicates/blank.rbs +6 -0
- data/sig/luoma/predicates/defined.rbs +6 -0
- data/sig/luoma/predicates/empty.rbs +6 -0
- data/sig/luoma/predicates/type.rbs +21 -0
- data/sig/luoma/static_analysis.rbs +141 -0
- data/sig/luoma/tags/assign.rbs +12 -0
- data/sig/luoma/tags/break.rbs +11 -0
- data/sig/luoma/tags/capture.rbs +14 -0
- data/sig/luoma/tags/case.rbs +36 -0
- data/sig/luoma/tags/comment.rbs +11 -0
- data/sig/luoma/tags/continue.rbs +11 -0
- data/sig/luoma/tags/define.rbs +16 -0
- data/sig/luoma/tags/else.rbs +17 -0
- data/sig/luoma/tags/for.rbs +26 -0
- data/sig/luoma/tags/if.rbs +45 -0
- data/sig/luoma/tags/import.rbs +13 -0
- data/sig/luoma/tags/include.rbs +13 -0
- data/sig/luoma/tags/output.rbs +13 -0
- data/sig/luoma/tags/raw.rbs +11 -0
- data/sig/luoma/tags/render.rbs +13 -0
- data/sig/luoma/tags/with.rbs +15 -0
- data/sig/luoma/template.rbs +117 -0
- data/sig/luoma/token.rbs +81 -0
- data/sig/luoma.rbs +11 -0
- data/zensical.toml +363 -0
- data.tar.gz.sig +0 -0
- metadata +233 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "chain_hash"
|
|
4
|
+
|
|
5
|
+
module Luoma
|
|
6
|
+
class RenderContext
|
|
7
|
+
attr_reader :env, :template, :disabled_tags, :context_depth, :assign_score, :assign_score_cumulative,
|
|
8
|
+
:registers, :globals, :scopes, :locals
|
|
9
|
+
|
|
10
|
+
attr_accessor :render_score, :render_score_cumulative, :interrupts
|
|
11
|
+
|
|
12
|
+
#: (Template, ?globals: _Namespace?) -> void
|
|
13
|
+
def initialize(
|
|
14
|
+
template,
|
|
15
|
+
globals: nil,
|
|
16
|
+
disabled_tags: nil,
|
|
17
|
+
context_depth: nil,
|
|
18
|
+
assign_score_carry: nil,
|
|
19
|
+
render_score_carry: nil
|
|
20
|
+
)
|
|
21
|
+
# The template being rendered.
|
|
22
|
+
@template = template
|
|
23
|
+
|
|
24
|
+
# The environment this render context and associated template is
|
|
25
|
+
# bound to.
|
|
26
|
+
@env = template.env
|
|
27
|
+
|
|
28
|
+
# Developer-defined template variables passed down from the environment
|
|
29
|
+
# and template.
|
|
30
|
+
@globals = globals || {} # steep:ignore
|
|
31
|
+
|
|
32
|
+
# The namespace for variables defined with `{% assign %}` and
|
|
33
|
+
# `{% capture %}`.
|
|
34
|
+
@locals = {} #: t_namespace
|
|
35
|
+
|
|
36
|
+
# A namespace for `{% increment %}` and `{% decrement %}`.
|
|
37
|
+
@counters = {} #: t_namespace
|
|
38
|
+
|
|
39
|
+
# The current template scope including `locals`, `globals` and `counters`.
|
|
40
|
+
# New block-scoped namespaces get pushed onto and popped off this chain
|
|
41
|
+
# map.
|
|
42
|
+
#
|
|
43
|
+
# Scopes are searched from right to left. New scopes are push on the
|
|
44
|
+
# right.
|
|
45
|
+
@scopes = ChainHash.new(@counters, @globals, @locals)
|
|
46
|
+
|
|
47
|
+
# Names of tags that are disallowed in this context.
|
|
48
|
+
@disabled_tags = disabled_tags
|
|
49
|
+
|
|
50
|
+
# The number of times this render context has been extended or copied.
|
|
51
|
+
@context_depth = context_depth || 0
|
|
52
|
+
|
|
53
|
+
# A non-specific indicator of template local scope usage.
|
|
54
|
+
@assign_score = 0
|
|
55
|
+
|
|
56
|
+
# A non-specific indicator of template local scope usage for the current
|
|
57
|
+
# template and all partial templates combined.
|
|
58
|
+
@assign_score_cumulative = assign_score_carry || 0
|
|
59
|
+
|
|
60
|
+
# The number of nodes rendered for the current template.
|
|
61
|
+
@render_score = 0
|
|
62
|
+
|
|
63
|
+
# The number of nodes rendered for the current template and all partial
|
|
64
|
+
# templates.
|
|
65
|
+
@render_score_cumulative = render_score_carry || 0
|
|
66
|
+
|
|
67
|
+
# A stack of interrupt signals used by `{% break %}` and
|
|
68
|
+
# `{% continue %}`, for example.
|
|
69
|
+
@interrupts = [] #: Array[Symbol]
|
|
70
|
+
|
|
71
|
+
# Registers supporting stateful tags. It's OK to use this map for storing
|
|
72
|
+
# custom tag state.
|
|
73
|
+
@registers = {}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#: (String) -> untyped
|
|
77
|
+
def resolve(name)
|
|
78
|
+
@scopes[name]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Follow path segments starting at `obj`. If the path from `obj` does not
|
|
82
|
+
# exist, :nothing is returned along with the index of the last segment to
|
|
83
|
+
# be successfully resolved.
|
|
84
|
+
#
|
|
85
|
+
#: (untyped, Array[untyped]) -> [untyped, Integer]
|
|
86
|
+
def resolve_path(obj, segments)
|
|
87
|
+
segment_index = -1
|
|
88
|
+
|
|
89
|
+
segments.each do |segment|
|
|
90
|
+
segment_index += 1
|
|
91
|
+
|
|
92
|
+
return [segment.call(self, obj), segment_index] if segment.is_a?(PredicateFunction)
|
|
93
|
+
|
|
94
|
+
obj = case obj
|
|
95
|
+
when Drop
|
|
96
|
+
segment = segment.to_primitive(:string, self) if segment.is_a?(Drop)
|
|
97
|
+
obj.fetch(segment, self, default: :nothing)
|
|
98
|
+
when Array
|
|
99
|
+
segment = segment.to_primitive(:numeric, self) if segment.is_a?(Drop)
|
|
100
|
+
resolve_array_segment(obj, segment)
|
|
101
|
+
when Hash
|
|
102
|
+
segment = segment.to_primitive(:data, self) if segment.is_a?(Drop)
|
|
103
|
+
resolve_hash_segment(obj, segment)
|
|
104
|
+
else
|
|
105
|
+
:nothing
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
return [:nothing, segment_index] if obj == :nothing
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
[obj, segment_index]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
#: (String, untyped) -> void
|
|
115
|
+
def assign(name, value)
|
|
116
|
+
if @env.max_assign_score || @env.max_assign_score_cumulative
|
|
117
|
+
score = assign_score_of(value)
|
|
118
|
+
@assign_score += score
|
|
119
|
+
@assign_score_cumulative += score
|
|
120
|
+
|
|
121
|
+
if (@env.max_assign_score && @assign_score > @env.max_assign_score) || # steep:ignore
|
|
122
|
+
(@env.max_assign_score_cumulative &&
|
|
123
|
+
@assign_score_cumulative > @env.max_assign_score_cumulative) # steep:ignore
|
|
124
|
+
raise ResourceLimitError.new("memory limits reached")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
@locals[name] = value
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Return a new render context with render state from this context.
|
|
133
|
+
#
|
|
134
|
+
# The caller is responsible for updating renderScoreCumulative when the new
|
|
135
|
+
# context is no longer needed.
|
|
136
|
+
#: (t_namespace, bool?, Set[String]?, Template?) -> RenderContext
|
|
137
|
+
def copy(namespace, block_scope: nil, disabled_tags: nil, template: nil)
|
|
138
|
+
raise_for_context_depth
|
|
139
|
+
|
|
140
|
+
ctx = RenderContext.new(
|
|
141
|
+
template || @template,
|
|
142
|
+
globals: block_scope ? ChainHash.new(namespace, @scopes) : ChainHash.new(namespace, @globals),
|
|
143
|
+
context_depth: @context_depth + 1,
|
|
144
|
+
assign_score_carry: @assign_score_cumulative,
|
|
145
|
+
render_score_carry: @render_score_cumulative
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
@env.persistent_registers.each { |r| ctx.registers[r] = @registers[r] if @registers.include?(r) }
|
|
149
|
+
ctx
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Extend the scope of this context with the given namespace for the
|
|
153
|
+
# duration of a block.
|
|
154
|
+
#
|
|
155
|
+
#: (t_namespace, ?template: Template?) { () -> untyped } -> void
|
|
156
|
+
def extends(namespace, template: nil)
|
|
157
|
+
raise_for_context_depth
|
|
158
|
+
|
|
159
|
+
assign_score_ = @assign_score
|
|
160
|
+
render_score_ = @render_score
|
|
161
|
+
template_ = @template
|
|
162
|
+
|
|
163
|
+
@template = template if template
|
|
164
|
+
@scopes << namespace
|
|
165
|
+
@context_depth += 1
|
|
166
|
+
@assign_score = 0
|
|
167
|
+
@render_score = 0
|
|
168
|
+
|
|
169
|
+
begin
|
|
170
|
+
yield
|
|
171
|
+
ensure
|
|
172
|
+
@template = template_ if template
|
|
173
|
+
@scopes.pop
|
|
174
|
+
@context_depth -= 1
|
|
175
|
+
@assign_score = assign_score_
|
|
176
|
+
@render_score = render_score_
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
#: () -> void
|
|
183
|
+
def raise_for_context_depth
|
|
184
|
+
if @context_depth + 1 > @env.max_context_depth
|
|
185
|
+
raise ContextDepthError.new(
|
|
186
|
+
"maximum context depth reached, possible recursive render"
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
#: (untyped) -> Integer
|
|
192
|
+
def assign_score_of(value)
|
|
193
|
+
case value
|
|
194
|
+
when String
|
|
195
|
+
value.bytesize
|
|
196
|
+
when Array
|
|
197
|
+
value.sum { |i| assign_score_of(i) } + 1
|
|
198
|
+
when Hash
|
|
199
|
+
value.reduce(1) { |a, p| a + assign_score_of(p.first) + assign_score_of(p.last) }
|
|
200
|
+
else
|
|
201
|
+
1
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
#: (Array[untyped], untyped) -> untyped
|
|
206
|
+
def resolve_array_segment(obj, segment)
|
|
207
|
+
index = if segment.is_a?(Integer)
|
|
208
|
+
segment.negative? && obj.length >= segment.abs ? obj.length + segment : segment
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
if index && index < obj.length
|
|
212
|
+
obj[index]
|
|
213
|
+
else
|
|
214
|
+
:nothing
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
#: (Hash[untyped, untyped], untyped) -> untyped
|
|
219
|
+
def resolve_hash_segment(obj, segment)
|
|
220
|
+
if obj.key?(segment)
|
|
221
|
+
obj[segment]
|
|
222
|
+
else
|
|
223
|
+
:nothing
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
data/lib/luoma/drop.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class Drop
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
#: (untyped, RenderContext) -> bool
|
|
8
|
+
def key?(obj, context)
|
|
9
|
+
false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
#: (String, RenderContext) -> untyped
|
|
13
|
+
def fetch(name, context, default: :nothing)
|
|
14
|
+
default
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def each(&block)
|
|
18
|
+
return enum_for(:each) unless block
|
|
19
|
+
|
|
20
|
+
[].to_enum # steep:ignore
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_a
|
|
24
|
+
each.to_a
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#: (untyped, RenderContext) -> bool
|
|
28
|
+
def eq?(obj, context)
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: (untyped, RenderContext) -> bool
|
|
33
|
+
def lt?(obj, context)
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#: (untyped, RenderContext) -> bool
|
|
38
|
+
def gt?(obj, context)
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#: (obj, RenderContext) -> bool
|
|
43
|
+
def contains?(obj, context)
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Return the length of this object.
|
|
48
|
+
# Along with `#slice`, `#length` is part of the iterator protocol.
|
|
49
|
+
#: (RenderContext) -> Integer
|
|
50
|
+
def length(context)
|
|
51
|
+
0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# TODO: pass context
|
|
55
|
+
#: (Integer, Integer, Integer) -> Enumerable[untyped]
|
|
56
|
+
def slice(start, stop, step)
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
|
|
61
|
+
def to_primitive(hint, context)
|
|
62
|
+
case hint
|
|
63
|
+
when :data
|
|
64
|
+
nil
|
|
65
|
+
when :numeric
|
|
66
|
+
0
|
|
67
|
+
when :string
|
|
68
|
+
""
|
|
69
|
+
when :boolean
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#: () -> String
|
|
75
|
+
def to_s
|
|
76
|
+
""
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#: (RenderContext) -> String?
|
|
80
|
+
def render(context)
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class BlockDrop < Drop
|
|
5
|
+
#: (t_block) -> void
|
|
6
|
+
def initialize(block)
|
|
7
|
+
super()
|
|
8
|
+
@block = block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
#: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
|
|
12
|
+
def to_primitive(hint, context)
|
|
13
|
+
case hint
|
|
14
|
+
when :numeric
|
|
15
|
+
:nothing
|
|
16
|
+
when :data, :string
|
|
17
|
+
render(context)
|
|
18
|
+
when :boolean
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def render(context)
|
|
24
|
+
buf = +""
|
|
25
|
+
|
|
26
|
+
context.extends({}) do # For recursive block detection.
|
|
27
|
+
Luoma.render_block(@block, context, buf)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
buf
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class ExpressionDrop < Drop
|
|
5
|
+
attr_reader :expr
|
|
6
|
+
|
|
7
|
+
#: (LambdaExpr) -> void
|
|
8
|
+
def initialize(expr)
|
|
9
|
+
super()
|
|
10
|
+
@expr = expr
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
#: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
|
|
14
|
+
def to_primitive(hint, context)
|
|
15
|
+
case hint
|
|
16
|
+
when :numeric
|
|
17
|
+
:nothing
|
|
18
|
+
when :data, :string
|
|
19
|
+
render(context)
|
|
20
|
+
when :boolean
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render(context)
|
|
26
|
+
@expr.to_s
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class HTMLSafe < Drop
|
|
5
|
+
#: (String | HTMLSafe) -> HTMLSafe
|
|
6
|
+
def self.escape(value)
|
|
7
|
+
value.is_a?(String) ? HTMLSafe.new(Luoma.escape(value)) : value
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
#: (String | HTMLSafe) -> HTMLSafe
|
|
11
|
+
def self.from(value)
|
|
12
|
+
value.is_a?(String) ? HTMLSafe.new(value) : value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
#: (String) -> void
|
|
16
|
+
def initialize(s)
|
|
17
|
+
super()
|
|
18
|
+
@s = s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
|
|
22
|
+
def to_primitive(hint, context)
|
|
23
|
+
case hint
|
|
24
|
+
when :string, :data
|
|
25
|
+
@s
|
|
26
|
+
else
|
|
27
|
+
:nothing
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#: (RenderContext) -> String?
|
|
32
|
+
def render(context)
|
|
33
|
+
@s
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class RangeDrop < Drop
|
|
5
|
+
attr_reader :start, :stop
|
|
6
|
+
|
|
7
|
+
def initialize(start, stop)
|
|
8
|
+
super()
|
|
9
|
+
@start = start
|
|
10
|
+
@stop = stop
|
|
11
|
+
@range = (start..stop)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#: (untyped, RenderContext) -> bool
|
|
15
|
+
def key?(obj, context)
|
|
16
|
+
obj == "first" || obj == "last" || obj == "size"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
#: (String, RenderContext) -> untyped
|
|
20
|
+
def fetch(name, context, default: :nothing)
|
|
21
|
+
case name
|
|
22
|
+
when "first"
|
|
23
|
+
@start
|
|
24
|
+
when "last"
|
|
25
|
+
@stop
|
|
26
|
+
when "size"
|
|
27
|
+
@range.size
|
|
28
|
+
else
|
|
29
|
+
default
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_a
|
|
34
|
+
@range.to_a
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def each(&)
|
|
38
|
+
@range.each
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
#: (untyped, RenderContext) -> bool
|
|
42
|
+
def eq?(obj, context)
|
|
43
|
+
obj.is_a?(RangeDrop) && obj.start == obj.stop
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#: (RenderContext) -> Integer
|
|
47
|
+
def length(context) # steep:ignore
|
|
48
|
+
@range.size
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def slice(start, stop, step)
|
|
52
|
+
@range.to_a[(start...stop).step(step)] # steep:ignore
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
|
|
56
|
+
def to_primitive(hint, context)
|
|
57
|
+
case hint
|
|
58
|
+
when :data
|
|
59
|
+
@range.to_a
|
|
60
|
+
when :numeric
|
|
61
|
+
:nothing
|
|
62
|
+
when :string
|
|
63
|
+
JSON.generate(@range.to_a)
|
|
64
|
+
when :boolean
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
attr_reader :path
|
|
5
|
+
|
|
6
|
+
# The default and base "undefined" type.
|
|
7
|
+
class UndefinedDrop < Drop
|
|
8
|
+
#: (String, t_token, String, String) -> void
|
|
9
|
+
def initialize(path, token, source, template_name)
|
|
10
|
+
super()
|
|
11
|
+
@path = path
|
|
12
|
+
@token = token
|
|
13
|
+
@source = source
|
|
14
|
+
@template_name = template_name
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#: (String, RenderContext) -> untyped
|
|
18
|
+
def fetch(name, context, default: :nothing)
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#: (untyped, RenderContext) -> bool
|
|
23
|
+
def eq?(obj, context)
|
|
24
|
+
obj.is_a?(UndefinedDrop)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#: (:data | :numeric | :string | :boolean, RenderContext) -> untyped
|
|
28
|
+
def to_primitive(hint, context)
|
|
29
|
+
case hint
|
|
30
|
+
when :data
|
|
31
|
+
nil
|
|
32
|
+
when :numeric
|
|
33
|
+
:nothing
|
|
34
|
+
when :string
|
|
35
|
+
""
|
|
36
|
+
when :boolean
|
|
37
|
+
false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# An "undefined" type that raises `UndefinedVariableError` in all contexts.
|
|
43
|
+
class StrictUndefinedDrop < UndefinedDrop
|
|
44
|
+
def key?(obj, context)
|
|
45
|
+
error
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fetch(name, context, default: :nothing)
|
|
49
|
+
error
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def each(&)
|
|
53
|
+
error
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def eq?(obj, context)
|
|
57
|
+
error
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def lt?(obj, context)
|
|
61
|
+
error
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def gt?(obj, context)
|
|
65
|
+
error
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def contains?(obj, context)
|
|
69
|
+
error
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def length(context)
|
|
73
|
+
error
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def slice(start, stop, step)
|
|
77
|
+
error
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def to_primitive(hint, context)
|
|
81
|
+
error
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def to_s
|
|
85
|
+
error
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def render(context)
|
|
89
|
+
error
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
protected
|
|
93
|
+
|
|
94
|
+
def error
|
|
95
|
+
raise UndefinedVariableError.new(
|
|
96
|
+
"#{@path.inspect} is undefined",
|
|
97
|
+
@token,
|
|
98
|
+
@source,
|
|
99
|
+
@template_name
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# An "undefined" type that can be tested for truthiness and compared to
|
|
105
|
+
# other objects without raising an error.
|
|
106
|
+
class FalsyStrictUndefinedDrop < StrictUndefinedDrop
|
|
107
|
+
def eq?(obj, context)
|
|
108
|
+
context.env.nothing?(obj)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def to_primitive(hint, context)
|
|
112
|
+
hint == :boolean ? false : error
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def key?(obj, context)
|
|
116
|
+
false
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|