1
0
Fork 0
mirror of https://github.com/haml/haml.git synced 2022-11-09 12:33:31 -05:00

Parse N-space indentation

Fixes #26.
This commit is contained in:
Takashi Kokubun 2015-06-11 23:51:59 +09:00
parent f27b44450e
commit 176f56db62
5 changed files with 107 additions and 44 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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