faml 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.travis.yml +27 -0
- data/Appraisals +26 -0
- data/CHANGELOG.md +47 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +115 -0
- data/Rakefile +28 -0
- data/benchmark/attribute_builder.haml +5 -0
- data/benchmark/rendering.rb +35 -0
- data/bin/faml +4 -0
- data/ext/attribute_builder/attribute_builder.c +261 -0
- data/ext/attribute_builder/extconf.rb +3 -0
- data/faml.gemspec +38 -0
- data/gemfiles/rails_4.0.gemfile +9 -0
- data/gemfiles/rails_4.1.gemfile +9 -0
- data/gemfiles/rails_4.2.gemfile +9 -0
- data/gemfiles/rails_edge.gemfile +10 -0
- data/haml_spec_test.rb +22 -0
- data/lib/faml.rb +10 -0
- data/lib/faml/ast.rb +112 -0
- data/lib/faml/cli.rb +38 -0
- data/lib/faml/compiler.rb +374 -0
- data/lib/faml/element_parser.rb +235 -0
- data/lib/faml/engine.rb +34 -0
- data/lib/faml/filter_compilers.rb +41 -0
- data/lib/faml/filter_compilers/base.rb +43 -0
- data/lib/faml/filter_compilers/cdata.rb +15 -0
- data/lib/faml/filter_compilers/coffee.rb +16 -0
- data/lib/faml/filter_compilers/css.rb +16 -0
- data/lib/faml/filter_compilers/escaped.rb +23 -0
- data/lib/faml/filter_compilers/javascript.rb +16 -0
- data/lib/faml/filter_compilers/markdown.rb +18 -0
- data/lib/faml/filter_compilers/plain.rb +15 -0
- data/lib/faml/filter_compilers/preserve.rb +26 -0
- data/lib/faml/filter_compilers/ruby.rb +17 -0
- data/lib/faml/filter_compilers/sass.rb +15 -0
- data/lib/faml/filter_compilers/scss.rb +16 -0
- data/lib/faml/filter_compilers/tilt_base.rb +34 -0
- data/lib/faml/filter_parser.rb +54 -0
- data/lib/faml/html.rb +58 -0
- data/lib/faml/indent_tracker.rb +84 -0
- data/lib/faml/line_parser.rb +66 -0
- data/lib/faml/newline.rb +30 -0
- data/lib/faml/parser.rb +211 -0
- data/lib/faml/parser_utils.rb +17 -0
- data/lib/faml/rails_handler.rb +10 -0
- data/lib/faml/railtie.rb +9 -0
- data/lib/faml/ruby_multiline.rb +23 -0
- data/lib/faml/script_parser.rb +84 -0
- data/lib/faml/static_hash_parser.rb +113 -0
- data/lib/faml/syntax_error.rb +10 -0
- data/lib/faml/text_compiler.rb +69 -0
- data/lib/faml/tilt.rb +17 -0
- data/lib/faml/version.rb +3 -0
- data/spec/compiler_newline_spec.rb +162 -0
- data/spec/rails/Rakefile +6 -0
- data/spec/rails/app/assets/images/.keep +0 -0
- data/spec/rails/app/assets/javascripts/application.js +13 -0
- data/spec/rails/app/assets/stylesheets/application.css +15 -0
- data/spec/rails/app/controllers/application_controller.rb +5 -0
- data/spec/rails/app/controllers/books_controller.rb +8 -0
- data/spec/rails/app/controllers/concerns/.keep +0 -0
- data/spec/rails/app/helpers/application_helper.rb +2 -0
- data/spec/rails/app/mailers/.keep +0 -0
- data/spec/rails/app/models/.keep +0 -0
- data/spec/rails/app/models/book.rb +9 -0
- data/spec/rails/app/models/concerns/.keep +0 -0
- data/spec/rails/app/views/books/hello.html.haml +2 -0
- data/spec/rails/app/views/books/with_capture.html.haml +4 -0
- data/spec/rails/app/views/books/with_variables.html.haml +4 -0
- data/spec/rails/app/views/layouts/application.html.haml +9 -0
- data/spec/rails/bin/bundle +3 -0
- data/spec/rails/bin/rails +4 -0
- data/spec/rails/bin/rake +4 -0
- data/spec/rails/bin/setup +29 -0
- data/spec/rails/config.ru +4 -0
- data/spec/rails/config/application.rb +12 -0
- data/spec/rails/config/boot.rb +3 -0
- data/spec/rails/config/database.yml +25 -0
- data/spec/rails/config/environment.rb +5 -0
- data/spec/rails/config/environments/development.rb +41 -0
- data/spec/rails/config/environments/production.rb +79 -0
- data/spec/rails/config/environments/test.rb +42 -0
- data/spec/rails/config/initializers/assets.rb +11 -0
- data/spec/rails/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails/config/initializers/cookies_serializer.rb +3 -0
- data/spec/rails/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/rails/config/initializers/inflections.rb +16 -0
- data/spec/rails/config/initializers/mime_types.rb +4 -0
- data/spec/rails/config/initializers/secret_key_base.rb +6 -0
- data/spec/rails/config/initializers/session_store.rb +3 -0
- data/spec/rails/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails/config/locales/en.yml +23 -0
- data/spec/rails/config/routes.rb +7 -0
- data/spec/rails/config/secrets.yml +22 -0
- data/spec/rails/db/seeds.rb +7 -0
- data/spec/rails/lib/assets/.keep +0 -0
- data/spec/rails/lib/tasks/.keep +0 -0
- data/spec/rails/log/.keep +0 -0
- data/spec/rails/public/404.html +67 -0
- data/spec/rails/public/422.html +67 -0
- data/spec/rails/public/500.html +66 -0
- data/spec/rails/public/favicon.ico +0 -0
- data/spec/rails/public/robots.txt +5 -0
- data/spec/rails/spec/requests/faml_spec.rb +41 -0
- data/spec/rails/vendor/assets/javascripts/.keep +0 -0
- data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
- data/spec/rails_helper.rb +4 -0
- data/spec/render/attribute_spec.rb +241 -0
- data/spec/render/comment_spec.rb +61 -0
- data/spec/render/doctype_spec.rb +57 -0
- data/spec/render/element_spec.rb +136 -0
- data/spec/render/filters/cdata_spec.rb +12 -0
- data/spec/render/filters/coffee_spec.rb +25 -0
- data/spec/render/filters/css_spec.rb +45 -0
- data/spec/render/filters/escaped_spec.rb +14 -0
- data/spec/render/filters/javascript_spec.rb +44 -0
- data/spec/render/filters/markdown_spec.rb +19 -0
- data/spec/render/filters/plain_spec.rb +24 -0
- data/spec/render/filters/preserve_spec.rb +24 -0
- data/spec/render/filters/ruby_spec.rb +13 -0
- data/spec/render/filters/sass_spec.rb +28 -0
- data/spec/render/filters/scss_spec.rb +32 -0
- data/spec/render/filters_spec.rb +11 -0
- data/spec/render/haml_comment_spec.rb +24 -0
- data/spec/render/multiline_spec.rb +39 -0
- data/spec/render/newline_spec.rb +83 -0
- data/spec/render/plain_spec.rb +20 -0
- data/spec/render/preserve_spec.rb +8 -0
- data/spec/render/sanitize_spec.rb +36 -0
- data/spec/render/script_spec.rb +81 -0
- data/spec/render/silent_script_spec.rb +97 -0
- data/spec/render/unescape_spec.rb +45 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/tilt_spec.rb +33 -0
- metadata +489 -0
data/faml.gemspec
ADDED
@@ -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
|
data/haml_spec_test.rb
ADDED
@@ -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
|
data/lib/faml.rb
ADDED
data/lib/faml/ast.rb
ADDED
@@ -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
|
data/lib/faml/cli.rb
ADDED
@@ -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/, '
').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
|