Skip to content

Instantly share code, notes, and snippets.

@tatat
Last active December 15, 2015 02:19
Show Gist options
  • Save tatat/5186880 to your computer and use it in GitHub Desktop.
Save tatat/5186880 to your computer and use it in GitHub Desktop.
たんぶらテンプレートのパース
require 'parslet'
module Tumblr
module Parser
class Base < Parslet::Parser
def space
match('\s').repeat(1)
end
def space?
space.maybe
end
def attribute(opts = {})
key = match('[a-zA-Z0-9_\-]').repeat(1)
key = key.as(:key) if opts[:capture]
space? >> key >> str('=') >> double_quotes(opts) >> space?
end
def named_attribute(opts = {})
key = str(opts[:name])
key = key.as(:key) if opts[:capture]
space? >> key >> str('=') >> double_quotes(opts) >> space?
end
def double_quotes(opts = {})
value = (str('\\') >> any | str('"').absent? >> any).repeat
value = value.as(:value) if opts[:capture]
str('"') >> value >> str('"')
end
end
class TemplateParser < Base
root :document
rule(:document) {
(
(
block_open.as(:open) >>
scope { document.as(:inner) } >>
block_close.as(:close)
) |
variable.as(:variable) |
text.as(:text)
).repeat
}
rule(:variable) {
str('{') >> variable_name >> str('}')
}
rule(:variable_name) {
(
str('/').maybe >>
str('block:')
).absent? >>
match('[a-zA-Z0-9 _\-:]').repeat(1).as(:name)
}
rule(:block_name) {
match('[a-zA-Z0-9_\-]').repeat(1)
}
rule(:block_open) {
str('{block:') >>
block_open_inner(capture: true) >>
str('}')
}
rule(:block_close) {
str('{/block:') >>
block_close_inner(capture: true) >>
str('}')
}
rule(:text) {
(
match('[^{}]') |
str('{') >> (
str('/').maybe >> str('block:') >> block_open_inner >> str('}') |
variable_name >> str('}')
).absent? |
(
str('{') >> str('/').maybe >> str('block:') >> block_close_inner |
str('{') >> variable_name
).absent? >> str('}')
).repeat(1)
}
def block_open_inner(opts = {})
parslet = block_name
parslet = parslet.capture(:block_name).as(:name) if opts[:capture]
parslet >> space >> attribute(opts).repeat(1).as(:attributes) | parslet
end
def block_close_inner(opts = {})
if opts[:capture]
dynamic {|source, context| str(context.captures[:block_name]).as(:name) }
else
block_name
end
end
end
class DeclarationParser < Base
root :declarations
rule(:declarations) {
(
(
str('<meta') >>
space >>
(
attribute_name.as(:name) |
attribute_content.as(:content) |
attribute
).repeat(2).as(:declaration) >>
str('/').maybe >>
str('>')
) |
str('</head>') >> any.repeat(1) |
match('[^<>]').repeat(1) |
any
).repeat.as(:declarations)
}
rule(:attribute_name) {
named_attribute name: 'name', capture: true
}
rule(:attribute_content) {
named_attribute name: 'content', capture: true
}
end
end
module Transformer
class Base < Parslet::Transform
end
class TemplateTransformer < Base
TF = self
rule(text: simple(:text)) {
{
type: :text,
value: text.to_s
}
}
rule(variable: {name: simple(:variable)}) {
{
type: :variable,
name: variable.to_s
}
}
rule(
open: {name: simple(:name), attributes: subtree(:attributes)},
inner: subtree(:inner),
close: {name: simple(:name)}
) {
TF.create_block(name, inner, attributes)
}
rule(
open: {name: simple(:name)},
inner: subtree(:inner),
close: {name: simple(:name)}
) {
TF.create_block(name, inner)
}
def self.create_block(name, inner, attributes = nil)
{
type: :block,
name: name.to_s,
attributes: attributes ? Hash[attributes.map{|value| [value[:key].to_sym, value[:value].to_s]}] : {},
inner: inner
}
end
end
class DeclarationTransformer < Base
rule(name: {key: simple(:key), value: simple(:value)}) {
value.to_s
}
rule(content: {key: simple(:key), value: simple(:value)}) {
value.to_s
}
rule(declaration: subtree(:declaration)) {
declaration
}
rule(declarations: subtree(:declarations)) {
Hash[
declarations
.reject{|value| value.length < 2 || !(/:/ =~ value.first) }
.map{|value| [value[0], value[1]]}
]
}
end
end
end
def parse_declarations(template)
template = template.scan(%r{<meta [^<>]+?/?>}i).join
tree = Tumblr::Parser::DeclarationParser.new.parse(template)
Tumblr::Transformer::DeclarationTransformer.new.apply(tree)
end
def parse_template(template)
template = template.gsub(/<!--(?!\[if )[\s\S]+?-->/, '').gsub(/^\s+/, '').gsub(/(?:\r\n|\r)/, "\n")
tree = Tumblr::Parser::TemplateParser.new.parse(template)
Tumblr::Transformer::TemplateTransformer.new.apply(tree)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment