hamlit 2.10.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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +47 -0
  4. data/CHANGELOG.md +702 -0
  5. data/Gemfile +30 -0
  6. data/LICENSE.txt +44 -0
  7. data/README.md +150 -0
  8. data/REFERENCE.md +272 -0
  9. data/Rakefile +117 -0
  10. data/benchmark/boolean_attribute.haml +6 -0
  11. data/benchmark/class_attribute.haml +5 -0
  12. data/benchmark/common_attribute.haml +3 -0
  13. data/benchmark/data_attribute.haml +4 -0
  14. data/benchmark/dynamic_attributes/boolean_attribute.haml +4 -0
  15. data/benchmark/dynamic_attributes/class_attribute.haml +4 -0
  16. data/benchmark/dynamic_attributes/common_attribute.haml +2 -0
  17. data/benchmark/dynamic_attributes/data_attribute.haml +2 -0
  18. data/benchmark/dynamic_attributes/id_attribute.haml +2 -0
  19. data/benchmark/dynamic_boolean_attribute.haml +4 -0
  20. data/benchmark/dynamic_merger/benchmark.rb +25 -0
  21. data/benchmark/dynamic_merger/hello.haml +50 -0
  22. data/benchmark/dynamic_merger/hello.string +50 -0
  23. data/benchmark/etc/attribute_builder.haml +5 -0
  24. data/benchmark/etc/real_sample.haml +888 -0
  25. data/benchmark/etc/real_sample.rb +11 -0
  26. data/benchmark/etc/static_analyzer.haml +1 -0
  27. data/benchmark/etc/string_interpolation.haml +2 -0
  28. data/benchmark/etc/tags.haml +3 -0
  29. data/benchmark/etc/tags_loop.haml +2 -0
  30. data/benchmark/ext/build_data.rb +17 -0
  31. data/benchmark/ext/build_id.rb +13 -0
  32. data/benchmark/id_attribute.haml +3 -0
  33. data/benchmark/plain.haml +4 -0
  34. data/benchmark/script.haml +4 -0
  35. data/benchmark/slim/LICENSE +21 -0
  36. data/benchmark/slim/context.rb +11 -0
  37. data/benchmark/slim/run-benchmarks.rb +94 -0
  38. data/benchmark/slim/view.erb +23 -0
  39. data/benchmark/slim/view.haml +18 -0
  40. data/benchmark/slim/view.slim +17 -0
  41. data/benchmark/utils/benchmark_ips_extension.rb +43 -0
  42. data/bin/bench +77 -0
  43. data/bin/console +11 -0
  44. data/bin/ruby +3 -0
  45. data/bin/setup +7 -0
  46. data/bin/stackprof +27 -0
  47. data/bin/test +24 -0
  48. data/exe/hamlit +6 -0
  49. data/ext/hamlit/extconf.rb +10 -0
  50. data/ext/hamlit/hamlit.c +555 -0
  51. data/ext/hamlit/hescape.c +108 -0
  52. data/ext/hamlit/hescape.h +20 -0
  53. data/hamlit.gemspec +47 -0
  54. data/lib/hamlit.rb +11 -0
  55. data/lib/hamlit/attribute_builder.rb +175 -0
  56. data/lib/hamlit/attribute_compiler.rb +125 -0
  57. data/lib/hamlit/attribute_parser.rb +110 -0
  58. data/lib/hamlit/cli.rb +130 -0
  59. data/lib/hamlit/compiler.rb +97 -0
  60. data/lib/hamlit/compiler/children_compiler.rb +112 -0
  61. data/lib/hamlit/compiler/comment_compiler.rb +36 -0
  62. data/lib/hamlit/compiler/doctype_compiler.rb +46 -0
  63. data/lib/hamlit/compiler/script_compiler.rb +106 -0
  64. data/lib/hamlit/compiler/silent_script_compiler.rb +24 -0
  65. data/lib/hamlit/compiler/tag_compiler.rb +76 -0
  66. data/lib/hamlit/dynamic_merger.rb +67 -0
  67. data/lib/hamlit/engine.rb +38 -0
  68. data/lib/hamlit/error.rb +15 -0
  69. data/lib/hamlit/escapable.rb +13 -0
  70. data/lib/hamlit/filters.rb +75 -0
  71. data/lib/hamlit/filters/base.rb +12 -0
  72. data/lib/hamlit/filters/cdata.rb +20 -0
  73. data/lib/hamlit/filters/coffee.rb +17 -0
  74. data/lib/hamlit/filters/css.rb +33 -0
  75. data/lib/hamlit/filters/erb.rb +10 -0
  76. data/lib/hamlit/filters/escaped.rb +22 -0
  77. data/lib/hamlit/filters/javascript.rb +33 -0
  78. data/lib/hamlit/filters/less.rb +20 -0
  79. data/lib/hamlit/filters/markdown.rb +10 -0
  80. data/lib/hamlit/filters/plain.rb +29 -0
  81. data/lib/hamlit/filters/preserve.rb +22 -0
  82. data/lib/hamlit/filters/ruby.rb +10 -0
  83. data/lib/hamlit/filters/sass.rb +15 -0
  84. data/lib/hamlit/filters/scss.rb +15 -0
  85. data/lib/hamlit/filters/text_base.rb +25 -0
  86. data/lib/hamlit/filters/tilt_base.rb +49 -0
  87. data/lib/hamlit/force_escapable.rb +29 -0
  88. data/lib/hamlit/helpers.rb +15 -0
  89. data/lib/hamlit/html.rb +14 -0
  90. data/lib/hamlit/identity.rb +13 -0
  91. data/lib/hamlit/object_ref.rb +30 -0
  92. data/lib/hamlit/parser.rb +49 -0
  93. data/lib/hamlit/parser/MIT-LICENSE +20 -0
  94. data/lib/hamlit/parser/README.md +30 -0
  95. data/lib/hamlit/parser/haml_buffer.rb +348 -0
  96. data/lib/hamlit/parser/haml_compiler.rb +553 -0
  97. data/lib/hamlit/parser/haml_error.rb +61 -0
  98. data/lib/hamlit/parser/haml_helpers.rb +727 -0
  99. data/lib/hamlit/parser/haml_options.rb +286 -0
  100. data/lib/hamlit/parser/haml_parser.rb +800 -0
  101. data/lib/hamlit/parser/haml_util.rb +288 -0
  102. data/lib/hamlit/parser/haml_xss_mods.rb +109 -0
  103. data/lib/hamlit/rails_helpers.rb +51 -0
  104. data/lib/hamlit/rails_template.rb +59 -0
  105. data/lib/hamlit/railtie.rb +10 -0
  106. data/lib/hamlit/ruby_expression.rb +32 -0
  107. data/lib/hamlit/string_splitter.rb +19 -0
  108. data/lib/hamlit/template.rb +28 -0
  109. data/lib/hamlit/utils.rb +20 -0
  110. data/lib/hamlit/version.rb +4 -0
  111. metadata +392 -0
@@ -0,0 +1,108 @@
1
+ #include <stdio.h>
2
+ #include <string.h>
3
+ #include <stdlib.h>
4
+ #include "hescape.h"
5
+
6
+ static const char *ESCAPED_STRING[] = {
7
+ "",
8
+ "&quot;",
9
+ "&amp;",
10
+ "&#39;",
11
+ "&lt;",
12
+ "&gt;",
13
+ };
14
+
15
+ // This is strlen(ESCAPED_STRING[x]) optimized specially.
16
+ // Mapping: 1 => 6, 2 => 5, 3 => 5, 4 => 4, 5 => 4
17
+ #define ESC_LEN(x) ((13 - x) / 2)
18
+
19
+ /*
20
+ * Given ASCII-compatible character, return index of ESCAPED_STRING.
21
+ *
22
+ * " (34) => 1 (&quot;)
23
+ * & (38) => 2 (&amp;)
24
+ * ' (39) => 3 (&#39;)
25
+ * < (60) => 4 (&lt;)
26
+ * > (62) => 5 (&gt;)
27
+ */
28
+ static const char HTML_ESCAPE_TABLE[] = {
29
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
30
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
31
+ 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0,
32
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 5, 0,
33
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
34
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
35
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
36
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
37
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
38
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
39
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
40
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
42
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
43
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
44
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
45
+ };
46
+
47
+ static char*
48
+ ensure_allocated(char *buf, size_t size, size_t *asize)
49
+ {
50
+ size_t new_size;
51
+
52
+ if (size < *asize)
53
+ return buf;
54
+
55
+ if (*asize == 0) {
56
+ new_size = size;
57
+ } else {
58
+ new_size = *asize;
59
+ }
60
+
61
+ // Increase buffer size by 1.5x if realloced multiple times.
62
+ while (new_size < size)
63
+ new_size = (new_size << 1) - (new_size >> 1);
64
+
65
+ // Round allocation up to multiple of 8.
66
+ new_size = (new_size + 7) & ~7;
67
+
68
+ *asize = new_size;
69
+ return realloc(buf, new_size);
70
+ }
71
+
72
+ size_t
73
+ hesc_escape_html(char **dest, const char *buf, size_t size)
74
+ {
75
+ size_t asize = 0, esc_i = 0, esize = 0, i = 0, rbuf_end = 0;
76
+ const char *esc;
77
+ char *rbuf = NULL;
78
+
79
+ while (i < size) {
80
+ // Loop here to skip non-escaped characters fast.
81
+ while (i < size && (esc_i = HTML_ESCAPE_TABLE[(unsigned char)buf[i]]) == 0)
82
+ i++;
83
+
84
+ if (i < size && esc_i) {
85
+ esc = ESCAPED_STRING[esc_i];
86
+ rbuf = ensure_allocated(rbuf, sizeof(char) * (size + esize + ESC_LEN(esc_i) + 1), &asize);
87
+
88
+ // Copy pending characters and escaped string.
89
+ memmove(rbuf + rbuf_end, buf + (rbuf_end - esize), i - (rbuf_end - esize));
90
+ memmove(rbuf + i + esize, esc, ESC_LEN(esc_i));
91
+ rbuf_end = i + esize + ESC_LEN(esc_i);
92
+ esize += ESC_LEN(esc_i) - 1;
93
+ }
94
+ i++;
95
+ }
96
+
97
+ if (rbuf_end == 0) {
98
+ // Return given buf and size if there are no escaped characters.
99
+ *dest = (char *)buf;
100
+ return size;
101
+ } else {
102
+ // Copy pending characters including NULL character.
103
+ memmove(rbuf + rbuf_end, buf + (rbuf_end - esize), (size + 1) - (rbuf_end - esize));
104
+
105
+ *dest = rbuf;
106
+ return size + esize;
107
+ }
108
+ }
@@ -0,0 +1,20 @@
1
+ #ifndef HESCAPE_H
2
+ #define HESCAPE_H
3
+
4
+ #include <sys/types.h>
5
+
6
+ /*
7
+ * Replace characters according to the following rules.
8
+ * Note that this function can handle only ASCII-compatible string.
9
+ *
10
+ * " => &quot;
11
+ * & => &amp;
12
+ * ' => &#39;
13
+ * < => &lt;
14
+ * > => &gt;
15
+ *
16
+ * @return size of dest. If it's larger than len, dest is required to be freed.
17
+ */
18
+ extern size_t hesc_escape_html(char **dest, const char *src, size_t size);
19
+
20
+ #endif
@@ -0,0 +1,47 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hamlit/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'hamlit'
8
+ spec.version = Hamlit::VERSION
9
+ spec.authors = ['Takashi Kokubun']
10
+ spec.email = ['takashikkbn@gmail.com']
11
+
12
+ spec.summary = %q{High Performance Haml Implementation}
13
+ spec.description = %q{High Performance Haml Implementation}
14
+ spec.homepage = 'https://github.com/k0kubun/hamlit'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|sample)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ if /java/ === RUBY_PLATFORM
23
+ spec.platform = 'java'
24
+ else
25
+ spec.extensions = ['ext/hamlit/extconf.rb']
26
+ spec.required_ruby_version = '>= 2.1.0'
27
+ end
28
+
29
+ spec.add_dependency 'temple', '>= 0.8.2'
30
+ spec.add_dependency 'thor'
31
+ spec.add_dependency 'tilt'
32
+
33
+ spec.add_development_dependency 'benchmark_driver'
34
+ spec.add_development_dependency 'bundler'
35
+ spec.add_development_dependency 'coffee-script'
36
+ spec.add_development_dependency 'erubi'
37
+ spec.add_development_dependency 'haml', '>= 5'
38
+ spec.add_development_dependency 'less'
39
+ spec.add_development_dependency 'minitest-reporters', '~> 1.1'
40
+ spec.add_development_dependency 'rails', '>= 4.0.0'
41
+ spec.add_development_dependency 'rake'
42
+ spec.add_development_dependency 'rake-compiler'
43
+ spec.add_development_dependency 'sass'
44
+ spec.add_development_dependency 'slim'
45
+ spec.add_development_dependency 'string_template'
46
+ spec.add_development_dependency 'unindent'
47
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ require 'hamlit/engine'
3
+ require 'hamlit/error'
4
+ require 'hamlit/version'
5
+ require 'hamlit/template'
6
+
7
+ begin
8
+ require 'rails'
9
+ require 'hamlit/railtie'
10
+ rescue LoadError
11
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+ require 'hamlit/object_ref'
3
+
4
+ module Hamlit::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, object_ref, *hashes)
17
+ hashes << Hamlit::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).sort.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).sort.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
+ Hamlit::Utils.escape_html(str)
162
+ else
163
+ str
164
+ end
165
+ end
166
+ end
167
+ else
168
+ # Hamlit::AttributeBuilder.build
169
+ # Hamlit::AttributeBuilder.build_id
170
+ # Hamlit::AttributeBuilder.build_class
171
+ # Hamlit::AttributeBuilder.build_data
172
+ # Hamlit::AttributeBuilder.build_aria
173
+ require 'hamlit/hamlit'
174
+ end
175
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+ require 'hamlit/attribute_builder'
3
+ require 'hamlit/attribute_parser'
4
+ require 'hamlit/ruby_expression'
5
+
6
+ module Hamlit
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[:attributes_hashes].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 = node.value[:attributes_hashes]
32
+ attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
33
+
34
+ args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect].push(node.value[:object_ref]) + attrs
35
+ [:html, :attrs, [:dynamic, "::Hamlit::AttributeBuilder.build(#{args.join(', ')})"]]
36
+ end
37
+
38
+ def static_compile(static_hash, dynamic_hashes)
39
+ temple = [:html, :attrs]
40
+ keys = [*static_hash.keys, *dynamic_hashes.map(&:keys).flatten].uniq.sort
41
+ keys.each do |key|
42
+ values = [[:static, static_hash[key]], *dynamic_hashes.map { |h| [:dynamic, h[key]] }]
43
+ values.select! { |_, exp| exp != nil }
44
+
45
+ case key
46
+ when 'id'
47
+ compile_id!(temple, key, values)
48
+ when 'class'
49
+ compile_class!(temple, key, values)
50
+ when 'data', 'aria'
51
+ compile_data!(temple, key, values)
52
+ when *AttributeBuilder::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
53
+ compile_boolean!(temple, key, values)
54
+ else
55
+ compile_common!(temple, key, values)
56
+ end
57
+ end
58
+ temple
59
+ end
60
+
61
+ def compile_id!(temple, key, values)
62
+ build_code = attribute_builder(:id, values)
63
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
64
+ temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
65
+ else
66
+ temple << [:html, :attr, key, [:dynamic, build_code]]
67
+ end
68
+ end
69
+
70
+ def compile_class!(temple, key, values)
71
+ build_code = attribute_builder(:class, values)
72
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
73
+ temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
74
+ else
75
+ temple << [:html, :attr, key, [:dynamic, build_code]]
76
+ end
77
+ end
78
+
79
+ def compile_data!(temple, key, values)
80
+ args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", values.map { |v| literal_for(v) }]
81
+ build_code = "::Hamlit::AttributeBuilder.build_#{key}(#{args.join(', ')})"
82
+
83
+ if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
84
+ temple << [:static, eval(build_code).to_s]
85
+ else
86
+ temple << [:dynamic, build_code]
87
+ end
88
+ end
89
+
90
+ def compile_boolean!(temple, key, values)
91
+ exp = literal_for(values.last)
92
+
93
+ if Temple::StaticAnalyzer.static?(exp)
94
+ value = eval(exp)
95
+ case value
96
+ when true then temple << [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]
97
+ when false, nil
98
+ else temple << [:html, :attr, key, [:fescape, @escape_attrs, [:static, value.to_s]]]
99
+ end
100
+ else
101
+ var = @identity.generate
102
+ temple << [
103
+ :case, "(#{var} = (#{exp}))",
104
+ ['true', [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]],
105
+ ['false, nil', [:multi]],
106
+ [:else, [:multi, [:static, " #{key}=#{@quote}"], [:fescape, @escape_attrs, [:dynamic, var]], [:static, @quote]]],
107
+ ]
108
+ end
109
+ end
110
+
111
+ def compile_common!(temple, key, values)
112
+ temple << [:html, :attr, key, [:fescape, @escape_attrs, values.last]]
113
+ end
114
+
115
+ def attribute_builder(type, values)
116
+ args = [@escape_attrs.inspect, *values.map { |v| literal_for(v) }]
117
+ "::Hamlit::AttributeBuilder.build_#{type}(#{args.join(', ')})"
118
+ end
119
+
120
+ def literal_for(value)
121
+ type, exp = value
122
+ type == :static ? "#{exp.inspect}.freeze" : exp
123
+ end
124
+ end
125
+ end