haml 6.0.0.beta.1-java
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/.github/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +40 -0
- data/.gitignore +19 -0
- data/CHANGELOG.md +1515 -0
- data/FAQ.md +147 -0
- data/Gemfile +23 -0
- data/MIT-LICENSE +20 -0
- data/README.md +210 -0
- data/REFERENCE.md +1380 -0
- data/Rakefile +116 -0
- data/bin/bench +66 -0
- data/bin/console +11 -0
- data/bin/ruby +3 -0
- data/bin/setup +7 -0
- data/bin/stackprof +27 -0
- data/bin/test +24 -0
- data/exe/haml +6 -0
- data/ext/haml/extconf.rb +10 -0
- data/ext/haml/haml.c +537 -0
- data/ext/haml/hescape.c +108 -0
- data/ext/haml/hescape.h +20 -0
- data/haml.gemspec +47 -0
- data/lib/haml/ambles.rb +20 -0
- data/lib/haml/attribute_builder.rb +175 -0
- data/lib/haml/attribute_compiler.rb +128 -0
- data/lib/haml/attribute_parser.rb +110 -0
- data/lib/haml/cli.rb +154 -0
- data/lib/haml/compiler/children_compiler.rb +126 -0
- data/lib/haml/compiler/comment_compiler.rb +39 -0
- data/lib/haml/compiler/doctype_compiler.rb +46 -0
- data/lib/haml/compiler/script_compiler.rb +116 -0
- data/lib/haml/compiler/silent_script_compiler.rb +24 -0
- data/lib/haml/compiler/tag_compiler.rb +76 -0
- data/lib/haml/compiler.rb +97 -0
- data/lib/haml/dynamic_merger.rb +67 -0
- data/lib/haml/engine.rb +53 -0
- data/lib/haml/error.rb +16 -0
- data/lib/haml/escapable.rb +13 -0
- data/lib/haml/filters/base.rb +12 -0
- data/lib/haml/filters/cdata.rb +20 -0
- data/lib/haml/filters/coffee.rb +17 -0
- data/lib/haml/filters/css.rb +33 -0
- data/lib/haml/filters/erb.rb +10 -0
- data/lib/haml/filters/escaped.rb +22 -0
- data/lib/haml/filters/javascript.rb +33 -0
- data/lib/haml/filters/less.rb +20 -0
- data/lib/haml/filters/markdown.rb +11 -0
- data/lib/haml/filters/plain.rb +29 -0
- data/lib/haml/filters/preserve.rb +22 -0
- data/lib/haml/filters/ruby.rb +10 -0
- data/lib/haml/filters/sass.rb +15 -0
- data/lib/haml/filters/scss.rb +15 -0
- data/lib/haml/filters/text_base.rb +25 -0
- data/lib/haml/filters/tilt_base.rb +49 -0
- data/lib/haml/filters.rb +75 -0
- data/lib/haml/force_escapable.rb +29 -0
- data/lib/haml/haml_error.rb +66 -0
- data/lib/haml/helpers.rb +15 -0
- data/lib/haml/html.rb +22 -0
- data/lib/haml/identity.rb +13 -0
- data/lib/haml/object_ref.rb +30 -0
- data/lib/haml/parser.rb +986 -0
- data/lib/haml/rails_helpers.rb +51 -0
- data/lib/haml/rails_template.rb +55 -0
- data/lib/haml/railtie.rb +15 -0
- data/lib/haml/ruby_expression.rb +32 -0
- data/lib/haml/string_splitter.rb +20 -0
- data/lib/haml/template.rb +20 -0
- data/lib/haml/temple_line_counter.rb +31 -0
- data/lib/haml/util.rb +260 -0
- data/lib/haml/version.rb +4 -0
- data/lib/haml.rb +13 -0
- metadata +359 -0
data/lib/haml/ambles.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Haml
|
3
|
+
class Ambles < Temple::Filter
|
4
|
+
define_options :preamble, :postamble
|
5
|
+
|
6
|
+
def initialize(*)
|
7
|
+
super
|
8
|
+
@preamble = options[:preamble]
|
9
|
+
@postamble = options[:postamble]
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(ast)
|
13
|
+
ret = [:multi]
|
14
|
+
ret << [:static, @preamble] if @preamble
|
15
|
+
ret << ast
|
16
|
+
ret << [:static, @postamble] if @postamble
|
17
|
+
ret
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'haml/object_ref'
|
3
|
+
|
4
|
+
module Haml::AttributeBuilder
|
5
|
+
BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer
|
6
|
+
autoplay controls loop selected hidden scoped async
|
7
|
+
defer reversed ismap seamless muted required
|
8
|
+
autofocus novalidate formnovalidate open pubdate
|
9
|
+
itemscope allowfullscreen default inert sortable
|
10
|
+
truespeed typemustmatch download].freeze
|
11
|
+
|
12
|
+
# Java extension is not implemented for JRuby yet.
|
13
|
+
# TruffleRuby does not implement `rb_ary_sort_bang`, etc.
|
14
|
+
if /java/ === RUBY_PLATFORM || RUBY_ENGINE == 'truffleruby'
|
15
|
+
class << self
|
16
|
+
def build(escape_attrs, quote, format, boolean_attributes, object_ref, *hashes)
|
17
|
+
hashes << Haml::ObjectRef.parse(object_ref) if object_ref
|
18
|
+
buf = []
|
19
|
+
hash = merge_all_attrs(hashes)
|
20
|
+
|
21
|
+
keys = hash.keys.sort!
|
22
|
+
keys.each do |key|
|
23
|
+
case key
|
24
|
+
when 'id'.freeze
|
25
|
+
buf << " id=#{quote}#{build_id(escape_attrs, *hash[key])}#{quote}"
|
26
|
+
when 'class'.freeze
|
27
|
+
buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
|
28
|
+
when 'data'.freeze
|
29
|
+
buf << build_data(escape_attrs, quote, *hash[key])
|
30
|
+
when *boolean_attributes, /\Adata-/
|
31
|
+
build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
|
32
|
+
else
|
33
|
+
buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
buf.join
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_id(escape_attrs, *values)
|
40
|
+
escape_html(escape_attrs, values.flatten.select { |v| v }.join('_'))
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_class(escape_attrs, *values)
|
44
|
+
if values.size == 1
|
45
|
+
value = values.first
|
46
|
+
case
|
47
|
+
when value.is_a?(String)
|
48
|
+
# noop
|
49
|
+
when value.is_a?(Array)
|
50
|
+
value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
|
51
|
+
when value
|
52
|
+
value = value.to_s
|
53
|
+
else
|
54
|
+
return ''
|
55
|
+
end
|
56
|
+
return escape_html(escape_attrs, value)
|
57
|
+
end
|
58
|
+
|
59
|
+
classes = []
|
60
|
+
values.each do |value|
|
61
|
+
case
|
62
|
+
when value.is_a?(String)
|
63
|
+
classes += value.split(' ')
|
64
|
+
when value.is_a?(Array)
|
65
|
+
classes += value.select { |v| v }
|
66
|
+
when value
|
67
|
+
classes << value.to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_data(escape_attrs, quote, *hashes)
|
74
|
+
build_data_attribute(:data, escape_attrs, quote, *hashes)
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_aria(escape_attrs, quote, *hashes)
|
78
|
+
build_data_attribute(:aria, escape_attrs, quote, *hashes)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def build_data_attribute(key, escape_attrs, quote, *hashes)
|
84
|
+
attrs = []
|
85
|
+
if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
|
86
|
+
data_value = merge_all_attrs(hashes)
|
87
|
+
else
|
88
|
+
data_value = hashes.last
|
89
|
+
end
|
90
|
+
hash = flatten_attributes(key => data_value)
|
91
|
+
|
92
|
+
hash.sort_by(&:first).each do |key, value|
|
93
|
+
case value
|
94
|
+
when true
|
95
|
+
attrs << " #{key}"
|
96
|
+
when nil, false
|
97
|
+
# noop
|
98
|
+
else
|
99
|
+
attrs << " #{key}=#{quote}#{escape_html(escape_attrs, value.to_s)}#{quote}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
attrs.join
|
103
|
+
end
|
104
|
+
|
105
|
+
def flatten_attributes(attributes)
|
106
|
+
flattened = {}
|
107
|
+
|
108
|
+
attributes.each do |key, value|
|
109
|
+
case value
|
110
|
+
when attributes
|
111
|
+
when Hash
|
112
|
+
flatten_attributes(value).each do |k, v|
|
113
|
+
if k.nil?
|
114
|
+
flattened[key] = v
|
115
|
+
else
|
116
|
+
flattened["#{key}-#{k.to_s.gsub(/_/, '-')}"] = v
|
117
|
+
end
|
118
|
+
end
|
119
|
+
else
|
120
|
+
flattened[key] = value if value
|
121
|
+
end
|
122
|
+
end
|
123
|
+
flattened
|
124
|
+
end
|
125
|
+
|
126
|
+
def merge_all_attrs(hashes)
|
127
|
+
merged = {}
|
128
|
+
hashes.each do |hash|
|
129
|
+
hash.each do |key, value|
|
130
|
+
key = key.to_s
|
131
|
+
case key
|
132
|
+
when 'id'.freeze, 'class'.freeze, 'data'.freeze
|
133
|
+
merged[key] ||= []
|
134
|
+
merged[key] << value
|
135
|
+
else
|
136
|
+
merged[key] = value
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
merged
|
141
|
+
end
|
142
|
+
|
143
|
+
def build_boolean!(escape_attrs, quote, format, buf, key, value)
|
144
|
+
case value
|
145
|
+
when true
|
146
|
+
case format
|
147
|
+
when :xhtml
|
148
|
+
buf << " #{key}=#{quote}#{key}#{quote}"
|
149
|
+
else
|
150
|
+
buf << " #{key}"
|
151
|
+
end
|
152
|
+
when false, nil
|
153
|
+
# omitted
|
154
|
+
else
|
155
|
+
buf << " #{key}=#{quote}#{escape_html(escape_attrs, value)}#{quote}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def escape_html(escape_attrs, str)
|
160
|
+
if escape_attrs
|
161
|
+
Haml::Util.escape_html(str)
|
162
|
+
else
|
163
|
+
str
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
else
|
168
|
+
# Haml::AttributeBuilder.build
|
169
|
+
# Haml::AttributeBuilder.build_id
|
170
|
+
# Haml::AttributeBuilder.build_class
|
171
|
+
# Haml::AttributeBuilder.build_data
|
172
|
+
# Haml::AttributeBuilder.build_aria
|
173
|
+
require 'haml/haml'
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'haml/attribute_builder'
|
3
|
+
require 'haml/attribute_parser'
|
4
|
+
require 'haml/ruby_expression'
|
5
|
+
|
6
|
+
module Haml
|
7
|
+
class AttributeCompiler
|
8
|
+
def initialize(identity, options)
|
9
|
+
@identity = identity
|
10
|
+
@quote = options[:attr_quote]
|
11
|
+
@format = options[:format]
|
12
|
+
@escape_attrs = options[:escape_attrs]
|
13
|
+
end
|
14
|
+
|
15
|
+
def compile(node)
|
16
|
+
hashes = []
|
17
|
+
if node.value[:object_ref] != :nil || !Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
|
18
|
+
return runtime_compile(node)
|
19
|
+
end
|
20
|
+
[node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
|
21
|
+
hash = AttributeParser.parse(attribute_str)
|
22
|
+
return runtime_compile(node) unless hash
|
23
|
+
hashes << hash
|
24
|
+
end
|
25
|
+
static_compile(node.value[:attributes], hashes)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def runtime_compile(node)
|
31
|
+
attrs = []
|
32
|
+
attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
|
33
|
+
|
34
|
+
args = [
|
35
|
+
@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect,
|
36
|
+
'::Haml::AttributeBuilder::BOOLEAN_ATTRIBUTES', node.value[:object_ref],
|
37
|
+
] + attrs
|
38
|
+
[:html, :attrs, [:dynamic, "::Haml::AttributeBuilder.build(#{args.join(', ')}, #{node.value[:dynamic_attributes].to_literal})"]]
|
39
|
+
end
|
40
|
+
|
41
|
+
def static_compile(static_hash, dynamic_hashes)
|
42
|
+
temple = [:html, :attrs]
|
43
|
+
keys = [*static_hash.keys, *dynamic_hashes.map(&:keys).flatten].uniq.sort
|
44
|
+
keys.each do |key|
|
45
|
+
values = [[:static, static_hash[key]], *dynamic_hashes.map { |h| [:dynamic, h[key]] }]
|
46
|
+
values.select! { |_, exp| exp != nil }
|
47
|
+
|
48
|
+
case key
|
49
|
+
when 'id'
|
50
|
+
compile_id!(temple, key, values)
|
51
|
+
when 'class'
|
52
|
+
compile_class!(temple, key, values)
|
53
|
+
when 'data', 'aria'
|
54
|
+
compile_data!(temple, key, values)
|
55
|
+
when *AttributeBuilder::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
|
56
|
+
compile_boolean!(temple, key, values)
|
57
|
+
else
|
58
|
+
compile_common!(temple, key, values)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
temple
|
62
|
+
end
|
63
|
+
|
64
|
+
def compile_id!(temple, key, values)
|
65
|
+
build_code = attribute_builder(:id, values)
|
66
|
+
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
67
|
+
temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
|
68
|
+
else
|
69
|
+
temple << [:html, :attr, key, [:dynamic, build_code]]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def compile_class!(temple, key, values)
|
74
|
+
build_code = attribute_builder(:class, values)
|
75
|
+
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
76
|
+
temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
|
77
|
+
else
|
78
|
+
temple << [:html, :attr, key, [:dynamic, build_code]]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def compile_data!(temple, key, values)
|
83
|
+
args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", values.map { |v| literal_for(v) }]
|
84
|
+
build_code = "::Haml::AttributeBuilder.build_#{key}(#{args.join(', ')})"
|
85
|
+
|
86
|
+
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
87
|
+
temple << [:static, eval(build_code).to_s]
|
88
|
+
else
|
89
|
+
temple << [:dynamic, build_code]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def compile_boolean!(temple, key, values)
|
94
|
+
exp = literal_for(values.last)
|
95
|
+
|
96
|
+
if Temple::StaticAnalyzer.static?(exp)
|
97
|
+
value = eval(exp)
|
98
|
+
case value
|
99
|
+
when true then temple << [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]
|
100
|
+
when false, nil
|
101
|
+
else temple << [:html, :attr, key, [:fescape, @escape_attrs, [:static, value.to_s]]]
|
102
|
+
end
|
103
|
+
else
|
104
|
+
var = @identity.generate
|
105
|
+
temple << [
|
106
|
+
:case, "(#{var} = (#{exp}))",
|
107
|
+
['true', [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]],
|
108
|
+
['false, nil', [:multi]],
|
109
|
+
[:else, [:multi, [:static, " #{key}=#{@quote}"], [:fescape, @escape_attrs, [:dynamic, var]], [:static, @quote]]],
|
110
|
+
]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def compile_common!(temple, key, values)
|
115
|
+
temple << [:html, :attr, key, [:fescape, @escape_attrs, values.last]]
|
116
|
+
end
|
117
|
+
|
118
|
+
def attribute_builder(type, values)
|
119
|
+
args = [@escape_attrs.inspect, *values.map { |v| literal_for(v) }]
|
120
|
+
"::Haml::AttributeBuilder.build_#{type}(#{args.join(', ')})"
|
121
|
+
end
|
122
|
+
|
123
|
+
def literal_for(value)
|
124
|
+
type, exp = value
|
125
|
+
type == :static ? "#{exp.inspect}.freeze" : exp
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'haml/ruby_expression'
|
3
|
+
|
4
|
+
module Haml
|
5
|
+
class AttributeParser
|
6
|
+
class ParseSkip < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(text)
|
10
|
+
self.new.parse(text)
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse(text)
|
14
|
+
exp = wrap_bracket(text)
|
15
|
+
return if RubyExpression.syntax_error?(exp)
|
16
|
+
|
17
|
+
hash = {}
|
18
|
+
tokens = Ripper.lex(exp)[1..-2] || []
|
19
|
+
each_attr(tokens) do |attr_tokens|
|
20
|
+
key = parse_key!(attr_tokens)
|
21
|
+
hash[key] = attr_tokens.map { |t| t[2] }.join.strip
|
22
|
+
end
|
23
|
+
hash
|
24
|
+
rescue ParseSkip
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def wrap_bracket(text)
|
31
|
+
text = text.strip
|
32
|
+
return text if text[0] == '{'
|
33
|
+
"{#{text}}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_key!(tokens)
|
37
|
+
_, type, str = tokens.shift
|
38
|
+
case type
|
39
|
+
when :on_sp
|
40
|
+
parse_key!(tokens)
|
41
|
+
when :on_label
|
42
|
+
str.tr(':', '')
|
43
|
+
when :on_symbeg
|
44
|
+
_, _, key = tokens.shift
|
45
|
+
assert_type!(tokens.shift, :on_tstring_end) if str != ':'
|
46
|
+
skip_until_hash_rocket!(tokens)
|
47
|
+
key
|
48
|
+
when :on_tstring_beg
|
49
|
+
_, _, key = tokens.shift
|
50
|
+
next_token = tokens.shift
|
51
|
+
unless next_token[1] == :on_label_end
|
52
|
+
assert_type!(next_token, :on_tstring_end)
|
53
|
+
skip_until_hash_rocket!(tokens)
|
54
|
+
end
|
55
|
+
key
|
56
|
+
else
|
57
|
+
raise ParseSkip
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def assert_type!(token, type)
|
62
|
+
raise ParseSkip if token[1] != type
|
63
|
+
end
|
64
|
+
|
65
|
+
def skip_until_hash_rocket!(tokens)
|
66
|
+
until tokens.empty?
|
67
|
+
_, type, str = tokens.shift
|
68
|
+
break if type == :on_op && str == '=>'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def each_attr(tokens)
|
73
|
+
attr_tokens = []
|
74
|
+
open_tokens = Hash.new { |h, k| h[k] = 0 }
|
75
|
+
|
76
|
+
tokens.each do |token|
|
77
|
+
_, type, _ = token
|
78
|
+
case type
|
79
|
+
when :on_comma
|
80
|
+
if open_tokens.values.all?(&:zero?)
|
81
|
+
yield(attr_tokens)
|
82
|
+
attr_tokens = []
|
83
|
+
next
|
84
|
+
end
|
85
|
+
when :on_lbracket
|
86
|
+
open_tokens[:array] += 1
|
87
|
+
when :on_rbracket
|
88
|
+
open_tokens[:array] -= 1
|
89
|
+
when :on_lbrace
|
90
|
+
open_tokens[:block] += 1
|
91
|
+
when :on_rbrace
|
92
|
+
open_tokens[:block] -= 1
|
93
|
+
when :on_lparen
|
94
|
+
open_tokens[:paren] += 1
|
95
|
+
when :on_rparen
|
96
|
+
open_tokens[:paren] -= 1
|
97
|
+
when :on_embexpr_beg
|
98
|
+
open_tokens[:embexpr] += 1
|
99
|
+
when :on_embexpr_end
|
100
|
+
open_tokens[:embexpr] -= 1
|
101
|
+
when :on_sp
|
102
|
+
next if attr_tokens.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
attr_tokens << token
|
106
|
+
end
|
107
|
+
yield(attr_tokens) unless attr_tokens.empty?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/haml/cli.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'haml'
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Haml
|
6
|
+
class CLI < Thor
|
7
|
+
class_option :escape_html, type: :boolean, default: true
|
8
|
+
class_option :escape_attrs, type: :boolean, default: true
|
9
|
+
|
10
|
+
desc 'render HAML', 'Render haml template'
|
11
|
+
option :load_path, type: :string, aliases: %w[-I]
|
12
|
+
option :require, type: :string, aliases: %w[-r]
|
13
|
+
def render(file)
|
14
|
+
process_load_options
|
15
|
+
code = generate_code(file)
|
16
|
+
puts eval(code, binding, file)
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'compile HAML', 'Show compile result'
|
20
|
+
option :actionview, type: :boolean, default: false, aliases: %w[-a]
|
21
|
+
option :color, type: :boolean, default: true
|
22
|
+
option :check, type: :boolean, default: false, aliases: %w[-c]
|
23
|
+
def compile(file)
|
24
|
+
code = generate_code(file)
|
25
|
+
if options[:check]
|
26
|
+
if error = validate_ruby(code, file)
|
27
|
+
abort error.message.split("\n").first
|
28
|
+
end
|
29
|
+
puts "Syntax OK"
|
30
|
+
return
|
31
|
+
end
|
32
|
+
puts_code(code, color: options[:color])
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'temple HAML', 'Show temple intermediate expression'
|
36
|
+
option :color, type: :boolean, default: true
|
37
|
+
def temple(file)
|
38
|
+
pp_object(generate_temple(file), color: options[:color])
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'parse HAML', 'Show parse result'
|
42
|
+
option :color, type: :boolean, default: true
|
43
|
+
def parse(file)
|
44
|
+
pp_object(generate_ast(file), color: options[:color])
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'version', 'Show the used haml version'
|
48
|
+
def version
|
49
|
+
puts Haml::VERSION
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def process_load_options
|
55
|
+
if options[:load_path]
|
56
|
+
options[:load_path].split(':').each do |dir|
|
57
|
+
$LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if options[:require]
|
62
|
+
require options[:require]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def read_file(file)
|
67
|
+
if file == '-'
|
68
|
+
STDIN.read
|
69
|
+
else
|
70
|
+
File.read(file)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def generate_code(file)
|
75
|
+
template = read_file(file)
|
76
|
+
if options[:actionview]
|
77
|
+
require 'action_view'
|
78
|
+
require 'action_view/base'
|
79
|
+
require 'haml/rails_template'
|
80
|
+
handler = Haml::RailsTemplate.new
|
81
|
+
template = ActionView::Template.new(template, 'inline template', handler, { locals: [] })
|
82
|
+
code = handler.call(template)
|
83
|
+
<<-end_src
|
84
|
+
def _inline_template___2144273726781623612_70327218547300(local_assigns, output_buffer)
|
85
|
+
_old_virtual_path, @virtual_path = @virtual_path, nil;_old_output_buffer = @output_buffer;;#{code}
|
86
|
+
ensure
|
87
|
+
@virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
|
88
|
+
end
|
89
|
+
end_src
|
90
|
+
else
|
91
|
+
Haml::Engine.new(engine_options).call(template)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def generate_ast(file)
|
96
|
+
template = read_file(file)
|
97
|
+
Haml::Parser.new(engine_options).call(template)
|
98
|
+
end
|
99
|
+
|
100
|
+
def generate_temple(file)
|
101
|
+
ast = generate_ast(file)
|
102
|
+
Haml::Compiler.new(engine_options).call(ast)
|
103
|
+
end
|
104
|
+
|
105
|
+
def engine_options
|
106
|
+
Haml::Engine.options.to_h.merge(
|
107
|
+
escape_attrs: options[:escape_attrs],
|
108
|
+
escape_html: options[:escape_html],
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Flexible default_task, compatible with haml's CLI
|
113
|
+
def method_missing(*args)
|
114
|
+
return super(*args) if args.length > 1
|
115
|
+
render(args.first.to_s)
|
116
|
+
end
|
117
|
+
|
118
|
+
def puts_code(code, color: true)
|
119
|
+
begin
|
120
|
+
require 'irb/color'
|
121
|
+
rescue LoadError
|
122
|
+
color = false
|
123
|
+
end
|
124
|
+
if color
|
125
|
+
puts IRB::Color.colorize_code(code)
|
126
|
+
else
|
127
|
+
puts code
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Enable colored pretty printing only for development environment.
|
132
|
+
def pp_object(arg, color: true)
|
133
|
+
begin
|
134
|
+
require 'irb/color_printer'
|
135
|
+
rescue LoadError
|
136
|
+
color = false
|
137
|
+
end
|
138
|
+
if color
|
139
|
+
IRB::ColorPrinter.pp(arg)
|
140
|
+
else
|
141
|
+
require 'pp'
|
142
|
+
pp(arg)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def validate_ruby(code, file)
|
147
|
+
begin
|
148
|
+
eval("BEGIN {return nil}; #{code}", binding, file)
|
149
|
+
rescue ::SyntaxError # Not to be confused with Haml::SyntaxError
|
150
|
+
$!
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'haml/temple_line_counter'
|
3
|
+
|
4
|
+
module Haml
|
5
|
+
class Compiler
|
6
|
+
class ChildrenCompiler
|
7
|
+
def initialize
|
8
|
+
@lineno = 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def compile(node, &block)
|
12
|
+
temple = [:multi]
|
13
|
+
return temple if node.children.empty?
|
14
|
+
|
15
|
+
temple << :whitespace if prepend_whitespace?(node)
|
16
|
+
node.children.each do |n|
|
17
|
+
rstrip_whitespace!(temple) if nuke_prev_whitespace?(n)
|
18
|
+
insert_newlines!(temple, n)
|
19
|
+
temple << moving_lineno(n) { block.call(n) }
|
20
|
+
temple << :whitespace if insert_whitespace?(n)
|
21
|
+
end
|
22
|
+
rstrip_whitespace!(temple) if nuke_inner_whitespace?(node)
|
23
|
+
confirm_whitespace(temple)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def insert_newlines!(temple, node)
|
29
|
+
(node.line - @lineno).times do
|
30
|
+
temple << [:newline]
|
31
|
+
end
|
32
|
+
|
33
|
+
@lineno = node.line
|
34
|
+
end
|
35
|
+
|
36
|
+
def moving_lineno(node, &block)
|
37
|
+
# before: As they may have children, we need to increment lineno before compilation.
|
38
|
+
case node.type
|
39
|
+
when :script, :silent_script
|
40
|
+
@lineno += 1
|
41
|
+
when :tag
|
42
|
+
[node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_hash|
|
43
|
+
@lineno += attribute_hash.count("\n")
|
44
|
+
end
|
45
|
+
@lineno += 1 if node.children.empty? && node.value[:parse]
|
46
|
+
end
|
47
|
+
|
48
|
+
temple = block.call # compile
|
49
|
+
|
50
|
+
# after: filter may not have children, and for some dynamic filters we can't predict the number of lines.
|
51
|
+
case node.type
|
52
|
+
when :filter
|
53
|
+
@lineno += TempleLineCounter.count_lines(temple)
|
54
|
+
end
|
55
|
+
|
56
|
+
temple
|
57
|
+
end
|
58
|
+
|
59
|
+
def confirm_whitespace(temple)
|
60
|
+
temple.map do |exp|
|
61
|
+
case exp
|
62
|
+
when :whitespace
|
63
|
+
[:static, "\n"]
|
64
|
+
else
|
65
|
+
exp
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def prepend_whitespace?(node)
|
71
|
+
return false unless %i[comment tag].include?(node.type)
|
72
|
+
!nuke_inner_whitespace?(node)
|
73
|
+
end
|
74
|
+
|
75
|
+
def nuke_inner_whitespace?(node)
|
76
|
+
case
|
77
|
+
when node.type == :tag
|
78
|
+
node.value[:nuke_inner_whitespace]
|
79
|
+
when node.parent.nil?
|
80
|
+
false
|
81
|
+
else
|
82
|
+
nuke_inner_whitespace?(node.parent)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def nuke_prev_whitespace?(node)
|
87
|
+
case node.type
|
88
|
+
when :tag
|
89
|
+
node.value[:nuke_outer_whitespace]
|
90
|
+
when :silent_script
|
91
|
+
!node.children.empty? && nuke_prev_whitespace?(node.children.first)
|
92
|
+
else
|
93
|
+
false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def nuke_outer_whitespace?(node)
|
98
|
+
return false if node.type != :tag
|
99
|
+
node.value[:nuke_outer_whitespace]
|
100
|
+
end
|
101
|
+
|
102
|
+
def rstrip_whitespace!(temple)
|
103
|
+
if temple[-1] == :whitespace
|
104
|
+
temple.delete_at(-1)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def insert_whitespace?(node)
|
109
|
+
return false if nuke_outer_whitespace?(node)
|
110
|
+
|
111
|
+
case node.type
|
112
|
+
when :doctype
|
113
|
+
node.value[:type] != 'xml'
|
114
|
+
when :comment, :plain, :tag
|
115
|
+
true
|
116
|
+
when :script
|
117
|
+
node.children.empty? && !nuke_inner_whitespace?(node)
|
118
|
+
when :filter
|
119
|
+
!%w[ruby].include?(node.value[:name])
|
120
|
+
else
|
121
|
+
false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|