ultra-lite-tool 0.0.1
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/haml-7.2.0/CHANGELOG.md +1705 -0
- data/haml-7.2.0/FAQ.md +147 -0
- data/haml-7.2.0/Gemfile +25 -0
- data/haml-7.2.0/MIT-LICENSE +20 -0
- data/haml-7.2.0/README.md +205 -0
- data/haml-7.2.0/REFERENCE.md +1309 -0
- data/haml-7.2.0/Rakefile +48 -0
- data/haml-7.2.0/bin/bench +66 -0
- data/haml-7.2.0/bin/console +10 -0
- data/haml-7.2.0/bin/ruby +3 -0
- data/haml-7.2.0/bin/setup +7 -0
- data/haml-7.2.0/bin/stackprof +27 -0
- data/haml-7.2.0/bin/test +24 -0
- data/haml-7.2.0/exe/haml +6 -0
- data/haml-7.2.0/haml.gemspec +45 -0
- data/haml-7.2.0/lib/haml/ambles.rb +20 -0
- data/haml-7.2.0/lib/haml/attribute_builder.rb +162 -0
- data/haml-7.2.0/lib/haml/attribute_compiler.rb +133 -0
- data/haml-7.2.0/lib/haml/attribute_parser.rb +116 -0
- data/haml-7.2.0/lib/haml/cli.rb +154 -0
- data/haml-7.2.0/lib/haml/compiler/children_compiler.rb +155 -0
- data/haml-7.2.0/lib/haml/compiler/comment_compiler.rb +51 -0
- data/haml-7.2.0/lib/haml/compiler/doctype_compiler.rb +52 -0
- data/haml-7.2.0/lib/haml/compiler/script_compiler.rb +114 -0
- data/haml-7.2.0/lib/haml/compiler/silent_script_compiler.rb +24 -0
- data/haml-7.2.0/lib/haml/compiler/tag_compiler.rb +76 -0
- data/haml-7.2.0/lib/haml/compiler.rb +97 -0
- data/haml-7.2.0/lib/haml/dynamic_merger.rb +67 -0
- data/haml-7.2.0/lib/haml/engine.rb +59 -0
- data/haml-7.2.0/lib/haml/error.rb +66 -0
- data/haml-7.2.0/lib/haml/escape.rb +13 -0
- data/haml-7.2.0/lib/haml/escape_any.rb +21 -0
- data/haml-7.2.0/lib/haml/filters/base.rb +12 -0
- data/haml-7.2.0/lib/haml/filters/cdata.rb +20 -0
- data/haml-7.2.0/lib/haml/filters/coffee.rb +17 -0
- data/haml-7.2.0/lib/haml/filters/css.rb +33 -0
- data/haml-7.2.0/lib/haml/filters/erb.rb +10 -0
- data/haml-7.2.0/lib/haml/filters/escaped.rb +22 -0
- data/haml-7.2.0/lib/haml/filters/javascript.rb +33 -0
- data/haml-7.2.0/lib/haml/filters/less.rb +20 -0
- data/haml-7.2.0/lib/haml/filters/markdown.rb +11 -0
- data/haml-7.2.0/lib/haml/filters/plain.rb +29 -0
- data/haml-7.2.0/lib/haml/filters/preserve.rb +22 -0
- data/haml-7.2.0/lib/haml/filters/ruby.rb +10 -0
- data/haml-7.2.0/lib/haml/filters/sass.rb +15 -0
- data/haml-7.2.0/lib/haml/filters/scss.rb +15 -0
- data/haml-7.2.0/lib/haml/filters/text_base.rb +25 -0
- data/haml-7.2.0/lib/haml/filters/tilt_base.rb +59 -0
- data/haml-7.2.0/lib/haml/filters.rb +75 -0
- data/haml-7.2.0/lib/haml/force_escape.rb +29 -0
- data/haml-7.2.0/lib/haml/helpers.rb +15 -0
- data/haml-7.2.0/lib/haml/html.rb +22 -0
- data/haml-7.2.0/lib/haml/identity.rb +13 -0
- data/haml-7.2.0/lib/haml/object_ref.rb +35 -0
- data/haml-7.2.0/lib/haml/parser.rb +991 -0
- data/haml-7.2.0/lib/haml/rails_helpers.rb +53 -0
- data/haml-7.2.0/lib/haml/rails_template.rb +62 -0
- data/haml-7.2.0/lib/haml/railtie.rb +10 -0
- data/haml-7.2.0/lib/haml/ruby_expression.rb +32 -0
- data/haml-7.2.0/lib/haml/string_splitter.rb +140 -0
- data/haml-7.2.0/lib/haml/template.rb +20 -0
- data/haml-7.2.0/lib/haml/temple_line_counter.rb +31 -0
- data/haml-7.2.0/lib/haml/util.rb +262 -0
- data/haml-7.2.0/lib/haml/version.rb +4 -0
- data/haml-7.2.0/lib/haml/whitespace.rb +8 -0
- data/haml-7.2.0/lib/haml.rb +9 -0
- data/ultra-lite-tool.gemspec +11 -0
- metadata +107 -0
data/haml-7.2.0/Rakefile
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'bundler/gem_tasks'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
|
|
4
|
+
Rake::TestTask.new do |t|
|
|
5
|
+
t.libs << 'lib' << 'test'
|
|
6
|
+
files = Dir['test/haml/**/*_test.rb']
|
|
7
|
+
t.ruby_opts = %w[-rtest_helper]
|
|
8
|
+
t.test_files = files
|
|
9
|
+
t.verbose = true
|
|
10
|
+
end
|
|
11
|
+
task :test
|
|
12
|
+
|
|
13
|
+
desc 'bench task for CI'
|
|
14
|
+
task :bench do
|
|
15
|
+
if ENV['SLIM_BENCH'] == '1'
|
|
16
|
+
cmd = %w[bundle exec ruby benchmark/slim/run-benchmarks.rb]
|
|
17
|
+
else
|
|
18
|
+
cmd = ['bin/bench', 'bench', ('-c' if ENV['COMPILE'] == '1'), *ENV['TEMPLATE'].split(',')].compact
|
|
19
|
+
end
|
|
20
|
+
exit system(*cmd)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
namespace :doc do
|
|
24
|
+
task :sass do
|
|
25
|
+
require 'sass'
|
|
26
|
+
Dir["yard/default/**/*.sass"].each do |sass|
|
|
27
|
+
File.open(sass.gsub(/sass$/, 'css'), 'w') do |f|
|
|
28
|
+
f.write(Sass::Engine.new(File.read(sass)).render)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "List all undocumented methods and classes."
|
|
34
|
+
task :undocumented do
|
|
35
|
+
command = 'yard --list --query '
|
|
36
|
+
command << '"object.docstring.blank? && '
|
|
37
|
+
command << '!(object.type == :method && object.is_alias?)"'
|
|
38
|
+
sh command
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
desc "Generate documentation"
|
|
43
|
+
task(:doc => 'doc:sass') {sh "yard"}
|
|
44
|
+
|
|
45
|
+
desc "Generate documentation incrementally"
|
|
46
|
+
task(:redoc) {sh "yard -c"}
|
|
47
|
+
|
|
48
|
+
task default: :test
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'haml'
|
|
5
|
+
require 'thor'
|
|
6
|
+
require 'benchmark/ips'
|
|
7
|
+
require_relative '../benchmark/utils/benchmark_ips_extension'
|
|
8
|
+
|
|
9
|
+
class Bench < Thor
|
|
10
|
+
class_option :show_template, type: :boolean, aliases: ['-t']
|
|
11
|
+
|
|
12
|
+
desc 'bench HAML', 'Benchmark haml template'
|
|
13
|
+
option :compile, type: :boolean, aliases: ['-c']
|
|
14
|
+
option :show_code, type: :boolean, aliases: ['-s']
|
|
15
|
+
def bench(*files)
|
|
16
|
+
files.each { |file| render(file) }
|
|
17
|
+
files.each { |file| compile(file) if options[:compile] }
|
|
18
|
+
files.each { |file| code(file) if options[:show_code] }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
desc 'compile HAML', 'Benchmark compilation'
|
|
22
|
+
def compile(file)
|
|
23
|
+
puts "#{?= * 49}\n Compilation: #{file}\n#{?= * 49}"
|
|
24
|
+
haml = File.read(file)
|
|
25
|
+
|
|
26
|
+
Benchmark.ips do |x|
|
|
27
|
+
x.report("haml v#{Haml::VERSION}") { Haml::Engine.new.call(haml) }
|
|
28
|
+
x.compare!
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc 'render HAML', 'Benchmark rendering'
|
|
33
|
+
def render(file)
|
|
34
|
+
puts "#{?= * 49}\n Rendering: #{file}\n#{?= * 49}"
|
|
35
|
+
haml = File.read(file)
|
|
36
|
+
puts haml + "\n" if options[:show_template]
|
|
37
|
+
object = Object.new
|
|
38
|
+
ruby_file = file.gsub(/\.haml\z/, '.rb')
|
|
39
|
+
if File.exist?(ruby_file)
|
|
40
|
+
object.instance_eval(File.read(ruby_file))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
object.instance_eval "def haml; #{Haml::Engine.new.call(haml)}; end"
|
|
44
|
+
|
|
45
|
+
Benchmark.ips do |x|
|
|
46
|
+
x.report("haml v#{Haml::VERSION}") { object.haml }
|
|
47
|
+
x.compare!
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
desc 'code HAML', 'Show compiled code'
|
|
52
|
+
def code(file)
|
|
53
|
+
haml = File.read(file)
|
|
54
|
+
puts "\n#{?= * 49}\n Haml Source: #{file}\n#{?= * 49}"
|
|
55
|
+
puts Haml::Engine.new.call(haml)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def method_missing(*args)
|
|
61
|
+
return super if args.length > 1
|
|
62
|
+
render(args.first.to_s)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
Bench.start
|
data/haml-7.2.0/bin/ruby
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'haml'
|
|
5
|
+
require 'stackprof'
|
|
6
|
+
|
|
7
|
+
def open_flamegraph(report)
|
|
8
|
+
temp = `mktemp /tmp/stackflame-XXXXXXXX`.strip
|
|
9
|
+
data_path = "#{temp}.js"
|
|
10
|
+
system("mv #{temp} #{data_path}")
|
|
11
|
+
|
|
12
|
+
File.open(data_path, 'w') do |f|
|
|
13
|
+
report.print_flamegraph(f)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
viewer_path = File.join(`bundle show stackprof`.strip, 'lib/stackprof/flamegraph/viewer.html')
|
|
17
|
+
url = "file://#{viewer_path}?data=#{data_path}"
|
|
18
|
+
system(%Q[osascript -e 'open location "#{url}"'])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
haml = File.read(ARGV.first)
|
|
22
|
+
StackProf.start(mode: :wall, interval: 1, raw: false)
|
|
23
|
+
Haml::Engine.new.call(haml)
|
|
24
|
+
StackProf.stop
|
|
25
|
+
|
|
26
|
+
report = StackProf::Report.new(StackProf.results)
|
|
27
|
+
report.print_text(false)
|
data/haml-7.2.0/bin/test
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
VERSIONS=(
|
|
4
|
+
2.1.10
|
|
5
|
+
2.2.5
|
|
6
|
+
2.3.1
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
trap 'echo "${VERSIONS[2]}" > .ruby-version' 0
|
|
11
|
+
|
|
12
|
+
function test_with() {
|
|
13
|
+
version=$1
|
|
14
|
+
rbenv local $version
|
|
15
|
+
if ! bundle check > /dev/null; then
|
|
16
|
+
bundle install
|
|
17
|
+
fi
|
|
18
|
+
ruby -v
|
|
19
|
+
bundle exec rake test
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for version in ${VERSIONS[@]}; do
|
|
23
|
+
test_with $version
|
|
24
|
+
done
|
data/haml-7.2.0/exe/haml
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'haml/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = 'haml'
|
|
8
|
+
spec.version = Haml::VERSION
|
|
9
|
+
spec.authors = ['Natalie Weizenbaum', 'Hampton Catlin', 'Norman Clarke', 'Akira Matsuda', 'Takashi Kokubun']
|
|
10
|
+
spec.email = ['haml@googlegroups.com', 'ronnie@dio.jp']
|
|
11
|
+
|
|
12
|
+
spec.summary = %q{An elegant, structured (X)HTML/XML templating engine.}
|
|
13
|
+
spec.description = %q{An elegant, structured (X)HTML/XML templating engine.}
|
|
14
|
+
spec.homepage = 'https://haml.info'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
|
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|sample|benchmark)/}) }
|
|
18
|
+
spec.bindir = 'exe'
|
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
|
+
spec.require_paths = ['lib']
|
|
21
|
+
|
|
22
|
+
spec.metadata = { 'rubygems_mfa_required' => 'true' }
|
|
23
|
+
|
|
24
|
+
spec.metadata["changelog_uri"] = "https://github.com/haml/haml/releases"
|
|
25
|
+
spec.metadata["source_code_uri"] = "https://github.com/haml/haml"
|
|
26
|
+
|
|
27
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
28
|
+
|
|
29
|
+
spec.add_dependency 'temple', '>= 0.8.2'
|
|
30
|
+
spec.add_dependency 'thor'
|
|
31
|
+
spec.add_dependency 'tilt'
|
|
32
|
+
|
|
33
|
+
spec.add_development_dependency 'benchmark_driver'
|
|
34
|
+
spec.add_development_dependency 'bundler'
|
|
35
|
+
spec.add_development_dependency 'coffee-script'
|
|
36
|
+
spec.add_development_dependency 'erubi'
|
|
37
|
+
spec.add_development_dependency 'less'
|
|
38
|
+
spec.add_development_dependency 'minitest-reporters', '~> 1.1'
|
|
39
|
+
spec.add_development_dependency 'rails', '>= 4.0'
|
|
40
|
+
spec.add_development_dependency 'rake'
|
|
41
|
+
spec.add_development_dependency 'sass'
|
|
42
|
+
spec.add_development_dependency 'slim'
|
|
43
|
+
spec.add_development_dependency 'string_template'
|
|
44
|
+
spec.add_development_dependency 'unindent'
|
|
45
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Haml
|
|
3
|
+
class Ambles < Temple::Filter
|
|
4
|
+
define_options :preamble, :postamble
|
|
5
|
+
|
|
6
|
+
def initialize(*)
|
|
7
|
+
super
|
|
8
|
+
@preamble = options[:preamble]
|
|
9
|
+
@postamble = options[:postamble]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(ast)
|
|
13
|
+
ret = [:multi]
|
|
14
|
+
ret << [:static, @preamble] if @preamble
|
|
15
|
+
ret << ast
|
|
16
|
+
ret << [:static, @postamble] if @postamble
|
|
17
|
+
ret
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'haml/object_ref'
|
|
3
|
+
|
|
4
|
+
module Haml::AttributeBuilder
|
|
5
|
+
class << self
|
|
6
|
+
def build(escape_attrs, quote, format, object_ref, *hashes)
|
|
7
|
+
hashes << Haml::ObjectRef.parse(object_ref) if object_ref
|
|
8
|
+
buf = []
|
|
9
|
+
hash = merge_all_attrs(hashes)
|
|
10
|
+
|
|
11
|
+
keys = hash.keys.sort!
|
|
12
|
+
keys.each do |key|
|
|
13
|
+
case key
|
|
14
|
+
when 'id'
|
|
15
|
+
buf << " id=#{quote}#{build_id(escape_attrs, *hash[key])}#{quote}"
|
|
16
|
+
when 'class'
|
|
17
|
+
buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
|
|
18
|
+
when 'data'
|
|
19
|
+
buf << build_data(escape_attrs, quote, format, *hash[key])
|
|
20
|
+
when 'aria'
|
|
21
|
+
buf << build_aria(escape_attrs, quote, format, *hash[key])
|
|
22
|
+
when *Haml::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
|
|
23
|
+
build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
|
|
24
|
+
else
|
|
25
|
+
buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
buf.join
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def build_id(escape_attrs, *values)
|
|
32
|
+
escape_html(escape_attrs, values.flatten.select { |v| v }.join('_'))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build_class(escape_attrs, *values)
|
|
36
|
+
if values.size == 1
|
|
37
|
+
value = values.first
|
|
38
|
+
case
|
|
39
|
+
when value.is_a?(String)
|
|
40
|
+
# noop
|
|
41
|
+
when value.is_a?(Array)
|
|
42
|
+
value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
|
|
43
|
+
when value
|
|
44
|
+
value = value.to_s
|
|
45
|
+
else
|
|
46
|
+
return ''
|
|
47
|
+
end
|
|
48
|
+
return escape_html(escape_attrs, value)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
classes = []
|
|
52
|
+
values.each do |value|
|
|
53
|
+
case
|
|
54
|
+
when value.is_a?(String)
|
|
55
|
+
classes += value.split(' ')
|
|
56
|
+
when value.is_a?(Array)
|
|
57
|
+
classes += value.flatten.select { |v| v }
|
|
58
|
+
when value
|
|
59
|
+
classes << value.to_s
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def build_data(escape_attrs, quote, format, *hashes)
|
|
66
|
+
build_data_attribute(:data, escape_attrs, quote, format, *hashes)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def build_aria(escape_attrs, quote, format, *hashes)
|
|
70
|
+
build_data_attribute(:aria, escape_attrs, quote, format, *hashes)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def build_data_attribute(key, escape_attrs, quote, format, *hashes)
|
|
76
|
+
attrs = []
|
|
77
|
+
if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
|
|
78
|
+
data_value = merge_all_attrs(hashes)
|
|
79
|
+
else
|
|
80
|
+
data_value = hashes.last
|
|
81
|
+
end
|
|
82
|
+
hash = flatten_attributes(key => data_value)
|
|
83
|
+
|
|
84
|
+
hash.sort_by(&:first).each do |key, value|
|
|
85
|
+
case value
|
|
86
|
+
when true
|
|
87
|
+
build_boolean!(escape_attrs, quote, format, attrs, key, value)
|
|
88
|
+
when nil, false
|
|
89
|
+
# noop
|
|
90
|
+
else
|
|
91
|
+
attrs << " #{key}=#{quote}#{escape_html(escape_attrs, value.to_s)}#{quote}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
attrs.join
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def flatten_attributes(attributes)
|
|
98
|
+
flattened = {}
|
|
99
|
+
|
|
100
|
+
attributes.each do |key, value|
|
|
101
|
+
case value
|
|
102
|
+
when attributes
|
|
103
|
+
when Hash
|
|
104
|
+
flatten_attributes(value).each do |k, v|
|
|
105
|
+
if k.nil?
|
|
106
|
+
flattened[key] = v
|
|
107
|
+
else
|
|
108
|
+
flattened["#{key}-#{k.to_s.tr('_', '-')}"] = v
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
flattened[key] = value if value
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
flattened
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def merge_all_attrs(hashes)
|
|
119
|
+
merged = {}
|
|
120
|
+
hashes.each do |hash|
|
|
121
|
+
unless hash.is_a?(Hash)
|
|
122
|
+
raise ArgumentError, "Non-hash object is given to attributes!"
|
|
123
|
+
end
|
|
124
|
+
hash.each do |key, value|
|
|
125
|
+
key = key.to_s
|
|
126
|
+
case key
|
|
127
|
+
when 'id', 'class', 'data', 'aria'
|
|
128
|
+
merged[key] ||= []
|
|
129
|
+
merged[key] << value
|
|
130
|
+
else
|
|
131
|
+
merged[key] = value
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
merged
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def build_boolean!(escape_attrs, quote, format, buf, key, value)
|
|
139
|
+
case value
|
|
140
|
+
when true
|
|
141
|
+
case format
|
|
142
|
+
when :xhtml
|
|
143
|
+
buf << " #{key}=#{quote}#{key}#{quote}"
|
|
144
|
+
else
|
|
145
|
+
buf << " #{key}"
|
|
146
|
+
end
|
|
147
|
+
when false, nil
|
|
148
|
+
# omitted
|
|
149
|
+
else
|
|
150
|
+
buf << " #{key}=#{quote}#{escape_html(escape_attrs, value)}#{quote}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def escape_html(escape_attrs, str)
|
|
155
|
+
if escape_attrs
|
|
156
|
+
Haml::Util.escape_html(str)
|
|
157
|
+
else
|
|
158
|
+
str
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'haml/attribute_builder'
|
|
3
|
+
require 'haml/attribute_parser'
|
|
4
|
+
require 'haml/ruby_expression'
|
|
5
|
+
|
|
6
|
+
module Haml
|
|
7
|
+
# The list of boolean attributes. You may add custom attributes to this constant.
|
|
8
|
+
BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer
|
|
9
|
+
autoplay controls loop selected hidden scoped async
|
|
10
|
+
defer reversed ismap seamless muted required
|
|
11
|
+
autofocus novalidate formnovalidate open pubdate
|
|
12
|
+
itemscope allowfullscreen default inert sortable
|
|
13
|
+
truespeed typemustmatch download]
|
|
14
|
+
|
|
15
|
+
class AttributeCompiler
|
|
16
|
+
def initialize(identity, options)
|
|
17
|
+
@identity = identity
|
|
18
|
+
@quote = options[:attr_quote]
|
|
19
|
+
@format = options[:format]
|
|
20
|
+
@escape_attrs = options[:escape_attrs]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def compile(node)
|
|
24
|
+
hashes = []
|
|
25
|
+
if node.value[:object_ref] != :nil || !AttributeParser.available?
|
|
26
|
+
return runtime_compile(node)
|
|
27
|
+
end
|
|
28
|
+
[node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
|
|
29
|
+
hash = AttributeParser.parse(attribute_str)
|
|
30
|
+
return runtime_compile(node) if hash.nil? || hash.any? { |_key, value| value.empty? }
|
|
31
|
+
hashes << hash
|
|
32
|
+
end
|
|
33
|
+
static_compile(node.value[:attributes], hashes)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def runtime_compile(node)
|
|
39
|
+
attrs = []
|
|
40
|
+
attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
|
|
41
|
+
|
|
42
|
+
args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect, node.value[:object_ref]] + attrs
|
|
43
|
+
[:html, :attrs, [:dynamic, "::Haml::AttributeBuilder.build(#{args.join(', ')}, #{node.value[:dynamic_attributes].to_literal})"]]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def static_compile(static_hash, dynamic_hashes)
|
|
47
|
+
temple = [:html, :attrs]
|
|
48
|
+
keys = [*static_hash.keys, *dynamic_hashes.map(&:keys).flatten].uniq.sort
|
|
49
|
+
keys.each do |key|
|
|
50
|
+
values = [[:static, static_hash[key]], *dynamic_hashes.map { |h| [:dynamic, h[key]] }]
|
|
51
|
+
values.select! { |_, exp| exp != nil }
|
|
52
|
+
|
|
53
|
+
case key
|
|
54
|
+
when 'id'
|
|
55
|
+
compile_id!(temple, key, values)
|
|
56
|
+
when 'class'
|
|
57
|
+
compile_class!(temple, key, values)
|
|
58
|
+
when 'data', 'aria'
|
|
59
|
+
compile_data!(temple, key, values)
|
|
60
|
+
when *BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
|
|
61
|
+
compile_boolean!(temple, key, values)
|
|
62
|
+
else
|
|
63
|
+
compile_common!(temple, key, values)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
temple
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def compile_id!(temple, key, values)
|
|
70
|
+
build_code = attribute_builder(:id, values)
|
|
71
|
+
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
|
72
|
+
temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
|
|
73
|
+
else
|
|
74
|
+
temple << [:html, :attr, key, [:dynamic, build_code]]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def compile_class!(temple, key, values)
|
|
79
|
+
build_code = attribute_builder(:class, values)
|
|
80
|
+
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
|
81
|
+
temple << [:html, :attr, key, [:static, eval(build_code).to_s]]
|
|
82
|
+
else
|
|
83
|
+
temple << [:html, :attr, key, [:dynamic, build_code]]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def compile_data!(temple, key, values)
|
|
88
|
+
args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect, values.map { |v| literal_for(v) }]
|
|
89
|
+
build_code = "::Haml::AttributeBuilder.build_#{key}(#{args.join(', ')})"
|
|
90
|
+
|
|
91
|
+
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
|
92
|
+
temple << [:static, eval(build_code).to_s]
|
|
93
|
+
else
|
|
94
|
+
temple << [:dynamic, build_code]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def compile_boolean!(temple, key, values)
|
|
99
|
+
exp = literal_for(values.last)
|
|
100
|
+
|
|
101
|
+
if values.last.first == :static || Temple::StaticAnalyzer.static?(exp)
|
|
102
|
+
value = eval(exp)
|
|
103
|
+
case value
|
|
104
|
+
when true then temple << [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]
|
|
105
|
+
when false, nil
|
|
106
|
+
else temple << [:html, :attr, key, [:fescape, @escape_attrs, [:static, value.to_s]]]
|
|
107
|
+
end
|
|
108
|
+
else
|
|
109
|
+
var = @identity.generate
|
|
110
|
+
temple << [
|
|
111
|
+
:case, "(#{var} = (#{exp}))",
|
|
112
|
+
['true', [:html, :attr, key, @format == :xhtml ? [:static, key] : [:multi]]],
|
|
113
|
+
['false, nil', [:multi]],
|
|
114
|
+
[:else, [:multi, [:static, " #{key}=#{@quote}"], [:fescape, @escape_attrs, [:dynamic, var]], [:static, @quote]]],
|
|
115
|
+
]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def compile_common!(temple, key, values)
|
|
120
|
+
temple << [:html, :attr, key, [:fescape, @escape_attrs, values.last]]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def attribute_builder(type, values)
|
|
124
|
+
args = [@escape_attrs.inspect, *values.map { |v| literal_for(v) }]
|
|
125
|
+
"::Haml::AttributeBuilder.build_#{type}(#{args.join(', ')})"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def literal_for(value)
|
|
129
|
+
type, exp = value
|
|
130
|
+
type == :static ? "#{exp.inspect}.freeze" : exp
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'haml/ruby_expression'
|
|
3
|
+
|
|
4
|
+
module Haml
|
|
5
|
+
class AttributeParser
|
|
6
|
+
class ParseSkip < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# @return [TrueClass, FalseClass] - return true if AttributeParser.parse can be used.
|
|
10
|
+
def self.available?
|
|
11
|
+
# TruffleRuby doesn't have Ripper.lex
|
|
12
|
+
defined?(Ripper) && Ripper.respond_to?(:lex) && Temple::StaticAnalyzer.available?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.parse(text)
|
|
16
|
+
self.new.parse(text)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parse(text)
|
|
20
|
+
exp = wrap_bracket(text)
|
|
21
|
+
return if Temple::StaticAnalyzer.syntax_error?(exp)
|
|
22
|
+
|
|
23
|
+
hash = {}
|
|
24
|
+
tokens = Ripper.lex(exp)[1..-2] || []
|
|
25
|
+
each_attr(tokens) do |attr_tokens|
|
|
26
|
+
key = parse_key!(attr_tokens)
|
|
27
|
+
hash[key] = attr_tokens.map { |t| t[2] }.join.strip
|
|
28
|
+
end
|
|
29
|
+
hash
|
|
30
|
+
rescue ParseSkip
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def wrap_bracket(text)
|
|
37
|
+
text = text.strip
|
|
38
|
+
return text if text[0] == '{'
|
|
39
|
+
"{#{text}}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parse_key!(tokens)
|
|
43
|
+
_, type, str = tokens.shift
|
|
44
|
+
case type
|
|
45
|
+
when :on_sp
|
|
46
|
+
parse_key!(tokens)
|
|
47
|
+
when :on_label
|
|
48
|
+
str.tr(':', '')
|
|
49
|
+
when :on_symbeg
|
|
50
|
+
_, _, key = tokens.shift
|
|
51
|
+
assert_type!(tokens.shift, :on_tstring_end) if str != ':'
|
|
52
|
+
skip_until_hash_rocket!(tokens)
|
|
53
|
+
key
|
|
54
|
+
when :on_tstring_beg
|
|
55
|
+
_, _, key = tokens.shift
|
|
56
|
+
next_token = tokens.shift
|
|
57
|
+
unless next_token[1] == :on_label_end
|
|
58
|
+
assert_type!(next_token, :on_tstring_end)
|
|
59
|
+
skip_until_hash_rocket!(tokens)
|
|
60
|
+
end
|
|
61
|
+
key
|
|
62
|
+
else
|
|
63
|
+
raise ParseSkip
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def assert_type!(token, type)
|
|
68
|
+
raise ParseSkip if token[1] != type
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def skip_until_hash_rocket!(tokens)
|
|
72
|
+
until tokens.empty?
|
|
73
|
+
_, type, str = tokens.shift
|
|
74
|
+
break if type == :on_op && str == '=>'
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def each_attr(tokens)
|
|
79
|
+
attr_tokens = []
|
|
80
|
+
open_tokens = Hash.new { |h, k| h[k] = 0 }
|
|
81
|
+
|
|
82
|
+
tokens.each do |token|
|
|
83
|
+
_, type, _ = token
|
|
84
|
+
case type
|
|
85
|
+
when :on_comma
|
|
86
|
+
if open_tokens.values.all?(&:zero?)
|
|
87
|
+
yield(attr_tokens)
|
|
88
|
+
attr_tokens = []
|
|
89
|
+
next
|
|
90
|
+
end
|
|
91
|
+
when :on_lbracket
|
|
92
|
+
open_tokens[:array] += 1
|
|
93
|
+
when :on_rbracket
|
|
94
|
+
open_tokens[:array] -= 1
|
|
95
|
+
when :on_lbrace
|
|
96
|
+
open_tokens[:block] += 1
|
|
97
|
+
when :on_rbrace
|
|
98
|
+
open_tokens[:block] -= 1
|
|
99
|
+
when :on_lparen
|
|
100
|
+
open_tokens[:paren] += 1
|
|
101
|
+
when :on_rparen
|
|
102
|
+
open_tokens[:paren] -= 1
|
|
103
|
+
when :on_embexpr_beg
|
|
104
|
+
open_tokens[:embexpr] += 1
|
|
105
|
+
when :on_embexpr_end
|
|
106
|
+
open_tokens[:embexpr] -= 1
|
|
107
|
+
when :on_sp
|
|
108
|
+
next if attr_tokens.empty?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
attr_tokens << token
|
|
112
|
+
end
|
|
113
|
+
yield(attr_tokens) unless attr_tokens.empty?
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|