orb_template 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
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/Makefile +45 -0
- data/README.md +429 -0
- data/Rakefile +15 -0
- data/lib/orb/ast/abstract_node.rb +27 -0
- data/lib/orb/ast/attribute.rb +51 -0
- data/lib/orb/ast/block_node.rb +26 -0
- data/lib/orb/ast/control_expression_node.rb +27 -0
- data/lib/orb/ast/newline_node.rb +22 -0
- data/lib/orb/ast/printing_expression_node.rb +29 -0
- data/lib/orb/ast/private_comment_node.rb +22 -0
- data/lib/orb/ast/public_comment_node.rb +22 -0
- data/lib/orb/ast/root_node.rb +11 -0
- data/lib/orb/ast/tag_node.rb +208 -0
- data/lib/orb/ast/text_node.rb +22 -0
- data/lib/orb/ast.rb +19 -0
- data/lib/orb/document.rb +19 -0
- data/lib/orb/errors.rb +40 -0
- data/lib/orb/parser.rb +182 -0
- data/lib/orb/patterns.rb +40 -0
- data/lib/orb/rails_derp.rb +138 -0
- data/lib/orb/rails_template.rb +101 -0
- data/lib/orb/railtie.rb +9 -0
- data/lib/orb/render_context.rb +36 -0
- data/lib/orb/template.rb +72 -0
- data/lib/orb/temple/attributes_compiler.rb +114 -0
- data/lib/orb/temple/compiler.rb +204 -0
- data/lib/orb/temple/engine.rb +40 -0
- data/lib/orb/temple/filters.rb +132 -0
- data/lib/orb/temple/generators.rb +108 -0
- data/lib/orb/temple/identity.rb +16 -0
- data/lib/orb/temple/parser.rb +46 -0
- data/lib/orb/temple.rb +16 -0
- data/lib/orb/token.rb +47 -0
- data/lib/orb/tokenizer.rb +757 -0
- data/lib/orb/tokenizer2.rb +591 -0
- data/lib/orb/utils/erb.rb +40 -0
- data/lib/orb/utils/orb.rb +12 -0
- data/lib/orb/version.rb +5 -0
- data/lib/orb.rb +50 -0
- metadata +89 -0
data/lib/orb/patterns.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ORB
|
|
4
|
+
module Patterns
|
|
5
|
+
SPACE_CHARS = /\s/
|
|
6
|
+
TAG_NAME = %r{[^\s>/=$]+}
|
|
7
|
+
ATTRIBUTE_NAME = %r{[^\s>/=]+}
|
|
8
|
+
UNQUOTED_VALUE_INVALID_CHARS = /["'=<`]/
|
|
9
|
+
UNQUOTED_VALUE = %r{[^\s/>]+}
|
|
10
|
+
BLOCK_NAME_CHARS = /[^\s}]+/
|
|
11
|
+
START_TAG_START = /</
|
|
12
|
+
START_TAG_END = />/
|
|
13
|
+
START_TAG_END_SELF_CLOSING = %r{/>}
|
|
14
|
+
START_TAG_END_VERBATIM = /\$>/
|
|
15
|
+
END_TAG_START = %r{</}
|
|
16
|
+
END_TAG_END = />/
|
|
17
|
+
END_TAG_END_VERBATIM = /\$>/
|
|
18
|
+
PUBLIC_COMMENT_START = /<!--/
|
|
19
|
+
PUBLIC_COMMENT_END = /-->/
|
|
20
|
+
PRIVATE_COMMENT_START = /{!--/
|
|
21
|
+
PRIVATE_COMMENT_END = /--}/
|
|
22
|
+
PRINTING_EXPRESSION_START = /{{ */
|
|
23
|
+
PRINTING_EXPRESSION_END = / *}}/
|
|
24
|
+
CONTROL_EXPRESSION_START = /{% */
|
|
25
|
+
CONTROL_EXPRESSION_END = / *%}/
|
|
26
|
+
BLOCK_OPEN = /{#/
|
|
27
|
+
BLOCK_CLOSE = %r[{/]
|
|
28
|
+
ATTRIBUTE_ASSIGN = /=/
|
|
29
|
+
SINGLE_QUOTE = /'/
|
|
30
|
+
DOUBLE_QUOTE = /"/
|
|
31
|
+
SPLAT_START = /\*/
|
|
32
|
+
BRACE_OPEN = /\{/
|
|
33
|
+
BRACE_CLOSE = /\}/
|
|
34
|
+
CR = /\r/
|
|
35
|
+
NEWLINE = /\n/
|
|
36
|
+
CRLF = /\r\n/
|
|
37
|
+
BLANK = /[[:blank:]]/
|
|
38
|
+
OTHER = /./
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# HACK DO NOT USE - THIS IF FOR DEBUGGING ERB ERROR SPOT HIGHLIGHTING ONLY
|
|
4
|
+
module ORB
|
|
5
|
+
class RailsDerp
|
|
6
|
+
class << self
|
|
7
|
+
def call(template, source = nil)
|
|
8
|
+
new.call(template, source)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Specify trim mode for the ERB compiler. Defaults to '-'.
|
|
13
|
+
# See ERB documentation for suitable values.
|
|
14
|
+
class_attribute :erb_trim_mode, default: "-"
|
|
15
|
+
|
|
16
|
+
# Default implementation used.
|
|
17
|
+
# class_attribute :erb_implementation, default: Erubi
|
|
18
|
+
|
|
19
|
+
# Do not escape templates of these mime types.
|
|
20
|
+
class_attribute :escape_ignore_list, default: ["text/plain"]
|
|
21
|
+
|
|
22
|
+
# Strip trailing newlines from rendered output
|
|
23
|
+
class_attribute :strip_trailing_newlines, default: false
|
|
24
|
+
|
|
25
|
+
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
|
|
26
|
+
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
|
|
27
|
+
|
|
28
|
+
# Translate an error location returned by ErrorHighlight to the correct
|
|
29
|
+
# source location inside the template.
|
|
30
|
+
def translate_location(spot, backtrace_location, source)
|
|
31
|
+
Rails.logger.debug "in translate_location"
|
|
32
|
+
Rails.logger.debug ""
|
|
33
|
+
Rails.logger.debug spot
|
|
34
|
+
Rails.logger.debug ""
|
|
35
|
+
Rails.logger.debug backtrace_location
|
|
36
|
+
Rails.logger.debug ""
|
|
37
|
+
Rails.logger.debug source
|
|
38
|
+
|
|
39
|
+
# Tokenize the source line
|
|
40
|
+
tokens = ORB::Utils::ERB.tokenize(source.lines[backtrace_location.lineno - 1])
|
|
41
|
+
new_first_column = find_offset(spot[:snippet], tokens, spot[:first_column])
|
|
42
|
+
lineno_delta = spot[:first_lineno] - backtrace_location.lineno
|
|
43
|
+
spot[:first_lineno] -= lineno_delta
|
|
44
|
+
spot[:last_lineno] -= lineno_delta
|
|
45
|
+
|
|
46
|
+
column_delta = spot[:first_column] - new_first_column
|
|
47
|
+
spot[:first_column] -= column_delta
|
|
48
|
+
spot[:last_column] -= column_delta
|
|
49
|
+
spot[:script_lines] = source.lines
|
|
50
|
+
|
|
51
|
+
spot
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def call(template, source)
|
|
55
|
+
# First, convert to BINARY, so in case the encoding is
|
|
56
|
+
# wrong, we can still find an encoding tag
|
|
57
|
+
# (<%# encoding %>) inside the String using a regular
|
|
58
|
+
# expression
|
|
59
|
+
template_source = source.b
|
|
60
|
+
|
|
61
|
+
erb = template_source.gsub(ENCODING_TAG, "")
|
|
62
|
+
encoding = ::Regexp.last_match(2)
|
|
63
|
+
|
|
64
|
+
erb.force_encoding valid_encoding(source.dup, encoding)
|
|
65
|
+
|
|
66
|
+
# Always make sure we return a String in the default_internal
|
|
67
|
+
erb.encode!
|
|
68
|
+
|
|
69
|
+
# Strip trailing newlines from the template if enabled
|
|
70
|
+
erb.chomp! if strip_trailing_newlines
|
|
71
|
+
|
|
72
|
+
options = {
|
|
73
|
+
escape: (self.class.escape_ignore_list.include? template.type),
|
|
74
|
+
trim: (self.class.erb_trim_mode == "-")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html
|
|
78
|
+
options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->';"
|
|
79
|
+
options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
ActionView::Template::Handlers::ERB::Erubi.new(erb, options).src
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def valid_encoding(string, encoding)
|
|
88
|
+
# If a magic encoding comment was found, tag the
|
|
89
|
+
# String with this encoding. This is for a case
|
|
90
|
+
# where the original String was assumed to be,
|
|
91
|
+
# for instance, UTF-8, but a magic comment
|
|
92
|
+
# proved otherwise
|
|
93
|
+
string.force_encoding(encoding) if encoding
|
|
94
|
+
|
|
95
|
+
# If the String is valid, return the encoding we found
|
|
96
|
+
return string.encoding if string.valid_encoding?
|
|
97
|
+
|
|
98
|
+
# Otherwise, raise an exception
|
|
99
|
+
raise WrongEncodingError.new(string, string.encoding)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def find_offset(compiled, source_tokens, error_column)
|
|
103
|
+
compiled = StringScanner.new(compiled)
|
|
104
|
+
|
|
105
|
+
passed_tokens = []
|
|
106
|
+
|
|
107
|
+
while (tok = source_tokens.shift)
|
|
108
|
+
tok_name, str = *tok
|
|
109
|
+
case tok_name
|
|
110
|
+
when :TEXT
|
|
111
|
+
raise unless compiled.scan(str)
|
|
112
|
+
when :CODE
|
|
113
|
+
raise "We went too far" if compiled.pos > error_column
|
|
114
|
+
|
|
115
|
+
if compiled.pos + str.bytesize >= error_column
|
|
116
|
+
offset = error_column - compiled.pos
|
|
117
|
+
return passed_tokens.map(&:last).join.bytesize + offset
|
|
118
|
+
else
|
|
119
|
+
raise unless compiled.scan(str)
|
|
120
|
+
end
|
|
121
|
+
when :OPEN, :CLOSE
|
|
122
|
+
next_tok = source_tokens.first.last
|
|
123
|
+
loop do
|
|
124
|
+
break if compiled.match?(next_tok)
|
|
125
|
+
|
|
126
|
+
compiled.getch
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
raise NotImplemented, tok.first
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
passed_tokens << tok
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
ActionView::Template.register_template_handler(:derp, RailsDerp.new)
|
|
138
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ORB
|
|
4
|
+
class RailsTemplate
|
|
5
|
+
require 'orb/utils/orb'
|
|
6
|
+
# Compatible with: https://github.com/judofyr/temple/blob/v0.7.7/lib/temple/mixins/options.rb#L15-L24
|
|
7
|
+
class << self
|
|
8
|
+
def options
|
|
9
|
+
@options ||= {
|
|
10
|
+
generator: ::Temple::Generators::RailsOutputBuffer,
|
|
11
|
+
use_html_safe: true,
|
|
12
|
+
streaming: true,
|
|
13
|
+
buffer_class: 'ActionView::OutputBuffer',
|
|
14
|
+
disable_capture: true,
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def set_options(opts)
|
|
19
|
+
options.update(opts)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(template, source = nil)
|
|
24
|
+
source ||= template.source
|
|
25
|
+
options = RailsTemplate.options
|
|
26
|
+
|
|
27
|
+
# Make the filename available in parser etc.
|
|
28
|
+
options = options.merge(file: template.identifier) if template.respond_to?(:identifier)
|
|
29
|
+
|
|
30
|
+
# Set type
|
|
31
|
+
options = options.merge(format: :xhtml) if template.respond_to?(:type) && template.type == 'text/xml'
|
|
32
|
+
|
|
33
|
+
# Annotations
|
|
34
|
+
if ActionView::Base.try(:annotate_rendered_view_with_filenames) && template.format == :html
|
|
35
|
+
options = options.merge(
|
|
36
|
+
preamble: "<!-- BEGIN #{template.short_identifier} -->\n",
|
|
37
|
+
postamble: "<!-- END #{template.short_identifier} -->\n",
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Pipe through the ORB Temple engine
|
|
42
|
+
ORB::Temple::Engine.new(options).call(source)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# See https://github.com/rails/rails/pull/47005
|
|
46
|
+
def translate_location(spot, backtrace_location, source)
|
|
47
|
+
offending_line_source = source.lines[backtrace_location.lineno - 1]
|
|
48
|
+
tokens = ORB::Utils::ORB.tokenize(offending_line_source)
|
|
49
|
+
new_column = find_offset(spot[:snippet], tokens, spot[:first_column])
|
|
50
|
+
|
|
51
|
+
lineno_delta = spot[:first_lineno] - backtrace_location.lineno
|
|
52
|
+
spot[:first_lineno] -= lineno_delta
|
|
53
|
+
spot[:last_lineno] -= lineno_delta
|
|
54
|
+
|
|
55
|
+
column_delta = spot[:first_column] - new_column
|
|
56
|
+
spot[:first_column] -= column_delta
|
|
57
|
+
spot[:last_column] -= column_delta
|
|
58
|
+
spot[:script_lines] = source.lines
|
|
59
|
+
|
|
60
|
+
spot
|
|
61
|
+
rescue StandardError => _e
|
|
62
|
+
spot
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def find_offset(snippet, src_tokens, snippet_error_column)
|
|
66
|
+
offset = 0
|
|
67
|
+
passed_tokens = []
|
|
68
|
+
|
|
69
|
+
# Pass over tokens until we are just over the snippet error column
|
|
70
|
+
# then the column of the last token is the offset (without the token static offset for tags {% %})
|
|
71
|
+
while (tok = src_tokens.shift)
|
|
72
|
+
offset = snippet.index(tok.value, offset)
|
|
73
|
+
raise "text not found" unless offset
|
|
74
|
+
raise "we went too far" if offset > snippet_error_column
|
|
75
|
+
|
|
76
|
+
passed_tokens << tok
|
|
77
|
+
end
|
|
78
|
+
rescue StandardError
|
|
79
|
+
offset_token = passed_tokens.last
|
|
80
|
+
offset_from_token(offset_token)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def offset_from_token(token)
|
|
84
|
+
case token.type
|
|
85
|
+
when :tag_open, :tag_close
|
|
86
|
+
token.column + 1
|
|
87
|
+
when :public_comment, :private_comment
|
|
88
|
+
token.column + 4
|
|
89
|
+
when :block_open, :block_close
|
|
90
|
+
token.column + 2 + token.value.length
|
|
91
|
+
when :printing_expression, :control_expression
|
|
92
|
+
token.column + 2
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def supports_streaming?
|
|
97
|
+
RailsTemplate.options[:streaming]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
ActionView::Template.register_template_handler(:orb, RailsTemplate.new)
|
|
101
|
+
end
|
data/lib/orb/railtie.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ORB
|
|
4
|
+
class RenderContext
|
|
5
|
+
def initialize(assigns = {})
|
|
6
|
+
@assigns = assigns
|
|
7
|
+
@errors = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def []=(key, value)
|
|
11
|
+
@assigns[key] = value
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def [](key)
|
|
15
|
+
resolve(key)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def has_key?(key)
|
|
19
|
+
resolve(key) != nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_reader :errors
|
|
23
|
+
|
|
24
|
+
def binding
|
|
25
|
+
# rubocop:disable Style/OpenStructUse
|
|
26
|
+
OpenStruct.new(@assigns).instance_eval { binding }
|
|
27
|
+
# rubocop:enable Style/OpenStructUse
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def resolve(key)
|
|
33
|
+
@assigns[key]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/orb/template.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ORB
|
|
4
|
+
class Template
|
|
5
|
+
attr_reader :doc
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# create a new `Template` instance and parse the given source
|
|
9
|
+
def parse(source, opts = {})
|
|
10
|
+
template = Template.new(**opts)
|
|
11
|
+
template.parse(source)
|
|
12
|
+
template
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Create a new `Template` instance. Use `Template.parse` instead.
|
|
17
|
+
def initialize(**opts)
|
|
18
|
+
@options = opts
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Parses the given `source` and returns `self` for chaining.
|
|
22
|
+
def parse(source)
|
|
23
|
+
@doc = Document.new(tokenize(source))
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Local assigns
|
|
28
|
+
def assigns
|
|
29
|
+
@assigns ||= {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Parsing and rendering errors
|
|
33
|
+
def errors
|
|
34
|
+
@errors ||= []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Render the template with the given `assigns`, which is a hash of local variables.
|
|
38
|
+
def render(*args)
|
|
39
|
+
# if we don't have a Document node, render to an empty string
|
|
40
|
+
retun "" unless @doc
|
|
41
|
+
|
|
42
|
+
# Determine the rendering context, which can either be a hash of local variables
|
|
43
|
+
# or an instance of `ORB::RenderContext`.
|
|
44
|
+
render_context = case args.first
|
|
45
|
+
when ORB::RenderContext
|
|
46
|
+
args.shift
|
|
47
|
+
when Hash
|
|
48
|
+
assigns.merge!(args.shift)
|
|
49
|
+
ORB::RenderContext.new(assigns)
|
|
50
|
+
when nil
|
|
51
|
+
ORB::RenderContext.new(assigns)
|
|
52
|
+
else
|
|
53
|
+
raise ArgumentError, "Expected a hash of local assigns or a ORB::RenderContext."
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Render loop
|
|
57
|
+
begin
|
|
58
|
+
@doc.render(render_context)
|
|
59
|
+
ensure
|
|
60
|
+
@errors = render_context.errors
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def tokenize(source)
|
|
67
|
+
return [] if source.nil? || source.empty?
|
|
68
|
+
|
|
69
|
+
ORB::Tokenizer2.new(source, **@options).tokenize!
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ORB
|
|
4
|
+
module Temple
|
|
5
|
+
class AttributesCompiler
|
|
6
|
+
def initialize(options = {})
|
|
7
|
+
@options = options
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Compile the given array of AST::Attribute objects into Temple capture expressions
|
|
11
|
+
# using the given prefix in variable names
|
|
12
|
+
def compile_captures(attributes, prefix)
|
|
13
|
+
result = []
|
|
14
|
+
|
|
15
|
+
attributes.each do |attribute|
|
|
16
|
+
# TODO: handle splat attributes
|
|
17
|
+
next if attribute.splat?
|
|
18
|
+
|
|
19
|
+
# generate a unique variable name for the attribute
|
|
20
|
+
var_name = prefixed_variable_name(attribute.name, prefix)
|
|
21
|
+
|
|
22
|
+
# inject a code expression for the attribute value and assign to the variable
|
|
23
|
+
if attribute.string?
|
|
24
|
+
result << [:code, "#{var_name} = \"#{attribute.value}\""]
|
|
25
|
+
elsif attribute.bool?
|
|
26
|
+
result << [:code, "#{var_name} = true"]
|
|
27
|
+
elsif attribute.expression?
|
|
28
|
+
result << [:code, "#{var_name} = #{attribute.value}"]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Compile the given array of AST::Attribute objects into a string of arguments
|
|
36
|
+
# the can be used in a ViewComponent constructor call, as long as the
|
|
37
|
+
# compiled captures are available in the same scope.
|
|
38
|
+
def compile_komponent_args(attributes, prefix)
|
|
39
|
+
args = {}
|
|
40
|
+
attributes.each do |attribute|
|
|
41
|
+
# TODO: handle splat attributes
|
|
42
|
+
next if attribute.splat?
|
|
43
|
+
|
|
44
|
+
var_name = prefixed_variable_name(attribute.name, prefix)
|
|
45
|
+
args = args.deep_merge(dash_to_hash(attribute.name, var_name))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
hash_to_args_list(args)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Compile the attributes of a node into a Temple core abstraction
|
|
52
|
+
def compile_attributes(attributes)
|
|
53
|
+
temple = [:html, :attrs]
|
|
54
|
+
|
|
55
|
+
attributes.each do |attribute|
|
|
56
|
+
# Ignore splat attributes
|
|
57
|
+
next if attribute.splat?
|
|
58
|
+
|
|
59
|
+
temple << compile_attribute(attribute)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
temple
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Compile splat attributes to a code string
|
|
67
|
+
def compile_splat_attributes(attributes)
|
|
68
|
+
attributes.map(&:value).join(',')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Compile a single attribute into Temple core abstraction
|
|
72
|
+
# an attribute can be a static string, a dynamic expression,
|
|
73
|
+
# or a boolean attribute (an attribute without a value, e.g. disabled, checked, etc.)
|
|
74
|
+
#
|
|
75
|
+
# For boolean attributes, we return a [:dynamic, "nil"] expression, so that the
|
|
76
|
+
# final render for the attribute will be `attribute` instead of `attribute="true"`
|
|
77
|
+
def compile_attribute(attribute)
|
|
78
|
+
if attribute.string?
|
|
79
|
+
[:html, :attr, attribute.name, [:static, attribute.value]]
|
|
80
|
+
elsif attribute.bool?
|
|
81
|
+
[:html, :attr, attribute.name, [:dynamic, "nil"]]
|
|
82
|
+
elsif attribute.expression?
|
|
83
|
+
[:html, :attr, attribute.name, [:dynamic, attribute.value]]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def dash_to_hash(name, value)
|
|
88
|
+
parts = name.split('-')
|
|
89
|
+
parts.reverse.inject(value) { |a, n| { n => a } }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def hash_to_args_list(obj, level = -1)
|
|
93
|
+
case obj
|
|
94
|
+
when String
|
|
95
|
+
obj
|
|
96
|
+
when Array
|
|
97
|
+
obj.map { |v| hash_to_args_list(v, level + 1) }.join(", ")
|
|
98
|
+
when Hash
|
|
99
|
+
down_the_rabbit_hole = obj.map { |k, v| "#{k}: #{hash_to_args_list(v, level + 1)}" }.join(", ")
|
|
100
|
+
return down_the_rabbit_hole if level.negative?
|
|
101
|
+
|
|
102
|
+
"{#{down_the_rabbit_hole}}"
|
|
103
|
+
|
|
104
|
+
else
|
|
105
|
+
raise "Invalid argument passed to hash_to_args_list: #{obj.inspect}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def prefixed_variable_name(name, prefix)
|
|
110
|
+
"#{prefix}_arg_#{name.underscore}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|