xdissent-less 0.8.12 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/README.md +10 -1
  2. data/Rakefile +21 -2
  3. data/VERSION +1 -1
  4. data/bin/lessc +0 -8
  5. data/less.gemspec +48 -15
  6. data/lib/less/command.rb +24 -29
  7. data/lib/less/engine/builder.rb +13 -0
  8. data/lib/less/engine/less.tt +379 -0
  9. data/lib/less/engine/nodes/element.rb +153 -0
  10. data/lib/less/engine/nodes/entity.rb +59 -0
  11. data/lib/less/engine/nodes/function.rb +61 -0
  12. data/lib/less/engine/nodes/literal.rb +132 -0
  13. data/lib/less/engine/nodes/property.rb +120 -0
  14. data/lib/less/engine/nodes/selector.rb +39 -0
  15. data/lib/less/engine/nodes.rb +8 -0
  16. data/lib/less/engine/parser.rb +3854 -0
  17. data/lib/less/engine.rb +61 -118
  18. data/lib/less.rb +65 -10
  19. data/spec/command_spec.rb +2 -6
  20. data/spec/css/accessors-1.0.css +20 -0
  21. data/spec/css/big-1.0.css +3770 -0
  22. data/spec/css/comments-1.0.css +11 -0
  23. data/spec/css/css-1.0.css +40 -0
  24. data/spec/css/functions-1.0.css +8 -0
  25. data/spec/css/import-1.0.css +13 -0
  26. data/spec/css/mixins-1.0.css +30 -0
  27. data/spec/css/operations-1.0.css +30 -0
  28. data/spec/css/rulesets-1.0.css +19 -0
  29. data/spec/css/scope-1.0.css +16 -0
  30. data/spec/css/strings-1.0.css +14 -0
  31. data/spec/css/variables-1.0.css +7 -0
  32. data/spec/css/whitespace-1.0.css +11 -0
  33. data/spec/engine_spec.rb +66 -18
  34. data/spec/less/accessors-1.0.less +20 -0
  35. data/spec/less/big-1.0.less +4810 -0
  36. data/spec/less/colors-1.0.less +0 -0
  37. data/spec/less/comments-1.0.less +46 -0
  38. data/spec/less/css-1.0.less +82 -0
  39. data/spec/less/exceptions/mixed-units-error.less +0 -0
  40. data/spec/less/exceptions/name-error-1.0.less +0 -0
  41. data/spec/less/exceptions/syntax-error-1.0.less +0 -0
  42. data/spec/less/functions-1.0.less +6 -0
  43. data/spec/less/import/import-test-a.less +2 -0
  44. data/spec/less/import/import-test-b.less +8 -0
  45. data/spec/less/import/import-test-c.less +5 -0
  46. data/spec/less/import-1.0.less +7 -0
  47. data/spec/less/mixins-1.0.less +43 -0
  48. data/spec/less/operations-1.0.less +39 -0
  49. data/spec/less/rulesets-1.0.less +30 -0
  50. data/spec/less/scope-1.0.less +33 -0
  51. data/spec/less/strings-1.0.less +14 -0
  52. data/spec/less/variables-1.0.less +16 -0
  53. data/spec/less/whitespace-1.0.less +21 -0
  54. data/spec/spec.css +81 -24
  55. data/spec/spec.less +2 -3
  56. data/spec/spec_helper.rb +4 -1
  57. metadata +46 -13
  58. data/lib/less/tree.rb +0 -82
  59. data/spec/css/less-0.8.10.css +0 -30
  60. data/spec/css/less-0.8.11.css +0 -31
  61. data/spec/css/less-0.8.12.css +0 -28
  62. data/spec/css/less-0.8.5.css +0 -24
  63. data/spec/css/less-0.8.6.css +0 -24
  64. data/spec/css/less-0.8.7.css +0 -24
  65. data/spec/css/less-0.8.8.css +0 -25
  66. 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 = "alexis@cloudhead.net"
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.8.12
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.8.12"
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-06-21}
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{alexis@cloudhead.net}
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/tree.rb",
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/less-0.8.10.css",
32
- "spec/css/less-0.8.11.css",
33
- "spec/css/less-0.8.12.css",
34
- "spec/css/less-0.8.5.css",
35
- "spec/css/less-0.8.6.css",
36
- "spec/css/less-0.8.7.css",
37
- "spec/css/less-0.8.8.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)/, CSS
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 &block
18
+ def watch
20
19
  begin
21
- block.call
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
- compile(true) unless File.exist? @destination
28
+ parse(true) unless File.exist? @destination
30
29
 
31
30
  if watch?
32
- log "Watching for changes in #@source ...Ctrl-C to abort.\n"
31
+ log "Watching for changes in #@source... Ctrl-C to abort.\n: "
33
32
 
34
33
  # Main watch loop
35
34
  loop do
@@ -37,59 +36,55 @@ module Less
37
36
 
38
37
  # File has changed
39
38
  if File.stat( @source ).mtime > File.stat( @destination ).mtime
40
- log "Change detected... "
39
+ print "Change detected... "
41
40
 
42
41
  # Loop until error is fixed
43
- until compile
44
- log "Press [enter] to continue..."
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
- compile
49
+ parse
51
50
  end
52
51
  end
53
52
 
54
- def compile new = false
53
+ def parse new = false
55
54
  begin
56
55
  # Create a new Less object with the contents of a file
57
- css = File.read( @source )
58
- # Process import rules
59
- imports = css.scan(/(@import\s+(?:url\()?\s*(?:\'|\")?)([a-zA-Z0-9.]*)((?:\'|\")?\)?;)/)
60
- imports.each { |f| css = css.gsub(f.join, File.read(f[1])) }
61
- css = Less::Engine.new(css).to_css @options[:inheritance]
56
+ css = Less::Engine.new(File.new @source).to_css
62
57
  css = css.delete " \n" if compress?
63
58
 
64
59
  File.open( @destination, "w" ) do |file|
65
60
  file.write css
66
61
  end
67
- puts "#{new ? '* [Created]' : ' [Updated]'} #{@destination.split('/').last}" if watch?
62
+ print "#{new ? '* [Created]' : '* [Updated]'} #{@destination.split('/').last}\n: " if watch?
68
63
  rescue Errno::ENOENT => e
69
64
  abort "#{e}"
70
- rescue SyntaxError
71
- error = debug?? $! : $!.message.split("\n")[1..-1].collect {|e|
72
- e.gsub(/\(eval\)\:(\d+)\:\s/, 'line \1: ')
73
- } * "\n"
74
- err "errors were found in the .less file! \n#{error}\n"
65
+ rescue SyntaxError => e
66
+ err "#{e}\n", "Parse"
75
67
  rescue MixedUnitsError => e
76
68
  err "`#{e}` you're mixing units together! What do you expect?\n"
77
- rescue CompoundOperationError => e
78
- err "`#{e}` operations in compound declarations aren't allowed, sorry!\n"
79
69
  rescue PathError => e
80
- 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"
81
75
  else
82
76
  true
83
77
  end
84
78
  end
85
79
 
86
- # Just a logging function to avoid typing '}'
80
+ # Just a logging function to avoid typing '*'
87
81
  def log s = ''
88
82
  print '* ' + s.to_s
89
83
  end
90
84
 
91
- def err s = ''
92
- print "!! #{s}"
85
+ def err s = '', type = ''
86
+ type = type.strip + ' ' unless type.empty?
87
+ print "! [#{type}Error] #{s}"
93
88
  end
94
89
  end
95
90
  end
@@ -0,0 +1,13 @@
1
+ module Builder
2
+ def build env = Less::Element.new
3
+ elements.map do |e|
4
+ e.build env if e.respond_to? :build
5
+ end
6
+ env
7
+ end
8
+ end
9
+
10
+ module Empty
11
+ def build env
12
+ end
13
+ 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