faml 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +27 -0
  6. data/Appraisals +26 -0
  7. data/CHANGELOG.md +47 -0
  8. data/Gemfile +8 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +115 -0
  11. data/Rakefile +28 -0
  12. data/benchmark/attribute_builder.haml +5 -0
  13. data/benchmark/rendering.rb +35 -0
  14. data/bin/faml +4 -0
  15. data/ext/attribute_builder/attribute_builder.c +261 -0
  16. data/ext/attribute_builder/extconf.rb +3 -0
  17. data/faml.gemspec +38 -0
  18. data/gemfiles/rails_4.0.gemfile +9 -0
  19. data/gemfiles/rails_4.1.gemfile +9 -0
  20. data/gemfiles/rails_4.2.gemfile +9 -0
  21. data/gemfiles/rails_edge.gemfile +10 -0
  22. data/haml_spec_test.rb +22 -0
  23. data/lib/faml.rb +10 -0
  24. data/lib/faml/ast.rb +112 -0
  25. data/lib/faml/cli.rb +38 -0
  26. data/lib/faml/compiler.rb +374 -0
  27. data/lib/faml/element_parser.rb +235 -0
  28. data/lib/faml/engine.rb +34 -0
  29. data/lib/faml/filter_compilers.rb +41 -0
  30. data/lib/faml/filter_compilers/base.rb +43 -0
  31. data/lib/faml/filter_compilers/cdata.rb +15 -0
  32. data/lib/faml/filter_compilers/coffee.rb +16 -0
  33. data/lib/faml/filter_compilers/css.rb +16 -0
  34. data/lib/faml/filter_compilers/escaped.rb +23 -0
  35. data/lib/faml/filter_compilers/javascript.rb +16 -0
  36. data/lib/faml/filter_compilers/markdown.rb +18 -0
  37. data/lib/faml/filter_compilers/plain.rb +15 -0
  38. data/lib/faml/filter_compilers/preserve.rb +26 -0
  39. data/lib/faml/filter_compilers/ruby.rb +17 -0
  40. data/lib/faml/filter_compilers/sass.rb +15 -0
  41. data/lib/faml/filter_compilers/scss.rb +16 -0
  42. data/lib/faml/filter_compilers/tilt_base.rb +34 -0
  43. data/lib/faml/filter_parser.rb +54 -0
  44. data/lib/faml/html.rb +58 -0
  45. data/lib/faml/indent_tracker.rb +84 -0
  46. data/lib/faml/line_parser.rb +66 -0
  47. data/lib/faml/newline.rb +30 -0
  48. data/lib/faml/parser.rb +211 -0
  49. data/lib/faml/parser_utils.rb +17 -0
  50. data/lib/faml/rails_handler.rb +10 -0
  51. data/lib/faml/railtie.rb +9 -0
  52. data/lib/faml/ruby_multiline.rb +23 -0
  53. data/lib/faml/script_parser.rb +84 -0
  54. data/lib/faml/static_hash_parser.rb +113 -0
  55. data/lib/faml/syntax_error.rb +10 -0
  56. data/lib/faml/text_compiler.rb +69 -0
  57. data/lib/faml/tilt.rb +17 -0
  58. data/lib/faml/version.rb +3 -0
  59. data/spec/compiler_newline_spec.rb +162 -0
  60. data/spec/rails/Rakefile +6 -0
  61. data/spec/rails/app/assets/images/.keep +0 -0
  62. data/spec/rails/app/assets/javascripts/application.js +13 -0
  63. data/spec/rails/app/assets/stylesheets/application.css +15 -0
  64. data/spec/rails/app/controllers/application_controller.rb +5 -0
  65. data/spec/rails/app/controllers/books_controller.rb +8 -0
  66. data/spec/rails/app/controllers/concerns/.keep +0 -0
  67. data/spec/rails/app/helpers/application_helper.rb +2 -0
  68. data/spec/rails/app/mailers/.keep +0 -0
  69. data/spec/rails/app/models/.keep +0 -0
  70. data/spec/rails/app/models/book.rb +9 -0
  71. data/spec/rails/app/models/concerns/.keep +0 -0
  72. data/spec/rails/app/views/books/hello.html.haml +2 -0
  73. data/spec/rails/app/views/books/with_capture.html.haml +4 -0
  74. data/spec/rails/app/views/books/with_variables.html.haml +4 -0
  75. data/spec/rails/app/views/layouts/application.html.haml +9 -0
  76. data/spec/rails/bin/bundle +3 -0
  77. data/spec/rails/bin/rails +4 -0
  78. data/spec/rails/bin/rake +4 -0
  79. data/spec/rails/bin/setup +29 -0
  80. data/spec/rails/config.ru +4 -0
  81. data/spec/rails/config/application.rb +12 -0
  82. data/spec/rails/config/boot.rb +3 -0
  83. data/spec/rails/config/database.yml +25 -0
  84. data/spec/rails/config/environment.rb +5 -0
  85. data/spec/rails/config/environments/development.rb +41 -0
  86. data/spec/rails/config/environments/production.rb +79 -0
  87. data/spec/rails/config/environments/test.rb +42 -0
  88. data/spec/rails/config/initializers/assets.rb +11 -0
  89. data/spec/rails/config/initializers/backtrace_silencers.rb +7 -0
  90. data/spec/rails/config/initializers/cookies_serializer.rb +3 -0
  91. data/spec/rails/config/initializers/filter_parameter_logging.rb +4 -0
  92. data/spec/rails/config/initializers/inflections.rb +16 -0
  93. data/spec/rails/config/initializers/mime_types.rb +4 -0
  94. data/spec/rails/config/initializers/secret_key_base.rb +6 -0
  95. data/spec/rails/config/initializers/session_store.rb +3 -0
  96. data/spec/rails/config/initializers/wrap_parameters.rb +14 -0
  97. data/spec/rails/config/locales/en.yml +23 -0
  98. data/spec/rails/config/routes.rb +7 -0
  99. data/spec/rails/config/secrets.yml +22 -0
  100. data/spec/rails/db/seeds.rb +7 -0
  101. data/spec/rails/lib/assets/.keep +0 -0
  102. data/spec/rails/lib/tasks/.keep +0 -0
  103. data/spec/rails/log/.keep +0 -0
  104. data/spec/rails/public/404.html +67 -0
  105. data/spec/rails/public/422.html +67 -0
  106. data/spec/rails/public/500.html +66 -0
  107. data/spec/rails/public/favicon.ico +0 -0
  108. data/spec/rails/public/robots.txt +5 -0
  109. data/spec/rails/spec/requests/faml_spec.rb +41 -0
  110. data/spec/rails/vendor/assets/javascripts/.keep +0 -0
  111. data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
  112. data/spec/rails_helper.rb +4 -0
  113. data/spec/render/attribute_spec.rb +241 -0
  114. data/spec/render/comment_spec.rb +61 -0
  115. data/spec/render/doctype_spec.rb +57 -0
  116. data/spec/render/element_spec.rb +136 -0
  117. data/spec/render/filters/cdata_spec.rb +12 -0
  118. data/spec/render/filters/coffee_spec.rb +25 -0
  119. data/spec/render/filters/css_spec.rb +45 -0
  120. data/spec/render/filters/escaped_spec.rb +14 -0
  121. data/spec/render/filters/javascript_spec.rb +44 -0
  122. data/spec/render/filters/markdown_spec.rb +19 -0
  123. data/spec/render/filters/plain_spec.rb +24 -0
  124. data/spec/render/filters/preserve_spec.rb +24 -0
  125. data/spec/render/filters/ruby_spec.rb +13 -0
  126. data/spec/render/filters/sass_spec.rb +28 -0
  127. data/spec/render/filters/scss_spec.rb +32 -0
  128. data/spec/render/filters_spec.rb +11 -0
  129. data/spec/render/haml_comment_spec.rb +24 -0
  130. data/spec/render/multiline_spec.rb +39 -0
  131. data/spec/render/newline_spec.rb +83 -0
  132. data/spec/render/plain_spec.rb +20 -0
  133. data/spec/render/preserve_spec.rb +8 -0
  134. data/spec/render/sanitize_spec.rb +36 -0
  135. data/spec/render/script_spec.rb +81 -0
  136. data/spec/render/silent_script_spec.rb +97 -0
  137. data/spec/render/unescape_spec.rb +45 -0
  138. data/spec/spec_helper.rb +47 -0
  139. data/spec/tilt_spec.rb +33 -0
  140. metadata +489 -0
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile('faml/attribute_builder')
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'faml/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "faml"
8
+ spec.version = Faml::VERSION
9
+ spec.authors = ["Kohei Suzuki"]
10
+ spec.email = ["eagletmt@gmail.com"]
11
+ spec.summary = %q{Faster implementation of Haml template language.}
12
+ spec.description = %q{Faster implementation of Haml template language.}
13
+ spec.homepage = "https://github.com/eagletmt/faml"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.extensions = ['ext/attribute_builder/extconf.rb']
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "escape_utils"
23
+ spec.add_dependency "parser"
24
+ spec.add_dependency "temple"
25
+ spec.add_dependency "tilt"
26
+ spec.add_development_dependency "appraisal"
27
+ spec.add_development_dependency "benchmark-ips"
28
+ spec.add_development_dependency "bundler"
29
+ spec.add_development_dependency "coffee-script"
30
+ spec.add_development_dependency "coveralls"
31
+ spec.add_development_dependency "haml"
32
+ spec.add_development_dependency "rake"
33
+ spec.add_development_dependency "rake-compiler"
34
+ spec.add_development_dependency "redcarpet"
35
+ spec.add_development_dependency "rspec", ">= 3"
36
+ spec.add_development_dependency "sass"
37
+ spec.add_development_dependency "simplecov"
38
+ end
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.0.0"
6
+ gem "rspec-rails"
7
+ gem "sqlite3"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.1.0"
6
+ gem "rspec-rails"
7
+ gem "sqlite3"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.2.0"
6
+ gem "rspec-rails"
7
+ gem "sqlite3"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", :git => "https://github.com/rails/rails"
6
+ gem "rspec-rails"
7
+ gem "sqlite3"
8
+ gem "arel", :git => "https://github.com/rails/arel"
9
+
10
+ gemspec :path => "../"
@@ -0,0 +1,22 @@
1
+ require 'minitest/autorun'
2
+ require 'json'
3
+ require 'faml'
4
+
5
+ class HamlTest < Minitest::Test
6
+ contexts = JSON.parse(File.read(File.join(__dir__, 'haml-spec', 'tests.json')))
7
+ contexts.each do |context|
8
+ context[1].each do |name, test|
9
+ define_method("test_spec: #{name} (#{context[0]})") do
10
+ html = test["html"]
11
+ haml = test["haml"]
12
+ locals = Hash[(test["locals"] || {}).map {|x, y| [x.to_sym, y]}]
13
+ options = Hash[(test["config"] || {}).map {|x, y| [x.to_sym, y]}]
14
+ options[:format] = options[:format].to_sym if options.key?(:format)
15
+ tilt = Tilt.new("#{name}.haml", nil, options) { haml }
16
+ result = tilt.render(Object.new, locals)
17
+
18
+ assert_equal html, result.strip
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ require 'faml/engine'
2
+ require 'faml/tilt'
3
+ require 'faml/version'
4
+
5
+ begin
6
+ gem 'rails'
7
+ require 'rails'
8
+ require 'faml/railtie'
9
+ rescue LoadError
10
+ end
@@ -0,0 +1,112 @@
1
+ module Faml
2
+ module Ast
3
+ module HasChildren
4
+ def initialize(*)
5
+ super
6
+ self.children ||= []
7
+ end
8
+
9
+ def <<(ast)
10
+ self.children << ast
11
+ end
12
+ end
13
+
14
+ class Root < Struct.new(:children)
15
+ include HasChildren
16
+ end
17
+
18
+ class Doctype < Struct.new(:doctype)
19
+ end
20
+
21
+ class Element < Struct.new(
22
+ :children,
23
+ :tag_name,
24
+ :static_class,
25
+ :static_id,
26
+ :attributes,
27
+ :oneline_child,
28
+ :self_closing,
29
+ :nuke_inner_whitespace,
30
+ :nuke_outer_whitespace,
31
+ )
32
+ include HasChildren
33
+
34
+ def initialize(*)
35
+ super
36
+ self.static_class ||= ''
37
+ self.static_id ||= ''
38
+ self.attributes ||= ''
39
+ self.self_closing ||= false
40
+ self.nuke_inner_whitespace ||= false
41
+ self.nuke_outer_whitespace ||= false
42
+ end
43
+ end
44
+
45
+ class Script < Struct.new(
46
+ :children,
47
+ :script,
48
+ :escape_html,
49
+ :preserve,
50
+ :mid_block_keyword,
51
+ )
52
+ include HasChildren
53
+
54
+ def initialize(*)
55
+ super
56
+ if self.escape_html.nil?
57
+ self.escape_html = true
58
+ end
59
+ if self.preserve.nil?
60
+ self.preserve = false
61
+ end
62
+ if self.mid_block_keyword.nil?
63
+ self.mid_block_keyword = false
64
+ end
65
+ end
66
+ end
67
+
68
+ class SilentScript < Struct.new(:children, :script, :mid_block_keyword)
69
+ include HasChildren
70
+
71
+ def initialize(*)
72
+ super
73
+ if self.mid_block_keyword.nil?
74
+ self.mid_block_keyword = false
75
+ end
76
+ end
77
+ end
78
+
79
+ class HtmlComment < Struct.new(:children, :comment, :conditional)
80
+ include HasChildren
81
+
82
+ def initialize(*)
83
+ super
84
+ self.comment ||= ''
85
+ self.conditional ||= ''
86
+ end
87
+ end
88
+
89
+ class HamlComment < Struct.new(:children)
90
+ include HasChildren
91
+ end
92
+
93
+ class Text < Struct.new(:text, :escape_html)
94
+ def initialize(*)
95
+ super
96
+ if self.escape_html.nil?
97
+ self.escape_html = true
98
+ end
99
+ end
100
+ end
101
+
102
+ class Filter < Struct.new(:name, :texts)
103
+ def initialize(*)
104
+ super
105
+ self.texts ||= []
106
+ end
107
+ end
108
+
109
+ class Empty
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,38 @@
1
+ require 'faml'
2
+ require 'thor'
3
+
4
+ module Faml
5
+ class CLI < Thor
6
+ desc 'render FILE', 'Render haml template'
7
+ def render(file)
8
+ puts eval(compile_file(file))
9
+ end
10
+
11
+ desc 'compile FILE', 'Compile haml template'
12
+ def compile(file)
13
+ puts compile_file(file)
14
+ end
15
+
16
+ desc 'parse FILE', 'Render faml AST'
17
+ def parse(file)
18
+ require 'pp'
19
+ pp parse_file(file)
20
+ end
21
+
22
+ desc 'temple FILE', 'Render temple AST'
23
+ def temple(file)
24
+ require 'pp'
25
+ pp Faml::Compiler.new.call(parse_file(file))
26
+ end
27
+
28
+ private
29
+
30
+ def compile_file(file)
31
+ Faml::Engine.new.call(File.read(file))
32
+ end
33
+
34
+ def parse_file(file)
35
+ Faml::Parser.new.call(File.read(file))
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,374 @@
1
+ require 'parser/current'
2
+ require 'temple'
3
+ require 'faml/ast'
4
+ require 'faml/filter_compilers'
5
+ require 'faml/static_hash_parser'
6
+ require 'faml/text_compiler'
7
+
8
+ module Faml
9
+ class Compiler < Temple::Parser
10
+ class UnparsableRubyCode < StandardError
11
+ end
12
+
13
+ DEFAULT_AUTO_CLOSE_TAGS = %w[
14
+ area base basefont br col command embed frame hr img input isindex keygen
15
+ link menuitem meta param source track wbr
16
+ ]
17
+ DEFAULT_PRESERVE_TAGS = %w[pre textarea code]
18
+
19
+ define_options(
20
+ autoclose: DEFAULT_AUTO_CLOSE_TAGS,
21
+ format: :html,
22
+ preserve: DEFAULT_PRESERVE_TAGS,
23
+ )
24
+
25
+ def initialize(*)
26
+ super
27
+ @text_compiler = TextCompiler.new
28
+ end
29
+
30
+ def call(ast)
31
+ compile(ast)
32
+ end
33
+
34
+ def self.find_and_preserve(input)
35
+ # Taken from the original haml code
36
+ re = /<(#{options[:preserve].map(&Regexp.method(:escape)).join('|')})([^>]*)>(.*?)(<\/\1>)/im
37
+ input.to_s.gsub(re) do |s|
38
+ s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
39
+ "<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
40
+ end
41
+ end
42
+
43
+ def self.preserve(input)
44
+ # Taken from the original haml code
45
+ input.to_s.chomp("\n").gsub(/\n/, '&#x000A;').gsub(/\r/, '')
46
+ end
47
+
48
+ private
49
+
50
+ def compile(ast)
51
+ case ast
52
+ when Ast::Root
53
+ compile_root(ast)
54
+ when Ast::Doctype
55
+ compile_doctype(ast)
56
+ when Ast::HtmlComment
57
+ compile_html_comment(ast)
58
+ when Ast::HamlComment
59
+ compile_haml_comment(ast)
60
+ when Ast::Empty
61
+ [:multi]
62
+ when Ast::Element
63
+ compile_element(ast)
64
+ when Ast::Script
65
+ compile_script(ast)
66
+ when Ast::SilentScript
67
+ compile_silent_script(ast)
68
+ when Ast::Text
69
+ compile_text(ast)
70
+ when Ast::Filter
71
+ compile_filter(ast)
72
+ else
73
+ raise "InternalError: Unknown AST node #{ast.class}: #{ast.inspect}"
74
+ end
75
+ end
76
+
77
+ def compile_root(ast)
78
+ [:multi].tap do |temple|
79
+ compile_children(ast, temple)
80
+ end
81
+ end
82
+
83
+ def compile_children(ast, temple)
84
+ ast.children.each do |c|
85
+ temple << compile(c)
86
+ if need_newline?(c)
87
+ temple << [:mknl]
88
+ end
89
+ unless suppress_code_newline?(c)
90
+ temple << [:newline]
91
+ end
92
+ end
93
+ end
94
+
95
+ def need_newline?(child)
96
+ case child
97
+ when Ast::Script
98
+ child.children.empty?
99
+ when Ast::SilentScript, Ast::HamlComment, Ast::Empty
100
+ false
101
+ when Ast::Element
102
+ !child.nuke_outer_whitespace
103
+ when Ast::Filter
104
+ FilterCompilers.find(child.name).need_newline?
105
+ else
106
+ true
107
+ end
108
+ end
109
+
110
+ def suppress_code_newline?(ast)
111
+ ast.is_a?(Ast::Script) ||
112
+ ast.is_a?(Ast::SilentScript) ||
113
+ (ast.is_a?(Ast::Element) && suppress_code_newline?(ast.oneline_child)) ||
114
+ (ast.is_a?(Ast::Element) && !ast.children.empty?) ||
115
+ (ast.is_a?(Ast::HtmlComment) && !ast.conditional.empty?)
116
+ end
117
+
118
+ def compile_text(ast)
119
+ @text_compiler.compile(ast.text, escape_html: ast.escape_html)
120
+ end
121
+
122
+ # html5 and html4 is deprecated in temple.
123
+ DEFAULT_DOCTYPE = {
124
+ html: 'html',
125
+ html5: 'html',
126
+ html4: 'transitional',
127
+ xhtml: 'transitional',
128
+ }.freeze
129
+
130
+ def compile_doctype(ast)
131
+ doctype = ast.doctype.downcase
132
+ if doctype.empty?
133
+ doctype = DEFAULT_DOCTYPE[options[:format]]
134
+ end
135
+ [:haml, :doctype, doctype]
136
+ end
137
+
138
+ def compile_html_comment(ast)
139
+ if ast.children.empty?
140
+ if ast.conditional.empty?
141
+ [:html, :comment, [:static, " #{ast.comment} "]]
142
+ else
143
+ [:html, :comment, [:static, "[#{ast.conditional}]> #{ast.comment} <![endif]"]]
144
+ end
145
+ else
146
+ temple = [:multi]
147
+ if ast.conditional.empty?
148
+ temple << [:mknl]
149
+ else
150
+ temple << [:static, "[#{ast.conditional}]>"] << [:mknl] << [:newline]
151
+ end
152
+ compile_children(ast, temple)
153
+ unless ast.conditional.empty?
154
+ temple << [:static, "<![endif]"]
155
+ end
156
+ [:multi, [:html, :comment, temple]]
157
+ end
158
+ end
159
+
160
+ def compile_haml_comment(ast)
161
+ [:multi].concat([[:newline]] * ast.children.size)
162
+ end
163
+
164
+ def compile_element(ast)
165
+ temple = [
166
+ :haml, :tag,
167
+ ast.tag_name,
168
+ self_closing?(ast),
169
+ compile_attributes(ast.attributes, ast.static_id, ast.static_class),
170
+ ]
171
+
172
+ if ast.oneline_child
173
+ temple << compile(ast.oneline_child)
174
+ elsif !ast.children.empty?
175
+ temple << compile_element_children(ast)
176
+ end
177
+
178
+ if ast.nuke_outer_whitespace
179
+ [:multi, [:rmnl], temple]
180
+ else
181
+ temple
182
+ end
183
+ end
184
+
185
+ def self_closing?(ast)
186
+ ast.self_closing || options[:autoclose].include?(ast.tag_name)
187
+ end
188
+
189
+ def compile_element_children(ast)
190
+ children = [:multi]
191
+ unless nuke_inner_whitespace?(ast)
192
+ children << [:mknl]
193
+ end
194
+ children << [:newline]
195
+ compile_children(ast, children)
196
+ if nuke_inner_whitespace?(ast)
197
+ children << [:rmnl]
198
+ end
199
+ children
200
+ end
201
+
202
+ def nuke_inner_whitespace?(ast)
203
+ ast.nuke_inner_whitespace || options[:preserve].include?(ast.tag_name)
204
+ end
205
+
206
+ def compile_attributes(text, static_id, static_class)
207
+ if text.empty?
208
+ return compile_static_id_and_class(static_id, static_class)
209
+ end
210
+
211
+ if attrs = try_optimize_attributes(text, static_id, static_class)
212
+ return [:html, :attrs, *attrs]
213
+ end
214
+
215
+ # Slow version
216
+
217
+ h = {}
218
+ unless static_class.empty?
219
+ h[:class] = static_class.split(/ +/)
220
+ end
221
+ unless static_id.empty?
222
+ h[:id] = static_id
223
+ end
224
+
225
+ t =
226
+ if h.empty?
227
+ text
228
+ else
229
+ "#{h.inspect}, #{text}"
230
+ end
231
+ [:haml, :attrs, t]
232
+ end
233
+
234
+ def compile_static_id_and_class(static_id, static_class)
235
+ [:html, :attrs].tap do |html_attrs|
236
+ unless static_class.empty?
237
+ html_attrs << [:haml, :attr, 'class', [:static, static_class]]
238
+ end
239
+ unless static_id.empty?
240
+ html_attrs << [:haml, :attr, 'id', [:static, static_id]]
241
+ end
242
+ end
243
+ end
244
+
245
+ def try_optimize_attributes(text, static_id, static_class)
246
+ parser = StaticHashParser.new
247
+ unless parser.parse("{#{text}}")
248
+ assert_valid_ruby_code!(text)
249
+ return nil
250
+ end
251
+
252
+ static_attributes, dynamic_attributes = build_optimized_attributes(parser, static_id, static_class)
253
+ if static_attributes.nil?
254
+ return nil
255
+ end
256
+
257
+ if dynamic_attributes.has_key?('data')
258
+ # XXX: Quit optimization...
259
+ return nil
260
+ end
261
+
262
+ (static_attributes.keys + dynamic_attributes.keys).sort.flat_map do |k|
263
+ if static_attributes.has_key?(k)
264
+ compile_static_attribute(k, static_attributes[k])
265
+ else
266
+ compile_dynamic_attribute(k, dynamic_attributes[k])
267
+ end
268
+ end
269
+ end
270
+
271
+ def assert_valid_ruby_code!(text)
272
+ parser = ::Parser::CurrentRuby.new
273
+ parser.diagnostics.consumer = nil
274
+ buffer = ::Parser::Source::Buffer.new('(faml)')
275
+ buffer.source = "call(#{text})"
276
+ parser.parse(buffer)
277
+ true
278
+ rescue ::Parser::SyntaxError
279
+ raise UnparsableRubyCode.new("Unparsable Ruby code is given to attributes: #{text}")
280
+ end
281
+
282
+ def build_optimized_attributes(parser, static_id, static_class)
283
+ static_attributes = build_optimized_static_attributes(parser, static_id, static_class)
284
+ dynamic_attributes = build_optimized_dynamic_attributes(parser, static_attributes)
285
+ if dynamic_attributes
286
+ [static_attributes, dynamic_attributes]
287
+ else
288
+ [nil, nil]
289
+ end
290
+ end
291
+
292
+ def build_optimized_static_attributes(parser, static_id, static_class)
293
+ static_attributes = {}
294
+ parser.static_attributes.each do |k, v|
295
+ static_attributes[k.to_s] = v
296
+ end
297
+ unless static_class.empty?
298
+ static_attributes['class'] = [static_class.split(/ +/), static_attributes['class']].compact.flatten.map(&:to_s).sort.join(' ')
299
+ end
300
+ unless static_id.empty?
301
+ static_attributes['id'] = [static_id, static_attributes['id']].compact.join('_')
302
+ end
303
+ static_attributes
304
+ end
305
+
306
+ def build_optimized_dynamic_attributes(parser, static_attributes)
307
+ dynamic_attributes = {}
308
+ parser.dynamic_attributes.each do |k, v|
309
+ k = k.to_s
310
+ if static_attributes.has_key?(k)
311
+ if StaticHashParser::SPECIAL_ATTRIBUTES.include?(k)
312
+ # XXX: Quit optimization
313
+ return nil
314
+ end
315
+ end
316
+ dynamic_attributes[k] = v
317
+ end
318
+ dynamic_attributes
319
+ end
320
+
321
+ def compile_static_attribute(key, value)
322
+ case
323
+ when value == true
324
+ [[:haml, :attr, key, [:multi]]]
325
+ when value == false || value == nil
326
+ [[:multi]]
327
+ when value.is_a?(Hash) && key == 'data'
328
+ data = AttributeBuilder.normalize_data(value)
329
+ data.keys.sort.map do |k|
330
+ [:haml, :attr, "data-#{k}", [:static, Temple::Utils.escape_html(data[k])]]
331
+ end
332
+ else
333
+ [[:haml, :attr, key, [:static, Temple::Utils.escape_html(value)]]]
334
+ end
335
+ end
336
+
337
+ def compile_dynamic_attribute(key, value)
338
+ [[:haml, :attr, key, [:dvalue, value]]]
339
+ end
340
+
341
+ def compile_script(ast)
342
+ sym = unique_name
343
+ temple = [:multi]
344
+ if ast.children.empty?
345
+ temple << [:code, "#{sym} = (#{ast.script}"] << [:newline] << [:code, ')']
346
+ else
347
+ temple << [:code, "#{sym} = #{ast.script}"] << [:newline]
348
+ compile_children(ast, temple)
349
+ if !ast.mid_block_keyword
350
+ temple << [:code, 'end']
351
+ end
352
+ end
353
+ if !ast.escape_html && ast.preserve
354
+ temple << [:haml, :preserve, sym]
355
+ else
356
+ temple << [:escape, ast.escape_html, [:dynamic, "#{sym}.to_s"]]
357
+ end
358
+ temple
359
+ end
360
+
361
+ def compile_silent_script(ast)
362
+ temple = [:multi, [:code, ast.script], [:newline]]
363
+ compile_children(ast, temple)
364
+ if !ast.children.empty? && !ast.mid_block_keyword
365
+ temple << [:code, 'end']
366
+ end
367
+ temple
368
+ end
369
+
370
+ def compile_filter(ast)
371
+ FilterCompilers.find(ast.name).compile(ast.texts)
372
+ end
373
+ end
374
+ end