hamlit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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