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,36 @@
1
+ module Hamlit
2
+ class Compiler
3
+ class CommentCompiler
4
+ def compile(node, &block)
5
+ if node.value[:conditional]
6
+ compile_conditional_comment(node, &block)
7
+ else
8
+ compile_html_comment(node, &block)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def compile_html_comment(node, &block)
15
+ if node.children.empty?
16
+ [:html, :comment, [:static, " #{node.value[:text]} "]]
17
+ else
18
+ [:html, :comment, yield(node)]
19
+ end
20
+ end
21
+
22
+ def compile_conditional_comment(node, &block)
23
+ condition = node.value[:conditional]
24
+ if node.value[:conditional] =~ /\A\[(\[*[^\[\]]+\]*)\]/
25
+ condition = $1
26
+ end
27
+
28
+ if node.children.empty?
29
+ [:html, :condcomment, condition, [:static, " #{node.value[:text]} "]]
30
+ else
31
+ [:html, :condcomment, condition, yield(node)]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ module Hamlit
3
+ class Compiler
4
+ class DoctypeCompiler
5
+ def initialize(options = {})
6
+ @format = options[:format]
7
+ end
8
+
9
+ def compile(node)
10
+ case node.value[:type]
11
+ when 'xml'
12
+ xml_doctype
13
+ when ''
14
+ html_doctype(node)
15
+ else
16
+ [:html, :doctype, node.value[:type]]
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def html_doctype(node)
23
+ version = node.value[:version] || :transitional
24
+ case @format
25
+ when :xhtml
26
+ [:html, :doctype, version]
27
+ when :html4
28
+ [:html, :doctype, :transitional]
29
+ when :html5
30
+ [:html, :doctype, :html]
31
+ else
32
+ [:html, :doctype, @format]
33
+ end
34
+ end
35
+
36
+ def xml_doctype
37
+ case @format
38
+ when :xhtml
39
+ [:static, "<?xml version='1.0' encoding='utf-8' ?>\n"]
40
+ else
41
+ [:multi]
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+ require 'temple/static_analyzer'
3
+ require 'hamlit/ruby_expression'
4
+ require 'hamlit/string_splitter'
5
+
6
+ module Hamlit
7
+ class Compiler
8
+ class ScriptCompiler
9
+ def initialize(identity)
10
+ @identity = identity
11
+ end
12
+
13
+ def compile(node, &block)
14
+ unless Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
15
+ return dynamic_compile(node, &block)
16
+ end
17
+
18
+ no_children = node.children.empty?
19
+ case
20
+ when no_children && node.value[:escape_interpolation]
21
+ compile_interpolated_plain(node)
22
+ when no_children && RubyExpression.string_literal?(node.value[:text])
23
+ delegate_optimization(node)
24
+ when no_children && Temple::StaticAnalyzer.static?(node.value[:text])
25
+ static_compile(node)
26
+ else
27
+ dynamic_compile(node, &block)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # String-interpolated plain text must be compiled with this method
34
+ # because we have to escape only interpolated values.
35
+ def compile_interpolated_plain(node)
36
+ temple = [:multi]
37
+ StringSplitter.compile(node.value[:text]).each do |type, value|
38
+ case type
39
+ when :static
40
+ temple << [:static, value]
41
+ when :dynamic
42
+ temple << [:escape, node.value[:escape_interpolation], [:dynamic, value]]
43
+ end
44
+ end
45
+ temple << [:newline]
46
+ end
47
+
48
+ # :dynamic is optimized in other filter: StringSplitter
49
+ def delegate_optimization(node)
50
+ [:multi,
51
+ [:escape, node.value[:escape_html], [:dynamic, node.value[:text]]],
52
+ [:newline],
53
+ ]
54
+ end
55
+
56
+ def static_compile(node)
57
+ str = eval(node.value[:text]).to_s
58
+ if node.value[:escape_html]
59
+ str = Hamlit::Utils.escape_html(str)
60
+ elsif node.value[:preserve]
61
+ str = ::Hamlit::HamlHelpers.find_and_preserve(str, %w(textarea pre code))
62
+ end
63
+ [:multi, [:static, str], [:newline]]
64
+ end
65
+
66
+ def dynamic_compile(node, &block)
67
+ var = @identity.generate
68
+ temple = compile_script_assign(var, node, &block)
69
+ temple << compile_script_result(var, node)
70
+ end
71
+
72
+ def compile_script_assign(var, node, &block)
73
+ if node.children.empty?
74
+ [:multi,
75
+ [:code, "#{var} = (#{node.value[:text]}"],
76
+ [:newline],
77
+ [:code, ')'],
78
+ ]
79
+ else
80
+ [:multi,
81
+ [:block, "#{var} = #{node.value[:text]}",
82
+ [:multi, [:newline], yield(node)],
83
+ ],
84
+ ]
85
+ end
86
+ end
87
+
88
+ def compile_script_result(result, node)
89
+ if !node.value[:escape_html] && node.value[:preserve]
90
+ result = find_and_preserve(result)
91
+ else
92
+ result = "(#{result}).to_s"
93
+ end
94
+ [:escape, node.value[:escape_html], [:dynamic, result]]
95
+ end
96
+
97
+ def find_and_preserve(code)
98
+ %Q[::Hamlit::HamlHelpers.find_and_preserve(#{code}, %w(textarea pre code))]
99
+ end
100
+
101
+ def escape_html(temple)
102
+ [:escape, true, temple]
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ module Hamlit
3
+ class Compiler
4
+ class SilentScriptCompiler
5
+ def compile(node, &block)
6
+ if node.children.empty?
7
+ [:multi, [:code, node.value[:text]], [:newline]]
8
+ else
9
+ compile_with_children(node, &block)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def compile_with_children(node, &block)
16
+ [:multi,
17
+ [:block, node.value[:text],
18
+ [:multi, [:newline], yield(node)],
19
+ ],
20
+ ]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+ require 'hamlit/parser/haml_util'
3
+ require 'hamlit/attribute_compiler'
4
+ require 'hamlit/string_splitter'
5
+
6
+ module Hamlit
7
+ class Compiler
8
+ class TagCompiler
9
+ def initialize(identity, options)
10
+ @autoclose = options[:autoclose]
11
+ @identity = identity
12
+ @attribute_compiler = AttributeCompiler.new(identity, options)
13
+ end
14
+
15
+ def compile(node, &block)
16
+ attrs = @attribute_compiler.compile(node)
17
+ contents = compile_contents(node, &block)
18
+ [:html, :tag, node.value[:name], attrs, contents]
19
+ end
20
+
21
+ private
22
+
23
+ def compile_contents(node, &block)
24
+ case
25
+ when !node.children.empty?
26
+ yield(node)
27
+ when node.value[:value].nil? && self_closing?(node)
28
+ nil
29
+ when node.value[:parse]
30
+ return compile_interpolated_plain(node) if node.value[:escape_interpolation]
31
+ if Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
32
+ return delegate_optimization(node) if RubyExpression.string_literal?(node.value[:value])
33
+ return delegate_optimization(node) if Temple::StaticAnalyzer.static?(node.value[:value])
34
+ end
35
+
36
+ var = @identity.generate
37
+ [:multi,
38
+ [:code, "#{var} = (#{node.value[:value]}"],
39
+ [:newline],
40
+ [:code, ')'],
41
+ [:escape, node.value[:escape_html], [:dynamic, var]]
42
+ ]
43
+ else
44
+ [:static, node.value[:value]]
45
+ end
46
+ end
47
+
48
+ # :dynamic is optimized in other filters: StringSplitter or StaticAnalyzer
49
+ def delegate_optimization(node)
50
+ [:multi,
51
+ [:escape, node.value[:escape_html], [:dynamic, node.value[:value]]],
52
+ [:newline],
53
+ ]
54
+ end
55
+
56
+ # We should handle interpolation here to escape only interpolated values.
57
+ def compile_interpolated_plain(node)
58
+ temple = [:multi]
59
+ StringSplitter.compile(node.value[:value]).each do |type, value|
60
+ case type
61
+ when :static
62
+ temple << [:static, value]
63
+ when :dynamic
64
+ temple << [:escape, node.value[:escape_interpolation], [:dynamic, value]]
65
+ end
66
+ end
67
+ temple << [:newline]
68
+ end
69
+
70
+ def self_closing?(node)
71
+ return true if @autoclose && @autoclose.include?(node.value[:name])
72
+ node.value[:self_closing]
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ module Hamlit
3
+ # Compile [:multi, [:static, 'foo'], [:dynamic, 'bar']] to [:dynamic, '"foo#{bar}"']
4
+ class DynamicMerger < Temple::Filter
5
+ def on_multi(*exps)
6
+ exps = exps.dup
7
+ result = [:multi]
8
+ buffer = []
9
+
10
+ until exps.empty?
11
+ type, arg = exps.first
12
+ if type == :dynamic && arg.count("\n") == 0
13
+ buffer << exps.shift
14
+ elsif type == :static && exps.size > (count = arg.count("\n")) &&
15
+ exps[1, count].all? { |e| e == [:newline] }
16
+ (1 + count).times { buffer << exps.shift }
17
+ elsif type == :newline && exps.size > (count = count_newline(exps)) &&
18
+ exps[count].first == :static && count == exps[count].last.count("\n")
19
+ (count + 1).times { buffer << exps.shift }
20
+ else
21
+ result.concat(merge_dynamic(buffer))
22
+ buffer = []
23
+ result << compile(exps.shift)
24
+ end
25
+ end
26
+ result.concat(merge_dynamic(buffer))
27
+
28
+ result.size == 2 ? result[1] : result
29
+ end
30
+
31
+ private
32
+
33
+ def merge_dynamic(exps)
34
+ # Merge exps only when they have both :static and :dynamic
35
+ unless exps.any? { |type,| type == :static } && exps.any? { |type,| type == :dynamic }
36
+ return exps
37
+ end
38
+
39
+ strlit_body = String.new
40
+ exps.each do |type, arg|
41
+ case type
42
+ when :static
43
+ strlit_body << arg.dump.sub!(/\A"/, '').sub!(/"\z/, '').gsub('\n', "\n")
44
+ when :dynamic
45
+ strlit_body << "\#{#{arg}}"
46
+ when :newline
47
+ # newline is added by `gsub('\n', "\n")`
48
+ else
49
+ raise "unexpected type #{type.inspect} is given to #merge_dynamic"
50
+ end
51
+ end
52
+ [[:dynamic, "%Q\0#{strlit_body}\0"]]
53
+ end
54
+
55
+ def count_newline(exps)
56
+ count = 0
57
+ exps.each do |exp|
58
+ if exp == [:newline]
59
+ count += 1
60
+ else
61
+ return count
62
+ end
63
+ end
64
+ return count
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ require 'temple'
3
+ require 'hamlit/parser'
4
+ require 'hamlit/compiler'
5
+ require 'hamlit/html'
6
+ require 'hamlit/escapable'
7
+ require 'hamlit/force_escapable'
8
+ require 'hamlit/dynamic_merger'
9
+
10
+ module Hamlit
11
+ class Engine < Temple::Engine
12
+ define_options(
13
+ :buffer_class,
14
+ generator: Temple::Generators::ArrayBuffer,
15
+ format: :html,
16
+ attr_quote: "'",
17
+ escape_html: true,
18
+ escape_attrs: true,
19
+ autoclose: %w(area base basefont br col command embed frame
20
+ hr img input isindex keygen link menuitem meta
21
+ param source track wbr),
22
+ filename: "",
23
+ )
24
+
25
+ use Parser
26
+ use Compiler
27
+ use HTML
28
+ filter :StringSplitter
29
+ filter :StaticAnalyzer
30
+ use Escapable
31
+ use ForceEscapable
32
+ filter :ControlFlow
33
+ filter :MultiFlattener
34
+ filter :StaticMerger
35
+ use DynamicMerger
36
+ use :Generator, -> { options[:generator] }
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Hamlit
3
+ class Error < StandardError
4
+ attr_reader :line
5
+
6
+ def initialize(message = nil, line = nil)
7
+ super(message)
8
+ @line = line
9
+ end
10
+ end
11
+
12
+ class SyntaxError < Error; end
13
+ class InternalError < Error; end
14
+ class FilterNotFound < Error; end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ require 'hamlit/utils'
3
+
4
+ module Hamlit
5
+ class Escapable < Temple::Filters::Escapable
6
+ def initialize(opts = {})
7
+ super
8
+ @escape_code = options[:escape_code] ||
9
+ "::Hamlit::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((%s))"
10
+ @escaper = eval("proc {|v| #{@escape_code % 'v'} }")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+ require 'hamlit/filters/base'
3
+ require 'hamlit/filters/text_base'
4
+ require 'hamlit/filters/tilt_base'
5
+ require 'hamlit/filters/coffee'
6
+ require 'hamlit/filters/css'
7
+ require 'hamlit/filters/erb'
8
+ require 'hamlit/filters/escaped'
9
+ require 'hamlit/filters/javascript'
10
+ require 'hamlit/filters/less'
11
+ require 'hamlit/filters/markdown'
12
+ require 'hamlit/filters/plain'
13
+ require 'hamlit/filters/preserve'
14
+ require 'hamlit/filters/ruby'
15
+ require 'hamlit/filters/sass'
16
+ require 'hamlit/filters/scss'
17
+ require 'hamlit/filters/cdata'
18
+
19
+ module Hamlit
20
+ class Filters
21
+ @registered = {}
22
+
23
+ class << self
24
+ attr_reader :registered
25
+
26
+ def remove_filter(name)
27
+ registered.delete(name.to_s.downcase.to_sym)
28
+ if constants.map(&:to_s).include?(name.to_s)
29
+ remove_const name.to_sym
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def register(name, compiler)
36
+ registered[name] = compiler
37
+ end
38
+ end
39
+
40
+ register :coffee, Coffee
41
+ register :coffeescript, CoffeeScript
42
+ register :css, Css
43
+ register :erb, Erb
44
+ register :escaped, Escaped
45
+ register :javascript, Javascript
46
+ register :less, Less
47
+ register :markdown, Markdown
48
+ register :plain, Plain
49
+ register :preserve, Preserve
50
+ register :ruby, Ruby
51
+ register :sass, Sass
52
+ register :scss, Scss
53
+ register :cdata, Cdata
54
+
55
+ def initialize(options = {})
56
+ @options = options
57
+ @compilers = {}
58
+ end
59
+
60
+ def compile(node)
61
+ node.value[:text] ||= ''
62
+ find_compiler(node).compile(node)
63
+ end
64
+
65
+ private
66
+
67
+ def find_compiler(node)
68
+ name = node.value[:name].to_sym
69
+ compiler = Filters.registered[name]
70
+ raise FilterNotFound.new("FilterCompiler for '#{name}' was not found", node.line.to_i - 1) unless compiler
71
+
72
+ @compilers[name] ||= compiler.new(@options)
73
+ end
74
+ end
75
+ end