less 1.0.16 → 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.0.16
1
+ 1.1.3
data/bin/lessc CHANGED
@@ -3,13 +3,23 @@
3
3
  $:.unshift File.dirname(__FILE__) + "/../lib"
4
4
 
5
5
  require 'optparse'
6
+ require 'rubygems'
6
7
  require 'less'
7
8
 
9
+ begin
10
+ require 'growl'
11
+ rescue LoadError
12
+ Less::GROWL = false
13
+ else
14
+ Less::GROWL = true
15
+ end
16
+
8
17
  # Argument defaults
9
18
  options = {
10
19
  :watch => false,
11
20
  :compress => false,
12
21
  :debug => false,
22
+ :growl => false
13
23
  }
14
24
 
15
25
  # Get arguments
@@ -22,6 +32,18 @@ opts = OptionParser.new do |o|
22
32
  options[:watch] = true
23
33
  end
24
34
 
35
+ # Growl
36
+ o.on("-g", "--growl", "growl notifications") do
37
+ if Less::GROWL && (Growl.installed? rescue false)
38
+ options[:growl] = true
39
+ elsif Less::GROWL
40
+ abort "Growl or 'growlnotify' wasn't found on your system."
41
+ else
42
+ abort "Growl gem not found, please install with: " +
43
+ "`sudo gem install visionmedia-growl -s http://gems.github.com`"
44
+ end
45
+ end
46
+
25
47
  # Compression needs a proper algorithm
26
48
  #
27
49
  # o.on("-x", "--compress", "compress css file") do
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{less}
5
- s.version = "1.0.16"
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"]
9
- s.date = %q{2009-07-23}
9
+ s.date = %q{2009-07-27}
10
10
  s.default_executable = %q{lessc}
11
11
  s.description = %q{LESS is leaner CSS}
12
12
  s.email = %q{self@cloudhead.net}
@@ -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",
@@ -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
@@ -7,8 +7,9 @@ require 'delegate'
7
7
 
8
8
  LESS_ROOT = File.expand_path(File.dirname(__FILE__))
9
9
  LESS_PARSER = File.join(LESS_ROOT, 'less', 'engine', 'parser.rb')
10
- LESS_GRAMMAR = File.join(LESS_ROOT, 'less', 'engine', 'less.tt')
10
+ LESS_GRAMMAR = File.join(LESS_ROOT, 'less', 'engine', 'grammar')
11
11
 
12
+ require 'ext'
12
13
  require 'less/command'
13
14
  require 'less/engine'
14
15
 
@@ -30,66 +31,3 @@ module Less
30
31
  Engine.new(less).to_css
31
32
  end
32
33
  end
33
-
34
- module Treetop
35
- module Runtime
36
- class CompiledParser
37
- def failure_message
38
- return nil unless (tf = terminal_failures) && tf.size > 0
39
- "on line #{failure_line}: expected " + (
40
- tf.size == 1 ?
41
- tf[0].expected_string :
42
- "one of #{tf.map {|f| f.expected_string }.uniq * ' '}"
43
- ) +
44
- " got `#{input[failure_index]}`" +
45
- " after:\n\n#{input[index...failure_index]}\n"
46
- end
47
- end
48
- end
49
- end
50
-
51
- class Object
52
- def verbose
53
- $verbose = true
54
- yield
55
- ensure
56
- $verbose = false
57
- end
58
-
59
- def tap
60
- yield self
61
- self
62
- end
63
-
64
- def log(s = '') puts "* #{s}" if $verbose end
65
- def log!(s = '') puts "* #{s}" end
66
- def error(s) $stderr.puts s end
67
- def error!(s) raise Exception, s end
68
- end
69
-
70
- class Array
71
- def dissolve
72
- ary = flatten.compact
73
- case ary.size
74
- when 0 then []
75
- when 1 then first
76
- else ary
77
- end
78
- end
79
-
80
- def one?
81
- size == 1
82
- end
83
- end
84
-
85
- class Class
86
- def to_sym
87
- self.to_s.to_sym
88
- end
89
- end
90
-
91
- class Symbol
92
- def to_proc
93
- proc {|obj, *args| obj.send(self, *args) }
94
- end
95
- end
@@ -1,4 +1,12 @@
1
1
  module Less
2
+ ESC = "\033"
3
+ RESET = "#{ESC}[0m"
4
+
5
+ GREEN = lambda {|s| "#{ESC}[1;32m#{s}#{RESET}"}
6
+ RED = lambda {|s| "#{ESC}[1;31m#{s}#{RESET}"}
7
+ YELLOW = lambda {|s| "#{ESC}[1;33m#{s}#{RESET}"}
8
+ BOLD = lambda {|s| "#{ESC}[1m#{s}#{RESET}"}
9
+
2
10
  class Command
3
11
  attr_accessor :source, :destination, :options
4
12
 
@@ -59,19 +67,20 @@ module Less
59
67
  File.open( @destination, "w" ) do |file|
60
68
  file.write css
61
69
  end
62
- print "#{new ? '* [Created]' : '* [Updated]'} #{@destination.split('/').last}\n: " if watch?
70
+ print "#{GREEN['* ' + (new ? 'Created' : 'Updated')]} " +
71
+ "#{@destination.split('/').last}\n: " if watch?
63
72
  rescue Errno::ENOENT => e
64
73
  abort "#{e}"
65
74
  rescue SyntaxError => e
66
- err "#{e}\n", "Parse"
75
+ err "#{e}\n", "Syntax"
67
76
  rescue MixedUnitsError => e
68
77
  err "`#{e}` you're mixing units together! What do you expect?\n"
69
78
  rescue PathError => e
70
79
  err "`#{e}` was not found.\n", "Path"
71
80
  rescue VariableNameError => e
72
- err "`#{e}` is undefined.\n", "Name"
81
+ err "#{YELLOW[e]} is undefined.\n", "Name"
73
82
  rescue MixinNameError => e
74
- err "`#{e}` is undefined.\n", "Name"
83
+ err "#{YELLOW[e]} is undefined.\n", "Name"
75
84
  else
76
85
  true
77
86
  end
@@ -84,7 +93,14 @@ module Less
84
93
 
85
94
  def err s = '', type = ''
86
95
  type = type.strip + ' ' unless type.empty?
87
- print "! [#{type}Error] #{s}"
96
+ print "#{RED["! #{type}Error"]}: #{s}"
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
88
104
  end
89
105
  end
90
- end
106
+ end
@@ -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
@@ -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