grammar Rhtml
rule doc
space node space x:doc? {
def convert
if x.empty?
node.convert
else
node.convert + x.convert
end
end
}
end
rule node
yield_with_name / yield / hprintlet / printlet / scriptlet / doctype / directive / self_closing_tag / imgtag / closetag / opentag / text
end
# Argh. For some reason I can't get this to work, so I split it into two rules
# rule yield
# '<%=' space 'yield' space (':' varname space)? '%>' {
# def convert
# var = "@content_for_" + varname.nil? ? "layout" : varname.text_value
# line "rawtext #{var} # Note: you must define #{var} elsewhere"
# end
# }
# end
rule yield_with_name
'<%=' space 'yield' space ':' varname space '%>' {
def convert
var = "@content_for_" + varname.text_value
line "rawtext #{var} # Note: you must define #{var} elsewhere"
end
}
end
rule yield
'<%=' space 'yield' space '%>' {
def convert
var = "@content_for_layout"
line "rawtext #{var} # Note: you must define #{var} elsewhere"
end
}
end
rule scriptlet
'<%' space code space '%>' {
def convert
text = code.text_value_removing_trims.strip
if text =~ /\bdo( |.*|)?$/
line_in text
elsif text == "end"
line_out text
else
line text
end
end
}
end
rule printlet
'<%=' space code space '%>' {
def convert
line "rawtext #{code.convert_removing_trims}"
end
}
end
rule hprintlet
'<%=' space 'h' ' '+ code space '%>' {
def convert
line "text #{code.convert_removing_trims}"
end
}
end
rule code
(('%' !'>') / [^%])* {
def convert_removing_trims
convert.gsub(/\s*\-\s*$/, '')
end
def text_value_removing_trims
text_value.gsub(/\s*\-\s*$/, '')
end
def convert
code = text_value.strip
# matches a word, followed by either a word, a string, or a symbol
result = code.gsub(/^(\w+) ([\w:"'].*)$/, '\1(\2)')
result
end
}
end
rule doctype
']* '>' {
def convert
line "rawtext '#{text_value}'"
end
}
end
rule directive
']* '>' {
def convert
line "rawtext '#{text_value}'"
end
}
end
rule tagname
[A-Za-z0-9_:-]+
end
rule varname
[A-Za-z0-9_]+
end
rule self_closing_tag
'<' tag_name:tagname attrs:attributes? space '/>' {
def convert
line "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert}"
end
}
end
rule opentag
'<' tag_name:tagname attrs:attributes? space '>' {
def convert
line_in "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert} do"
end
}
end
rule imgtag
'<' tag_name:'img' attrs:attributes? space '>' {
def convert
line "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert}"
end
}
end
rule closetag
'' tag_name:tagname '>' {
def convert
line_out "end"
end
}
end
rule text
(([<>] !(tagname / [/%!])) / [^<>])+ {
def convert
stripped = text_value.strip
if stripped.empty?
""
else
line "text '#{CGI.unescapeHTML(stripped).gsub(/\'/, "\\\\'")
}'"
end
end
}
end
rule attributes
first:attribute rest:attributes* {
def convert(internal = false)
out = " " + first.convert +
if rest.empty?
""
else
",#{rest.elements.first.convert(true)}" # this is hacky -- is there a better way?
end
if (! internal) && out =~ /[\(\)]/ && out =~ /^(\s*)(.*?)(\s*)$/
out = "(#{$2})#{$3}"
end
out
end
}
end
rule attribute
space n:(tagname) space '=' space v:quoted space {
def convert
attr_name = (n.text_value =~ /[-:]/) ? "'#{n.text_value}'" : ":#{n.text_value}"
"#{attr_name} => #{v.convert}"
end
}
end
rule quoted
(('"' val:([^"]*) '"') / ('\'' val:([^']*) '\'')) {
def value
val.text_value
end
def convert
extract_erb(val.text_value)
end
def parenthesize_if_necessary(s)
return s if s.strip =~ /^\(.*\)$/ || s =~ /^[A-Z0-9_]*$/i
"(" + s + ")"
end
def escape_single_quotes(s)
s.gsub(/[']/, '\\\\\'')
end
def escape_quoted(s)
escape_single_quotes(CGI.unescapeHTML(s))
end
def extract_erb(s, parenthesize = true)
if s =~ /^(.*?)<%=(.*?)%>(.*?)$/
pre, code, post = escape_quoted($1), $2, escape_quoted($3)
out = ""
out = "'#{pre}' + " unless pre.length == 0
out += parenthesize_if_necessary(code.strip)
unless post.length == 0
post = extract_erb(post, false)
out += " + #{post}"
end
out = parenthesize_if_necessary(out) if parenthesize
out
else
"'" + escape_quoted(s) + "'"
end
end
}
end
rule space
[ \n\t]*
end
end