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,39 @@
1
+ module Hamlit
2
+ module Compilers
3
+ module Doctype
4
+ def on_haml_doctype(format, type)
5
+ if type == 'XML'
6
+ return xml_doctype_tag(format)
7
+ elsif type
8
+ return doctype_tag(type)
9
+ end
10
+
11
+ case format
12
+ when :html4
13
+ doctype_tag(:transitional)
14
+ when :html5
15
+ doctype_tag(:html)
16
+ when :xhtml
17
+ doctype_tag(:transitional)
18
+ else
19
+ doctype_tag(format)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def xml_doctype_tag(format)
26
+ case format
27
+ when :html4, :html5
28
+ [:multi]
29
+ else
30
+ [:multi, [:static, "<?xml version='1.0' encoding='utf-8' ?>"], [:static, "\n"]]
31
+ end
32
+ end
33
+
34
+ def doctype_tag(type)
35
+ [:multi, [:html, :doctype, type.to_s], [:static, "\n"]]
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ module Hamlit
2
+ module Compilers
3
+ module Dynamic
4
+ def on_dynamic(exp)
5
+ [:dynamic, "(#{exp}).to_s"]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,53 @@
1
+ require 'hamlit/concerns/included'
2
+ require 'hamlit/concerns/registerable'
3
+ require 'hamlit/filters/coffee'
4
+ require 'hamlit/filters/css'
5
+ require 'hamlit/filters/erb'
6
+ require 'hamlit/filters/escaped'
7
+ require 'hamlit/filters/javascript'
8
+ require 'hamlit/filters/less'
9
+ require 'hamlit/filters/plain'
10
+ require 'hamlit/filters/preserve'
11
+ require 'hamlit/filters/ruby'
12
+ require 'hamlit/filters/sass'
13
+ require 'hamlit/filters/markdown'
14
+ require 'hamlit/filters/scss'
15
+
16
+ module Hamlit
17
+ module Compilers
18
+ module Filter
19
+ extend Concerns::Included
20
+
21
+ included do
22
+ extend Concerns::Registerable
23
+
24
+ define_options :format
25
+
26
+ register :coffee, Filters::Coffee
27
+ register :coffeescript, Filters::Coffee
28
+ register :css, Filters::Css
29
+ register :erb, Filters::Erb
30
+ register :escaped, Filters::Escaped
31
+ register :javascript, Filters::Javascript
32
+ register :less, Filters::Less
33
+ register :markdown, Filters::Markdown
34
+ register :plain, Filters::Plain
35
+ register :preserve, Filters::Preserve
36
+ register :ruby, Filters::Ruby
37
+ register :sass, Filters::Sass
38
+ register :scss, Filters::Scss
39
+ end
40
+
41
+ def on_haml_filter(name, lines)
42
+ ast = compile_filter(name, lines)
43
+ compile(ast)
44
+ end
45
+
46
+ private
47
+
48
+ def compile_filter(name, exp)
49
+ self.class.find(name).new(options).compile(exp)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,107 @@
1
+ require 'hamlit/concerns/ripperable'
2
+
3
+ # This module compiles new-style attributes, which is
4
+ # surrounded by parentheses.
5
+ module Hamlit
6
+ module Compilers
7
+ module NewAttribute
8
+ include Concerns::Ripperable
9
+
10
+ def compile_new_attribute(str)
11
+ str = str.gsub(/\A\(|\)\Z/, '')
12
+ attrs = parse_new_attributes(str)
13
+ attrs.map do |key, value|
14
+ next true_attribute(key) if value == 'true'
15
+ [:html, :attr, key, [:dynamic, value]]
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def parse_new_attributes(str)
22
+ attributes = {}
23
+
24
+ while str.length > 0
25
+ tokens = Ripper.lex(str)
26
+ key = read_key!(tokens)
27
+ val = read_value!(tokens)
28
+ if key && val
29
+ attributes[key] = val
30
+ elsif key
31
+ attributes[key] = 'true'
32
+ end
33
+
34
+ token = tokens.first
35
+ break unless token
36
+
37
+ pos = token.first.last
38
+ str = str[pos..-1]
39
+ end
40
+
41
+ attributes
42
+ end
43
+
44
+ def read_key!(tokens)
45
+ skip_tokens!(tokens, :on_sp)
46
+ (row, col), type, key = tokens.shift
47
+ key
48
+ end
49
+
50
+ def read_value!(tokens)
51
+ skip_tokens!(tokens, :on_sp)
52
+ (row, col), type, str = tokens.shift
53
+ return nil if str != '='
54
+
55
+ skip_tokens!(tokens, :on_sp)
56
+ return if tokens.empty?
57
+
58
+ case tokens.first[TYPE_POSITION]
59
+ when :on_tstring_beg
60
+ val = fetch_balanced_quotes(tokens)
61
+ else
62
+ val = fetch_until(tokens, :on_sp)
63
+ end
64
+ val.length.times { tokens.shift }
65
+ val.map(&:last).join
66
+ end
67
+
68
+ def fetch_balanced_quotes(all_tokens)
69
+ tokens = []
70
+ open_count = 0
71
+
72
+ all_tokens.each do |token|
73
+ (row, col), type, str = token
74
+ case type
75
+ when :on_tstring_beg then open_count += 1
76
+ when :on_tstring_end then open_count -= 1
77
+ end
78
+
79
+ tokens << token
80
+ break if open_count == 0
81
+ end
82
+
83
+ tokens
84
+ end
85
+
86
+ def fetch_until(all_tokens, type)
87
+ tokens = []
88
+
89
+ all_tokens.each do |token|
90
+ break if token[TYPE_POSITION] == type
91
+ tokens << token
92
+ end
93
+
94
+ tokens
95
+ end
96
+
97
+ def true_attribute(key)
98
+ case options[:format]
99
+ when :xhtml
100
+ [:html, :attr, key, [:static, key]]
101
+ else
102
+ [:html, :attr, key, [:multi]]
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,147 @@
1
+ require 'hamlit/attribute'
2
+ require 'hamlit/concerns/attribute_builder'
3
+ require 'hamlit/concerns/balanceable'
4
+ require 'hamlit/concerns/ripperable'
5
+
6
+ # This module compiles only old-style attribute, which is
7
+ # surrounded by brackets.
8
+ module Hamlit
9
+ module Compilers
10
+ module OldAttribute
11
+ include Concerns::AttributeBuilder
12
+ include Concerns::Balanceable
13
+ include Concerns::Ripperable
14
+
15
+ def compile_old_attribute(str)
16
+ return runtime_build(str) unless Ripper.sexp(str)
17
+
18
+ attrs = parse_old_attributes(str)
19
+ flatten_attributes(attrs).map do |key, value|
20
+ next true_attribute(key) if value == 'true'
21
+ [:html, :attr, key, [:dynamic, value]]
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # Parse brace-balanced string and return the result as hash
28
+ def parse_old_attributes(str)
29
+ attributes = {}
30
+
31
+ split_hash(str).each do |attr|
32
+ tokens = Ripper.lex("{#{attr}")
33
+ tokens = tokens.drop(1)
34
+
35
+ key = read_hash_key!(tokens)
36
+ val = tokens.map(&:last).join.strip
37
+
38
+ skip_tokens!(tokens, :on_sp)
39
+ if type_of(tokens.first) == :on_lbrace
40
+ val = parse_old_attributes(val)
41
+ end
42
+
43
+ attributes[key] = val if key
44
+ end
45
+
46
+ attributes
47
+ end
48
+
49
+ def read_hash_key!(tokens)
50
+ skip_tokens!(tokens, :on_sp)
51
+
52
+ (row, col), type, str = tokens.shift
53
+ case type
54
+ when :on_label
55
+ str.gsub!(/:\Z/, '')
56
+ when :on_symbeg
57
+ if %w[:" :'].include?(str)
58
+ str = read_string!(tokens)
59
+ else
60
+ (row, col), type, str = tokens.shift
61
+ end
62
+ assert_rocket!(tokens)
63
+ when :on_tstring_beg
64
+ str = read_string!(tokens)
65
+ assert_rocket!(tokens)
66
+ end
67
+ str
68
+ end
69
+
70
+ def read_string!(tokens)
71
+ (row, col), type, str = tokens.shift
72
+ return '' if type == :on_tstring_end
73
+
74
+ raise SyntaxError if type_of(tokens.shift) != :on_tstring_end
75
+ str
76
+ end
77
+
78
+ def assert_rocket!(tokens, *types)
79
+ skip_tokens!(tokens, :on_sp)
80
+ (row, col), type, str = tokens.shift
81
+
82
+ raise SyntaxError unless type == :on_op && str == '=>'
83
+ end
84
+
85
+ def runtime_build(str)
86
+ str = str.gsub(/(\A\{|\}\Z)/, '')
87
+ quote = options[:attr_quote].inspect
88
+ code = "::Hamlit::Attribute.build(#{quote}, #{str})"
89
+ [[:dynamic, code]]
90
+ end
91
+
92
+ def split_hash(str)
93
+ columns = HashParser.assoc_columns(str)
94
+ columns = reject_nested_columns(str, columns)
95
+
96
+ splitted = []
97
+ start_pos = 1
98
+ columns.each do |end_pos|
99
+ splitted << str[start_pos..(end_pos - 1)]
100
+ start_pos = end_pos + 1
101
+ end
102
+
103
+ splitted
104
+ end
105
+
106
+ def reject_nested_columns(str, columns)
107
+ result = []
108
+ open_count = 0
109
+
110
+ Ripper.lex(str).each do |(row, col), type, str|
111
+ if columns.include?(col) && open_count == 1
112
+ result << col
113
+ end
114
+
115
+ case type
116
+ when :on_lbrace
117
+ open_count += 1
118
+ when :on_rbrace
119
+ open_count -= 1
120
+ end
121
+ end
122
+ result
123
+ end
124
+
125
+ class HashParser < Ripper
126
+ attr_reader :columns
127
+
128
+ def self.assoc_columns(src)
129
+ parser = new(src)
130
+ parser.parse
131
+ parser.columns
132
+ end
133
+
134
+ def initialize(src)
135
+ super(src)
136
+ @columns = []
137
+ end
138
+
139
+ private
140
+
141
+ def on_assoc_new(*args)
142
+ @columns << column - 1
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,10 @@
1
+ module Hamlit
2
+ module Compilers
3
+ module Preserve
4
+ def on_haml_preserve(code)
5
+ code = "Hamlit::Helpers.find_and_preserve(#{code})"
6
+ [:dynamic, code]
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ require 'hamlit/concerns/escapable'
2
+ require 'hamlit/concerns/included'
3
+
4
+ module Hamlit
5
+ module Compilers
6
+ module Script
7
+ extend Concerns::Included
8
+
9
+ included do
10
+ include Concerns::Escapable
11
+ end
12
+
13
+ def on_haml_script(code, options, *exps)
14
+ variable = result_identifier
15
+
16
+ assign = [:code, "#{variable} = #{code}"]
17
+ result = escape_html([:dynamic, variable], options[:force_escape])
18
+ result = [:dynamic, variable] if options[:disable_escape]
19
+ [:multi, assign, *exps.map { |exp| compile(exp) }, compile(result)]
20
+ end
21
+
22
+ private
23
+
24
+ def result_identifier
25
+ @id_auto_increment ||= -1
26
+ @id_auto_increment += 1
27
+ "_hamlit_compiler#{@id_auto_increment}"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ module Hamlit
2
+ module Compilers
3
+ module Strip
4
+ def on_haml_strip(*exps)
5
+ stripped = strip_newline(exps)
6
+ on_multi(*stripped)
7
+ end
8
+
9
+ private
10
+
11
+ def strip_newline(content)
12
+ indexes = newline_indexes(content)
13
+ return content if indexes.length < 2
14
+
15
+ content = content.dup
16
+ content.delete_at(indexes.last)
17
+ content.delete_at(indexes.first)
18
+ content
19
+ end
20
+
21
+ def newline_indexes(exps)
22
+ indexes = []
23
+ exps.each_with_index do |exp, index|
24
+ indexes << index if exp == [:static, "\n"]
25
+ end
26
+ indexes
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ require 'hamlit/concerns/string_interpolation'
2
+
3
+ module Hamlit
4
+ module Compilers
5
+ module Text
6
+ include Concerns::StringInterpolation
7
+
8
+ def on_haml_text(exp)
9
+ return [:static, exp] unless contains_interpolation?(exp)
10
+
11
+ [:dynamic, string_literal(exp)]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module Hamlit
2
+ module Concerns
3
+ module AttributeBuilder
4
+ def flatten_attributes(attributes)
5
+ flattened = {}
6
+
7
+ attributes.each do |key, value|
8
+ case value
9
+ when Hash
10
+ flatten_attributes(value).each do |k, v|
11
+ flattened["#{key}-#{k}"] = v
12
+ end
13
+ else
14
+ flattened[key] = value
15
+ end
16
+ end
17
+ flattened
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ require 'ripper'
2
+
3
+ module Hamlit
4
+ module Concerns
5
+ module Balanceable
6
+ def fetch_balanced_braces(all_tokens)
7
+ fetch_balanced_tokens(all_tokens, :on_lbrace, :on_rbrace)
8
+ end
9
+
10
+ def fetch_balanced_parentheses(all_tokens)
11
+ fetch_balanced_tokens(all_tokens, :on_lparen, :on_rparen)
12
+ end
13
+
14
+ def balanced_braces_exist?(tokens)
15
+ balanced_tokens_exist?(tokens, :on_lbrace, :on_rbrace)
16
+ end
17
+
18
+ def balanced_parens_exist?(tokens)
19
+ balanced_tokens_exist?(tokens, :on_lparen, :on_rparen)
20
+ end
21
+
22
+ private
23
+
24
+ def fetch_balanced_tokens(all_tokens, open_token, close_token)
25
+ tokens = []
26
+ open_count = 0
27
+
28
+ all_tokens.each_with_index do |token, index|
29
+ (row, col), type, str = token
30
+ case type
31
+ when open_token then open_count += 1
32
+ when close_token then open_count -= 1
33
+ end
34
+
35
+ tokens << token
36
+ break if open_count == 0
37
+ end
38
+
39
+ tokens
40
+ end
41
+
42
+ def balanced_tokens_exist?(tokens, open_token, close_token)
43
+ open_count = 0
44
+
45
+ tokens.each do |token|
46
+ (row, col), type, str = token
47
+ case type
48
+ when open_token then open_count += 1
49
+ when close_token then open_count -= 1
50
+ end
51
+
52
+ break if open_count == 0
53
+ end
54
+
55
+ open_count == 0
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,20 @@
1
+ # A module to suppress deprecation warnings by temple.
2
+ # These deprecated options are specified in haml-spec.
3
+ module Hamlit
4
+ module Concerns
5
+ module Deprecation
6
+ DEPCATED_OPTIONS = %i[html4 html5].freeze
7
+
8
+ def initialize(opts = {})
9
+ super rewrite_deprecated_options(opts)
10
+ end
11
+
12
+ # Temple's warning is noisy in haml-spec.
13
+ def rewrite_deprecated_options(options)
14
+ options = options.dup
15
+ options[:format] = :html if DEPCATED_OPTIONS.include?(options[:format])
16
+ options
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ module Hamlit
2
+ class SyntaxError < StandardError; end
3
+ class CompileError < StandardError; end
4
+
5
+ module Concerns
6
+ module Error
7
+ # Template engine should raise Exception on runtime to
8
+ # show template's error backtrace.
9
+ def syntax_error(message)
10
+ code = %Q{raise Hamlit::SyntaxError.new(%q{#{message}})}
11
+ [:code, code]
12
+ end
13
+
14
+ def assert!(message)
15
+ raise CompileError.new(message)
16
+ end
17
+
18
+ def assert_scan!(scanner, regexp)
19
+ result = scanner.scan(regexp)
20
+ unless result
21
+ raise CompileError.new("Expected to scan #{regexp} but got nil")
22
+ end
23
+ result
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ require 'hamlit/concerns/included'
2
+
3
+ module Hamlit
4
+ module Concerns
5
+ module Escapable
6
+ extend Included
7
+
8
+ included do
9
+ define_options escape_html: false
10
+ end
11
+
12
+ def escape_html(exp, force_escape = false)
13
+ [:escape, force_escape || @options[:escape_html], exp]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # Mini ActiveSupport::Concern
2
+ module Hamlit
3
+ module Concerns
4
+ class MultipleIncludedBlocks < StandardError
5
+ def initialize
6
+ super "Cannot define multiple 'included' blocks for a Concern"
7
+ end
8
+ end
9
+
10
+ module Included
11
+ def self.extended(klass)
12
+ klass.class_eval do
13
+ def self.included(base = nil, &block)
14
+ if block_given?
15
+ raise MultipleIncludedBlocks if defined?(@_included_block)
16
+
17
+ @_included_block = block
18
+ return
19
+ end
20
+
21
+ base.instance_exec(&@_included_block) if defined?(@_included_block)
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,53 @@
1
+ require 'hamlit/concerns/error'
2
+
3
+ module Hamlit
4
+ EOF = -1
5
+
6
+ module Concerns
7
+ module Indentable
8
+ include Concerns::Error
9
+
10
+ def reset_indent
11
+ @current_indent = 0
12
+ end
13
+
14
+ # Return nearest line's indent level since next line. This method ignores
15
+ # empty line. It returns -1 if next_line does not exist.
16
+ def next_indent
17
+ count_indent(next_line)
18
+ end
19
+
20
+ def next_width
21
+ count_width(next_line)
22
+ end
23
+
24
+ def with_indented(&block)
25
+ @current_indent += 1
26
+ result = block.call
27
+ @current_indent -= 1
28
+
29
+ result
30
+ end
31
+
32
+ def count_indent(line, strict: false)
33
+ return EOF unless line
34
+ width = count_width(line)
35
+
36
+ return (width + 1) / 2 unless strict
37
+ assert!('Expected to count even-width indent') if width.odd?
38
+
39
+ width / 2
40
+ end
41
+
42
+ def count_width(line)
43
+ return EOF unless line
44
+ line[/\A +/].to_s.length
45
+ end
46
+
47
+ def same_indent?(line)
48
+ return false unless line
49
+ count_indent(line) == @current_indent
50
+ end
51
+ end
52
+ end
53
+ end