2015-03-16 01:32:02 +09:00
|
|
|
require 'strscan'
|
2015-03-10 00:37:41 +09:00
|
|
|
require 'temple'
|
2015-03-22 23:33:13 +09:00
|
|
|
require 'hamlit/concerns/balanceable'
|
2015-03-16 03:02:31 +09:00
|
|
|
require 'hamlit/concerns/escapable'
|
2015-03-16 02:37:10 +09:00
|
|
|
require 'hamlit/concerns/indentable'
|
|
|
|
require 'hamlit/concerns/line_reader'
|
2015-03-10 00:37:41 +09:00
|
|
|
|
2015-03-12 09:07:49 +09:00
|
|
|
module Hamlit
|
2015-03-16 03:02:31 +09:00
|
|
|
class Parser < Temple::Parser
|
2015-03-22 23:33:13 +09:00
|
|
|
include Concerns::Balanceable
|
2015-03-16 03:02:31 +09:00
|
|
|
include Concerns::Escapable
|
2015-03-16 02:37:10 +09:00
|
|
|
include Concerns::Indentable
|
|
|
|
include Concerns::LineReader
|
2015-03-13 00:35:21 +09:00
|
|
|
|
2015-03-11 01:00:08 +09:00
|
|
|
TAG_ID_CLASS_REGEXP = /[a-zA-Z0-9_-]+/
|
2015-03-11 22:35:33 +09:00
|
|
|
INTERNAL_STATEMENTS = %w[else elsif when].freeze
|
2015-03-11 22:16:50 +09:00
|
|
|
SKIP_NEWLINE_EXPS = %i[newline code multi].freeze
|
2015-03-27 21:20:29 +09:00
|
|
|
TAG_REGEXP = /[a-zA-Z0-9\-_:]+/
|
2015-03-11 02:20:42 +09:00
|
|
|
DEFAULT_TAG = 'div'
|
2015-03-11 01:00:08 +09:00
|
|
|
|
2015-03-25 01:51:40 +09:00
|
|
|
define_options :format
|
|
|
|
|
2015-03-10 00:37:41 +09:00
|
|
|
def call(template)
|
2015-03-10 23:38:42 +09:00
|
|
|
reset(template)
|
2015-03-10 20:01:33 +09:00
|
|
|
|
2015-03-10 23:38:42 +09:00
|
|
|
ast = [:multi]
|
|
|
|
ast += parse_lines
|
|
|
|
ast
|
2015-03-10 00:37:41 +09:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2015-03-16 02:37:10 +09:00
|
|
|
# Reset the parser state.
|
|
|
|
def reset(template)
|
|
|
|
reset_lines(template.split("\n"))
|
|
|
|
reset_indent
|
|
|
|
end
|
|
|
|
|
2015-03-10 23:38:42 +09:00
|
|
|
# Parse lines in current_indent and return ASTs.
|
|
|
|
def parse_lines
|
|
|
|
ast = []
|
2015-03-11 00:17:49 +09:00
|
|
|
while next_indent == @current_indent
|
|
|
|
@current_lineno += 1
|
2015-03-16 08:11:07 +09:00
|
|
|
ast << parse_line(current_line)
|
2015-03-11 21:26:46 +09:00
|
|
|
ast << [:static, "\n"] unless skip_newline?(ast.last)
|
2015-03-16 00:51:31 +09:00
|
|
|
ast << [:newline]
|
2015-03-10 23:38:42 +09:00
|
|
|
end
|
|
|
|
ast
|
2015-03-10 20:01:33 +09:00
|
|
|
end
|
|
|
|
|
2015-03-10 23:38:42 +09:00
|
|
|
# Parse current line and return AST.
|
|
|
|
def parse_line(line)
|
|
|
|
return [:newline] if empty_line?(line)
|
|
|
|
|
|
|
|
scanner = StringScanner.new(line)
|
|
|
|
scanner.scan(/ +/)
|
2015-03-17 08:17:31 +09:00
|
|
|
return parse_text(scanner) if scanner.match?(/\#{/)
|
2015-03-10 23:38:42 +09:00
|
|
|
|
|
|
|
case scanner.peek(1)
|
2015-03-10 19:16:07 +09:00
|
|
|
when '!'
|
|
|
|
parse_doctype(scanner)
|
2015-03-11 02:20:42 +09:00
|
|
|
when '%', '.', '#'
|
2015-03-10 23:38:42 +09:00
|
|
|
parse_tag(scanner)
|
2015-03-11 19:51:04 +09:00
|
|
|
when '='
|
|
|
|
parse_script(scanner)
|
2015-03-28 17:28:09 +09:00
|
|
|
when '~'
|
|
|
|
parse_preservation(scanner)
|
2015-03-11 21:26:46 +09:00
|
|
|
when '-'
|
|
|
|
parse_silent_script(scanner)
|
2015-03-15 00:47:44 +09:00
|
|
|
when '/'
|
|
|
|
parse_comment(scanner)
|
2015-03-16 18:18:44 +09:00
|
|
|
when ':'
|
|
|
|
parse_filter(scanner)
|
2015-03-10 20:01:33 +09:00
|
|
|
else
|
|
|
|
parse_text(scanner)
|
2015-03-10 00:37:41 +09:00
|
|
|
end
|
|
|
|
end
|
2015-03-10 19:16:07 +09:00
|
|
|
|
|
|
|
def parse_doctype(scanner)
|
2015-03-10 23:38:42 +09:00
|
|
|
raise SyntaxError unless scanner.scan(/!!!/)
|
2015-03-10 19:16:07 +09:00
|
|
|
|
2015-03-23 01:43:47 +09:00
|
|
|
type = nil
|
2015-03-23 01:35:06 +09:00
|
|
|
if scanner.scan(/ +/) && scanner.rest?
|
2015-03-23 01:43:47 +09:00
|
|
|
type = scanner.rest.strip
|
2015-03-23 01:35:06 +09:00
|
|
|
end
|
|
|
|
|
2015-03-23 01:43:47 +09:00
|
|
|
[:haml, :doctype, options[:format], type]
|
2015-03-10 19:16:07 +09:00
|
|
|
end
|
|
|
|
|
2015-03-10 23:38:42 +09:00
|
|
|
def parse_tag(scanner)
|
2015-03-11 02:20:42 +09:00
|
|
|
tag = DEFAULT_TAG
|
|
|
|
tag = scanner.scan(TAG_REGEXP) if scanner.scan(/%/)
|
2015-03-10 19:16:07 +09:00
|
|
|
|
2015-03-15 04:22:19 +09:00
|
|
|
attrs = [:haml, :attrs]
|
2015-03-13 11:47:51 +09:00
|
|
|
attrs += parse_tag_id_and_class(scanner)
|
2015-03-13 00:50:00 +09:00
|
|
|
attrs += parse_attributes(scanner)
|
2015-03-10 23:38:42 +09:00
|
|
|
|
2015-03-11 01:00:08 +09:00
|
|
|
ast = [:html, :tag, tag, attrs]
|
2015-03-11 19:51:04 +09:00
|
|
|
|
|
|
|
if scanner.match?(/=/)
|
|
|
|
ast << parse_script(scanner)
|
|
|
|
return ast
|
2015-03-27 13:26:19 +09:00
|
|
|
elsif scanner.scan(/\//)
|
|
|
|
return ast
|
2015-03-18 01:54:21 +09:00
|
|
|
elsif scanner.rest.match(/[^ ]/)
|
2015-03-11 19:38:22 +09:00
|
|
|
ast << parse_text(scanner)
|
2015-03-10 23:38:42 +09:00
|
|
|
return ast
|
2015-03-11 19:51:04 +09:00
|
|
|
elsif next_indent <= @current_indent
|
2015-03-25 01:43:59 +09:00
|
|
|
ast << [:multi]
|
2015-03-11 19:51:04 +09:00
|
|
|
return ast
|
2015-03-10 19:16:07 +09:00
|
|
|
end
|
2015-03-10 23:38:42 +09:00
|
|
|
|
|
|
|
content = [:multi, [:static, "\n"]]
|
|
|
|
content += with_indented { parse_lines }
|
|
|
|
ast << content
|
2015-03-10 19:16:07 +09:00
|
|
|
ast
|
|
|
|
end
|
2015-03-10 20:01:33 +09:00
|
|
|
|
2015-03-11 19:51:04 +09:00
|
|
|
def parse_script(scanner)
|
|
|
|
raise SyntaxError unless scanner.scan(/=/)
|
|
|
|
|
2015-03-16 13:46:35 +09:00
|
|
|
code = scan_code(scanner)
|
2015-03-16 03:02:31 +09:00
|
|
|
return escape_html([:dynamic, code]) unless has_block?
|
2015-03-15 04:02:13 +09:00
|
|
|
|
2015-03-15 04:22:19 +09:00
|
|
|
ast = [:haml, :script, code]
|
2015-03-15 04:02:13 +09:00
|
|
|
ast += with_indented { parse_lines }
|
|
|
|
ast << [:code, 'end']
|
2015-03-11 19:51:04 +09:00
|
|
|
ast
|
|
|
|
end
|
|
|
|
|
2015-03-28 17:28:09 +09:00
|
|
|
def parse_preservation(scanner)
|
|
|
|
raise SyntaxError unless scanner.scan(/~/)
|
|
|
|
|
|
|
|
code = scan_code(scanner)
|
|
|
|
code = "Hamlit::Helpers.find_and_preserve(#{code})"
|
|
|
|
escape_html([:dynamic, code])
|
|
|
|
end
|
|
|
|
|
2015-03-11 21:26:46 +09:00
|
|
|
def parse_silent_script(scanner)
|
|
|
|
raise SyntaxError unless scanner.scan(/-/)
|
2015-03-16 06:26:36 +09:00
|
|
|
if scanner.scan(/#/)
|
|
|
|
with_indented { skip_lines }
|
|
|
|
return [:newline]
|
|
|
|
end
|
2015-03-11 21:26:46 +09:00
|
|
|
|
|
|
|
ast = [:code]
|
2015-03-16 13:46:35 +09:00
|
|
|
ast << scan_code(scanner)
|
2015-03-15 04:02:13 +09:00
|
|
|
return ast unless has_block?
|
2015-03-11 21:49:28 +09:00
|
|
|
|
|
|
|
ast = [:multi, ast]
|
|
|
|
ast += with_indented { parse_lines }
|
2015-03-16 00:08:08 +09:00
|
|
|
ast << [:code, 'end'] unless same_indent?(next_line) && internal_statement?(next_line)
|
2015-03-11 21:26:46 +09:00
|
|
|
ast
|
|
|
|
end
|
|
|
|
|
2015-03-15 00:47:44 +09:00
|
|
|
def parse_comment(scanner)
|
|
|
|
raise SyntaxError unless scanner.scan(/\//)
|
|
|
|
|
|
|
|
ast = [:html, :comment]
|
2015-03-28 10:29:59 +09:00
|
|
|
if scanner.rest.match(/[^ ]/)
|
|
|
|
ast << [:static, " #{scanner.scan(/.+/).strip} "]
|
|
|
|
return ast
|
|
|
|
end
|
|
|
|
|
|
|
|
# FIXME: parse_lines may not be enough
|
|
|
|
content = [:multi, [:static, "\n"]]
|
|
|
|
content += with_indented { parse_lines }
|
|
|
|
ast << content
|
2015-03-15 00:47:44 +09:00
|
|
|
ast
|
|
|
|
end
|
|
|
|
|
2015-03-16 18:18:44 +09:00
|
|
|
def parse_filter(scanner)
|
|
|
|
raise SyntaxError unless scanner.scan(/:/)
|
|
|
|
|
|
|
|
name = scanner.scan(/.+/).strip
|
|
|
|
lines = with_indented { read_lines }
|
|
|
|
[:haml, :filter, name, lines]
|
|
|
|
end
|
|
|
|
|
2015-03-10 20:01:33 +09:00
|
|
|
def parse_text(scanner)
|
2015-03-17 08:17:31 +09:00
|
|
|
ast = [:haml, :text]
|
2015-03-18 01:54:21 +09:00
|
|
|
ast << scanner.scan(/.+/).strip
|
2015-03-10 23:38:42 +09:00
|
|
|
ast
|
|
|
|
end
|
2015-03-10 20:01:33 +09:00
|
|
|
|
2015-03-13 00:50:00 +09:00
|
|
|
def parse_attributes(scanner)
|
2015-03-22 23:33:13 +09:00
|
|
|
if scanner.match?(/{/)
|
|
|
|
parse_old_attributes(scanner)
|
|
|
|
else
|
|
|
|
parse_new_attributes(scanner)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_old_attributes(scanner)
|
|
|
|
[read_braces(scanner)].compact
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_new_attributes(scanner)
|
|
|
|
[read_parentheses(scanner)].compact
|
2015-03-13 00:50:00 +09:00
|
|
|
end
|
|
|
|
|
2015-03-11 01:00:08 +09:00
|
|
|
def parse_tag_id_and_class(scanner)
|
2015-03-13 00:50:00 +09:00
|
|
|
attributes = Hash.new { |h, k| h[k] = [] }
|
2015-03-11 01:00:08 +09:00
|
|
|
|
|
|
|
while prefix = scanner.scan(/[#.]/)
|
|
|
|
name = scanner.scan(TAG_ID_CLASS_REGEXP)
|
|
|
|
raise SyntaxError unless name
|
|
|
|
|
|
|
|
case prefix
|
|
|
|
when '#'
|
2015-03-27 13:45:18 +09:00
|
|
|
attributes['id'] = [name]
|
2015-03-11 01:00:08 +09:00
|
|
|
when '.'
|
2015-03-13 00:50:00 +09:00
|
|
|
attributes['class'] << name
|
2015-03-11 01:00:08 +09:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-03-13 11:47:51 +09:00
|
|
|
ast = []
|
|
|
|
attributes.each do |name, values|
|
|
|
|
ast << [:html, :attr, name, [:static, values.join(' ')]]
|
|
|
|
end
|
|
|
|
ast
|
2015-03-11 01:00:08 +09:00
|
|
|
end
|
|
|
|
|
2015-03-11 22:35:33 +09:00
|
|
|
def internal_statement?(line)
|
2015-03-11 22:16:50 +09:00
|
|
|
return false unless line
|
|
|
|
|
|
|
|
scanner = StringScanner.new(line)
|
2015-03-15 18:49:59 +09:00
|
|
|
scanner.scan(/ +/)
|
2015-03-11 22:16:50 +09:00
|
|
|
return false unless scanner.scan(/-/)
|
|
|
|
|
|
|
|
scanner.scan(/ +/)
|
|
|
|
statement = scanner.scan(/[^ ]+/)
|
2015-03-11 22:20:06 +09:00
|
|
|
INTERNAL_STATEMENTS.include?(statement)
|
2015-03-11 22:16:50 +09:00
|
|
|
end
|
|
|
|
|
2015-03-11 21:26:46 +09:00
|
|
|
def skip_newline?(ast)
|
2015-03-27 16:48:43 +09:00
|
|
|
SKIP_NEWLINE_EXPS.include?(ast.first) || (ast[0..1] == [:haml, :doctype])
|
2015-03-11 21:26:46 +09:00
|
|
|
end
|
2015-03-15 04:02:13 +09:00
|
|
|
|
|
|
|
def has_block?
|
|
|
|
next_indent == @current_indent + 1
|
|
|
|
end
|
2015-03-16 13:46:35 +09:00
|
|
|
|
|
|
|
def scan_code(scanner)
|
|
|
|
code = ''
|
|
|
|
loop do
|
|
|
|
code += scanner.scan(/.+/).strip
|
|
|
|
break unless code =~ /,\Z/
|
|
|
|
|
|
|
|
@current_lineno += 1
|
|
|
|
scanner = StringScanner.new(current_line)
|
|
|
|
code += ' '
|
|
|
|
end
|
|
|
|
code
|
|
|
|
end
|
2015-03-10 00:37:41 +09:00
|
|
|
end
|
|
|
|
end
|