faml 0.2.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.
- 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
|