mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
parent
f27b44450e
commit
176f56db62
5 changed files with 107 additions and 44 deletions
|
@ -8,12 +8,14 @@ module Hamlit
|
|||
include Concerns::Error
|
||||
|
||||
def reset_indent
|
||||
@indent_logs = []
|
||||
@current_indent = 0
|
||||
end
|
||||
|
||||
# Return nearest line's indent level since next line. This method ignores
|
||||
# empty line. It returns -1 if next_line does not exist.
|
||||
def next_indent
|
||||
return 1 if !@indent_space && fetch_indent(next_line).length > 0
|
||||
count_indent(next_line)
|
||||
end
|
||||
|
||||
|
@ -28,19 +30,16 @@ module Hamlit
|
|||
@current_indent -= 1
|
||||
end
|
||||
|
||||
def count_indent(line, strict: false)
|
||||
def count_indent(line)
|
||||
return EOF unless line
|
||||
width = count_width(line)
|
||||
return 0 if indent_rule == 0
|
||||
|
||||
return (width + 1) / 2 unless strict
|
||||
compile_error!('Expected to count even-width indent') if width.odd?
|
||||
|
||||
width / 2
|
||||
line.match(/\A[ \t]+/).to_s.length / indent_rule
|
||||
end
|
||||
|
||||
def count_width(line)
|
||||
return EOF unless line
|
||||
line[/\A +/].to_s.length
|
||||
line[/\A[ \t]+/].to_s.length
|
||||
end
|
||||
|
||||
def same_indent?(line)
|
||||
|
@ -50,31 +49,30 @@ module Hamlit
|
|||
|
||||
# Validate current line's indentation
|
||||
def validate_indentation!(ast)
|
||||
width = next_width
|
||||
return false if width == @current_indent * 2
|
||||
return true unless next_line
|
||||
|
||||
if width != Hamlit::EOF && (width > @current_indent * 2 || width.odd?)
|
||||
ast << [:newline]
|
||||
ast << syntax_error(
|
||||
"inconsistent indentation: #{2 * @current_indent} spaces used for indentation, "\
|
||||
"but the rest of the document was indented using #{width} spaces"
|
||||
)
|
||||
indent = fetch_indent(next_line)
|
||||
if indent.include?(' ') && indent.include?("\t")
|
||||
syntax_error!("Indentation can't use both tabs and spaces.")
|
||||
end
|
||||
true
|
||||
@indent_logs << indent
|
||||
|
||||
if !@indent_space && @indent_logs.last != ''
|
||||
@indent_space = @indent_logs.last
|
||||
end
|
||||
validate_indentation_consistency!(indent)
|
||||
|
||||
next_indent != @current_indent
|
||||
end
|
||||
|
||||
# Validate the template is using consitent indentation, 2 spaces or a tab.
|
||||
def validate_indentation_consistency!(template)
|
||||
last_indent = ''
|
||||
def validate_indentation_consistency!(indent)
|
||||
return false if indent.empty?
|
||||
return false if !@indent_space || @indent_space.empty?
|
||||
|
||||
indents = template.scan(/^[ \t]+/)
|
||||
indents.each do |indent|
|
||||
if last_indent.include?(' ') && indent.include?("\t") ||
|
||||
last_indent.include?("\t") && indent.include?(' ')
|
||||
syntax_error!(%Q{Inconsistent indentation: #{indent_label(indent)} used for indentation, but the rest of the document was indented using #{indent_label(last_indent)}.})
|
||||
end
|
||||
|
||||
last_indent = indent
|
||||
if indent[0] != @indent_space[0] || indent.length < @indent_space.length
|
||||
syntax_error!("Inconsistent indentation: #{indent_label(indent)} used for indentation, "\
|
||||
"but the rest of the document was indented using #{indent_label(@indent_space)}.")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -87,19 +85,43 @@ module Hamlit
|
|||
"#{length} #{label}#{'s' if length > 1}"
|
||||
end
|
||||
|
||||
# Replace hard tabs into 2 spaces
|
||||
def replace_hard_tabs(template)
|
||||
lines = []
|
||||
template.each_line do |line|
|
||||
lines << line.gsub(/^\t+/) do |match|
|
||||
' ' * (match.length * 2)
|
||||
end
|
||||
end
|
||||
lines.join
|
||||
def has_block?
|
||||
return false unless next_line
|
||||
return fetch_indent(next_line).length > 0 unless @indent_space
|
||||
|
||||
next_indent > @current_indent
|
||||
end
|
||||
|
||||
def has_block?
|
||||
next_indent > @current_indent
|
||||
private
|
||||
|
||||
def indent_label(indent)
|
||||
return %Q{"#{indent}"} if indent.include?(' ') && indent.include?("\t")
|
||||
|
||||
label = indent.include?(' ') ? 'space' : 'tab'
|
||||
length = indent.match(/[ \t]+/).to_s.length
|
||||
|
||||
"#{length} #{label}#{'s' if length > 1}"
|
||||
end
|
||||
|
||||
def count_width(line)
|
||||
return EOF unless line
|
||||
line[/\A +/].to_s.length
|
||||
end
|
||||
|
||||
def next_space
|
||||
next_line[/\A +/].to_s
|
||||
end
|
||||
|
||||
def next_width
|
||||
count_width(next_line)
|
||||
end
|
||||
|
||||
def indent_rule
|
||||
(@indent_space || '').length
|
||||
end
|
||||
|
||||
def fetch_indent(str)
|
||||
str.match(/^[ \t]+/).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ module Hamlit
|
|||
end
|
||||
|
||||
def skip_lines
|
||||
while next_indent >= @current_indent
|
||||
while next_line && next_indent >= @current_indent
|
||||
@current_lineno += 1
|
||||
end
|
||||
end
|
||||
|
@ -47,7 +47,8 @@ module Hamlit
|
|||
end
|
||||
|
||||
def read_line?
|
||||
return true if count_indent(next_line, strict: false) >= @current_indent
|
||||
return false unless next_line
|
||||
return true if next_line.index(/^#{@indent_logs.last}[ \t]/)
|
||||
|
||||
line = @lines[@current_lineno + 1]
|
||||
return false unless line
|
||||
|
|
|
@ -38,7 +38,6 @@ module Hamlit
|
|||
# Reset the parser state.
|
||||
def reset(template)
|
||||
validate_indentation_consistency!(template)
|
||||
template = replace_hard_tabs(template)
|
||||
template = preprocess_multilines(template)
|
||||
|
||||
reset_lines(template.split("\n"))
|
||||
|
@ -62,6 +61,9 @@ module Hamlit
|
|||
ast << [:static, "\n"] unless skip_newline?(node)
|
||||
end
|
||||
ast
|
||||
rescue => e
|
||||
ast << syntax_error(e.message)
|
||||
ast
|
||||
end
|
||||
|
||||
# Parse current line and return AST.
|
||||
|
@ -69,7 +71,7 @@ module Hamlit
|
|||
return [:multi] if empty_line?(line)
|
||||
|
||||
scanner = wrap_scanner(line)
|
||||
scanner.scan(/ +/)
|
||||
scanner.scan(/[ \t]+/)
|
||||
|
||||
unless inline
|
||||
ast = parse_outer_line(scanner)
|
||||
|
|
|
@ -11,16 +11,18 @@ describe Hamlit::Engine do
|
|||
expect { render_string(<<-HAML.unindent) }.
|
||||
%a
|
||||
%b
|
||||
%a
|
||||
HAML
|
||||
to raise_error(Hamlit::SyntaxError, 'inconsistent indentation: 2 spaces used for indentation, but the rest of the document was indented using 4 spaces')
|
||||
to raise_error(Hamlit::SyntaxError, 'Inconsistent indentation: 1 space used for indentation, but the rest of the document was indented using 4 spaces.')
|
||||
end
|
||||
|
||||
it 'raises syntax error for illegal indentation' do
|
||||
expect { render_string(<<-HAML.unindent) }.
|
||||
%a
|
||||
%b
|
||||
\t\t%a
|
||||
\t%a
|
||||
HAML
|
||||
to raise_error(Hamlit::SyntaxError, 'inconsistent indentation: 2 spaces used for indentation, but the rest of the document was indented using 1 spaces')
|
||||
to raise_error(Hamlit::SyntaxError, 'Inconsistent indentation: 1 tab used for indentation, but the rest of the document was indented using 2 tabs.')
|
||||
end
|
||||
|
||||
it 'raises syntax error which has correct line number in backtrace' do
|
||||
|
@ -59,5 +61,13 @@ describe Hamlit::Engine do
|
|||
HAML
|
||||
to raise_error(Hamlit::SyntaxError, 'Unbalanced brackets.')
|
||||
end
|
||||
|
||||
it 'raises syntax error for an inconsistent indentation' do
|
||||
expect { render_string(<<-HAML.unindent) }.
|
||||
%p
|
||||
\t %span
|
||||
HAML
|
||||
to raise_error(Hamlit::SyntaxError, "Indentation can't use both tabs and spaces.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,5 +10,33 @@ describe Hamlit::Engine do
|
|||
</p>
|
||||
HTML
|
||||
end
|
||||
|
||||
it 'accepts N-space indentation' do
|
||||
assert_render(<<-HAML, <<-HTML)
|
||||
%p
|
||||
%span
|
||||
foo
|
||||
HAML
|
||||
<p>
|
||||
<span>
|
||||
foo
|
||||
</span>
|
||||
</p>
|
||||
HTML
|
||||
end
|
||||
|
||||
it 'accepts N-tab indentation' do
|
||||
assert_render(<<-HAML, <<-HTML, compatible_only: :haml)
|
||||
%p
|
||||
\t%span
|
||||
\t\tfoo
|
||||
HAML
|
||||
<p>
|
||||
<span>
|
||||
foo
|
||||
</span>
|
||||
</p>
|
||||
HTML
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue