hamlit 0.1.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/.rspec +2 -0
- data/.travis.yml +34 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +45 -0
- data/Rakefile +34 -0
- data/benchmarks/benchmark.rb +94 -0
- data/benchmarks/context.rb +13 -0
- data/benchmarks/view.erb +23 -0
- data/benchmarks/view.haml +18 -0
- data/benchmarks/view.rbhtml +23 -0
- data/benchmarks/view.slim +17 -0
- data/bin/hamlit +6 -0
- data/hamlit.gemspec +40 -0
- data/lib/hamlit/attribute.rb +31 -0
- data/lib/hamlit/cli.rb +89 -0
- data/lib/hamlit/compiler.rb +24 -0
- data/lib/hamlit/compilers/attributes.rb +78 -0
- data/lib/hamlit/compilers/comment.rb +13 -0
- data/lib/hamlit/compilers/doctype.rb +39 -0
- data/lib/hamlit/compilers/dynamic.rb +9 -0
- data/lib/hamlit/compilers/filter.rb +53 -0
- data/lib/hamlit/compilers/new_attribute.rb +107 -0
- data/lib/hamlit/compilers/old_attribute.rb +147 -0
- data/lib/hamlit/compilers/preserve.rb +10 -0
- data/lib/hamlit/compilers/script.rb +31 -0
- data/lib/hamlit/compilers/strip.rb +30 -0
- data/lib/hamlit/compilers/text.rb +15 -0
- data/lib/hamlit/concerns/attribute_builder.rb +21 -0
- data/lib/hamlit/concerns/balanceable.rb +59 -0
- data/lib/hamlit/concerns/deprecation.rb +20 -0
- data/lib/hamlit/concerns/error.rb +27 -0
- data/lib/hamlit/concerns/escapable.rb +17 -0
- data/lib/hamlit/concerns/included.rb +28 -0
- data/lib/hamlit/concerns/indentable.rb +53 -0
- data/lib/hamlit/concerns/line_reader.rb +60 -0
- data/lib/hamlit/concerns/registerable.rb +24 -0
- data/lib/hamlit/concerns/ripperable.rb +20 -0
- data/lib/hamlit/concerns/string_interpolation.rb +48 -0
- data/lib/hamlit/engine.rb +42 -0
- data/lib/hamlit/filters/base.rb +44 -0
- data/lib/hamlit/filters/coffee.rb +12 -0
- data/lib/hamlit/filters/css.rb +34 -0
- data/lib/hamlit/filters/erb.rb +11 -0
- data/lib/hamlit/filters/escaped.rb +19 -0
- data/lib/hamlit/filters/javascript.rb +34 -0
- data/lib/hamlit/filters/less.rb +12 -0
- data/lib/hamlit/filters/markdown.rb +11 -0
- data/lib/hamlit/filters/plain.rb +21 -0
- data/lib/hamlit/filters/preserve.rb +11 -0
- data/lib/hamlit/filters/ruby.rb +11 -0
- data/lib/hamlit/filters/sass.rb +12 -0
- data/lib/hamlit/filters/scss.rb +12 -0
- data/lib/hamlit/filters/tilt.rb +41 -0
- data/lib/hamlit/helpers.rb +38 -0
- data/lib/hamlit/html/pretty.rb +49 -0
- data/lib/hamlit/html/ugly.rb +10 -0
- data/lib/hamlit/parser.rb +123 -0
- data/lib/hamlit/parsers/attribute.rb +52 -0
- data/lib/hamlit/parsers/comment.rb +27 -0
- data/lib/hamlit/parsers/doctype.rb +18 -0
- data/lib/hamlit/parsers/filter.rb +18 -0
- data/lib/hamlit/parsers/multiline.rb +54 -0
- data/lib/hamlit/parsers/script.rb +93 -0
- data/lib/hamlit/parsers/tag.rb +71 -0
- data/lib/hamlit/parsers/text.rb +11 -0
- data/lib/hamlit/parsers/whitespace.rb +38 -0
- data/lib/hamlit/railtie.rb +21 -0
- data/lib/hamlit/template.rb +9 -0
- data/lib/hamlit/version.rb +3 -0
- data/lib/hamlit.rb +8 -0
- data/spec/Rakefile +79 -0
- data/spec/hamlit/compilers/script_spec.rb +46 -0
- data/spec/hamlit/engine/comment_spec.rb +29 -0
- data/spec/hamlit/engine/doctype_spec.rb +19 -0
- data/spec/hamlit/engine/error_spec.rb +47 -0
- data/spec/hamlit/engine/multiline_spec.rb +44 -0
- data/spec/hamlit/engine/new_attribute_spec.rb +19 -0
- data/spec/hamlit/engine/old_attributes_spec.rb +85 -0
- data/spec/hamlit/engine/preservation_spec.rb +11 -0
- data/spec/hamlit/engine/script_spec.rb +87 -0
- data/spec/hamlit/engine/silent_script_spec.rb +135 -0
- data/spec/hamlit/engine/tag_spec.rb +210 -0
- data/spec/hamlit/engine/text_spec.rb +29 -0
- data/spec/hamlit/engine_spec.rb +20 -0
- data/spec/hamlit/filters/coffee_spec.rb +41 -0
- data/spec/hamlit/filters/css_spec.rb +33 -0
- data/spec/hamlit/filters/erb_spec.rb +16 -0
- data/spec/hamlit/filters/javascript_spec.rb +82 -0
- data/spec/hamlit/filters/less_spec.rb +22 -0
- data/spec/hamlit/filters/markdown_spec.rb +28 -0
- data/spec/hamlit/filters/ruby_spec.rb +24 -0
- data/spec/hamlit/filters/sass_spec.rb +37 -0
- data/spec/hamlit/filters/scss_spec.rb +21 -0
- data/spec/hamlit/ugly_spec.rb +912 -0
- data/spec/rails/.gitignore +17 -0
- data/spec/rails/.rspec +2 -0
- data/spec/rails/Gemfile +19 -0
- data/spec/rails/Gemfile.lock +177 -0
- data/spec/rails/README.rdoc +28 -0
- data/spec/rails/Rakefile +6 -0
- data/spec/rails/app/assets/images/.keep +0 -0
- data/spec/rails/app/assets/javascripts/application.js +15 -0
- data/spec/rails/app/assets/stylesheets/application.css +15 -0
- data/spec/rails/app/controllers/application_controller.rb +8 -0
- data/spec/rails/app/controllers/concerns/.keep +0 -0
- data/spec/rails/app/controllers/users_controller.rb +20 -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/concerns/.keep +0 -0
- data/spec/rails/app/views/application/index.html.haml +15 -0
- data/spec/rails/app/views/layouts/application.html.haml +12 -0
- data/spec/rails/app/views/users/capture.html.haml +5 -0
- data/spec/rails/app/views/users/capture_haml.html.haml +5 -0
- data/spec/rails/app/views/users/form.html.haml +2 -0
- data/spec/rails/app/views/users/helpers.html.haml +1 -0
- data/spec/rails/app/views/users/index.html.haml +9 -0
- data/spec/rails/app/views/users/safe_buffer.html.haml +4 -0
- data/spec/rails/bin/bundle +3 -0
- data/spec/rails/bin/rails +8 -0
- data/spec/rails/bin/rake +8 -0
- data/spec/rails/bin/setup +29 -0
- data/spec/rails/bin/spring +15 -0
- data/spec/rails/config/application.rb +34 -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/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 +13 -0
- data/spec/rails/config/secrets.yml +22 -0
- data/spec/rails/config.ru +4 -0
- data/spec/rails/db/schema.rb +16 -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/hamlit_spec.rb +87 -0
- data/spec/rails/spec/rails_helper.rb +56 -0
- data/spec/rails/spec/spec_helper.rb +91 -0
- data/spec/rails/vendor/assets/javascripts/.keep +0 -0
- data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
- data/spec/spec_helper.rb +48 -0
- metadata +560 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'strscan'
|
|
2
|
+
require 'temple'
|
|
3
|
+
require 'hamlit/parsers/attribute'
|
|
4
|
+
require 'hamlit/parsers/comment'
|
|
5
|
+
require 'hamlit/parsers/doctype'
|
|
6
|
+
require 'hamlit/parsers/filter'
|
|
7
|
+
require 'hamlit/parsers/multiline'
|
|
8
|
+
require 'hamlit/parsers/script'
|
|
9
|
+
require 'hamlit/parsers/tag'
|
|
10
|
+
require 'hamlit/parsers/text'
|
|
11
|
+
require 'hamlit/parsers/whitespace'
|
|
12
|
+
|
|
13
|
+
module Hamlit
|
|
14
|
+
class Parser < Temple::Parser
|
|
15
|
+
include Parsers::Attribute
|
|
16
|
+
include Parsers::Comment
|
|
17
|
+
include Parsers::Doctype
|
|
18
|
+
include Parsers::Filter
|
|
19
|
+
include Parsers::Multiline
|
|
20
|
+
include Parsers::Script
|
|
21
|
+
include Parsers::Tag
|
|
22
|
+
include Parsers::Text
|
|
23
|
+
include Parsers::Whitespace
|
|
24
|
+
|
|
25
|
+
SKIP_NEWLINE_EXPS = %i[newline code multi].freeze
|
|
26
|
+
SKIP_NEWLINE_FILTERS = %w[ruby markdown erb].freeze
|
|
27
|
+
|
|
28
|
+
define_options :format
|
|
29
|
+
|
|
30
|
+
def call(template)
|
|
31
|
+
reset(template)
|
|
32
|
+
|
|
33
|
+
ast = [:multi]
|
|
34
|
+
ast += parse_lines
|
|
35
|
+
ast
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Reset the parser state.
|
|
41
|
+
def reset(template)
|
|
42
|
+
template = preprocess_multilines(template)
|
|
43
|
+
reset_lines(template.split("\n"))
|
|
44
|
+
reset_indent
|
|
45
|
+
reset_outer_removal
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Parse lines in current_indent and return ASTs.
|
|
49
|
+
def parse_lines
|
|
50
|
+
ast = []
|
|
51
|
+
loop do
|
|
52
|
+
width = next_width
|
|
53
|
+
if width != @current_indent * 2
|
|
54
|
+
if width != Hamlit::EOF && (width > @current_indent * 2 || width.odd?)
|
|
55
|
+
ast << [:multi, [:newline], [:newline]]
|
|
56
|
+
ast << syntax_error(
|
|
57
|
+
"inconsistent indentation: #{2 * @current_indent} spaces used for indentation, "\
|
|
58
|
+
"but the rest of the document was indented using #{width} spaces"
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
break
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@current_lineno += 1
|
|
65
|
+
node = parse_line(current_line)
|
|
66
|
+
if @outer_removal.include?(@current_indent) && ast.last == [:static, "\n"]
|
|
67
|
+
ast.delete_at(-1)
|
|
68
|
+
end
|
|
69
|
+
ast << node
|
|
70
|
+
ast << [:newline]
|
|
71
|
+
ast << [:static, "\n"] unless skip_newline?(node)
|
|
72
|
+
end
|
|
73
|
+
ast
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Parse current line and return AST.
|
|
77
|
+
def parse_line(line)
|
|
78
|
+
return [:multi] if empty_line?(line)
|
|
79
|
+
|
|
80
|
+
scanner = StringScanner.new(line)
|
|
81
|
+
scanner.scan(/ +/)
|
|
82
|
+
if scanner.match?(/\#{/)
|
|
83
|
+
return parse_text(scanner)
|
|
84
|
+
elsif scanner.match?(/&=/)
|
|
85
|
+
return parse_script(scanner, force_escape: true)
|
|
86
|
+
elsif scanner.match?(/!=/)
|
|
87
|
+
return parse_script(scanner, disable_escape: true)
|
|
88
|
+
elsif scanner.match?(/[.#](\Z|[^a-zA-Z0-9_-])/)
|
|
89
|
+
return parse_text(scanner)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
case scanner.peek(1)
|
|
93
|
+
when '!'
|
|
94
|
+
parse_doctype(scanner)
|
|
95
|
+
when '%', '.', '#'
|
|
96
|
+
parse_tag(scanner)
|
|
97
|
+
when '='
|
|
98
|
+
parse_script(scanner)
|
|
99
|
+
when '~'
|
|
100
|
+
parse_preserve(scanner)
|
|
101
|
+
when '-'
|
|
102
|
+
parse_silent_script(scanner)
|
|
103
|
+
when '/'
|
|
104
|
+
parse_comment(scanner)
|
|
105
|
+
when ':'
|
|
106
|
+
parse_filter(scanner)
|
|
107
|
+
else
|
|
108
|
+
parse_text(scanner)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def skip_newline?(ast)
|
|
113
|
+
SKIP_NEWLINE_EXPS.include?(ast.first) ||
|
|
114
|
+
(ast[0..1] == [:haml, :doctype]) ||
|
|
115
|
+
newline_skip_filter?(ast) ||
|
|
116
|
+
@outer_removal.include?(@current_indent)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def newline_skip_filter?(ast)
|
|
120
|
+
ast[0..1] == [:haml, :filter] && SKIP_NEWLINE_FILTERS.include?(ast[2])
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'ripper'
|
|
2
|
+
require 'hamlit/concerns/balanceable'
|
|
3
|
+
|
|
4
|
+
module Hamlit
|
|
5
|
+
module Parsers
|
|
6
|
+
module Attribute
|
|
7
|
+
include Concerns::Balanceable
|
|
8
|
+
|
|
9
|
+
def parse_attributes(scanner)
|
|
10
|
+
if scanner.match?(/{/)
|
|
11
|
+
parse_old_attributes(scanner) + parse_new_attributes(scanner)
|
|
12
|
+
else
|
|
13
|
+
parse_new_attributes(scanner) + parse_old_attributes(scanner)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def parse_old_attributes(scanner)
|
|
20
|
+
return [] unless scanner.match?(/{/)
|
|
21
|
+
|
|
22
|
+
tokens = Ripper.lex(scanner.rest)
|
|
23
|
+
until balanced_braces_exist?(tokens)
|
|
24
|
+
@current_lineno += 1
|
|
25
|
+
break unless @lines[@current_lineno]
|
|
26
|
+
scanner.concat(current_line)
|
|
27
|
+
tokens = Ripper.lex(scanner.rest)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
tokens = fetch_balanced_braces(tokens)
|
|
31
|
+
scanner.pos += tokens.last.first.last + 1
|
|
32
|
+
[tokens.map(&:last).join]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def parse_new_attributes(scanner)
|
|
36
|
+
return [] unless scanner.match?(/\(/)
|
|
37
|
+
|
|
38
|
+
tokens = Ripper.lex(scanner.rest)
|
|
39
|
+
until balanced_parens_exist?(tokens)
|
|
40
|
+
@current_lineno += 1
|
|
41
|
+
break unless @lines[@current_lineno]
|
|
42
|
+
scanner.concat(current_line)
|
|
43
|
+
tokens = Ripper.lex(scanner.rest)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
tokens = fetch_balanced_parentheses(tokens)
|
|
47
|
+
scanner.pos += tokens.last.first.last + 1
|
|
48
|
+
[tokens.map(&:last).join]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'hamlit/concerns/error'
|
|
2
|
+
require 'hamlit/concerns/indentable'
|
|
3
|
+
|
|
4
|
+
module Hamlit
|
|
5
|
+
module Parsers
|
|
6
|
+
module Comment
|
|
7
|
+
include Concerns::Indentable
|
|
8
|
+
|
|
9
|
+
def parse_comment(scanner)
|
|
10
|
+
assert_scan!(scanner, /\//)
|
|
11
|
+
|
|
12
|
+
ast = [:html, :comment]
|
|
13
|
+
text = (scanner.scan(/.+/) || '').strip
|
|
14
|
+
|
|
15
|
+
if text.empty?
|
|
16
|
+
content = with_indented { parse_lines }
|
|
17
|
+
return ast << [:multi, [:static, "\n"], *content]
|
|
18
|
+
elsif !text.match(/\[.*\]/)
|
|
19
|
+
return ast << [:static, " #{text} "]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
content = with_indented { parse_lines }
|
|
23
|
+
[:haml, :comment, text, content]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'hamlit/concerns/error'
|
|
2
|
+
|
|
3
|
+
module Hamlit
|
|
4
|
+
module Parsers
|
|
5
|
+
module Doctype
|
|
6
|
+
def parse_doctype(scanner)
|
|
7
|
+
assert_scan!(scanner, /!!!/)
|
|
8
|
+
|
|
9
|
+
type = nil
|
|
10
|
+
if scanner.scan(/ +/) && scanner.rest?
|
|
11
|
+
type = scanner.rest.strip
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
[:haml, :doctype, options[:format], type]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'hamlit/concerns/error'
|
|
2
|
+
require 'hamlit/concerns/indentable'
|
|
3
|
+
|
|
4
|
+
module Hamlit
|
|
5
|
+
module Parsers
|
|
6
|
+
module Filter
|
|
7
|
+
include Concerns::Indentable
|
|
8
|
+
|
|
9
|
+
def parse_filter(scanner)
|
|
10
|
+
assert_scan!(scanner, /:/)
|
|
11
|
+
|
|
12
|
+
name = scanner.scan(/.+/).strip
|
|
13
|
+
lines = with_indented { read_lines }
|
|
14
|
+
[:haml, :filter, name, lines]
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'hamlit/concerns/line_reader'
|
|
2
|
+
|
|
3
|
+
module Hamlit
|
|
4
|
+
module Parsers
|
|
5
|
+
module Multiline
|
|
6
|
+
include Concerns::LineReader
|
|
7
|
+
|
|
8
|
+
def preprocess_multilines(template)
|
|
9
|
+
reset_lines(template.split("\n"))
|
|
10
|
+
result = []
|
|
11
|
+
|
|
12
|
+
while @lines[@current_lineno + 1]
|
|
13
|
+
@current_lineno += 1
|
|
14
|
+
|
|
15
|
+
unless end_with_pipe?(current_line)
|
|
16
|
+
result << current_line
|
|
17
|
+
next
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
prefix = current_line[/\A */]
|
|
21
|
+
lines = scan_multilines
|
|
22
|
+
|
|
23
|
+
result << prefix + build_multiline(lines)
|
|
24
|
+
(lines.length - 1).times { result << '' }
|
|
25
|
+
end
|
|
26
|
+
result.map { |line| "#{line}\n" }.join
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def end_with_pipe?(line)
|
|
32
|
+
return false unless line
|
|
33
|
+
line.strip =~ / \|\Z/
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def scan_multilines
|
|
37
|
+
lines = []
|
|
38
|
+
while end_with_pipe?(current_line)
|
|
39
|
+
lines << current_line
|
|
40
|
+
@current_lineno += 1
|
|
41
|
+
end
|
|
42
|
+
@current_lineno -= 1
|
|
43
|
+
lines
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def build_multiline(lines)
|
|
47
|
+
lines = lines.map do |line|
|
|
48
|
+
line.strip.gsub(/ *\|\Z/, '')
|
|
49
|
+
end
|
|
50
|
+
lines.join(' ')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'hamlit/concerns/escapable'
|
|
2
|
+
require 'hamlit/concerns/error'
|
|
3
|
+
require 'hamlit/concerns/included'
|
|
4
|
+
require 'hamlit/concerns/indentable'
|
|
5
|
+
|
|
6
|
+
module Hamlit
|
|
7
|
+
module Parsers
|
|
8
|
+
module Script
|
|
9
|
+
extend Concerns::Included
|
|
10
|
+
include Concerns::Error
|
|
11
|
+
include Concerns::Indentable
|
|
12
|
+
|
|
13
|
+
INTERNAL_STATEMENTS = %w[else elsif when].freeze
|
|
14
|
+
DEFAULT_SCRIPT_OPTIONS = { force_escape: false, disable_escape: false }.freeze
|
|
15
|
+
|
|
16
|
+
included do
|
|
17
|
+
include Concerns::Escapable
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse_script(scanner, options = {})
|
|
21
|
+
assert_scan!(scanner, /=|&=|!=/)
|
|
22
|
+
options = DEFAULT_SCRIPT_OPTIONS.merge(options)
|
|
23
|
+
|
|
24
|
+
code = scan_code(scanner)
|
|
25
|
+
return syntax_error("There's no Ruby code for = to evaluate.") if code.empty?
|
|
26
|
+
unless has_block?
|
|
27
|
+
return [:dynamic, code] if options[:disable_escape]
|
|
28
|
+
return escape_html([:dynamic, code], options[:force_escape])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
ast = [:haml, :script, code, options]
|
|
32
|
+
ast += with_indented { parse_lines }
|
|
33
|
+
ast << [:code, 'end']
|
|
34
|
+
ast
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse_preserve(scanner)
|
|
38
|
+
assert_scan!(scanner, /~/)
|
|
39
|
+
|
|
40
|
+
code = scan_code(scanner)
|
|
41
|
+
escape_html([:haml, :preserve, code])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parse_silent_script(scanner)
|
|
45
|
+
assert_scan!(scanner, /-/)
|
|
46
|
+
if scanner.scan(/#/)
|
|
47
|
+
with_indented { skip_lines }
|
|
48
|
+
return [:multi]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
ast = [:code]
|
|
52
|
+
ast << scan_code(scanner)
|
|
53
|
+
return ast unless has_block?
|
|
54
|
+
|
|
55
|
+
ast = [:multi, ast]
|
|
56
|
+
ast += with_indented { parse_lines }
|
|
57
|
+
ast << [:code, 'end'] unless same_indent?(next_line) && internal_statement?(next_line)
|
|
58
|
+
ast
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def scan_code(scanner)
|
|
64
|
+
code = ''
|
|
65
|
+
loop do
|
|
66
|
+
code += (scanner.scan(/.+/) || '').strip
|
|
67
|
+
break unless code =~ /,\Z/
|
|
68
|
+
|
|
69
|
+
@current_lineno += 1
|
|
70
|
+
scanner = StringScanner.new(current_line)
|
|
71
|
+
code += ' '
|
|
72
|
+
end
|
|
73
|
+
code
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def has_block?
|
|
77
|
+
next_indent == @current_indent + 1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def internal_statement?(line)
|
|
81
|
+
return false unless line
|
|
82
|
+
|
|
83
|
+
scanner = StringScanner.new(line)
|
|
84
|
+
scanner.scan(/ +/)
|
|
85
|
+
return false unless scanner.scan(/-/)
|
|
86
|
+
|
|
87
|
+
scanner.scan(/ +/)
|
|
88
|
+
statement = scanner.scan(/[^ ]+/)
|
|
89
|
+
INTERNAL_STATEMENTS.include?(statement)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'hamlit/concerns/error'
|
|
2
|
+
require 'hamlit/concerns/indentable'
|
|
3
|
+
require 'hamlit/helpers'
|
|
4
|
+
|
|
5
|
+
module Hamlit
|
|
6
|
+
module Parsers
|
|
7
|
+
module Tag
|
|
8
|
+
include Concerns::Error
|
|
9
|
+
include Concerns::Indentable
|
|
10
|
+
|
|
11
|
+
TAG_ID_CLASS_REGEXP = /[a-zA-Z0-9_-]+/
|
|
12
|
+
TAG_REGEXP = /[a-zA-Z0-9\-_:]+/
|
|
13
|
+
DEFAULT_TAG = 'div'
|
|
14
|
+
|
|
15
|
+
def parse_tag(scanner)
|
|
16
|
+
tag = DEFAULT_TAG
|
|
17
|
+
tag = scanner.scan(TAG_REGEXP) if scanner.scan(/%/)
|
|
18
|
+
|
|
19
|
+
attrs = [:haml, :attrs]
|
|
20
|
+
attrs += parse_tag_id_and_class(scanner)
|
|
21
|
+
attrs += parse_attributes(scanner)
|
|
22
|
+
|
|
23
|
+
inner_removal = parse_whitespace_removal(scanner)
|
|
24
|
+
ast = [:html, :tag, tag, attrs]
|
|
25
|
+
|
|
26
|
+
if scanner.match?(/=/)
|
|
27
|
+
ast << parse_script(scanner)
|
|
28
|
+
return ast
|
|
29
|
+
elsif scanner.scan(/\//)
|
|
30
|
+
return ast
|
|
31
|
+
elsif scanner.rest.match(/[^ ]/)
|
|
32
|
+
ast << parse_text(scanner)
|
|
33
|
+
return ast
|
|
34
|
+
elsif next_indent <= @current_indent
|
|
35
|
+
return ast << [:multi]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
content = [:multi, [:static, "\n"]]
|
|
39
|
+
if inner_removal || Helpers::DEFAULT_PRESERVE_TAGS.include?(tag)
|
|
40
|
+
content[0, 1] = [:haml, :strip]
|
|
41
|
+
end
|
|
42
|
+
content += with_indented { parse_lines }
|
|
43
|
+
ast << content
|
|
44
|
+
ast
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def parse_tag_id_and_class(scanner)
|
|
50
|
+
attributes = Hash.new { |h, k| h[k] = [] }
|
|
51
|
+
|
|
52
|
+
while prefix = scanner.scan(/[#.]/)
|
|
53
|
+
name = assert_scan!(scanner, TAG_ID_CLASS_REGEXP)
|
|
54
|
+
|
|
55
|
+
case prefix
|
|
56
|
+
when '#'
|
|
57
|
+
attributes['id'] = [name]
|
|
58
|
+
when '.'
|
|
59
|
+
attributes['class'] << name
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ast = []
|
|
64
|
+
attributes.each do |name, values|
|
|
65
|
+
ast << [:html, :attr, name, [:static, values.join(' ')]]
|
|
66
|
+
end
|
|
67
|
+
ast
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
# Hamlit::Parsers::Whitespace cares about "whitespace removal",
|
|
4
|
+
# which is achieved by '<' or '>' after html tag.
|
|
5
|
+
module Hamlit
|
|
6
|
+
module Parsers
|
|
7
|
+
module Whitespace
|
|
8
|
+
def parse_whitespace_removal(scanner)
|
|
9
|
+
if scanner.match?(/</)
|
|
10
|
+
inner_removal = parse_inner_removal(scanner)
|
|
11
|
+
parse_outer_removal(scanner)
|
|
12
|
+
else
|
|
13
|
+
parse_outer_removal(scanner)
|
|
14
|
+
inner_removal = parse_inner_removal(scanner)
|
|
15
|
+
end
|
|
16
|
+
inner_removal
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def reset_outer_removal
|
|
22
|
+
@outer_removal = Set.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def parse_inner_removal(scanner)
|
|
26
|
+
scanner.scan(/</)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parse_outer_removal(scanner)
|
|
30
|
+
if scanner.scan(/>/)
|
|
31
|
+
@outer_removal.add(@current_indent)
|
|
32
|
+
else
|
|
33
|
+
@outer_removal.delete(@current_indent)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'rails'
|
|
2
|
+
require 'temple'
|
|
3
|
+
|
|
4
|
+
module Hamlit
|
|
5
|
+
class Railtie < ::Rails::Railtie
|
|
6
|
+
initializer :hamlit do |app|
|
|
7
|
+
Hamlit::RailsTemplate = Temple::Templates::Rails.create(
|
|
8
|
+
Hamlit::Engine,
|
|
9
|
+
generator: Temple::Generators::RailsOutputBuffer,
|
|
10
|
+
register_as: :haml,
|
|
11
|
+
escape_html: true,
|
|
12
|
+
streaming: true,
|
|
13
|
+
ugly: true,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Haml extends Haml::Helpers in ActionView each time.
|
|
17
|
+
# It costs much, so Hamlit includes a compatible module at first.
|
|
18
|
+
ActionView::Base.send :include, Hamlit::Helpers
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/hamlit.rb
ADDED
data/spec/Rakefile
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'unindent'
|
|
5
|
+
require 'open-uri'
|
|
6
|
+
|
|
7
|
+
def generate_spec(mode)
|
|
8
|
+
spec = <<-SPEC.unindent
|
|
9
|
+
require "haml"
|
|
10
|
+
|
|
11
|
+
# This is a spec converted by haml-spec.
|
|
12
|
+
# See: https://github.com/haml/haml-spec
|
|
13
|
+
describe "haml #{mode} mode" do
|
|
14
|
+
def assert_pretty(haml, locals, options)
|
|
15
|
+
engine = Haml::Engine.new(haml, options)
|
|
16
|
+
hamlit = Hamlit::Template.new(options) { haml }
|
|
17
|
+
expect(hamlit.render(Object.new, locals)).to eq(engine.render(Object.new, locals))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def assert_ugly(haml, locals, options)
|
|
21
|
+
assert_pretty(haml, locals, { ugly: true }.merge(options))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
SPEC
|
|
25
|
+
|
|
26
|
+
url = 'https://raw.githubusercontent.com/haml/haml-spec/master/tests.json'
|
|
27
|
+
contexts = JSON.parse(open(url).read)
|
|
28
|
+
contexts.each_with_index do |context, index|
|
|
29
|
+
spec += "\n" if index != 0
|
|
30
|
+
spec += " context \"#{context[0]}\" do\n"
|
|
31
|
+
|
|
32
|
+
tests = []
|
|
33
|
+
context[1].each do |name, test|
|
|
34
|
+
tests << {
|
|
35
|
+
name: name,
|
|
36
|
+
html: test['html'],
|
|
37
|
+
haml: test['haml'],
|
|
38
|
+
locals: test['locals'],
|
|
39
|
+
config: test['config'],
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
spec += tests.map { |test|
|
|
44
|
+
locals = Hash[(test[:locals] || {}).map {|x, y| [x.to_sym, y]}]
|
|
45
|
+
options = Hash[(test[:config] || {}).map {|x, y| [x.to_sym, y]}]
|
|
46
|
+
options[:format] = options[:format].to_sym if options[:format]
|
|
47
|
+
|
|
48
|
+
generate_specify(test, locals, options, mode)
|
|
49
|
+
}.join("\n")
|
|
50
|
+
spec += " end\n"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
spec += "end\n"
|
|
54
|
+
File.write("hamlit/#{mode}_spec.rb", spec)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def generate_specify(test, locals, options, mode)
|
|
58
|
+
<<-SPEC
|
|
59
|
+
specify \"#{test[:name]}\" do
|
|
60
|
+
haml = %q{#{test[:haml]}}
|
|
61
|
+
html = %q{#{test[:html]}}
|
|
62
|
+
locals = #{locals}
|
|
63
|
+
options = #{options}
|
|
64
|
+
assert_#{mode}(haml, locals, options)
|
|
65
|
+
end
|
|
66
|
+
SPEC
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
desc 'Convert tests.json into ugly rspec tests'
|
|
70
|
+
task :ugly do
|
|
71
|
+
generate_spec(:ugly)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
desc 'Convert tests.json into pretty rspec tests'
|
|
75
|
+
task :pretty do
|
|
76
|
+
generate_spec(:pretty)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
task default: [:ugly, :pretty]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
describe Hamlit::Compiler do
|
|
2
|
+
describe 'script' do
|
|
3
|
+
let(:options) do
|
|
4
|
+
{ force_escape: false, disable_escape: false }
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
it 'compiles hamlit script ast into assigning' do
|
|
8
|
+
assert_compile(
|
|
9
|
+
[:haml,
|
|
10
|
+
:script,
|
|
11
|
+
'link_to user_path do',
|
|
12
|
+
options,
|
|
13
|
+
[:static, 'user']],
|
|
14
|
+
[:multi,
|
|
15
|
+
[:code, "_hamlit_compiler0 = link_to user_path do"],
|
|
16
|
+
[:static, "user"],
|
|
17
|
+
[:escape, false, [:dynamic, "(_hamlit_compiler0).to_s"]]],
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'compiles multiple hamlit scripts' do
|
|
22
|
+
assert_compile(
|
|
23
|
+
[:multi,
|
|
24
|
+
[:haml,
|
|
25
|
+
:script,
|
|
26
|
+
'link_to user_path do',
|
|
27
|
+
options,
|
|
28
|
+
[:static, 'user']],
|
|
29
|
+
[:haml,
|
|
30
|
+
:script,
|
|
31
|
+
'link_to repo_path do',
|
|
32
|
+
options,
|
|
33
|
+
[:static, 'repo']]],
|
|
34
|
+
[:multi,
|
|
35
|
+
[:multi,
|
|
36
|
+
[:code, "_hamlit_compiler0 = link_to user_path do"],
|
|
37
|
+
[:static, "user"],
|
|
38
|
+
[:escape, false, [:dynamic, "(_hamlit_compiler0).to_s"]]],
|
|
39
|
+
[:multi,
|
|
40
|
+
[:code, "_hamlit_compiler1 = link_to repo_path do"],
|
|
41
|
+
[:static, "repo"],
|
|
42
|
+
[:escape, false, [:dynamic, "(_hamlit_compiler1).to_s"]]]],
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|