hamlit 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.
Files changed (162) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +34 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +23 -0
  7. data/README.md +45 -0
  8. data/Rakefile +34 -0
  9. data/benchmarks/benchmark.rb +94 -0
  10. data/benchmarks/context.rb +13 -0
  11. data/benchmarks/view.erb +23 -0
  12. data/benchmarks/view.haml +18 -0
  13. data/benchmarks/view.rbhtml +23 -0
  14. data/benchmarks/view.slim +17 -0
  15. data/bin/hamlit +6 -0
  16. data/hamlit.gemspec +40 -0
  17. data/lib/hamlit/attribute.rb +31 -0
  18. data/lib/hamlit/cli.rb +89 -0
  19. data/lib/hamlit/compiler.rb +24 -0
  20. data/lib/hamlit/compilers/attributes.rb +78 -0
  21. data/lib/hamlit/compilers/comment.rb +13 -0
  22. data/lib/hamlit/compilers/doctype.rb +39 -0
  23. data/lib/hamlit/compilers/dynamic.rb +9 -0
  24. data/lib/hamlit/compilers/filter.rb +53 -0
  25. data/lib/hamlit/compilers/new_attribute.rb +107 -0
  26. data/lib/hamlit/compilers/old_attribute.rb +147 -0
  27. data/lib/hamlit/compilers/preserve.rb +10 -0
  28. data/lib/hamlit/compilers/script.rb +31 -0
  29. data/lib/hamlit/compilers/strip.rb +30 -0
  30. data/lib/hamlit/compilers/text.rb +15 -0
  31. data/lib/hamlit/concerns/attribute_builder.rb +21 -0
  32. data/lib/hamlit/concerns/balanceable.rb +59 -0
  33. data/lib/hamlit/concerns/deprecation.rb +20 -0
  34. data/lib/hamlit/concerns/error.rb +27 -0
  35. data/lib/hamlit/concerns/escapable.rb +17 -0
  36. data/lib/hamlit/concerns/included.rb +28 -0
  37. data/lib/hamlit/concerns/indentable.rb +53 -0
  38. data/lib/hamlit/concerns/line_reader.rb +60 -0
  39. data/lib/hamlit/concerns/registerable.rb +24 -0
  40. data/lib/hamlit/concerns/ripperable.rb +20 -0
  41. data/lib/hamlit/concerns/string_interpolation.rb +48 -0
  42. data/lib/hamlit/engine.rb +42 -0
  43. data/lib/hamlit/filters/base.rb +44 -0
  44. data/lib/hamlit/filters/coffee.rb +12 -0
  45. data/lib/hamlit/filters/css.rb +34 -0
  46. data/lib/hamlit/filters/erb.rb +11 -0
  47. data/lib/hamlit/filters/escaped.rb +19 -0
  48. data/lib/hamlit/filters/javascript.rb +34 -0
  49. data/lib/hamlit/filters/less.rb +12 -0
  50. data/lib/hamlit/filters/markdown.rb +11 -0
  51. data/lib/hamlit/filters/plain.rb +21 -0
  52. data/lib/hamlit/filters/preserve.rb +11 -0
  53. data/lib/hamlit/filters/ruby.rb +11 -0
  54. data/lib/hamlit/filters/sass.rb +12 -0
  55. data/lib/hamlit/filters/scss.rb +12 -0
  56. data/lib/hamlit/filters/tilt.rb +41 -0
  57. data/lib/hamlit/helpers.rb +38 -0
  58. data/lib/hamlit/html/pretty.rb +49 -0
  59. data/lib/hamlit/html/ugly.rb +10 -0
  60. data/lib/hamlit/parser.rb +123 -0
  61. data/lib/hamlit/parsers/attribute.rb +52 -0
  62. data/lib/hamlit/parsers/comment.rb +27 -0
  63. data/lib/hamlit/parsers/doctype.rb +18 -0
  64. data/lib/hamlit/parsers/filter.rb +18 -0
  65. data/lib/hamlit/parsers/multiline.rb +54 -0
  66. data/lib/hamlit/parsers/script.rb +93 -0
  67. data/lib/hamlit/parsers/tag.rb +71 -0
  68. data/lib/hamlit/parsers/text.rb +11 -0
  69. data/lib/hamlit/parsers/whitespace.rb +38 -0
  70. data/lib/hamlit/railtie.rb +21 -0
  71. data/lib/hamlit/template.rb +9 -0
  72. data/lib/hamlit/version.rb +3 -0
  73. data/lib/hamlit.rb +8 -0
  74. data/spec/Rakefile +79 -0
  75. data/spec/hamlit/compilers/script_spec.rb +46 -0
  76. data/spec/hamlit/engine/comment_spec.rb +29 -0
  77. data/spec/hamlit/engine/doctype_spec.rb +19 -0
  78. data/spec/hamlit/engine/error_spec.rb +47 -0
  79. data/spec/hamlit/engine/multiline_spec.rb +44 -0
  80. data/spec/hamlit/engine/new_attribute_spec.rb +19 -0
  81. data/spec/hamlit/engine/old_attributes_spec.rb +85 -0
  82. data/spec/hamlit/engine/preservation_spec.rb +11 -0
  83. data/spec/hamlit/engine/script_spec.rb +87 -0
  84. data/spec/hamlit/engine/silent_script_spec.rb +135 -0
  85. data/spec/hamlit/engine/tag_spec.rb +210 -0
  86. data/spec/hamlit/engine/text_spec.rb +29 -0
  87. data/spec/hamlit/engine_spec.rb +20 -0
  88. data/spec/hamlit/filters/coffee_spec.rb +41 -0
  89. data/spec/hamlit/filters/css_spec.rb +33 -0
  90. data/spec/hamlit/filters/erb_spec.rb +16 -0
  91. data/spec/hamlit/filters/javascript_spec.rb +82 -0
  92. data/spec/hamlit/filters/less_spec.rb +22 -0
  93. data/spec/hamlit/filters/markdown_spec.rb +28 -0
  94. data/spec/hamlit/filters/ruby_spec.rb +24 -0
  95. data/spec/hamlit/filters/sass_spec.rb +37 -0
  96. data/spec/hamlit/filters/scss_spec.rb +21 -0
  97. data/spec/hamlit/ugly_spec.rb +912 -0
  98. data/spec/rails/.gitignore +17 -0
  99. data/spec/rails/.rspec +2 -0
  100. data/spec/rails/Gemfile +19 -0
  101. data/spec/rails/Gemfile.lock +177 -0
  102. data/spec/rails/README.rdoc +28 -0
  103. data/spec/rails/Rakefile +6 -0
  104. data/spec/rails/app/assets/images/.keep +0 -0
  105. data/spec/rails/app/assets/javascripts/application.js +15 -0
  106. data/spec/rails/app/assets/stylesheets/application.css +15 -0
  107. data/spec/rails/app/controllers/application_controller.rb +8 -0
  108. data/spec/rails/app/controllers/concerns/.keep +0 -0
  109. data/spec/rails/app/controllers/users_controller.rb +20 -0
  110. data/spec/rails/app/helpers/application_helper.rb +2 -0
  111. data/spec/rails/app/mailers/.keep +0 -0
  112. data/spec/rails/app/models/.keep +0 -0
  113. data/spec/rails/app/models/concerns/.keep +0 -0
  114. data/spec/rails/app/views/application/index.html.haml +15 -0
  115. data/spec/rails/app/views/layouts/application.html.haml +12 -0
  116. data/spec/rails/app/views/users/capture.html.haml +5 -0
  117. data/spec/rails/app/views/users/capture_haml.html.haml +5 -0
  118. data/spec/rails/app/views/users/form.html.haml +2 -0
  119. data/spec/rails/app/views/users/helpers.html.haml +1 -0
  120. data/spec/rails/app/views/users/index.html.haml +9 -0
  121. data/spec/rails/app/views/users/safe_buffer.html.haml +4 -0
  122. data/spec/rails/bin/bundle +3 -0
  123. data/spec/rails/bin/rails +8 -0
  124. data/spec/rails/bin/rake +8 -0
  125. data/spec/rails/bin/setup +29 -0
  126. data/spec/rails/bin/spring +15 -0
  127. data/spec/rails/config/application.rb +34 -0
  128. data/spec/rails/config/boot.rb +3 -0
  129. data/spec/rails/config/database.yml +25 -0
  130. data/spec/rails/config/environment.rb +5 -0
  131. data/spec/rails/config/environments/development.rb +41 -0
  132. data/spec/rails/config/environments/production.rb +79 -0
  133. data/spec/rails/config/environments/test.rb +42 -0
  134. data/spec/rails/config/initializers/assets.rb +11 -0
  135. data/spec/rails/config/initializers/backtrace_silencers.rb +7 -0
  136. data/spec/rails/config/initializers/cookies_serializer.rb +3 -0
  137. data/spec/rails/config/initializers/filter_parameter_logging.rb +4 -0
  138. data/spec/rails/config/initializers/inflections.rb +16 -0
  139. data/spec/rails/config/initializers/mime_types.rb +4 -0
  140. data/spec/rails/config/initializers/session_store.rb +3 -0
  141. data/spec/rails/config/initializers/wrap_parameters.rb +14 -0
  142. data/spec/rails/config/locales/en.yml +23 -0
  143. data/spec/rails/config/routes.rb +13 -0
  144. data/spec/rails/config/secrets.yml +22 -0
  145. data/spec/rails/config.ru +4 -0
  146. data/spec/rails/db/schema.rb +16 -0
  147. data/spec/rails/db/seeds.rb +7 -0
  148. data/spec/rails/lib/assets/.keep +0 -0
  149. data/spec/rails/lib/tasks/.keep +0 -0
  150. data/spec/rails/log/.keep +0 -0
  151. data/spec/rails/public/404.html +67 -0
  152. data/spec/rails/public/422.html +67 -0
  153. data/spec/rails/public/500.html +66 -0
  154. data/spec/rails/public/favicon.ico +0 -0
  155. data/spec/rails/public/robots.txt +5 -0
  156. data/spec/rails/spec/hamlit_spec.rb +87 -0
  157. data/spec/rails/spec/rails_helper.rb +56 -0
  158. data/spec/rails/spec/spec_helper.rb +91 -0
  159. data/spec/rails/vendor/assets/javascripts/.keep +0 -0
  160. data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
  161. data/spec/spec_helper.rb +48 -0
  162. metadata +560 -0
@@ -0,0 +1,123 @@
1
+ require 'strscan'
2
+ require 'temple'
3
+ require 'hamlit/parsers/attribute'
4
+ require 'hamlit/parsers/comment'
5
+ require 'hamlit/parsers/doctype'
6
+ require 'hamlit/parsers/filter'
7
+ require 'hamlit/parsers/multiline'
8
+ require 'hamlit/parsers/script'
9
+ require 'hamlit/parsers/tag'
10
+ require 'hamlit/parsers/text'
11
+ require 'hamlit/parsers/whitespace'
12
+
13
+ module Hamlit
14
+ class Parser < Temple::Parser
15
+ include Parsers::Attribute
16
+ include Parsers::Comment
17
+ include Parsers::Doctype
18
+ include Parsers::Filter
19
+ include Parsers::Multiline
20
+ include Parsers::Script
21
+ include Parsers::Tag
22
+ include Parsers::Text
23
+ include Parsers::Whitespace
24
+
25
+ SKIP_NEWLINE_EXPS = %i[newline code multi].freeze
26
+ SKIP_NEWLINE_FILTERS = %w[ruby markdown erb].freeze
27
+
28
+ define_options :format
29
+
30
+ def call(template)
31
+ reset(template)
32
+
33
+ ast = [:multi]
34
+ ast += parse_lines
35
+ ast
36
+ end
37
+
38
+ private
39
+
40
+ # Reset the parser state.
41
+ def reset(template)
42
+ template = preprocess_multilines(template)
43
+ reset_lines(template.split("\n"))
44
+ reset_indent
45
+ reset_outer_removal
46
+ end
47
+
48
+ # Parse lines in current_indent and return ASTs.
49
+ def parse_lines
50
+ ast = []
51
+ loop do
52
+ width = next_width
53
+ if width != @current_indent * 2
54
+ if width != Hamlit::EOF && (width > @current_indent * 2 || width.odd?)
55
+ ast << [:multi, [:newline], [:newline]]
56
+ ast << syntax_error(
57
+ "inconsistent indentation: #{2 * @current_indent} spaces used for indentation, "\
58
+ "but the rest of the document was indented using #{width} spaces"
59
+ )
60
+ end
61
+ break
62
+ end
63
+
64
+ @current_lineno += 1
65
+ node = parse_line(current_line)
66
+ if @outer_removal.include?(@current_indent) && ast.last == [:static, "\n"]
67
+ ast.delete_at(-1)
68
+ end
69
+ ast << node
70
+ ast << [:newline]
71
+ ast << [:static, "\n"] unless skip_newline?(node)
72
+ end
73
+ ast
74
+ end
75
+
76
+ # Parse current line and return AST.
77
+ def parse_line(line)
78
+ return [:multi] if empty_line?(line)
79
+
80
+ scanner = StringScanner.new(line)
81
+ scanner.scan(/ +/)
82
+ if scanner.match?(/\#{/)
83
+ return parse_text(scanner)
84
+ elsif scanner.match?(/&=/)
85
+ return parse_script(scanner, force_escape: true)
86
+ elsif scanner.match?(/!=/)
87
+ return parse_script(scanner, disable_escape: true)
88
+ elsif scanner.match?(/[.#](\Z|[^a-zA-Z0-9_-])/)
89
+ return parse_text(scanner)
90
+ end
91
+
92
+ case scanner.peek(1)
93
+ when '!'
94
+ parse_doctype(scanner)
95
+ when '%', '.', '#'
96
+ parse_tag(scanner)
97
+ when '='
98
+ parse_script(scanner)
99
+ when '~'
100
+ parse_preserve(scanner)
101
+ when '-'
102
+ parse_silent_script(scanner)
103
+ when '/'
104
+ parse_comment(scanner)
105
+ when ':'
106
+ parse_filter(scanner)
107
+ else
108
+ parse_text(scanner)
109
+ end
110
+ end
111
+
112
+ def skip_newline?(ast)
113
+ SKIP_NEWLINE_EXPS.include?(ast.first) ||
114
+ (ast[0..1] == [:haml, :doctype]) ||
115
+ newline_skip_filter?(ast) ||
116
+ @outer_removal.include?(@current_indent)
117
+ end
118
+
119
+ def newline_skip_filter?(ast)
120
+ ast[0..1] == [:haml, :filter] && SKIP_NEWLINE_FILTERS.include?(ast[2])
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,52 @@
1
+ require 'ripper'
2
+ require 'hamlit/concerns/balanceable'
3
+
4
+ module Hamlit
5
+ module Parsers
6
+ module Attribute
7
+ include Concerns::Balanceable
8
+
9
+ def parse_attributes(scanner)
10
+ if scanner.match?(/{/)
11
+ parse_old_attributes(scanner) + parse_new_attributes(scanner)
12
+ else
13
+ parse_new_attributes(scanner) + parse_old_attributes(scanner)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def parse_old_attributes(scanner)
20
+ return [] unless scanner.match?(/{/)
21
+
22
+ tokens = Ripper.lex(scanner.rest)
23
+ until balanced_braces_exist?(tokens)
24
+ @current_lineno += 1
25
+ break unless @lines[@current_lineno]
26
+ scanner.concat(current_line)
27
+ tokens = Ripper.lex(scanner.rest)
28
+ end
29
+
30
+ tokens = fetch_balanced_braces(tokens)
31
+ scanner.pos += tokens.last.first.last + 1
32
+ [tokens.map(&:last).join]
33
+ end
34
+
35
+ def parse_new_attributes(scanner)
36
+ return [] unless scanner.match?(/\(/)
37
+
38
+ tokens = Ripper.lex(scanner.rest)
39
+ until balanced_parens_exist?(tokens)
40
+ @current_lineno += 1
41
+ break unless @lines[@current_lineno]
42
+ scanner.concat(current_line)
43
+ tokens = Ripper.lex(scanner.rest)
44
+ end
45
+
46
+ tokens = fetch_balanced_parentheses(tokens)
47
+ scanner.pos += tokens.last.first.last + 1
48
+ [tokens.map(&:last).join]
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ require 'hamlit/concerns/error'
2
+ require 'hamlit/concerns/indentable'
3
+
4
+ module Hamlit
5
+ module Parsers
6
+ module Comment
7
+ include Concerns::Indentable
8
+
9
+ def parse_comment(scanner)
10
+ assert_scan!(scanner, /\//)
11
+
12
+ ast = [:html, :comment]
13
+ text = (scanner.scan(/.+/) || '').strip
14
+
15
+ if text.empty?
16
+ content = with_indented { parse_lines }
17
+ return ast << [:multi, [:static, "\n"], *content]
18
+ elsif !text.match(/\[.*\]/)
19
+ return ast << [:static, " #{text} "]
20
+ end
21
+
22
+ content = with_indented { parse_lines }
23
+ [:haml, :comment, text, content]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ require 'hamlit/concerns/error'
2
+
3
+ module Hamlit
4
+ module Parsers
5
+ module Doctype
6
+ def parse_doctype(scanner)
7
+ assert_scan!(scanner, /!!!/)
8
+
9
+ type = nil
10
+ if scanner.scan(/ +/) && scanner.rest?
11
+ type = scanner.rest.strip
12
+ end
13
+
14
+ [:haml, :doctype, options[:format], type]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ require 'hamlit/concerns/error'
2
+ require 'hamlit/concerns/indentable'
3
+
4
+ module Hamlit
5
+ module Parsers
6
+ module Filter
7
+ include Concerns::Indentable
8
+
9
+ def parse_filter(scanner)
10
+ assert_scan!(scanner, /:/)
11
+
12
+ name = scanner.scan(/.+/).strip
13
+ lines = with_indented { read_lines }
14
+ [:haml, :filter, name, lines]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ require 'hamlit/concerns/line_reader'
2
+
3
+ module Hamlit
4
+ module Parsers
5
+ module Multiline
6
+ include Concerns::LineReader
7
+
8
+ def preprocess_multilines(template)
9
+ reset_lines(template.split("\n"))
10
+ result = []
11
+
12
+ while @lines[@current_lineno + 1]
13
+ @current_lineno += 1
14
+
15
+ unless end_with_pipe?(current_line)
16
+ result << current_line
17
+ next
18
+ end
19
+
20
+ prefix = current_line[/\A */]
21
+ lines = scan_multilines
22
+
23
+ result << prefix + build_multiline(lines)
24
+ (lines.length - 1).times { result << '' }
25
+ end
26
+ result.map { |line| "#{line}\n" }.join
27
+ end
28
+
29
+ private
30
+
31
+ def end_with_pipe?(line)
32
+ return false unless line
33
+ line.strip =~ / \|\Z/
34
+ end
35
+
36
+ def scan_multilines
37
+ lines = []
38
+ while end_with_pipe?(current_line)
39
+ lines << current_line
40
+ @current_lineno += 1
41
+ end
42
+ @current_lineno -= 1
43
+ lines
44
+ end
45
+
46
+ def build_multiline(lines)
47
+ lines = lines.map do |line|
48
+ line.strip.gsub(/ *\|\Z/, '')
49
+ end
50
+ lines.join(' ')
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,93 @@
1
+ require 'hamlit/concerns/escapable'
2
+ require 'hamlit/concerns/error'
3
+ require 'hamlit/concerns/included'
4
+ require 'hamlit/concerns/indentable'
5
+
6
+ module Hamlit
7
+ module Parsers
8
+ module Script
9
+ extend Concerns::Included
10
+ include Concerns::Error
11
+ include Concerns::Indentable
12
+
13
+ INTERNAL_STATEMENTS = %w[else elsif when].freeze
14
+ DEFAULT_SCRIPT_OPTIONS = { force_escape: false, disable_escape: false }.freeze
15
+
16
+ included do
17
+ include Concerns::Escapable
18
+ end
19
+
20
+ def parse_script(scanner, options = {})
21
+ assert_scan!(scanner, /=|&=|!=/)
22
+ options = DEFAULT_SCRIPT_OPTIONS.merge(options)
23
+
24
+ code = scan_code(scanner)
25
+ return syntax_error("There's no Ruby code for = to evaluate.") if code.empty?
26
+ unless has_block?
27
+ return [:dynamic, code] if options[:disable_escape]
28
+ return escape_html([:dynamic, code], options[:force_escape])
29
+ end
30
+
31
+ ast = [:haml, :script, code, options]
32
+ ast += with_indented { parse_lines }
33
+ ast << [:code, 'end']
34
+ ast
35
+ end
36
+
37
+ def parse_preserve(scanner)
38
+ assert_scan!(scanner, /~/)
39
+
40
+ code = scan_code(scanner)
41
+ escape_html([:haml, :preserve, code])
42
+ end
43
+
44
+ def parse_silent_script(scanner)
45
+ assert_scan!(scanner, /-/)
46
+ if scanner.scan(/#/)
47
+ with_indented { skip_lines }
48
+ return [:multi]
49
+ end
50
+
51
+ ast = [:code]
52
+ ast << scan_code(scanner)
53
+ return ast unless has_block?
54
+
55
+ ast = [:multi, ast]
56
+ ast += with_indented { parse_lines }
57
+ ast << [:code, 'end'] unless same_indent?(next_line) && internal_statement?(next_line)
58
+ ast
59
+ end
60
+
61
+ private
62
+
63
+ def scan_code(scanner)
64
+ code = ''
65
+ loop do
66
+ code += (scanner.scan(/.+/) || '').strip
67
+ break unless code =~ /,\Z/
68
+
69
+ @current_lineno += 1
70
+ scanner = StringScanner.new(current_line)
71
+ code += ' '
72
+ end
73
+ code
74
+ end
75
+
76
+ def has_block?
77
+ next_indent == @current_indent + 1
78
+ end
79
+
80
+ def internal_statement?(line)
81
+ return false unless line
82
+
83
+ scanner = StringScanner.new(line)
84
+ scanner.scan(/ +/)
85
+ return false unless scanner.scan(/-/)
86
+
87
+ scanner.scan(/ +/)
88
+ statement = scanner.scan(/[^ ]+/)
89
+ INTERNAL_STATEMENTS.include?(statement)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,71 @@
1
+ require 'hamlit/concerns/error'
2
+ require 'hamlit/concerns/indentable'
3
+ require 'hamlit/helpers'
4
+
5
+ module Hamlit
6
+ module Parsers
7
+ module Tag
8
+ include Concerns::Error
9
+ include Concerns::Indentable
10
+
11
+ TAG_ID_CLASS_REGEXP = /[a-zA-Z0-9_-]+/
12
+ TAG_REGEXP = /[a-zA-Z0-9\-_:]+/
13
+ DEFAULT_TAG = 'div'
14
+
15
+ def parse_tag(scanner)
16
+ tag = DEFAULT_TAG
17
+ tag = scanner.scan(TAG_REGEXP) if scanner.scan(/%/)
18
+
19
+ attrs = [:haml, :attrs]
20
+ attrs += parse_tag_id_and_class(scanner)
21
+ attrs += parse_attributes(scanner)
22
+
23
+ inner_removal = parse_whitespace_removal(scanner)
24
+ ast = [:html, :tag, tag, attrs]
25
+
26
+ if scanner.match?(/=/)
27
+ ast << parse_script(scanner)
28
+ return ast
29
+ elsif scanner.scan(/\//)
30
+ return ast
31
+ elsif scanner.rest.match(/[^ ]/)
32
+ ast << parse_text(scanner)
33
+ return ast
34
+ elsif next_indent <= @current_indent
35
+ return ast << [:multi]
36
+ end
37
+
38
+ content = [:multi, [:static, "\n"]]
39
+ if inner_removal || Helpers::DEFAULT_PRESERVE_TAGS.include?(tag)
40
+ content[0, 1] = [:haml, :strip]
41
+ end
42
+ content += with_indented { parse_lines }
43
+ ast << content
44
+ ast
45
+ end
46
+
47
+ private
48
+
49
+ def parse_tag_id_and_class(scanner)
50
+ attributes = Hash.new { |h, k| h[k] = [] }
51
+
52
+ while prefix = scanner.scan(/[#.]/)
53
+ name = assert_scan!(scanner, TAG_ID_CLASS_REGEXP)
54
+
55
+ case prefix
56
+ when '#'
57
+ attributes['id'] = [name]
58
+ when '.'
59
+ attributes['class'] << name
60
+ end
61
+ end
62
+
63
+ ast = []
64
+ attributes.each do |name, values|
65
+ ast << [:html, :attr, name, [:static, values.join(' ')]]
66
+ end
67
+ ast
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,11 @@
1
+ module Hamlit
2
+ module Parsers
3
+ module Text
4
+ def parse_text(scanner)
5
+ ast = [:haml, :text]
6
+ ast << scanner.scan(/.+/).strip
7
+ ast
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ require 'set'
2
+
3
+ # Hamlit::Parsers::Whitespace cares about "whitespace removal",
4
+ # which is achieved by '<' or '>' after html tag.
5
+ module Hamlit
6
+ module Parsers
7
+ module Whitespace
8
+ def parse_whitespace_removal(scanner)
9
+ if scanner.match?(/</)
10
+ inner_removal = parse_inner_removal(scanner)
11
+ parse_outer_removal(scanner)
12
+ else
13
+ parse_outer_removal(scanner)
14
+ inner_removal = parse_inner_removal(scanner)
15
+ end
16
+ inner_removal
17
+ end
18
+
19
+ private
20
+
21
+ def reset_outer_removal
22
+ @outer_removal = Set.new
23
+ end
24
+
25
+ def parse_inner_removal(scanner)
26
+ scanner.scan(/</)
27
+ end
28
+
29
+ def parse_outer_removal(scanner)
30
+ if scanner.scan(/>/)
31
+ @outer_removal.add(@current_indent)
32
+ else
33
+ @outer_removal.delete(@current_indent)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ require 'rails'
2
+ require 'temple'
3
+
4
+ module Hamlit
5
+ class Railtie < ::Rails::Railtie
6
+ initializer :hamlit do |app|
7
+ Hamlit::RailsTemplate = Temple::Templates::Rails.create(
8
+ Hamlit::Engine,
9
+ generator: Temple::Generators::RailsOutputBuffer,
10
+ register_as: :haml,
11
+ escape_html: true,
12
+ streaming: true,
13
+ ugly: true,
14
+ )
15
+
16
+ # Haml extends Haml::Helpers in ActionView each time.
17
+ # It costs much, so Hamlit includes a compatible module at first.
18
+ ActionView::Base.send :include, Hamlit::Helpers
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ require 'temple'
2
+ require 'hamlit/engine'
3
+
4
+ module Hamlit
5
+ Template = Temple::Templates::Tilt.create(
6
+ Hamlit::Engine,
7
+ register_as: :haml,
8
+ )
9
+ end
@@ -0,0 +1,3 @@
1
+ module Hamlit
2
+ VERSION = "0.1.0"
3
+ end
data/lib/hamlit.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'hamlit/engine'
2
+ require 'hamlit/template'
3
+ require 'hamlit/version'
4
+
5
+ begin
6
+ require 'hamlit/railtie'
7
+ rescue LoadError
8
+ end
data/spec/Rakefile ADDED
@@ -0,0 +1,79 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'json'
4
+ require 'unindent'
5
+ require 'open-uri'
6
+
7
+ def generate_spec(mode)
8
+ spec = <<-SPEC.unindent
9
+ require "haml"
10
+
11
+ # This is a spec converted by haml-spec.
12
+ # See: https://github.com/haml/haml-spec
13
+ describe "haml #{mode} mode" do
14
+ def assert_pretty(haml, locals, options)
15
+ engine = Haml::Engine.new(haml, options)
16
+ hamlit = Hamlit::Template.new(options) { haml }
17
+ expect(hamlit.render(Object.new, locals)).to eq(engine.render(Object.new, locals))
18
+ end
19
+
20
+ def assert_ugly(haml, locals, options)
21
+ assert_pretty(haml, locals, { ugly: true }.merge(options))
22
+ end
23
+
24
+ SPEC
25
+
26
+ url = 'https://raw.githubusercontent.com/haml/haml-spec/master/tests.json'
27
+ contexts = JSON.parse(open(url).read)
28
+ contexts.each_with_index do |context, index|
29
+ spec += "\n" if index != 0
30
+ spec += " context \"#{context[0]}\" do\n"
31
+
32
+ tests = []
33
+ context[1].each do |name, test|
34
+ tests << {
35
+ name: name,
36
+ html: test['html'],
37
+ haml: test['haml'],
38
+ locals: test['locals'],
39
+ config: test['config'],
40
+ }
41
+ end
42
+
43
+ spec += tests.map { |test|
44
+ locals = Hash[(test[:locals] || {}).map {|x, y| [x.to_sym, y]}]
45
+ options = Hash[(test[:config] || {}).map {|x, y| [x.to_sym, y]}]
46
+ options[:format] = options[:format].to_sym if options[:format]
47
+
48
+ generate_specify(test, locals, options, mode)
49
+ }.join("\n")
50
+ spec += " end\n"
51
+ end
52
+
53
+ spec += "end\n"
54
+ File.write("hamlit/#{mode}_spec.rb", spec)
55
+ end
56
+
57
+ def generate_specify(test, locals, options, mode)
58
+ <<-SPEC
59
+ specify \"#{test[:name]}\" do
60
+ haml = %q{#{test[:haml]}}
61
+ html = %q{#{test[:html]}}
62
+ locals = #{locals}
63
+ options = #{options}
64
+ assert_#{mode}(haml, locals, options)
65
+ end
66
+ SPEC
67
+ end
68
+
69
+ desc 'Convert tests.json into ugly rspec tests'
70
+ task :ugly do
71
+ generate_spec(:ugly)
72
+ end
73
+
74
+ desc 'Convert tests.json into pretty rspec tests'
75
+ task :pretty do
76
+ generate_spec(:pretty)
77
+ end
78
+
79
+ task default: [:ugly, :pretty]
@@ -0,0 +1,46 @@
1
+ describe Hamlit::Compiler do
2
+ describe 'script' do
3
+ let(:options) do
4
+ { force_escape: false, disable_escape: false }
5
+ end
6
+
7
+ it 'compiles hamlit script ast into assigning' do
8
+ assert_compile(
9
+ [:haml,
10
+ :script,
11
+ 'link_to user_path do',
12
+ options,
13
+ [:static, 'user']],
14
+ [:multi,
15
+ [:code, "_hamlit_compiler0 = link_to user_path do"],
16
+ [:static, "user"],
17
+ [:escape, false, [:dynamic, "(_hamlit_compiler0).to_s"]]],
18
+ )
19
+ end
20
+
21
+ it 'compiles multiple hamlit scripts' do
22
+ assert_compile(
23
+ [:multi,
24
+ [:haml,
25
+ :script,
26
+ 'link_to user_path do',
27
+ options,
28
+ [:static, 'user']],
29
+ [:haml,
30
+ :script,
31
+ 'link_to repo_path do',
32
+ options,
33
+ [:static, 'repo']]],
34
+ [:multi,
35
+ [:multi,
36
+ [:code, "_hamlit_compiler0 = link_to user_path do"],
37
+ [:static, "user"],
38
+ [:escape, false, [:dynamic, "(_hamlit_compiler0).to_s"]]],
39
+ [:multi,
40
+ [:code, "_hamlit_compiler1 = link_to repo_path do"],
41
+ [:static, "repo"],
42
+ [:escape, false, [:dynamic, "(_hamlit_compiler1).to_s"]]]],
43
+ )
44
+ end
45
+ end
46
+ end