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.
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 -25
  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 +42 -139
  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 -23
  55. data/spec/spec.less +3 -4
  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,55 +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 = Less::Engine.new( File.read( @source ) ).to_css @options[:inheritance]
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
- puts "#{new ? '* [Created]' : ' [Updated]'} #{@destination.split('/').last}" if watch?
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
- error = debug?? $! : $!.message.split("\n")[1..-1].collect {|e|
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
- print "!! #{s}"
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,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