Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

423 lines (373 sloc) 8.069 kB
grammar Less
rule primary
(declaration / ruleset / import / comment)+ <Builder> / declaration* <Builder> / import* <Builder> / comment*
end
rule comment
ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
end
#
# div, .class, body > p {...}
#
rule ruleset
selectors "{" ws primary ws "}" ws {
def build env
# Build the ruleset for each selector
selectors.build(env, :ruleset).each do |sel|
primary.build sel
end
end
} / ws selectors ';' ws {
def build env
selectors.build(env, :mixin).each do |path|
rules = path.inject(env.root) do |current, node|
current.descend(node.selector, node) or raise MixinNameError, path.join
end.rules
env.rules += rules
end
end
}
end
rule import
"@import" S url:(string / url) medias? s ';' ws {
def build env
path = File.join(env.root.file, url.value)
path += '.less' unless path =~ /\.(le|c)ss$/
if File.exist? path
imported = Less::Engine.new(File.new(path)).to_tree
env.rules += imported.rules
else
raise ImportError, path
end
end
}
end
rule url
'url(' path:(string / [-a-zA-Z0-9_%$/.&=:;#+?]+) ')' {
def build env = nil
Node::String.new CGI.unescape(path.text_value)
end
def value
build
end
}
end
rule medias
[-a-z]+ (s ',' s [a-z]+)*
end
rule selectors
ws selector tail:(s ',' ws selector)* ws {
def build env, method
all.map do |e|
e.send(method, env) if e.respond_to? method
end.compact
end
def all
[selector] + tail.elements.map {|e| e.selector }
end
}
end
#
# div > p a {...}
#
rule selector
sel:(s select element s)+ arguments? {
def ruleset env
sel.elements.inject(env) do |node, e|
node << Node::Element.new(e.element.text_value, e.select.text_value)
node.last
end
end
def mixin env
sel.elements.map do |e|
Node::Element.new(e.element.text_value, e.select.text_value)
end
end
}
end
#
# @my-var: 12px;
# height: 100%;
#
rule declaration
ws name:(ident / variable) s ':' s expressions s (';'/ ws &'}') ws {
def build env
env << (name.text_value =~ /^@/ ?
Node::Variable : Node::Property).new(name.text_value, expressions.build(env), env)
end
# Empty rule
} / ws ident s ':' s ';' ws
end
rule expressions
expression+ {
def build env
elements.map do |e|
e.build(env) if e.respond_to? :build
end.compact
end
}
end
#
# An operation or compound value
#
rule expression
s '(' s expressions s ')' s {
def build env
Node::Expression.new(['('] + expressions.build(env).flatten + [')'])
end
} / entity tail:(operator entity)* ws {
def build env
exp = all.map do |e|
e.method(:build).arity.zero??
e.build : e.build(env) if e.respond_to? :build
end.dissolve
exp.is_a?(Array) ? Node::Expression.new(exp) : exp
end
def all
[entity] + tail.elements.map {|i| [i.operator, i.entity] }.flatten.compact
end
}
end
#
# Entity: Any whitespace delimited token
#
rule entity
function / fonts / keyword / accessor / variable / literal / important
end
rule fonts
font family:(s ',' s font)+ {
def build
Node::FontFamily.new(all.map(&:build))
end
def all
[font] + family.elements.map {|f| f.font }
end
}
end
rule font
[a-zA-Z] [-a-zA-Z0-9]* {
def build
Node::Keyword.new(text_value)
end
} / string {
def build
Node::String.new(text_value)
end
}
end
#
# An identifier
#
rule ident
'*'? '-'? [-a-z0-9_]+
end
rule variable
'@' [-a-zA-Z0-9_]+ {
def build
Node::Variable.new(text_value)
end
}
end
#
# div / .class / #id / input[type="text"] / lang(fr)
#
rule element
(class_id / tag / ident) attribute* ('(' ident? attribute* ')')? / attribute+ / '@media' / '@font-face'
end
rule class_id
tag? (class / id)+
end
#
# [type="text"]
#
rule attribute
'[' tag ([|~*$^]? '=') (tag / string) ']' / '[' (tag / string) ']'
end
rule class
'.' [_a-z] [-a-zA-Z0-9_]*
end
rule id
'#' [_a-z] [-a-zA-Z0-9_]*
end
rule tag
[a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
end
rule select
(s [+>~] s / s ':' / S)?
end
# TODO: Merge this with attribute rule
rule accessor
ident:(class_id / tag) '[' attr:(string / variable) ']' {
def build env
env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
end
}
end
rule operator
S [-+*/] S {
def build
Node::Operator.new(text_value.strip)
end
} / [-+*/] {
def build
Node::Operator.new(text_value)
end
}
end
#
# Tokens which don't need to be evaluated
#
rule literal
color / (dimension / [-a-z]+) '/' dimension {
def build
Node::Anonymous.new(text_value)
end
} / number unit {
def build
Node::Number.new(number.text_value, unit.text_value)
end
} / string {
def build
Node::String.new(text_value)
end
}
end
# !important
rule important
'!important' {
def build
Node::Keyword.new(text_value)
end
}
end
#
# `blue`, `small`, `normal` etc.
#
rule keyword
[-a-zA-Z]+ !ns {
def build
Node::Keyword.new(text_value)
end
}
end
#
# 'hello world' / "hello world"
#
rule string
"'" content:(!"'" . )* "'" {
def value
content.text_value
end
} / ["] content:(!["] . )* ["] {
def value
content.text_value
end
}
end
#
# Numbers & Units
#
rule dimension
number unit
end
rule number
'-'? [0-9]* '.' [0-9]+ / '-'? [0-9]+
end
rule unit
('px'/'em'/'pc'/'%'/'pt'/'cm'/'mm')?
end
#
# Color
#
rule color
'#' rgb {
def build
Node::Color.new(*rgb.build)
end
} / fn:(('hsl'/'rgb') 'a'?) arguments {
def build
Node::Function.new(fn.text_value, arguments.build.flatten)
end
}
end
#
# 00ffdd / 0fd
#
rule rgb
r:(hex hex) g:(hex hex) b:(hex hex) {
def build
[r.text_value, g.text_value, b.text_value]
end
} / r:hex g:hex b:hex {
def build
[r.text_value, g.text_value, b.text_value].map {|c| c * 2 }
end
}
end
rule hex
[a-fA-F0-9]
end
#
# Functions and arguments
#
rule function
name:([-a-zA-Z_]+) arguments {
def build
Node::Function.new(name.text_value, [arguments.build].flatten)
end
}
end
rule arguments
'(' s argument s tail:(',' s argument s)* ')' {
def build
all.map do |e|
e.build if e.respond_to? :build
end.compact
end
def all
[argument] + tail.elements.map {|e| e.argument }
end
}
end
rule argument
color {
def build
Node::Color.new text_value
end
} / number unit {
def build
Node::Number.new number.text_value, unit.text_value
end
} / string {
def build
Node::String.new text_value
end
} / [a-zA-Z]+ '=' dimension {
def build
Node::Anonymous.new text_value
end
} / [-a-zA-Z0-9_%$/.&=:;#+?]+ {
def build
Node::String.new text_value
end
}
end
#
# Whitespace
#
rule s
[ ]*
end
rule S
[ ]+
end
rule ws
[\n ]*
end
rule WS
[\n ]+
end
# Non-space char
rule ns
![ ;\n] .
end
end
Jump to Line
Something went wrong with that request. Please try again.