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,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class RenderTag < Markup
|
|
5
|
+
#: (t_token, String, Parser) -> Markup
|
|
6
|
+
def self.parse(token, tag_name, parser)
|
|
7
|
+
name_expr = parser.parse_string_literal
|
|
8
|
+
name_value = name_expr.value
|
|
9
|
+
|
|
10
|
+
unless name_value
|
|
11
|
+
raise TemplateSyntaxError.new(
|
|
12
|
+
"expected a string literal",
|
|
13
|
+
name_expr.span,
|
|
14
|
+
parser.source,
|
|
15
|
+
parser.template_name
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
name = Name.new(name_expr.token, name_value)
|
|
20
|
+
|
|
21
|
+
# Leading commas are OK
|
|
22
|
+
parser.next if parser.kind == :token_comma
|
|
23
|
+
|
|
24
|
+
args = parser.parse_keyword_arguments(require_commas: true)
|
|
25
|
+
parser.carry_whitespace_control
|
|
26
|
+
parser.eat(:token_tag_end)
|
|
27
|
+
new(token, tag_name, name, args)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#: (t_token, String, Name, Array[KeywordArgument]) -> void
|
|
31
|
+
def initialize(token, tag_name, template_name, args)
|
|
32
|
+
super(token)
|
|
33
|
+
@tag_name = tag_name
|
|
34
|
+
@template_name = template_name
|
|
35
|
+
@args = args
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#: (RenderContext, String) -> void
|
|
39
|
+
def render(context, buffer)
|
|
40
|
+
name = @template_name.value
|
|
41
|
+
|
|
42
|
+
begin
|
|
43
|
+
template = context.env.get_template(
|
|
44
|
+
name,
|
|
45
|
+
context: context,
|
|
46
|
+
tag: "render"
|
|
47
|
+
)
|
|
48
|
+
rescue TemplateNotFoundError => e
|
|
49
|
+
raise NoSuchTemplateError.new(
|
|
50
|
+
e.message,
|
|
51
|
+
@template_name.span,
|
|
52
|
+
context.template.source,
|
|
53
|
+
context.template.name
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
ctx = context.copy(
|
|
58
|
+
@args.to_h { |arg| [arg.name.value, arg.expression.evaluate(context)] },
|
|
59
|
+
block_scope: false,
|
|
60
|
+
template: template
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
template.render_with_context(ctx, buffer)
|
|
64
|
+
context.render_score_cumulative += ctx.render_score
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#: () -> Array[Expression]
|
|
68
|
+
def expressions
|
|
69
|
+
@args.map(&:expression)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
#: (RenderContext) -> Partial?
|
|
73
|
+
def partial(static_context)
|
|
74
|
+
name = @template_name.value
|
|
75
|
+
|
|
76
|
+
template = static_context.env.get_template(
|
|
77
|
+
name,
|
|
78
|
+
context: static_context,
|
|
79
|
+
tag: "render"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
scope = @args.map(&:name)
|
|
83
|
+
|
|
84
|
+
Partial.new(
|
|
85
|
+
template,
|
|
86
|
+
:isolated,
|
|
87
|
+
scope,
|
|
88
|
+
Luoma.fnv1a32("#{name}-#{scope.map(&:value).join(":")}")
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class WithTag < Markup
|
|
5
|
+
END_WITH_BLOCK = Set["endwith"]
|
|
6
|
+
|
|
7
|
+
#: (t_token, String, Parser) -> Markup
|
|
8
|
+
def self.parse(token, tag_name, parser)
|
|
9
|
+
args = parser.parse_keyword_arguments(require_commas: true)
|
|
10
|
+
parser.carry_whitespace_control
|
|
11
|
+
parser.eat(:token_tag_end)
|
|
12
|
+
block = parser.parse_block(stop: END_WITH_BLOCK)
|
|
13
|
+
parser.eat_empty_tag("endwith")
|
|
14
|
+
new(token, tag_name, args, block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#: (t_token, String, Array[KeywordArgument], t_block) -> void
|
|
18
|
+
def initialize(token, tag_name, args, block)
|
|
19
|
+
super(token)
|
|
20
|
+
@tag_name = tag_name
|
|
21
|
+
@args = args
|
|
22
|
+
@block = block
|
|
23
|
+
@blank = Luoma.blank_block?(block)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#: (RenderContext, String) -> void
|
|
27
|
+
def render(context, buffer)
|
|
28
|
+
scope = {} #: t_namespace
|
|
29
|
+
|
|
30
|
+
context.extends(scope) do
|
|
31
|
+
# NOTE: Later arguments can use earlier arguments.
|
|
32
|
+
@args.each do |arg|
|
|
33
|
+
scope[arg.name.value] = arg.expression.evaluate(context)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Luoma.render_block(@block, context, buffer)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#: (RenderContext) -> Array[Markup]
|
|
41
|
+
def children(static_context)
|
|
42
|
+
@block.grep_v(String) #: Array[Markup]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#: () -> Array[Expression]
|
|
46
|
+
def expressions
|
|
47
|
+
@args.map(&:expression)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#: () -> Array[Name]
|
|
51
|
+
def block_scope
|
|
52
|
+
@args.map(&:name)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
class Template
|
|
5
|
+
attr_reader :env, :source, :nodes, :globals, :name, :overlay, :up_to_date
|
|
6
|
+
|
|
7
|
+
#: (Environment,
|
|
8
|
+
# String,
|
|
9
|
+
# t_block,
|
|
10
|
+
# ?globals: t_namespace?,
|
|
11
|
+
# ?name: String?,
|
|
12
|
+
# ?overlay: t_namespace?,
|
|
13
|
+
# ?up_to_date: Proc::_Callable?) -> void
|
|
14
|
+
def initialize(env, source, nodes, globals: nil, name: nil, overlay: nil, up_to_date: nil)
|
|
15
|
+
@env = env
|
|
16
|
+
@source = source
|
|
17
|
+
@nodes = nodes
|
|
18
|
+
@globals = globals
|
|
19
|
+
@name = name || ""
|
|
20
|
+
@overlay = overlay
|
|
21
|
+
@up_to_date = up_to_date
|
|
22
|
+
@lines = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Render this template with template variables from `data`.
|
|
26
|
+
#: (t_namespace?) -> String
|
|
27
|
+
def render(data = nil)
|
|
28
|
+
buffer = +""
|
|
29
|
+
context = RenderContext.new(self, globals: make_globals(data))
|
|
30
|
+
render_with_context(context, buffer)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Render this template to `buffer` with data from `context`.
|
|
34
|
+
# Returns buffer.
|
|
35
|
+
#
|
|
36
|
+
# _isolated_ templates raise errors for orphaned `{% break %}` and
|
|
37
|
+
# `{% continue %}` tags.
|
|
38
|
+
#
|
|
39
|
+
#: (RenderContext, String, ?isolated: bool) -> String
|
|
40
|
+
def render_with_context(context, buffer, isolated: true)
|
|
41
|
+
Luoma.render_block(@nodes, context, buffer, root: isolated)
|
|
42
|
+
buffer
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Return a copy of this template with different globals.
|
|
46
|
+
#: (t_namespace) -> Template
|
|
47
|
+
def with_globals(globals)
|
|
48
|
+
Template.new(
|
|
49
|
+
@env, @source, @nodes,
|
|
50
|
+
globals: globals,
|
|
51
|
+
name: @name,
|
|
52
|
+
overlay: @overlay,
|
|
53
|
+
up_to_date: @up_to_date
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Return this template's source code split into lines.
|
|
58
|
+
#
|
|
59
|
+
#: () -> Array[String]
|
|
60
|
+
def lines
|
|
61
|
+
@lines ||= @source.lines(chomp: false)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Return `false` if this template is stale and needs to be loaded again.
|
|
65
|
+
# `nil` is returned if an `up_to_date` proc is not available.
|
|
66
|
+
def up_to_date?
|
|
67
|
+
@up_to_date&.call
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# TODO: static analysis methods
|
|
71
|
+
|
|
72
|
+
# Statically analyze this template and report variable, tag and filter usage.
|
|
73
|
+
#
|
|
74
|
+
#: (?include_partials: bool) -> Luoma::StaticAnalysis::Result
|
|
75
|
+
def analyze(include_partials: false)
|
|
76
|
+
Luoma::StaticAnalysis.analyze(self, include_partials: include_partials)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Return an array of variable names used in this template, without path segments.
|
|
80
|
+
#
|
|
81
|
+
#: (?include_partials: bool) -> Array[String]
|
|
82
|
+
def variables(include_partials: false)
|
|
83
|
+
analyze(include_partials: include_partials).variables.keys
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Return an array of variables used in this template, including path segments.
|
|
87
|
+
#
|
|
88
|
+
#: (?include_partials: bool) -> Array[String]
|
|
89
|
+
def variable_paths(include_partials: false)
|
|
90
|
+
analyze(include_partials: include_partials).variables.values.flatten.map { |v| v[:path] }.uniq
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Return an array of variables used in this template, each as an array of segments.
|
|
94
|
+
#
|
|
95
|
+
#: (?include_partials: bool) -> Array[Segments]
|
|
96
|
+
def variable_segments(include_partials: false)
|
|
97
|
+
analyze(include_partials: include_partials).variables.values.flatten.map { |v| v[:segments] }.uniq
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Return an array of global variables used in this template, without path segments.
|
|
101
|
+
#
|
|
102
|
+
#: (?include_partials: bool) -> Array[String]
|
|
103
|
+
def global_variables(include_partials: false)
|
|
104
|
+
analyze(include_partials: include_partials).globals.keys
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Return an array of global variables used in this template, including path segments.
|
|
108
|
+
#
|
|
109
|
+
#: (?include_partials: bool) -> Array[String]
|
|
110
|
+
def global_variable_paths(include_partials: false)
|
|
111
|
+
analyze(include_partials: include_partials).globals.values.flatten.map { |v| v[:path] }.uniq
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Return an array of global variables used in this template, each as an array of segments.
|
|
115
|
+
#
|
|
116
|
+
#: (?include_partials: bool) -> Array[Segments]
|
|
117
|
+
def global_variable_segments(include_partials: false)
|
|
118
|
+
analyze(include_partials: include_partials).globals.values.flatten.map { |v| v[:segments] }.uniq
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Return the names of all filters used in this template.
|
|
122
|
+
#
|
|
123
|
+
#: (?include_partials: bool) -> Array[String]
|
|
124
|
+
def filter_names(include_partials: false)
|
|
125
|
+
analyze(include_partials: include_partials).filters.keys
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Return the names of all tags used in this template.
|
|
129
|
+
#
|
|
130
|
+
#: (?include_partials: bool) -> Array[String]
|
|
131
|
+
def tag_names(include_partials: false)
|
|
132
|
+
analyze(include_partials: include_partials).tags.keys
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Return an array of comment nodes found in this template.
|
|
136
|
+
#
|
|
137
|
+
# Comment nodes have `token` and `text` attributes. Use `template.comments.map(&:text)`
|
|
138
|
+
# to get an array of comment strings. Each comment string includes leading and trailing
|
|
139
|
+
# whitespace.
|
|
140
|
+
#
|
|
141
|
+
# Note that this method does not try to load included or render templates when looking.
|
|
142
|
+
# for comment nodes.
|
|
143
|
+
#
|
|
144
|
+
#: () -> Array[Comment]
|
|
145
|
+
def comments
|
|
146
|
+
context = RenderContext.new(self)
|
|
147
|
+
nodes = [] # : Array[Comment]
|
|
148
|
+
|
|
149
|
+
# @type var visit: ^(Markup) -> void
|
|
150
|
+
visit = lambda do |node|
|
|
151
|
+
nodes << node if node.is_a?(Comment)
|
|
152
|
+
|
|
153
|
+
node.children(context).each do |child|
|
|
154
|
+
visit.call(child)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
@nodes.each { |node| visit.call(node) unless node.is_a?(String) }
|
|
159
|
+
|
|
160
|
+
nodes
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
protected
|
|
164
|
+
|
|
165
|
+
# Return a new namespace including data from `namespace` and other
|
|
166
|
+
# namespaces pinned to this template.
|
|
167
|
+
#: (t_namespace?) -> t_namespace?
|
|
168
|
+
def make_globals(namespace)
|
|
169
|
+
return @globals if namespace.nil? && @overlay.nil?
|
|
170
|
+
|
|
171
|
+
namespace_ = {} #: t_namespace
|
|
172
|
+
namespace_.merge!(@globals) if @globals # steep:ignore
|
|
173
|
+
namespace_.merge!(@overlay) if @overlay # steep:ignore
|
|
174
|
+
namespace_.merge!(namespace) if namespace
|
|
175
|
+
namespace_
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
data/lib/luoma/token.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Luoma
|
|
4
|
+
#: (t_token, String) -> String
|
|
5
|
+
def self.get_token_value(token, source)
|
|
6
|
+
source.byteslice(token[1]..token[2]) || raise
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Return a new token spanning `start` and `stop`.
|
|
10
|
+
#: (t_token, t_token) -> t_token
|
|
11
|
+
def self.span(start, stop)
|
|
12
|
+
[:token_span, start[1], stop[2]]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
TOKEN_KIND_MAP = {
|
|
16
|
+
token_add: "ADD",
|
|
17
|
+
token_and: "AND",
|
|
18
|
+
token_arrow: "ARROW",
|
|
19
|
+
token_assign: "ASSIGN",
|
|
20
|
+
token_colon: "COLON",
|
|
21
|
+
token_comma: "COMMA",
|
|
22
|
+
token_comment: "COMMENT",
|
|
23
|
+
token_comment_end: "COMMENT_END",
|
|
24
|
+
token_comment_start: "COMMENT_START",
|
|
25
|
+
token_contains: "CONTAINS",
|
|
26
|
+
token_div: "DIV",
|
|
27
|
+
token_dot: "DOT",
|
|
28
|
+
token_double_dot: "DOUBLE_DOT",
|
|
29
|
+
token_double_escaped: "DOUBLE_ESCAPED",
|
|
30
|
+
token_double_quote: "DOUBLE_QUOTE",
|
|
31
|
+
token_double_quoted: "DOUBLE_QUOTED",
|
|
32
|
+
token_else: "ELSE",
|
|
33
|
+
token_eq: "EQ",
|
|
34
|
+
token_false: "FALSE",
|
|
35
|
+
token_float: "FLOAT",
|
|
36
|
+
token_ge: "GE",
|
|
37
|
+
token_gt: "GT",
|
|
38
|
+
token_ident: "IDENT",
|
|
39
|
+
token_if: "IF",
|
|
40
|
+
token_in: "IN",
|
|
41
|
+
token_int: "INT",
|
|
42
|
+
token_interpolation_end: "INTERPOLATION_END",
|
|
43
|
+
token_interpolation_start: "INTERPOLATION_START",
|
|
44
|
+
token_lbrace: "LBRACE",
|
|
45
|
+
token_lbracket: "LBRACKET",
|
|
46
|
+
token_le: "LE",
|
|
47
|
+
token_lparen: "LPAREN",
|
|
48
|
+
token_lt: "LT",
|
|
49
|
+
token_mod: "MOD",
|
|
50
|
+
token_mul: "MUL",
|
|
51
|
+
token_ne: "NE",
|
|
52
|
+
token_nil: "NIL",
|
|
53
|
+
token_not: "NOT",
|
|
54
|
+
token_null: "NULL",
|
|
55
|
+
token_or_else: "OR_ELSE",
|
|
56
|
+
token_or: "OR",
|
|
57
|
+
token_out_end: "OUT_END",
|
|
58
|
+
token_out_start: "OUT_START",
|
|
59
|
+
token_pipe: "PIPE",
|
|
60
|
+
token_question: "QUESTION",
|
|
61
|
+
token_rbrace: "RBRACE",
|
|
62
|
+
token_rbracket: "RBRACKET",
|
|
63
|
+
token_rparen: "RPAREN",
|
|
64
|
+
token_single_escaped: "SINGLE_ESCAPED",
|
|
65
|
+
token_single_quote: "SINGLE_QUOTE",
|
|
66
|
+
token_single_quoted: "SINGLE_QUOTED",
|
|
67
|
+
token_sub: "SUB",
|
|
68
|
+
token_tag_end: "TAG_END",
|
|
69
|
+
token_tag_name: "TAG_NAME",
|
|
70
|
+
token_tag_start: "TAG_START",
|
|
71
|
+
token_text: "TEXT",
|
|
72
|
+
token_triple_dot: "TRIPLE_DOT",
|
|
73
|
+
token_true: "TRUE",
|
|
74
|
+
token_wc: "WC",
|
|
75
|
+
token_eoi: "EOI",
|
|
76
|
+
token_unknown: "UNKNOWN",
|
|
77
|
+
token_hash: "HASH",
|
|
78
|
+
token_span: "SPAN",
|
|
79
|
+
token_blank: "BLANK",
|
|
80
|
+
token_empty: "EMPTY",
|
|
81
|
+
token_for: "FOR",
|
|
82
|
+
token_with: "WITH"
|
|
83
|
+
}.freeze #: Hash[t_token_kind, String]
|
|
84
|
+
end
|
data/lib/luoma.rb
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal/util"
|
|
4
|
+
require "time"
|
|
5
|
+
require_relative "luoma/chain_hash"
|
|
6
|
+
require_relative "luoma/context"
|
|
7
|
+
require_relative "luoma/drop"
|
|
8
|
+
require_relative "luoma/environment"
|
|
9
|
+
require_relative "luoma/errors"
|
|
10
|
+
require_relative "luoma/expression"
|
|
11
|
+
require_relative "luoma/filter"
|
|
12
|
+
require_relative "luoma/fnv"
|
|
13
|
+
require_relative "luoma/lexer"
|
|
14
|
+
require_relative "luoma/lexer_unified"
|
|
15
|
+
require_relative "luoma/loader"
|
|
16
|
+
require_relative "luoma/loaders/choice_loader"
|
|
17
|
+
require_relative "luoma/loaders/mixins"
|
|
18
|
+
require_relative "luoma/loaders/file_system_loader"
|
|
19
|
+
require_relative "luoma/markup"
|
|
20
|
+
require_relative "luoma/parser"
|
|
21
|
+
require_relative "luoma/parser_unified"
|
|
22
|
+
require_relative "luoma/template"
|
|
23
|
+
require_relative "luoma/token"
|
|
24
|
+
require_relative "luoma/version"
|
|
25
|
+
require_relative "luoma/drops/block"
|
|
26
|
+
require_relative "luoma/drops/expression"
|
|
27
|
+
require_relative "luoma/drops/range"
|
|
28
|
+
require_relative "luoma/drops/undefined"
|
|
29
|
+
require_relative "luoma/filters/array"
|
|
30
|
+
require_relative "luoma/filters/date"
|
|
31
|
+
require_relative "luoma/filters/default"
|
|
32
|
+
require_relative "luoma/filters/json"
|
|
33
|
+
require_relative "luoma/filters/math"
|
|
34
|
+
require_relative "luoma/filters/size"
|
|
35
|
+
require_relative "luoma/filters/slice"
|
|
36
|
+
require_relative "luoma/filters/sort"
|
|
37
|
+
require_relative "luoma/filters/string"
|
|
38
|
+
require_relative "luoma/predicates/type"
|
|
39
|
+
require_relative "luoma/predicates/blank"
|
|
40
|
+
require_relative "luoma/predicates/empty"
|
|
41
|
+
require_relative "luoma/predicates/defined"
|
|
42
|
+
require_relative "luoma/static_analysis"
|
|
43
|
+
require_relative "luoma/tags/assign"
|
|
44
|
+
require_relative "luoma/tags/break"
|
|
45
|
+
require_relative "luoma/tags/capture"
|
|
46
|
+
require_relative "luoma/tags/case"
|
|
47
|
+
require_relative "luoma/tags/comment"
|
|
48
|
+
require_relative "luoma/tags/continue"
|
|
49
|
+
require_relative "luoma/tags/define"
|
|
50
|
+
require_relative "luoma/tags/else"
|
|
51
|
+
require_relative "luoma/tags/for"
|
|
52
|
+
require_relative "luoma/tags/if"
|
|
53
|
+
require_relative "luoma/tags/import"
|
|
54
|
+
require_relative "luoma/tags/include"
|
|
55
|
+
require_relative "luoma/tags/output"
|
|
56
|
+
require_relative "luoma/tags/raw"
|
|
57
|
+
require_relative "luoma/tags/render"
|
|
58
|
+
require_relative "luoma/tags/with"
|
|
59
|
+
|
|
60
|
+
module Luoma
|
|
61
|
+
DEFAULT_ENVIRONMENT = Environment.new
|
|
62
|
+
|
|
63
|
+
# Parse _source_ text as a template using the default template environment.
|
|
64
|
+
#: (String, ?globals: Hash[String, untyped]?) -> Template
|
|
65
|
+
def self.parse(source, globals: nil)
|
|
66
|
+
DEFAULT_ENVIRONMENT.parse(source, globals: globals)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Parse and render template _source_ with _data_ as template variables and
|
|
70
|
+
# the default template environment.
|
|
71
|
+
#: (String, ?Hash[String, untyped]?) -> String
|
|
72
|
+
def self.render(source, data = nil)
|
|
73
|
+
DEFAULT_ENVIRONMENT.render(source, data)
|
|
74
|
+
end
|
|
75
|
+
end
|
data/sig/luoma/cache.rbs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Luoma
|
|
2
|
+
# A least recently used cache relying on Ruby hash insertion order.
|
|
3
|
+
class LRUCache
|
|
4
|
+
@data: Hash[untyped, untyped]
|
|
5
|
+
|
|
6
|
+
@max_size: Integer
|
|
7
|
+
|
|
8
|
+
attr_reader max_size: Integer
|
|
9
|
+
|
|
10
|
+
def initialize: (?::Integer max_size) -> void
|
|
11
|
+
|
|
12
|
+
# Return the cached value or nil if _key_ does not exist.
|
|
13
|
+
def []: (untyped key) -> (nil | untyped)
|
|
14
|
+
|
|
15
|
+
def []=: (untyped key, untyped value) -> untyped
|
|
16
|
+
|
|
17
|
+
def length: () -> Integer
|
|
18
|
+
|
|
19
|
+
def keys: () -> Array[untyped]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# A thread safe least recently used cache.
|
|
23
|
+
class ThreadSafeLRUCache < LRUCache
|
|
24
|
+
include MonitorMixin
|
|
25
|
+
|
|
26
|
+
alias unsafe_get []
|
|
27
|
+
|
|
28
|
+
alias unsafe_set []=
|
|
29
|
+
|
|
30
|
+
alias unsafe_length length
|
|
31
|
+
|
|
32
|
+
alias unsafe_keys keys
|
|
33
|
+
|
|
34
|
+
def initialize: (?::Integer max_size) -> void
|
|
35
|
+
|
|
36
|
+
def []: (untyped key) -> untyped
|
|
37
|
+
|
|
38
|
+
def []=: (untyped key, untyped value) -> untyped
|
|
39
|
+
|
|
40
|
+
def length: () -> Integer
|
|
41
|
+
|
|
42
|
+
def keys: () -> Array[Integer]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Luoma
|
|
2
|
+
# Combine multiple hashes for sequential lookup.
|
|
3
|
+
class ChainHash
|
|
4
|
+
@hashes: Array[Luoma::_Namespace]
|
|
5
|
+
|
|
6
|
+
# (*_Namespace) -> void
|
|
7
|
+
def initialize: (*_Namespace hashes) -> void
|
|
8
|
+
|
|
9
|
+
# (String) -> untyped
|
|
10
|
+
def []: (String key) -> untyped
|
|
11
|
+
|
|
12
|
+
# (String) -> bool
|
|
13
|
+
def key?: (String key) -> untyped
|
|
14
|
+
|
|
15
|
+
# (String, untyped) -> untyped
|
|
16
|
+
def fetch: (String key, ?untyped default) -> untyped
|
|
17
|
+
|
|
18
|
+
def size: () -> Integer
|
|
19
|
+
|
|
20
|
+
def push: (_Namespace hash) -> void
|
|
21
|
+
|
|
22
|
+
alias << push
|
|
23
|
+
|
|
24
|
+
def pop: () -> _Namespace?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module Luoma
|
|
2
|
+
class RenderContext
|
|
3
|
+
# The template being rendered.
|
|
4
|
+
@template: Template
|
|
5
|
+
|
|
6
|
+
# The Luoma environment this render context and associated template is
|
|
7
|
+
# bound to.
|
|
8
|
+
@env: Environment
|
|
9
|
+
|
|
10
|
+
# Developer-defined template variables passed down from the environment
|
|
11
|
+
# and template.
|
|
12
|
+
@globals: _Namespace
|
|
13
|
+
|
|
14
|
+
# The namespace for variables defined with `{% assign %}` and
|
|
15
|
+
# `{% capture %}`.
|
|
16
|
+
@locals: Hash[String, untyped]
|
|
17
|
+
|
|
18
|
+
# A namespace for `{% increment %}` and `{% decrement %}`.
|
|
19
|
+
@counters: Hash[String, untyped]
|
|
20
|
+
|
|
21
|
+
# The current template scope including `locals`, `globals` and `counters`.
|
|
22
|
+
# New block-scoped namespaces get pushed onto and popped off this chain
|
|
23
|
+
# map.
|
|
24
|
+
#
|
|
25
|
+
# Scopes are searched from right to left. New scopes are push on the
|
|
26
|
+
# right.
|
|
27
|
+
@scopes: ChainHash
|
|
28
|
+
|
|
29
|
+
# Names of tags that are disallowed in this context.
|
|
30
|
+
@disabled_tags: Set[String]?
|
|
31
|
+
|
|
32
|
+
# The number of times this render context has been extended or copied.
|
|
33
|
+
@context_depth: Integer
|
|
34
|
+
|
|
35
|
+
# A non-specific indicator of template local scope usage.
|
|
36
|
+
@assign_score: Integer
|
|
37
|
+
|
|
38
|
+
# A non-specific indicator of template local scope usage for the current
|
|
39
|
+
# template and all partial templates combined.
|
|
40
|
+
@assign_score_cumulative: Integer
|
|
41
|
+
|
|
42
|
+
# The number of nodes rendered for the current template.
|
|
43
|
+
@render_score: Integer
|
|
44
|
+
|
|
45
|
+
# The number of nodes rendered for the current template and all partial
|
|
46
|
+
# templates.
|
|
47
|
+
@render_score_cumulative: Integer
|
|
48
|
+
|
|
49
|
+
# A stack of interrupt signals used by `{% break %}` and
|
|
50
|
+
# `{% continue %}`, for example.
|
|
51
|
+
@interrupts: Array[Symbol]
|
|
52
|
+
|
|
53
|
+
# Registers supporting stateful tags. It's OK to use this map for storing
|
|
54
|
+
# custom tag state.
|
|
55
|
+
@registers: Hash[Symbol, untyped]
|
|
56
|
+
|
|
57
|
+
attr_reader env: Environment
|
|
58
|
+
|
|
59
|
+
attr_reader template: Template
|
|
60
|
+
|
|
61
|
+
attr_reader scopes: ChainHash
|
|
62
|
+
|
|
63
|
+
attr_reader disabled_tags: Set[String]?
|
|
64
|
+
|
|
65
|
+
attr_reader context_depth: Integer
|
|
66
|
+
|
|
67
|
+
attr_reader assign_score: Integer
|
|
68
|
+
|
|
69
|
+
attr_reader assign_score_cumulative: Integer
|
|
70
|
+
|
|
71
|
+
attr_accessor render_score: Integer
|
|
72
|
+
|
|
73
|
+
attr_accessor render_score_cumulative: Integer
|
|
74
|
+
|
|
75
|
+
attr_accessor interrupts: Array[Symbol]
|
|
76
|
+
|
|
77
|
+
attr_reader registers: Hash[Symbol, untyped]
|
|
78
|
+
|
|
79
|
+
attr_reader globals: _Namespace
|
|
80
|
+
|
|
81
|
+
attr_reader locals: Hash[String, untyped]
|
|
82
|
+
|
|
83
|
+
# (Template, ?globals: _Namespace?) -> void
|
|
84
|
+
def initialize: (Template template, ?globals: _Namespace?, ?disabled_tags: Set[String]?, ?context_depth: Integer?, ?assign_score_carry: Integer?, ?render_score_carry: Integer?) -> void
|
|
85
|
+
|
|
86
|
+
# (String) -> untyped
|
|
87
|
+
def resolve: (String name) -> untyped
|
|
88
|
+
|
|
89
|
+
# (untyped, Array[untyped]) -> untyped
|
|
90
|
+
def resolve_path: (untyped obj, untyped segments) -> untyped
|
|
91
|
+
|
|
92
|
+
# (String, untyped) -> void
|
|
93
|
+
def assign: (String name, untyped value) -> void
|
|
94
|
+
|
|
95
|
+
# (t_namespace, bool?, Set[String]?, Template?) -> RenderContext
|
|
96
|
+
def copy: (t_namespace namespace, ?block_scope: bool?, ?disabled_tags: Set[String]?, ?template: Template?) -> RenderContext
|
|
97
|
+
|
|
98
|
+
# (t_namespace, ?template: Template?) { () -> untyped } -> void
|
|
99
|
+
def extends: (t_namespace namespace, ?template: Template?) { () -> untyped } -> void
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# (untyped) -> Integer
|
|
104
|
+
def assign_score_of: (untyped value) -> Integer
|
|
105
|
+
|
|
106
|
+
#: () -> void
|
|
107
|
+
def raise_for_context_depth: () -> void
|
|
108
|
+
|
|
109
|
+
# (Array[untyped], untyped) -> untyped
|
|
110
|
+
def resolve_array_segment: (Array[untyped] obj, untyped segment) -> untyped
|
|
111
|
+
|
|
112
|
+
# (Hash[untyped, untyped], untyped) -> untyped
|
|
113
|
+
def resolve_hash_segment: (Hash[untyped, untyped] obj, untyped segment) -> untyped
|
|
114
|
+
end
|
|
115
|
+
end
|