cloudhead-less 1.1.2 → 1.1.3

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/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