cloudhead-less 0.8.12 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +10 -1
- data/Rakefile +21 -2
- data/VERSION +1 -1
- data/bin/lessc +0 -8
- data/less.gemspec +48 -15
- data/lib/less/command.rb +24 -25
- data/lib/less/engine/builder.rb +13 -0
- data/lib/less/engine/less.tt +379 -0
- data/lib/less/engine/nodes/element.rb +153 -0
- data/lib/less/engine/nodes/entity.rb +59 -0
- data/lib/less/engine/nodes/function.rb +61 -0
- data/lib/less/engine/nodes/literal.rb +132 -0
- data/lib/less/engine/nodes/property.rb +120 -0
- data/lib/less/engine/nodes/selector.rb +39 -0
- data/lib/less/engine/nodes.rb +8 -0
- data/lib/less/engine/parser.rb +3854 -0
- data/lib/less/engine.rb +42 -139
- data/lib/less.rb +65 -10
- data/spec/command_spec.rb +2 -6
- data/spec/css/accessors-1.0.css +20 -0
- data/spec/css/big-1.0.css +3770 -0
- data/spec/css/comments-1.0.css +11 -0
- data/spec/css/css-1.0.css +40 -0
- data/spec/css/functions-1.0.css +8 -0
- data/spec/css/import-1.0.css +13 -0
- data/spec/css/mixins-1.0.css +30 -0
- data/spec/css/operations-1.0.css +30 -0
- data/spec/css/rulesets-1.0.css +19 -0
- data/spec/css/scope-1.0.css +16 -0
- data/spec/css/strings-1.0.css +14 -0
- data/spec/css/variables-1.0.css +7 -0
- data/spec/css/whitespace-1.0.css +11 -0
- data/spec/engine_spec.rb +66 -18
- data/spec/less/accessors-1.0.less +20 -0
- data/spec/less/big-1.0.less +4810 -0
- data/spec/less/colors-1.0.less +0 -0
- data/spec/less/comments-1.0.less +46 -0
- data/spec/less/css-1.0.less +82 -0
- data/spec/less/exceptions/mixed-units-error.less +0 -0
- data/spec/less/exceptions/name-error-1.0.less +0 -0
- data/spec/less/exceptions/syntax-error-1.0.less +0 -0
- data/spec/less/functions-1.0.less +6 -0
- data/spec/less/import/import-test-a.less +2 -0
- data/spec/less/import/import-test-b.less +8 -0
- data/spec/less/import/import-test-c.less +5 -0
- data/spec/less/import-1.0.less +7 -0
- data/spec/less/mixins-1.0.less +43 -0
- data/spec/less/operations-1.0.less +39 -0
- data/spec/less/rulesets-1.0.less +30 -0
- data/spec/less/scope-1.0.less +33 -0
- data/spec/less/strings-1.0.less +14 -0
- data/spec/less/variables-1.0.less +16 -0
- data/spec/less/whitespace-1.0.less +21 -0
- data/spec/spec.css +81 -23
- data/spec/spec.less +3 -4
- data/spec/spec_helper.rb +4 -1
- metadata +46 -13
- data/lib/less/tree.rb +0 -82
- data/spec/css/less-0.8.10.css +0 -30
- data/spec/css/less-0.8.11.css +0 -31
- data/spec/css/less-0.8.12.css +0 -28
- data/spec/css/less-0.8.5.css +0 -24
- data/spec/css/less-0.8.6.css +0 -24
- data/spec/css/less-0.8.7.css +0 -24
- data/spec/css/less-0.8.8.css +0 -25
- data/spec/tree_spec.rb +0 -5
data/README.md
CHANGED
@@ -27,4 +27,13 @@ LESS allows you to write CSS the way (I think) it was meant to, that is: with *v
|
|
27
27
|
If you have CSS nightmares, just
|
28
28
|
$ lessc style.less
|
29
29
|
|
30
|
-
For more information, see you at http://lesscss.org
|
30
|
+
For more information, see you at [http://lesscss.org]
|
31
|
+
|
32
|
+
People without whom this wouldn't have happened a.k.a *Credits*
|
33
|
+
---------------------------------------------------------------
|
34
|
+
|
35
|
+
- **Dmitry Fadeyev**, for pushing me to do this, and designing our awesome website
|
36
|
+
- **August Lilleaas**, for initiating the work on the treetop grammar, as well as writing the rails plugin
|
37
|
+
- **Nathan Sobo**, for creating treetop
|
38
|
+
- **Jason Garber**, for his magical performance optimizations on treetop
|
39
|
+
- And finally, the people of #ruby-lang for answering all my ruby questions. **apeiros**, **manveru** and **rue** come to mind
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ begin
|
|
3
3
|
Jeweler::Tasks.new do |s|
|
4
4
|
s.name = "less"
|
5
5
|
s.authors = ["cloudhead"]
|
6
|
-
s.email = "
|
6
|
+
s.email = "self@cloudhead.net"
|
7
7
|
s.summary = "LESS compiler"
|
8
8
|
s.homepage = "http://www.lesscss.org"
|
9
9
|
s.description = "LESS is leaner CSS"
|
@@ -58,4 +58,23 @@ begin
|
|
58
58
|
t.rcov = true
|
59
59
|
t.rcov_opts = ['--exclude', '^spec,/gems/']
|
60
60
|
end
|
61
|
-
end
|
61
|
+
end
|
62
|
+
|
63
|
+
begin
|
64
|
+
require 'lib/less'
|
65
|
+
|
66
|
+
task :compile do
|
67
|
+
puts "compiling #{Less::GRAMMAR}..."
|
68
|
+
File.open(Less::PARSER, 'w') {|f| f.write Treetop::Compiler::GrammarCompiler.new.ruby_source(Less::GRAMMAR) }
|
69
|
+
end
|
70
|
+
|
71
|
+
task :benchmark do
|
72
|
+
puts "benchmarking..."
|
73
|
+
less = File.read("spec/less/big-1.0.less")
|
74
|
+
start = Time.now.to_f
|
75
|
+
Less::Engine.new(less).parse
|
76
|
+
total = Time.now.to_f - start
|
77
|
+
puts "total time: #{total}s"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/bin/lessc
CHANGED
@@ -5,14 +5,11 @@ $:.unshift File.dirname(__FILE__) + "/../lib"
|
|
5
5
|
require 'optparse'
|
6
6
|
require 'less'
|
7
7
|
|
8
|
-
CSS = '.css'
|
9
|
-
|
10
8
|
# Argument defaults
|
11
9
|
options = {
|
12
10
|
:watch => false,
|
13
11
|
:compress => false,
|
14
12
|
:debug => false,
|
15
|
-
:inheritance => :desc
|
16
13
|
}
|
17
14
|
|
18
15
|
# Get arguments
|
@@ -25,11 +22,6 @@ opts = OptionParser.new do |o|
|
|
25
22
|
options[:watch] = true
|
26
23
|
end
|
27
24
|
|
28
|
-
# Child-type inheritance
|
29
|
-
o.on("-c", "--child", "nesting uses child-type inheritance") do
|
30
|
-
options[:chain] = :child
|
31
|
-
end
|
32
|
-
|
33
25
|
# Compression needs a proper algorithm
|
34
26
|
#
|
35
27
|
# o.on("-x", "--compress", "compress css file") do
|
data/less.gemspec
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{less}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "1.0.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["cloudhead"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-07-08}
|
10
10
|
s.default_executable = %q{lessc}
|
11
11
|
s.description = %q{LESS is leaner CSS}
|
12
|
-
s.email = %q{
|
12
|
+
s.email = %q{self@cloudhead.net}
|
13
13
|
s.executables = ["lessc"]
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
@@ -26,20 +26,54 @@ Gem::Specification.new do |s|
|
|
26
26
|
"lib/less.rb",
|
27
27
|
"lib/less/command.rb",
|
28
28
|
"lib/less/engine.rb",
|
29
|
-
"lib/less/
|
29
|
+
"lib/less/engine/builder.rb",
|
30
|
+
"lib/less/engine/less.tt",
|
31
|
+
"lib/less/engine/nodes.rb",
|
32
|
+
"lib/less/engine/nodes/element.rb",
|
33
|
+
"lib/less/engine/nodes/entity.rb",
|
34
|
+
"lib/less/engine/nodes/function.rb",
|
35
|
+
"lib/less/engine/nodes/literal.rb",
|
36
|
+
"lib/less/engine/nodes/property.rb",
|
37
|
+
"lib/less/engine/nodes/selector.rb",
|
38
|
+
"lib/less/engine/parser.rb",
|
30
39
|
"spec/command_spec.rb",
|
31
|
-
"spec/css/
|
32
|
-
"spec/css/
|
33
|
-
"spec/css/
|
34
|
-
"spec/css/
|
35
|
-
"spec/css/
|
36
|
-
"spec/css/
|
37
|
-
"spec/css/
|
40
|
+
"spec/css/accessors-1.0.css",
|
41
|
+
"spec/css/big-1.0.css",
|
42
|
+
"spec/css/comments-1.0.css",
|
43
|
+
"spec/css/css-1.0.css",
|
44
|
+
"spec/css/functions-1.0.css",
|
45
|
+
"spec/css/import-1.0.css",
|
46
|
+
"spec/css/mixins-1.0.css",
|
47
|
+
"spec/css/operations-1.0.css",
|
48
|
+
"spec/css/rulesets-1.0.css",
|
49
|
+
"spec/css/scope-1.0.css",
|
50
|
+
"spec/css/strings-1.0.css",
|
51
|
+
"spec/css/variables-1.0.css",
|
52
|
+
"spec/css/whitespace-1.0.css",
|
38
53
|
"spec/engine_spec.rb",
|
54
|
+
"spec/less/accessors-1.0.less",
|
55
|
+
"spec/less/big-1.0.less",
|
56
|
+
"spec/less/colors-1.0.less",
|
57
|
+
"spec/less/comments-1.0.less",
|
58
|
+
"spec/less/css-1.0.less",
|
59
|
+
"spec/less/exceptions/mixed-units-error.less",
|
60
|
+
"spec/less/exceptions/name-error-1.0.less",
|
61
|
+
"spec/less/exceptions/syntax-error-1.0.less",
|
62
|
+
"spec/less/functions-1.0.less",
|
63
|
+
"spec/less/import-1.0.less",
|
64
|
+
"spec/less/import/import-test-a.less",
|
65
|
+
"spec/less/import/import-test-b.less",
|
66
|
+
"spec/less/import/import-test-c.less",
|
67
|
+
"spec/less/mixins-1.0.less",
|
68
|
+
"spec/less/operations-1.0.less",
|
69
|
+
"spec/less/rulesets-1.0.less",
|
70
|
+
"spec/less/scope-1.0.less",
|
71
|
+
"spec/less/strings-1.0.less",
|
72
|
+
"spec/less/variables-1.0.less",
|
73
|
+
"spec/less/whitespace-1.0.less",
|
39
74
|
"spec/spec.css",
|
40
75
|
"spec/spec.less",
|
41
|
-
"spec/spec_helper.rb"
|
42
|
-
"spec/tree_spec.rb"
|
76
|
+
"spec/spec_helper.rb"
|
43
77
|
]
|
44
78
|
s.has_rdoc = true
|
45
79
|
s.homepage = %q{http://www.lesscss.org}
|
@@ -51,8 +85,7 @@ Gem::Specification.new do |s|
|
|
51
85
|
s.test_files = [
|
52
86
|
"spec/command_spec.rb",
|
53
87
|
"spec/engine_spec.rb",
|
54
|
-
"spec/spec_helper.rb"
|
55
|
-
"spec/tree_spec.rb"
|
88
|
+
"spec/spec_helper.rb"
|
56
89
|
]
|
57
90
|
|
58
91
|
if s.respond_to? :specification_version then
|
data/lib/less/command.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
module Less
|
2
2
|
class Command
|
3
|
-
CSS = '.css'
|
4
|
-
|
5
3
|
attr_accessor :source, :destination, :options
|
6
4
|
|
7
5
|
def initialize options
|
6
|
+
$verbose = options[:debug]
|
8
7
|
@source = options[:source]
|
9
|
-
@destination = (options[:destination] || options[:source]).gsub /\.(less|lss)/,
|
8
|
+
@destination = (options[:destination] || options[:source]).gsub /\.(less|lss)/, '.css'
|
10
9
|
@options = options
|
11
10
|
end
|
12
11
|
|
@@ -16,9 +15,9 @@ module Less
|
|
16
15
|
|
17
16
|
# little function which allows us to
|
18
17
|
# Ctrl-C exit inside the passed block
|
19
|
-
def watch
|
18
|
+
def watch
|
20
19
|
begin
|
21
|
-
|
20
|
+
yield
|
22
21
|
rescue Interrupt
|
23
22
|
puts
|
24
23
|
exit 0
|
@@ -26,10 +25,10 @@ module Less
|
|
26
25
|
end
|
27
26
|
|
28
27
|
def run!
|
29
|
-
|
28
|
+
parse(true) unless File.exist? @destination
|
30
29
|
|
31
30
|
if watch?
|
32
|
-
log "Watching for changes in #@source
|
31
|
+
log "Watching for changes in #@source... Ctrl-C to abort.\n: "
|
33
32
|
|
34
33
|
# Main watch loop
|
35
34
|
loop do
|
@@ -37,55 +36,55 @@ module Less
|
|
37
36
|
|
38
37
|
# File has changed
|
39
38
|
if File.stat( @source ).mtime > File.stat( @destination ).mtime
|
40
|
-
|
39
|
+
print "Change detected... "
|
41
40
|
|
42
41
|
# Loop until error is fixed
|
43
|
-
until
|
44
|
-
log "Press [
|
42
|
+
until parse
|
43
|
+
log "Press [return] to continue..."
|
45
44
|
watch { $stdin.gets }
|
46
45
|
end
|
47
46
|
end
|
48
47
|
end
|
49
48
|
else
|
50
|
-
|
49
|
+
parse
|
51
50
|
end
|
52
51
|
end
|
53
52
|
|
54
|
-
def
|
53
|
+
def parse new = false
|
55
54
|
begin
|
56
55
|
# Create a new Less object with the contents of a file
|
57
|
-
css = Less::Engine.new(
|
56
|
+
css = Less::Engine.new(File.new @source).to_css
|
58
57
|
css = css.delete " \n" if compress?
|
59
58
|
|
60
59
|
File.open( @destination, "w" ) do |file|
|
61
60
|
file.write css
|
62
61
|
end
|
63
|
-
|
62
|
+
print "#{new ? '* [Created]' : '* [Updated]'} #{@destination.split('/').last}\n: " if watch?
|
64
63
|
rescue Errno::ENOENT => e
|
65
64
|
abort "#{e}"
|
66
|
-
rescue SyntaxError
|
67
|
-
|
68
|
-
e.gsub(/\(eval\)\:(\d+)\:\s/, 'line \1: ')
|
69
|
-
} * "\n"
|
70
|
-
err "errors were found in the .less file! \n#{error}\n"
|
65
|
+
rescue SyntaxError => e
|
66
|
+
err "#{e}\n", "Parse"
|
71
67
|
rescue MixedUnitsError => e
|
72
68
|
err "`#{e}` you're mixing units together! What do you expect?\n"
|
73
|
-
rescue CompoundOperationError => e
|
74
|
-
err "`#{e}` operations in compound declarations aren't allowed, sorry!\n"
|
75
69
|
rescue PathError => e
|
76
|
-
err "`#{e}` was not found.\n"
|
70
|
+
err "`#{e}` was not found.\n", "Path"
|
71
|
+
rescue VariableNameError => e
|
72
|
+
err "`#{e}` is undefined.\n", "Name"
|
73
|
+
rescue MixinNameError => e
|
74
|
+
err "`#{e}` is undefined.\n", "Name"
|
77
75
|
else
|
78
76
|
true
|
79
77
|
end
|
80
78
|
end
|
81
79
|
|
82
|
-
# Just a logging function to avoid typing '
|
80
|
+
# Just a logging function to avoid typing '*'
|
83
81
|
def log s = ''
|
84
82
|
print '* ' + s.to_s
|
85
83
|
end
|
86
84
|
|
87
|
-
def err s = ''
|
88
|
-
|
85
|
+
def err s = '', type = ''
|
86
|
+
type = type.strip + ' ' unless type.empty?
|
87
|
+
print "! [#{type}Error] #{s}"
|
89
88
|
end
|
90
89
|
end
|
91
90
|
end
|
@@ -0,0 +1,379 @@
|
|
1
|
+
grammar Less
|
2
|
+
rule primary
|
3
|
+
(declaration / ruleset / import / comment)+ <Builder> / declaration* <Builder> / import* <Builder> / comment*
|
4
|
+
end
|
5
|
+
|
6
|
+
rule comment
|
7
|
+
ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# div, .class, body > p {...}
|
12
|
+
#
|
13
|
+
rule ruleset
|
14
|
+
selectors "{" ws primary ws "}" ws {
|
15
|
+
def build env
|
16
|
+
# Build the ruleset for each selector
|
17
|
+
selectors.build(env, :tree).each do |sel|
|
18
|
+
primary.build sel
|
19
|
+
end
|
20
|
+
end
|
21
|
+
} / ws selectors ';' ws {
|
22
|
+
def build env
|
23
|
+
log "[mixin]: #{selectors.text_value}"
|
24
|
+
selectors.build(env, :path).each do |path|
|
25
|
+
|
26
|
+
rules = path.inject(env.root) do |current, node|
|
27
|
+
current.descend(node.selector, node) or raise MixinNameError, path.join
|
28
|
+
end.rules
|
29
|
+
|
30
|
+
env.rules += rules
|
31
|
+
end
|
32
|
+
end
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
rule import
|
37
|
+
"@import" S url:(string / url) medias? s ';' ws {
|
38
|
+
def build env
|
39
|
+
path = File.join(env.root.file, url.value)
|
40
|
+
path += '.less' unless path =~ /\.less$/
|
41
|
+
if File.exist? path
|
42
|
+
log "\nimporting #{path}"
|
43
|
+
imported = Less::Engine.new(File.new path).to_tree
|
44
|
+
env.rules += imported.rules
|
45
|
+
else
|
46
|
+
raise ImportError, path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
rule url
|
53
|
+
'url(' path:(string / [-a-zA-Z0-9_%$/.&=:;#+?]+) ')' {
|
54
|
+
def build env = nil
|
55
|
+
Node::String.new(CGI.unescape path.text_value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def value
|
59
|
+
build
|
60
|
+
end
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
rule medias
|
65
|
+
[-a-z]+ (s ',' s [a-z]+)*
|
66
|
+
end
|
67
|
+
|
68
|
+
rule selectors
|
69
|
+
ws selector tail:(s ',' ws selector)* ws {
|
70
|
+
def build env, method
|
71
|
+
all.map do |e|
|
72
|
+
e.send(method, env) if e.respond_to? method
|
73
|
+
end.compact
|
74
|
+
end
|
75
|
+
|
76
|
+
def all
|
77
|
+
[selector] + tail.elements.map {|e| e.selector }
|
78
|
+
end
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# div > p a {...}
|
84
|
+
#
|
85
|
+
rule selector
|
86
|
+
(s select element s)+ {
|
87
|
+
def tree env
|
88
|
+
log "\n% element: #{text_value}\n"
|
89
|
+
elements.inject(env) do |node, e|
|
90
|
+
node << Node::Element.new(e.element.text_value, e.select.text_value)
|
91
|
+
node.last
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def path env
|
96
|
+
elements.map do |e|
|
97
|
+
Node::Element.new(e.element.text_value, e.select.text_value)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# @my-var: 12px;
|
105
|
+
# height: 100%;
|
106
|
+
#
|
107
|
+
rule declaration
|
108
|
+
ws name:(ident / variable) s ':' s expression s (';'/ ws &'}') ws {
|
109
|
+
def build env
|
110
|
+
env << (name.text_value =~ /^@/ ? Node::Variable : Node::Property).new(name.text_value)
|
111
|
+
expression.build env
|
112
|
+
end
|
113
|
+
# Empty rule
|
114
|
+
} / ws ident s ':' s ';' ws
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# An operation or compound value
|
119
|
+
#
|
120
|
+
rule expression
|
121
|
+
entity (operator / S) expression <Builder> / entity
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Entity: Any whitespace delimited token
|
126
|
+
#
|
127
|
+
rule entity
|
128
|
+
function / fonts / keyword / accessor / variable / literal / important
|
129
|
+
end
|
130
|
+
|
131
|
+
rule fonts
|
132
|
+
font family:(s ',' s font)+ {
|
133
|
+
def build env
|
134
|
+
fonts = ([font] + family.elements.map {|f| f.font }).map do |font|
|
135
|
+
font.build env
|
136
|
+
end
|
137
|
+
env.identifiers.last << Node::FontFamily.new(fonts)
|
138
|
+
end
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
rule font
|
143
|
+
[a-zA-Z] [-a-zA-Z0-9]* {
|
144
|
+
def build env
|
145
|
+
Node::Keyword.new(text_value)
|
146
|
+
end
|
147
|
+
} / string {
|
148
|
+
def build env
|
149
|
+
Node::String.new(text_value)
|
150
|
+
end
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# An identifier
|
156
|
+
#
|
157
|
+
rule ident
|
158
|
+
'-'? [-a-z0-9_]+
|
159
|
+
end
|
160
|
+
|
161
|
+
rule variable
|
162
|
+
'@' [-a-zA-Z0-9_]+ {
|
163
|
+
def build env
|
164
|
+
env.identifiers.last << env.nearest(text_value)
|
165
|
+
end
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# div / .class / #id / input[type="text"] / lang(fr)
|
171
|
+
#
|
172
|
+
rule element
|
173
|
+
(class_id / tag) attribute* ('(' ident ')')? / '@media' / '@font-face'
|
174
|
+
end
|
175
|
+
|
176
|
+
rule class_id
|
177
|
+
tag? class+ / tag? id
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# [type="text"]
|
182
|
+
#
|
183
|
+
rule attribute
|
184
|
+
'[' [a-z]+ ([|~]? '=')? (tag / string) ']'
|
185
|
+
end
|
186
|
+
|
187
|
+
rule class
|
188
|
+
'.' [_a-z] [-a-zA-Z0-9_]*
|
189
|
+
end
|
190
|
+
|
191
|
+
rule id
|
192
|
+
'#' [_a-z] [-a-zA-Z0-9_]*
|
193
|
+
end
|
194
|
+
|
195
|
+
rule tag
|
196
|
+
[a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
|
197
|
+
end
|
198
|
+
|
199
|
+
rule select
|
200
|
+
(s [:+>] s / S)?
|
201
|
+
end
|
202
|
+
|
203
|
+
# TODO: Merge this with attribute rule
|
204
|
+
rule accessor
|
205
|
+
ident:(class_id / tag) '[' attr:(string / variable) ']' {
|
206
|
+
def build env
|
207
|
+
env.identifiers.last << env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
|
208
|
+
end
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
rule operator
|
213
|
+
S op:([-+*/]) S {
|
214
|
+
def build env
|
215
|
+
env.identifiers.last << Node::Operator.new(op.text_value)
|
216
|
+
end
|
217
|
+
} / [-+*/] {
|
218
|
+
def build env
|
219
|
+
env.identifiers.last << Node::Operator.new(text_value)
|
220
|
+
end
|
221
|
+
}
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# Tokens which don't need to be evaluated
|
226
|
+
#
|
227
|
+
rule literal
|
228
|
+
color / (dimension / [-a-z]+) '/' dimension {
|
229
|
+
def build env
|
230
|
+
env.identifiers.last << Node::Anonymous.new(text_value)
|
231
|
+
end
|
232
|
+
} / number unit {
|
233
|
+
def build env
|
234
|
+
env.identifiers.last << Node::Number.new(number.text_value, unit.text_value)
|
235
|
+
end
|
236
|
+
} / string {
|
237
|
+
def build env
|
238
|
+
env.identifiers.last << Node::String.new(text_value)
|
239
|
+
end
|
240
|
+
}
|
241
|
+
end
|
242
|
+
|
243
|
+
# !important
|
244
|
+
rule important
|
245
|
+
'!important' {
|
246
|
+
def build env
|
247
|
+
env.identifiers.last << Node::Keyword.new(text_value)
|
248
|
+
end
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
rule empty
|
253
|
+
"" <Empty>
|
254
|
+
end
|
255
|
+
|
256
|
+
#
|
257
|
+
# `blue`, `small`, `normal` etc.
|
258
|
+
#
|
259
|
+
rule keyword
|
260
|
+
[a-zA-Z] [-a-zA-Z]* !ns {
|
261
|
+
def build env
|
262
|
+
env.identifiers.last << Node::Keyword.new(text_value)
|
263
|
+
end
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
#
|
268
|
+
# 'hello world' / "hello world"
|
269
|
+
#
|
270
|
+
rule string
|
271
|
+
"'" content:(!"'" . )* "'" {
|
272
|
+
def value
|
273
|
+
content.text_value
|
274
|
+
end
|
275
|
+
} / ["] content:(!["] . )* ["] {
|
276
|
+
def value
|
277
|
+
content.text_value
|
278
|
+
end
|
279
|
+
}
|
280
|
+
end
|
281
|
+
|
282
|
+
#
|
283
|
+
# Numbers & Units
|
284
|
+
#
|
285
|
+
rule dimension
|
286
|
+
number unit
|
287
|
+
end
|
288
|
+
|
289
|
+
rule number
|
290
|
+
'-'? [0-9]* '.' [0-9]+ / '-'? [0-9]+
|
291
|
+
end
|
292
|
+
|
293
|
+
rule unit
|
294
|
+
('px'/'em'/'pc'/'%'/'pt'/'cm'/'mm')?
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
#
|
299
|
+
# Color
|
300
|
+
#
|
301
|
+
rule color
|
302
|
+
'#' hex {
|
303
|
+
def build env
|
304
|
+
env.identifiers.last << Node::Color.new(hex.text_value)
|
305
|
+
end
|
306
|
+
} / fn:(('hsl'/'rgb') 'a'?) '(' arguments ')' {
|
307
|
+
def build env
|
308
|
+
args = arguments.build env
|
309
|
+
env.identifiers.last << Node::Function.new(fn.text_value, args.flatten)
|
310
|
+
end
|
311
|
+
}
|
312
|
+
end
|
313
|
+
|
314
|
+
rule hex
|
315
|
+
[a-fA-F0-9] [a-fA-F0-9] [a-fA-F0-9]+
|
316
|
+
end
|
317
|
+
|
318
|
+
#
|
319
|
+
# Functions and arguments
|
320
|
+
#
|
321
|
+
rule function
|
322
|
+
name:([-a-zA-Z_]+) '(' arguments ')' {
|
323
|
+
def build env
|
324
|
+
args = arguments.build env
|
325
|
+
env.identifiers.last << Node::Function.new(name.text_value, [args].flatten)
|
326
|
+
end
|
327
|
+
}
|
328
|
+
end
|
329
|
+
|
330
|
+
rule arguments
|
331
|
+
argument s ',' s arguments {
|
332
|
+
def build env
|
333
|
+
elements.map do |e|
|
334
|
+
e.build env if e.respond_to? :build
|
335
|
+
end.compact
|
336
|
+
end
|
337
|
+
} / argument
|
338
|
+
end
|
339
|
+
|
340
|
+
rule argument
|
341
|
+
color {
|
342
|
+
def build env
|
343
|
+
Node::Color.new text_value
|
344
|
+
end
|
345
|
+
} / number unit {
|
346
|
+
def build env
|
347
|
+
Node::Number.new number.text_value, unit.text_value
|
348
|
+
end
|
349
|
+
} / string {
|
350
|
+
def build env
|
351
|
+
Node::String.new text_value
|
352
|
+
end
|
353
|
+
} / [a-zA-Z]+ '=' dimension {
|
354
|
+
def build env
|
355
|
+
Node::Anonymous.new text_value
|
356
|
+
end
|
357
|
+
}
|
358
|
+
end
|
359
|
+
|
360
|
+
#
|
361
|
+
# Whitespace
|
362
|
+
#
|
363
|
+
rule s
|
364
|
+
[ ]*
|
365
|
+
end
|
366
|
+
|
367
|
+
rule S
|
368
|
+
[ ]+
|
369
|
+
end
|
370
|
+
|
371
|
+
rule ws
|
372
|
+
[\n ]*
|
373
|
+
end
|
374
|
+
|
375
|
+
# Non-space char
|
376
|
+
rule ns
|
377
|
+
![ ;] .
|
378
|
+
end
|
379
|
+
end
|