hamlit 0.1.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/.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
|