csscss 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 1.2.0 - 4/14/2013 ##
2
+
3
+ * 0 and 0px are now reconciled as redundancies
4
+ * Disables color support by default for windows & ruby < 2.0
5
+ * Fixes bug where unquoted url(data...) isn't parsed correctly
6
+ * Adds support for LESS files
7
+
1
8
  ## 1.1.0 - 4/12/2013 ##
2
9
 
3
10
  * Fixes bug where CLI --no-color wasn't respected
data/CONTRIBUTORS.md CHANGED
@@ -2,3 +2,5 @@
2
2
  * Carson McDonald @carsonmcdonald
3
3
  * Martin Kuckert @MKuckert
4
4
  * Ivan Lazarevic @kopipejst
5
+ * Matt DuVall @mduvall twitter:@mduvall_
6
+ * Mekka Okereke @mekka @mekkaokereke
data/Gemfile CHANGED
@@ -6,6 +6,8 @@ gemspec
6
6
  # optional runtime dependencies
7
7
  gem "sass"
8
8
  gem "compass"
9
+ gem "less"
10
+ gem "therubyracer", :platform => :mri
9
11
 
10
12
  gem "rake", :require => false
11
13
  gem "debugger"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- csscss (1.1.0)
4
+ csscss (1.2.0)
5
5
  colorize
6
6
  parslet (~> 1.5)
7
7
 
@@ -12,6 +12,7 @@ GEM
12
12
  chunky_png (1.2.7)
13
13
  colorize (0.5.8)
14
14
  columnize (0.3.6)
15
+ commonjs (0.2.6)
15
16
  compass (0.12.2)
16
17
  chunky_png (~> 1.2)
17
18
  fssm (>= 0.2.7)
@@ -23,6 +24,9 @@ GEM
23
24
  debugger-linecache (1.2.0)
24
25
  debugger-ruby_core_source (1.2.0)
25
26
  fssm (0.2.10)
27
+ less (2.3.1)
28
+ commonjs (~> 0.2.6)
29
+ libv8 (3.11.8.17)
26
30
  m (1.3.1)
27
31
  method_source (>= 0.6.7)
28
32
  rake (>= 0.9.2.2)
@@ -32,8 +36,12 @@ GEM
32
36
  parslet (1.5.0)
33
37
  blankslate (~> 2.0)
34
38
  rake (10.0.3)
39
+ ref (1.0.4)
35
40
  ruby-prof (0.13.0)
36
41
  sass (3.2.7)
42
+ therubyracer (0.11.4)
43
+ libv8 (~> 3.11.8.12)
44
+ ref
37
45
 
38
46
  PLATFORMS
39
47
  ruby
@@ -42,9 +50,11 @@ DEPENDENCIES
42
50
  compass
43
51
  csscss!
44
52
  debugger
53
+ less
45
54
  m
46
55
  minitest
47
56
  minitest-rg
48
57
  rake
49
58
  ruby-prof
50
59
  sass
60
+ therubyracer
data/README.md CHANGED
@@ -45,11 +45,20 @@ rulesets that have fewer matches.
45
45
 
46
46
  $ csscss -n 10 -v path/to/style.css # ignores rulesets with < 10 matches
47
47
 
48
- If you prefer writing in sass, you can also parse your sass/scss files.
48
+ If you prefer writing in [sass](http://sass-lang.com/), you can also parse your sass/scss files.
49
49
 
50
50
  $ gem install sass
51
51
  $ csscss path/to/style.scss
52
52
 
53
+ If you prefer writing in [LESS](http://lesscss.org/), you can also parse your LESS files.
54
+
55
+ $ gem install less
56
+ $ csscss path/to/style.less
57
+
58
+ LESS requires an additional javascript runtime.
59
+ [v8/therubyracer](https://rubygems.org/gems/therubyracer) on most
60
+ rubies, and [therubyrhino](https://rubygems.org/gems/therubyrhino) on
61
+ jruby.
53
62
 
54
63
  ## I found bugs ##
55
64
 
data/lib/csscss/cli.rb CHANGED
@@ -3,12 +3,12 @@ module Csscss
3
3
  def initialize(argv)
4
4
  @argv = argv
5
5
  @verbose = false
6
- @color = true
6
+ @color = !windows_1_9
7
7
  @minimum = 3
8
8
  @compass = false
9
9
  @ignored_properties = []
10
10
  @ignored_selectors = []
11
- @match_shorthand = true
11
+ @match_shorthand = true
12
12
  end
13
13
 
14
14
  def run
@@ -16,31 +16,22 @@ module Csscss
16
16
  execute
17
17
  end
18
18
 
19
+ private
19
20
  def execute
20
21
  warn_old_debug_flag if ENV["CSSCSS_DEBUG"]
21
22
 
22
- all_contents = @argv.map do |filename|
23
- if %w(.scss .sass).include?(File.extname(filename).downcase) && !(filename =~ URI.regexp)
24
- begin
25
- require "sass"
26
- rescue LoadError
27
- abort "Must install sass gem before parsing sass/scss files"
28
- end
29
-
30
- sass_options = {cache:false}
31
- sass_options[:load_paths] = Compass.configuration.sass_load_paths if @compass
32
- begin
33
- Sass::Engine.for_file(filename, sass_options).render
34
- rescue Sass::SyntaxError => e
35
- if e.message =~ /compass/ && !@compass
36
- puts "Enable --compass option to use compass's extensions"
37
- exit 1
38
- else
39
- raise e
40
- end
41
- end
23
+ all_contents= @argv.map do |filename|
24
+ if filename =~ URI.regexp
25
+ load_css_file(filename)
42
26
  else
43
- open(filename) {|f| f.read }
27
+ case File.extname(filename).downcase
28
+ when ".scss", ".sass"
29
+ load_sass_file(filename)
30
+ when ".less"
31
+ load_less_file(filename)
32
+ else
33
+ load_css_file(filename)
34
+ end
44
35
  end
45
36
  end.join("\n")
46
37
 
@@ -80,7 +71,7 @@ module Csscss
80
71
  @verbose = v
81
72
  end
82
73
 
83
- opts.on("--[no-]color", "Colorize output (default is true)") do |c|
74
+ opts.on("--[no-]color", "Colorize output (default is #{@color})") do |c|
84
75
  @color = c
85
76
  end
86
77
 
@@ -133,7 +124,6 @@ module Csscss
133
124
  print_help(opts)
134
125
  end
135
126
 
136
- private
137
127
  def print_help(opts)
138
128
  puts opts
139
129
  exit
@@ -155,6 +145,51 @@ module Csscss
155
145
  abort "Must install compass gem before enabling its extensions"
156
146
  end
157
147
 
148
+ def windows_1_9
149
+ RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ && RUBY_VERSION =~ /^1\.9/
150
+ end
151
+
152
+ def gem_installed?(gem_name)
153
+ begin
154
+ require gem_name
155
+ true
156
+ rescue LoadError
157
+ false
158
+ end
159
+ end
160
+
161
+ def load_sass_file(filename)
162
+ if !gem_installed?("sass") then
163
+ abort 'Must install the "sass" gem before parsing sass/scss files'
164
+ end
165
+
166
+ sass_options = {cache:false}
167
+ sass_options[:load_paths] = Compass.configuration.sass_load_paths if @compass
168
+ begin
169
+ Sass::Engine.for_file(filename, sass_options).render
170
+ rescue Sass::SyntaxError => e
171
+ if e.message =~ /compass/ && !@compass
172
+ puts "Enable --compass option to use compass's extensions"
173
+ exit 1
174
+ else
175
+ raise e
176
+ end
177
+ end
178
+ end
179
+
180
+ def load_less_file(filename)
181
+ if !gem_installed?("less") then
182
+ abort 'Must install the "less" gem before parsing less files'
183
+ end
184
+
185
+ contents = load_css_file(filename)
186
+ Less::Parser.new.parse(contents).to_css
187
+ end
188
+
189
+ def load_css_file(filename)
190
+ open(filename) {|f| f.read }
191
+ end
192
+
158
193
  class << self
159
194
  def run(argv)
160
195
  new(argv).run
@@ -5,17 +5,19 @@ module Csscss
5
5
 
6
6
  UNITS = %w(px em ex in cm mm pt pc)
7
7
 
8
- rule(:space) { match['\s'].repeat(1) }
9
- rule(:space?) { space.maybe }
10
- rule(:number) { match["0-9"] }
11
- rule(:numbers) { number.repeat(1) }
12
- rule(:decimal) { numbers >> str(".").maybe >> numbers.maybe }
13
- rule(:percent) { decimal >> stri("%") >> space? }
14
- rule(:length) { decimal >> stri_list(UNITS) >> space? }
15
- rule(:identifier) { match["a-zA-Z"].repeat(1) }
16
- rule(:inherit) { stri("inherit") }
17
- rule(:eof) { any.absent? }
18
- rule(:nada) { any.repeat.as(:nada) }
8
+ rule(:space) { match['\s'].repeat(1) }
9
+ rule(:space?) { space.maybe }
10
+ rule(:number) { match["0-9"] }
11
+ rule(:numbers) { number.repeat(1) }
12
+ rule(:decimal) { numbers >> str(".").maybe >> numbers.maybe }
13
+ rule(:percent) { decimal >> stri("%") >> space? }
14
+ rule(:non_zero_length) { decimal >> stri_list(UNITS) >> space? }
15
+ rule(:zero_length) { match["0"] }
16
+ rule(:length) { zero_length | non_zero_length }
17
+ rule(:identifier) { match["a-zA-Z"].repeat(1) }
18
+ rule(:inherit) { stri("inherit") }
19
+ rule(:eof) { any.absent? }
20
+ rule(:nada) { any.repeat.as(:nada) }
19
21
 
20
22
  rule(:http) {
21
23
  (match['a-zA-Z0-9.:/\-'] | str('\(') | str('\)')).repeat >> space?
@@ -29,7 +31,7 @@ module Csscss
29
31
  stri("url") >> parens do
30
32
  (any_quoted { http } >> space?) |
31
33
  (any_quoted { data } >> space?) |
32
- http
34
+ data | http
33
35
  end
34
36
  }
35
37
 
@@ -25,7 +25,13 @@ module Csscss
25
25
  rule(:attribute) {
26
26
  match["^:{}"].repeat(1).as(:property) >>
27
27
  str(":") >>
28
- match["^;}"].repeat(1).as(:value) >>
28
+ (match["^;}"].repeat(1).capture(:stuff) >> dynamic {|source, context|
29
+ if context.captures[:stuff].to_s =~ /data:/
30
+ str(";") >> match["^;}"].repeat(1)
31
+ else
32
+ any.present?
33
+ end
34
+ }).as(:value) >>
29
35
  str(";").maybe >>
30
36
  space?
31
37
  }
@@ -65,7 +65,6 @@ module Csscss
65
65
  end
66
66
  end
67
67
 
68
-
69
68
  # trims any derivative declarations alongside shorthand
70
69
  inverted_matches.each do |selectors, declarations|
71
70
  redundant_derivatives = declarations.select do |dec|
data/lib/csscss/types.rb CHANGED
@@ -30,14 +30,15 @@ module Csscss
30
30
 
31
31
  def ==(other)
32
32
  if other.respond_to?(:property) && other.respond_to?(:value)
33
- property == other.property && value == other.value
33
+ # using eql? tanks performance
34
+ property == other.property && normalize_value(value) == normalize_value(other.value)
34
35
  else
35
36
  false
36
37
  end
37
38
  end
38
39
 
39
40
  def hash
40
- [property, value].hash
41
+ [property, normalize_value(value)].hash
41
42
  end
42
43
 
43
44
  def eql?(other)
@@ -67,6 +68,15 @@ module Csscss
67
68
  "<#{self.class} #{to_s}>"
68
69
  end
69
70
  end
71
+
72
+ private
73
+ def normalize_value(value)
74
+ if value =~ /^0(#{Csscss::Parser::Common::UNITS.join("|")}|%)$/
75
+ "0"
76
+ else
77
+ value
78
+ end
79
+ end
70
80
  end
71
81
 
72
82
  class Selector < Struct.new(:selectors)
@@ -1,3 +1,3 @@
1
1
  module Csscss
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -106,6 +106,8 @@ module Csscss::Parser
106
106
  @parser.length.must_parse "123px"
107
107
  @parser.length.must_parse "123EM"
108
108
  @parser.length.must_parse "1.23Pt"
109
+ @parser.length.must_parse "0"
110
+ @parser.length.wont_parse "1"
109
111
  end
110
112
  end
111
113
 
@@ -115,15 +117,16 @@ module Csscss::Parser
115
117
  @parser.http.must_parse 'foo\(bar\).jpg'
116
118
  @parser.http.must_parse 'http://foo\(bar\).jpg'
117
119
  @parser.http.must_parse 'http://foo.com/baz/\(bar\).jpg'
118
- @parser.http.must_parse '//foo.com/foo.jpg'
119
- @parser.http.must_parse 'https://foo.com/foo.jpg'
120
- @parser.http.must_parse 'http://foo100.com/foo.jpg'
121
- @parser.http.must_parse 'http://foo-bar.com/foo.jpg'
120
+ @parser.http.must_parse "//foo.com/foo.jpg"
121
+ @parser.http.must_parse "https://foo.com/foo.jpg"
122
+ @parser.http.must_parse "http://foo100.com/foo.jpg"
123
+ @parser.http.must_parse "http://foo-bar.com/foo.jpg"
122
124
  end
123
125
 
124
126
  it "parses data" do
125
- @parser.data.must_parse 'data:image/jpg;base64,IMGDATAGOESHERE=='
126
- @parser.data.must_parse 'data:image/svg+xml;base64,IMGDATAGOESHERE4/5/h/1+=='
127
+ @parser.data.must_parse "data:image/jpg;base64,IMGDATAGOESHERE=="
128
+ @parser.data.must_parse "data:image/svg+xml;base64,IMGDATAGOESHERE4/5/h/1+=="
129
+ @parser.data.must_parse "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAACECAYAAABRaEHiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHRJREFUeNqkUjESwCAIw+T/X/UHansdkLTQDnXgCAHNEW2tZbDz/Aq994bzqoY5Z8wEwiEcmmfwiRK+EGOMTVBrtz4mY9kEAyz6+E3sJ7MWBs1PaUy1lHLLmgTqElltNxLiINTBbWi0Vj5DZC9CaqZEOwQYAPhxY/7527NfAAAAAElFTkSuQmCC"
127
130
  end
128
131
 
129
132
  it "parses urls" do
@@ -133,6 +136,7 @@ module Csscss::Parser
133
136
  @parser.url.must_parse "url('foo.jpg')"
134
137
  @parser.url.must_parse "url('foo.jpg' )"
135
138
  @parser.url.must_parse 'url(foo\(bar\).jpg)'
139
+ @parser.url.must_parse "url(data:image/svg+xml;base64,IMGDATAGOESHERE4/5/h/1+==)"
136
140
  @parser.url.must_parse "url('data:image/svg+xml;base64,IMGDATAGOESHERE4/5/h/1+==')"
137
141
  end
138
142
  end
@@ -134,6 +134,39 @@ module Csscss::Parser
134
134
  rs(sel("h1"), [dec("display", "none")])
135
135
  ])
136
136
  end
137
+
138
+ it "parses attributes with encoded data that include semicolons" do
139
+ trans(%$
140
+ .foo1 {
141
+ background: rgb(123, 123, 123) url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAACECAYAAABRaEHiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHRJREFUeNqkUjESwCAIw+T/X/UHansdkLTQDnXgCAHNEW2tZbDz/Aq994bzqoY5Z8wEwiEcmmfwiRK+EGOMTVBrtz4mY9kEAyz6+E3sJ7MWBs1PaUy1lHLLmgTqElltNxLiINTBbWi0Vj5DZC9CaqZEOwQYAPhxY/7527NfAAAAAElFTkSuQmCC) repeat-x;
142
+ display: block;
143
+ }
144
+
145
+ .foo2 {
146
+ background: white url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAACECAYAAABRaEHiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHRJREFUeNqkUjESwCAIw+T/X/UHansdkLTQDnXgCAHNEW2tZbDz/Aq994bzqoY5Z8wEwiEcmmfwiRK+EGOMTVBrtz4mY9kEAyz6+E3sJ7MWBs1PaUy1lHLLmgTqElltNxLiINTBbWi0Vj5DZC9CaqZEOwQYAPhxY/7527NfAAAAAElFTkSuQmCC) repeat-x
147
+ }
148
+
149
+ .foo3 {
150
+ outline: 1px;
151
+ background: white url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAACECAYAAABRaEHiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHRJREFUeNqkUjESwCAIw+T/X/UHansdkLTQDnXgCAHNEW2tZbDz/Aq994bzqoY5Z8wEwiEcmmfwiRK+EGOMTVBrtz4mY9kEAyz6+E3sJ7MWBs1PaUy1lHLLmgTqElltNxLiINTBbWi0Vj5DZC9CaqZEOwQYAPhxY/7527NfAAAAAElFTkSuQmCC) repeat-x;
152
+ display: block;
153
+ }
154
+
155
+ .foo4 {
156
+ background: blue url(images/bg-bolt-inactive.png) no-repeat 99% 5px;
157
+ display: block;
158
+ }
159
+ $).must_equal([
160
+ rs(sel(".foo1"), [dec("background", "rgb(123, 123, 123) url(data:image/png;base64,ivborw0kggoaaaansuheugaaaaeaaacecayaaabraehiaaaagxrfwhrtb2z0d2fyzqbbzg9izsbjbwfnzvjlywr5ccllpaaaahrjrefuenqkujeswcaiw+t/x/uhansdkltqdnxgcahnew2tzbdz/aq994bzqoy5z8wewiecmmfwirk+egomtvbrtz4my9keayz6+e3sj7mwbs1pauy1lhllmgtqelltnxliintbbwi0vj5dzc9caqzeowqyaphxy/7527nfaaaaaelftksuqmcc) repeat-x"),
161
+ dec("display", "block")]),
162
+ rs(sel(".foo2"), [dec("background", "white url(data:image/png;base64,ivborw0kggoaaaansuheugaaaaeaaacecayaaabraehiaaaagxrfwhrtb2z0d2fyzqbbzg9izsbjbwfnzvjlywr5ccllpaaaahrjrefuenqkujeswcaiw+t/x/uhansdkltqdnxgcahnew2tzbdz/aq994bzqoy5z8wewiecmmfwirk+egomtvbrtz4my9keayz6+e3sj7mwbs1pauy1lhllmgtqelltnxliintbbwi0vj5dzc9caqzeowqyaphxy/7527nfaaaaaelftksuqmcc) repeat-x")]),
163
+ rs(sel(".foo3"), [dec("outline", "1px"),
164
+ dec("background", "white url(data:image/png;base64,ivborw0kggoaaaansuheugaaaaeaaacecayaaabraehiaaaagxrfwhrtb2z0d2fyzqbbzg9izsbjbwfnzvjlywr5ccllpaaaahrjrefuenqkujeswcaiw+t/x/uhansdkltqdnxgcahnew2tzbdz/aq994bzqoy5z8wewiecmmfwirk+egomtvbrtz4my9keayz6+e3sj7mwbs1pauy1lhllmgtqelltnxliintbbwi0vj5dzc9caqzeowqyaphxy/7527nfaaaaaelftksuqmcc) repeat-x"),
165
+ dec("display", "block")]),
166
+ rs(sel(".foo4"), [dec("background", "blue url(images/bg-bolt-inactive.png) no-repeat 99% 5px"),
167
+ dec("display", "block")])
168
+ ])
169
+ end
137
170
  end
138
171
  end
139
172
  end
@@ -275,6 +275,18 @@ module Csscss
275
275
  })
276
276
  end
277
277
 
278
+ it "matches 0 and 0px" do
279
+ css = %$
280
+ .bar { padding: 0; }
281
+ .foo { padding: 0px; }
282
+ $
283
+
284
+ RedundancyAnalyzer.new(css).redundancies.must_equal({
285
+ [sel(".bar"), sel(".foo")] => [dec("padding", "0")]
286
+ })
287
+ end
288
+
289
+
278
290
  # TODO: someday
279
291
  # it "reports duplication within the same selector" do
280
292
  # css = %$
@@ -69,5 +69,13 @@ module Csscss
69
69
  h[dec2].must_equal true
70
70
  h[dec3].must_equal true
71
71
  end
72
+
73
+ it "equates 0 length with and without units" do
74
+ Declaration.new("padding", "0px").must_equal Declaration.new("padding", "0")
75
+ Declaration.new("padding", "0%").must_equal Declaration.new("padding", "0")
76
+ Declaration.new("padding", "0").must_equal Declaration.new("padding", "0em")
77
+
78
+ Declaration.new("padding", "1").wont_equal Declaration.new("padding", "1px")
79
+ end
72
80
  end
73
81
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csscss
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-12 00:00:00.000000000 Z
12
+ date: 2013-04-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parslet
@@ -127,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
127
  version: '0'
128
128
  segments:
129
129
  - 0
130
- hash: -506187209623006521
130
+ hash: 4177886489285701178
131
131
  requirements: []
132
132
  rubyforge_project:
133
133
  rubygems_version: 1.8.25