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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +40 -0
  4. data/.gitignore +19 -0
  5. data/CHANGELOG.md +1515 -0
  6. data/FAQ.md +147 -0
  7. data/Gemfile +23 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +210 -0
  10. data/REFERENCE.md +1380 -0
  11. data/Rakefile +116 -0
  12. data/bin/bench +66 -0
  13. data/bin/console +11 -0
  14. data/bin/ruby +3 -0
  15. data/bin/setup +7 -0
  16. data/bin/stackprof +27 -0
  17. data/bin/test +24 -0
  18. data/exe/haml +6 -0
  19. data/ext/haml/extconf.rb +10 -0
  20. data/ext/haml/haml.c +537 -0
  21. data/ext/haml/hescape.c +108 -0
  22. data/ext/haml/hescape.h +20 -0
  23. data/haml.gemspec +47 -0
  24. data/lib/haml/ambles.rb +20 -0
  25. data/lib/haml/attribute_builder.rb +175 -0
  26. data/lib/haml/attribute_compiler.rb +128 -0
  27. data/lib/haml/attribute_parser.rb +110 -0
  28. data/lib/haml/cli.rb +154 -0
  29. data/lib/haml/compiler/children_compiler.rb +126 -0
  30. data/lib/haml/compiler/comment_compiler.rb +39 -0
  31. data/lib/haml/compiler/doctype_compiler.rb +46 -0
  32. data/lib/haml/compiler/script_compiler.rb +116 -0
  33. data/lib/haml/compiler/silent_script_compiler.rb +24 -0
  34. data/lib/haml/compiler/tag_compiler.rb +76 -0
  35. data/lib/haml/compiler.rb +97 -0
  36. data/lib/haml/dynamic_merger.rb +67 -0
  37. data/lib/haml/engine.rb +53 -0
  38. data/lib/haml/error.rb +16 -0
  39. data/lib/haml/escapable.rb +13 -0
  40. data/lib/haml/filters/base.rb +12 -0
  41. data/lib/haml/filters/cdata.rb +20 -0
  42. data/lib/haml/filters/coffee.rb +17 -0
  43. data/lib/haml/filters/css.rb +33 -0
  44. data/lib/haml/filters/erb.rb +10 -0
  45. data/lib/haml/filters/escaped.rb +22 -0
  46. data/lib/haml/filters/javascript.rb +33 -0
  47. data/lib/haml/filters/less.rb +20 -0
  48. data/lib/haml/filters/markdown.rb +11 -0
  49. data/lib/haml/filters/plain.rb +29 -0
  50. data/lib/haml/filters/preserve.rb +22 -0
  51. data/lib/haml/filters/ruby.rb +10 -0
  52. data/lib/haml/filters/sass.rb +15 -0
  53. data/lib/haml/filters/scss.rb +15 -0
  54. data/lib/haml/filters/text_base.rb +25 -0
  55. data/lib/haml/filters/tilt_base.rb +49 -0
  56. data/lib/haml/filters.rb +75 -0
  57. data/lib/haml/force_escapable.rb +29 -0
  58. data/lib/haml/haml_error.rb +66 -0
  59. data/lib/haml/helpers.rb +15 -0
  60. data/lib/haml/html.rb +22 -0
  61. data/lib/haml/identity.rb +13 -0
  62. data/lib/haml/object_ref.rb +30 -0
  63. data/lib/haml/parser.rb +986 -0
  64. data/lib/haml/rails_helpers.rb +51 -0
  65. data/lib/haml/rails_template.rb +55 -0
  66. data/lib/haml/railtie.rb +15 -0
  67. data/lib/haml/ruby_expression.rb +32 -0
  68. data/lib/haml/string_splitter.rb +20 -0
  69. data/lib/haml/template.rb +20 -0
  70. data/lib/haml/temple_line_counter.rb +31 -0
  71. data/lib/haml/util.rb +260 -0
  72. data/lib/haml/version.rb +4 -0
  73. data/lib/haml.rb +13 -0
  74. metadata +359 -0
@@ -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