cloudhead-less 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -61,19 +61,30 @@ end
61
61
 
62
62
  begin
63
63
  require 'lib/less'
64
+ require 'benchmark'
64
65
 
65
66
  task :compile do
67
+ abort "compiling isn't necessary anymore."
66
68
  puts "compiling #{LESS_GRAMMAR.split('/').last}..."
67
69
  File.open(LESS_PARSER, 'w') {|f| f.write Treetop::Compiler::GrammarCompiler.new.ruby_source(LESS_GRAMMAR) }
68
70
  end
69
71
 
70
72
  task :benchmark do
71
- print "benchmarking... "
72
- less = File.read("spec/less/big.less")
73
- start = Time.now.to_f
74
- Less::Engine.new(less).parse
75
- total = Time.now.to_f - start
76
- puts "#{total}s"
73
+ #require 'profile'
74
+ puts "benchmarking... "
75
+ less, tree = File.read("spec/less/big.less"), nil
76
+
77
+ parse = Benchmark.measure do
78
+ tree = Less::Engine.new(less).parse(false)
79
+ end.total.round(2)
80
+
81
+ build = Benchmark.measure do
82
+ tree.build(Less::Node::Element.new)
83
+ end.total.round(2)
84
+
85
+ puts "parse: #{parse}s\nbuild: #{build}s"
86
+ puts "------------"
87
+ puts "total: #{parse + build}s"
77
88
  end
78
89
  end
79
90
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.2
1
+ 1.1.3
data/less.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{less}
5
- s.version = "1.1.2"
5
+ s.version = "1.1.3"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["cloudhead"]
@@ -23,11 +23,14 @@ Gem::Specification.new do |s|
23
23
  "VERSION",
24
24
  "bin/lessc",
25
25
  "less.gemspec",
26
+ "lib/ext.rb",
26
27
  "lib/less.rb",
27
28
  "lib/less/command.rb",
28
29
  "lib/less/engine.rb",
29
30
  "lib/less/engine/builder.rb",
30
- "lib/less/engine/less.tt",
31
+ "lib/less/engine/grammar/common.tt",
32
+ "lib/less/engine/grammar/entity.tt",
33
+ "lib/less/engine/grammar/less.tt",
31
34
  "lib/less/engine/nodes.rb",
32
35
  "lib/less/engine/nodes/element.rb",
33
36
  "lib/less/engine/nodes/entity.rb",
@@ -36,7 +39,6 @@ Gem::Specification.new do |s|
36
39
  "lib/less/engine/nodes/property.rb",
37
40
  "lib/less/engine/nodes/ruleset.rb",
38
41
  "lib/less/engine/nodes/selector.rb",
39
- "lib/less/engine/parser.rb",
40
42
  "lib/vendor/treetop/.gitignore",
41
43
  "lib/vendor/treetop/LICENSE",
42
44
  "lib/vendor/treetop/README",
data/lib/ext.rb ADDED
@@ -0,0 +1,62 @@
1
+ module Treetop
2
+ module Runtime
3
+ class CompiledParser
4
+ def failure_message
5
+ return nil unless (tf = terminal_failures) && tf.size > 0
6
+ "on line #{failure_line}: expected " + (
7
+ tf.size == 1 ?
8
+ tf[0].expected_string :
9
+ "one of #{Less::YELLOW[tf.map {|f| f.expected_string }.uniq * ' ']}"
10
+ ) +
11
+ " got #{Less::YELLOW[input[failure_index]]}" +
12
+ " after:\n\n#{input[index...failure_index]}\n"
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ class Object
19
+ def verbose
20
+ $verbose = true
21
+ yield
22
+ ensure
23
+ $verbose = false
24
+ end
25
+
26
+ def tap
27
+ yield self
28
+ self
29
+ end
30
+
31
+ def log(s = '') puts "* #{s}" if $verbose end
32
+ def log!(s = '') puts "* #{s}" end
33
+ def error(s) $stderr.puts s end
34
+ def error!(s) raise Exception, s end
35
+ end
36
+
37
+ class Array
38
+ def dissolve
39
+ ary = flatten.compact
40
+ case ary.size
41
+ when 0 then []
42
+ when 1 then first
43
+ else ary
44
+ end
45
+ end
46
+
47
+ def one?
48
+ size == 1
49
+ end
50
+ end
51
+
52
+ class Class
53
+ def to_sym
54
+ self.to_s.to_sym
55
+ end
56
+ end
57
+
58
+ class Symbol
59
+ def to_proc
60
+ proc {|obj, *args| obj.send(self, *args) }
61
+ end
62
+ end
data/lib/less/command.rb CHANGED
@@ -14,7 +14,6 @@ module Less
14
14
  $verbose = options[:debug]
15
15
  @source = options[:source]
16
16
  @destination = (options[:destination] || options[:source]).gsub /\.(less|lss)/, '.css'
17
- @growl = Growl.new if options[:growl]
18
17
  @options = options
19
18
  end
20
19
 
@@ -95,10 +94,13 @@ module Less
95
94
  def err s = '', type = ''
96
95
  type = type.strip + ' ' unless type.empty?
97
96
  print "#{RED["! #{type}Error"]}: #{s}"
98
- @growl.title = "LESS"
99
- @growl.message = "#{type}Error in #@source!" if @options[:growl]
100
- @growl.run
101
- false
97
+ if @options[:growl]
98
+ growl = Growl.new
99
+ growl.title = "LESS"
100
+ growl.message = "#{type}Error in #@source!"
101
+ growl.run
102
+ false
103
+ end
102
104
  end
103
105
  end
104
106
  end
@@ -0,0 +1,29 @@
1
+ module Less
2
+ module StyleSheet
3
+ grammar Common
4
+ #
5
+ # Whitespace
6
+ #
7
+ rule s
8
+ [ ]*
9
+ end
10
+
11
+ rule S
12
+ [ ]+
13
+ end
14
+
15
+ rule ws
16
+ [\n ]*
17
+ end
18
+
19
+ rule WS
20
+ [\n ]+
21
+ end
22
+
23
+ # Non-space char
24
+ rule ns
25
+ ![ ;\n] .
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,139 @@
1
+ module Less
2
+ module StyleSheet
3
+ grammar Entity
4
+ #
5
+ # Entity: Any whitespace delimited token
6
+ #
7
+ rule entity
8
+ function / fonts / keyword / accessor / variable / literal / important
9
+ end
10
+
11
+ rule fonts
12
+ font family:(s ',' s font)+ {
13
+ def build
14
+ Node::FontFamily.new(all.map(&:build))
15
+ end
16
+
17
+ def all
18
+ [font] + family.elements.map {|f| f.font }
19
+ end
20
+ }
21
+ end
22
+
23
+ rule font
24
+ [a-zA-Z] [-a-zA-Z0-9]* {
25
+ def build
26
+ Node::Keyword.new(text_value)
27
+ end
28
+ } / string {
29
+ def build
30
+ Node::String.new(text_value)
31
+ end
32
+ }
33
+ end
34
+
35
+ #
36
+ # Tokens which don't need to be evaluated
37
+ #
38
+ rule literal
39
+ color / (dimension / [-a-z]+) '/' dimension {
40
+ def build
41
+ Node::Anonymous.new(text_value)
42
+ end
43
+ } / number unit {
44
+ def build
45
+ Node::Number.new(number.text_value, unit.text_value)
46
+ end
47
+ } / string {
48
+ def build
49
+ Node::String.new(text_value)
50
+ end
51
+ }
52
+ end
53
+
54
+ # !important
55
+ rule important
56
+ '!important' {
57
+ def build
58
+ Node::Keyword.new(text_value)
59
+ end
60
+ }
61
+ end
62
+
63
+ #
64
+ # `blue`, `small`, `normal` etc.
65
+ #
66
+ rule keyword
67
+ [-a-zA-Z]+ !ns {
68
+ def build
69
+ Node::Keyword.new(text_value)
70
+ end
71
+ }
72
+ end
73
+
74
+ #
75
+ # 'hello world' / "hello world"
76
+ #
77
+ rule string
78
+ "'" content:(!"'" . )* "'" {
79
+ def value
80
+ content.text_value
81
+ end
82
+ } / ["] content:(!["] . )* ["] {
83
+ def value
84
+ content.text_value
85
+ end
86
+ }
87
+ end
88
+
89
+ #
90
+ # Numbers & Units
91
+ #
92
+ rule dimension
93
+ number unit
94
+ end
95
+
96
+ rule number
97
+ '-'? [0-9]* '.' [0-9]+ / '-'? [0-9]+
98
+ end
99
+
100
+ rule unit
101
+ ('px'/'em'/'pc'/'%'/'pt'/'cm'/'mm')?
102
+ end
103
+
104
+ #
105
+ # Color
106
+ #
107
+ rule color
108
+ '#' rgb {
109
+ def build
110
+ Node::Color.new(*rgb.build)
111
+ end
112
+ } / fn:(('hsl'/'rgb') 'a'?) arguments {
113
+ def build
114
+ Node::Function.new(fn.text_value, arguments.build.flatten)
115
+ end
116
+ }
117
+ end
118
+
119
+ #
120
+ # 00ffdd / 0fd
121
+ #
122
+ rule rgb
123
+ r:(hex hex) g:(hex hex) b:(hex hex) {
124
+ def build
125
+ [r.text_value, g.text_value, b.text_value]
126
+ end
127
+ } / r:hex g:hex b:hex {
128
+ def build
129
+ [r.text_value, g.text_value, b.text_value].map {|c| c * 2 }
130
+ end
131
+ }
132
+ end
133
+
134
+ rule hex
135
+ [a-fA-F0-9]
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,271 @@
1
+ module Less
2
+ grammar StyleSheet
3
+ include Common
4
+ include Entity
5
+
6
+ rule primary
7
+ (declaration / ruleset / import / comment)+ <Builder> / declaration* <Builder> / import* <Builder> / comment*
8
+ end
9
+
10
+ rule comment
11
+ ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
12
+ end
13
+
14
+ #
15
+ # div, .class, body > p {...}
16
+ #
17
+ rule ruleset
18
+ selectors "{" ws primary ws "}" ws {
19
+ def build env
20
+ # Build the ruleset for each selector
21
+ selectors.build(env, :ruleset).each do |sel|
22
+ primary.build sel
23
+ end
24
+ end
25
+ } / ws selectors ';' ws {
26
+ def build env
27
+ selectors.build(env, :mixin).each do |path|
28
+ rules = path.inject(env.root) do |current, node|
29
+ current.descend(node.selector, node) or raise MixinNameError, path.join
30
+ end.rules
31
+ env.rules += rules
32
+ end
33
+ end
34
+ }
35
+ end
36
+
37
+ rule import
38
+ "@import" S url:(string / url) medias? s ';' ws {
39
+ def build env
40
+ path = File.join(env.root.file, url.value)
41
+ path += '.less' unless path =~ /\.(le|c)ss$/
42
+ if File.exist? 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
+ sel:(s select element s)+ arguments? {
87
+ def ruleset env
88
+ sel.elements.inject(env) do |node, e|
89
+ node << Node::Element.new(e.element.text_value, e.select.text_value)
90
+ node.last
91
+ end
92
+ end
93
+
94
+ def mixin env
95
+ sel.elements.map do |e|
96
+ Node::Element.new(e.element.text_value, e.select.text_value)
97
+ end
98
+ end
99
+ }
100
+ end
101
+
102
+ #
103
+ # @my-var: 12px;
104
+ # height: 100%;
105
+ #
106
+ rule declaration
107
+ ws name:(ident / variable) s ':' s expressions s (';'/ ws &'}') ws {
108
+ def build env
109
+ env << (name.text_value =~ /^@/ ?
110
+ Node::Variable : Node::Property).new(name.text_value, expressions.build(env), env)
111
+ end
112
+ # Empty rule
113
+ } / ws ident s ':' s ';' ws
114
+ end
115
+
116
+ #
117
+ # An operation or compound value
118
+ #
119
+ rule expressions
120
+ # Operation
121
+ expression tail:(operator expression)+ {
122
+ def build env
123
+ all.map {|e| e.build(env) }.dissolve
124
+ end
125
+
126
+ def all
127
+ [expression] + tail.elements.map {|i| [i.operator, i.expression] }.flatten.compact
128
+ end
129
+ # Space-delimited expressions
130
+ } / expression tail:(WS expression)* {
131
+ def build env
132
+ all.map {|e| e.build(env) if e.respond_to? :build }.compact
133
+ end
134
+
135
+ def all
136
+ [expression] + tail.elements.map {|f| f.expression }
137
+ end
138
+ }
139
+ end
140
+
141
+ rule expression
142
+ '(' s expressions s ')' {
143
+ def build env
144
+ Node::Expression.new(['('] + expressions.build(env).flatten + [')'])
145
+ end
146
+ } / entity '' {
147
+ def build env
148
+ entity.method(:build).arity.zero?? entity.build : entity.build(env)
149
+ end
150
+ }
151
+ end
152
+
153
+ #
154
+ # An identifier
155
+ #
156
+ rule ident
157
+ '*'? '-'? [-a-z0-9_]+
158
+ end
159
+
160
+ rule variable
161
+ '@' [-a-zA-Z0-9_]+ {
162
+ def build
163
+ Node::Variable.new(text_value)
164
+ end
165
+ }
166
+ end
167
+
168
+ #
169
+ # div / .class / #id / input[type="text"] / lang(fr)
170
+ #
171
+ rule element
172
+ (class_id / tag / ident) attribute* ('(' ident? attribute* ')')? / attribute+ / '@media' / '@font-face'
173
+ end
174
+
175
+ rule class_id
176
+ tag? (class / id)+
177
+ end
178
+
179
+ #
180
+ # [type="text"]
181
+ #
182
+ rule attribute
183
+ '[' tag ([|~*$^]? '=') (tag / string) ']' / '[' (tag / string) ']'
184
+ end
185
+
186
+ rule class
187
+ '.' [_a-z] [-a-zA-Z0-9_]*
188
+ end
189
+
190
+ rule id
191
+ '#' [_a-z] [-a-zA-Z0-9_]*
192
+ end
193
+
194
+ rule tag
195
+ [a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
196
+ end
197
+
198
+ rule select
199
+ (s [+>~] s / s ':' / S)?
200
+ end
201
+
202
+ # TODO: Merge this with attribute rule
203
+ rule accessor
204
+ ident:(class_id / tag) '[' attr:(string / variable) ']' {
205
+ def build env
206
+ env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
207
+ end
208
+ }
209
+ end
210
+
211
+ rule operator
212
+ S [-+*/] S {
213
+ def build env
214
+ Node::Operator.new(text_value.strip)
215
+ end
216
+ } / [-+*/] {
217
+ def build env
218
+ Node::Operator.new(text_value)
219
+ end
220
+ }
221
+ end
222
+
223
+ #
224
+ # Functions and arguments
225
+ #
226
+ rule function
227
+ name:([-a-zA-Z_]+) arguments {
228
+ def build
229
+ Node::Function.new(name.text_value, [arguments.build].flatten)
230
+ end
231
+ }
232
+ end
233
+
234
+ rule arguments
235
+ '(' s argument s tail:(',' s argument s)* ')' {
236
+ def build
237
+ all.map do |e|
238
+ e.build if e.respond_to? :build
239
+ end.compact
240
+ end
241
+
242
+ def all
243
+ [argument] + tail.elements.map {|e| e.argument }
244
+ end
245
+ }
246
+ end
247
+
248
+ rule argument
249
+ color / number unit {
250
+ def build
251
+ Node::Number.new number.text_value, unit.text_value
252
+ end
253
+ } / string {
254
+ def build
255
+ Node::String.new text_value
256
+ end
257
+ } / [a-zA-Z]+ '=' dimension {
258
+ def build
259
+ Node::Anonymous.new text_value
260
+ end
261
+ } / [-a-zA-Z0-9_%$/.&=:;#+?]+ {
262
+ def build
263
+ Node::String.new text_value
264
+ end
265
+ } / function / keyword other:(S keyword)* {
266
+ def build
267
+ end
268
+ }
269
+ end
270
+ end
271
+ end
@@ -36,6 +36,10 @@ module Less
36
36
 
37
37
  rgba hue[ h + 1/3 ], hue[ h ], hue[ h - 1/3 ], a
38
38
  end
39
+
40
+ def self.available
41
+ self.instance_methods.map(&:to_sym)
42
+ end
39
43
  end
40
44
 
41
45
  module Node
@@ -45,31 +49,30 @@ module Less
45
49
  # it calls functions from the Functions module
46
50
  #
47
51
  class Function < ::String
48
- include Functions
49
52
  include Entity
53
+ include Functions
50
54
 
51
55
  def initialize name, *args
52
56
  @args = args.flatten
53
57
  super name
54
58
  end
55
-
59
+
56
60
  def to_css
57
61
  self.evaluate.to_css
58
62
  end
59
63
 
60
64
  #
61
65
  # Call the function
62
- #
63
- def evaluate
64
- send self.to_sym, *@args
65
- end
66
-
67
66
  #
68
67
  # If the function isn't found, we just print it out,
69
68
  # this is the case for url(), for example,
70
69
  #
71
- def method_missing meth, *args
72
- Node::Anonymous.new("#{meth}(#{args.map(&:to_css) * ', '})")
70
+ def evaluate
71
+ if Functions.available.include? self.to_sym
72
+ send to_sym, *@args
73
+ else
74
+ Node::Anonymous.new("#{to_sym}(#{@args.map(&:to_css) * ', '})")
75
+ end
73
76
  end
74
77
  end
75
78
  end
data/lib/less/engine.rb CHANGED
@@ -6,7 +6,9 @@ require 'engine/nodes'
6
6
  begin
7
7
  require 'engine/parser'
8
8
  rescue LoadError
9
- Treetop.load LESS_GRAMMAR
9
+ Treetop.load File.join(LESS_GRAMMAR, 'common.tt')
10
+ Treetop.load File.join(LESS_GRAMMAR, 'entity.tt')
11
+ Treetop.load File.join(LESS_GRAMMAR, 'less.tt')
10
12
  end
11
13
 
12
14
  module Less
@@ -23,12 +25,14 @@ module Less
23
25
  raise ArgumentError, "argument must be an instance of File or String!"
24
26
  end
25
27
 
26
- @parser = LessParser.new
28
+ @parser = StyleSheetParser.new
27
29
  end
28
30
 
29
- def parse env = Node::Element.new
31
+ def parse build = true, env = Node::Element.new
30
32
  root = @parser.parse(self.prepare)
31
33
 
34
+ return root unless build
35
+
32
36
  if root
33
37
  @tree = root.build env.tap {|e| e.file = @path }
34
38
  else