csscss 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rspec
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec.default ADDED
@@ -0,0 +1 @@
1
+ --debug
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in csscss.gemspec
4
+ gemspec
5
+
6
+ gem "rake", :require => false
7
+ gem "debugger"
8
+ gem "m"
9
+ gem "minitest-rg"
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Zach Moazeni
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # Csscss
2
+
3
+ [![Build Status](https://travis-ci.org/zmoazeni/csscss.png?branch=master)](https://travis-ci.org/zmoazeni/csscss)
4
+
5
+ A CSS redundancy analyzer that analyzes redundancy. My first attempt was
6
+ in Haskell with https://github.com/zmoazeni/csscss-haskell this is the
7
+ second in Ruby.
8
+
9
+ ## (Not ready yet) Installation
10
+
11
+ (Soon) Install it as:
12
+
13
+ $ gem install csscss
14
+
15
+ ## Usage
16
+
17
+ TODO: Write usage instructions here
18
+
19
+ ## Contributing
20
+
21
+ 1. Fork it
22
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
23
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
24
+ 4. Push to the branch (`git push origin my-new-feature`)
25
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.pattern = "test/**/*_test.rb"
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
data/bin/csscss ADDED
@@ -0,0 +1,5 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "debugger"
4
+ require "csscss"
5
+ Csscss::CLI.run(ARGV)
data/csscss.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'csscss/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "csscss"
8
+ gem.version = Csscss::VERSION
9
+ gem.authors = ["Zach Moazeni"]
10
+ gem.email = ["zach.moazeni@gmail.com"]
11
+ gem.description = %q{A CSS redundancy analyzer that analyzes redundancy.}
12
+ gem.summary = %q{A CSS redundancy analyzer that analyzes redundancy.}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "csspool", "~> 3.0"
21
+ gem.add_dependency "parslet", "~> 1.5"
22
+ end
data/lib/csscss/cli.rb ADDED
@@ -0,0 +1,78 @@
1
+ module Csscss
2
+ class CLI
3
+ def initialize(argv)
4
+ @argv = argv
5
+ @verbose = false
6
+ @minimum = 3
7
+ end
8
+
9
+ def run
10
+ parse(@argv)
11
+ execute
12
+ end
13
+
14
+ def execute
15
+ all_redundancies = @argv.map do |filename|
16
+ contents = File.read(filename)
17
+ RedundancyAnalyzer.new(contents).redundancies(@minimum)
18
+ end
19
+
20
+ combined_redundancies = all_redundancies.inject({}) do |combined, redundancies|
21
+ if combined.empty?
22
+ redundancies
23
+ else
24
+ combined.merge(redundancies) do |_, v1, v2|
25
+ (v1 + v2).uniq
26
+ end
27
+ end
28
+ end
29
+
30
+ puts Reporter.new(combined_redundancies).report(@verbose)
31
+ end
32
+
33
+ def parse(argv)
34
+ opts = OptionParser.new do |opts|
35
+ opts.banner = "Usage: csscss [files..]"
36
+ opts.version = Csscss::VERSION
37
+
38
+ opts.on("-v", "--[no-]verbose", "Display each rule") do |v|
39
+ @verbose = v
40
+ end
41
+
42
+ opts.on("-n", "--num N", Integer, "Print matches with at least this many rules. Defaults to 3") do |n|
43
+ @minimum = n
44
+ end
45
+
46
+ opts.on("-V", "--version", "Show version") do |v|
47
+ puts opts.ver
48
+ exit
49
+ end
50
+
51
+ opts.on("-j", "--[no-]json", "Output results in JSON") do |j|
52
+ @json = j
53
+ end
54
+
55
+ opts.on_tail("-h", "--help", "Show this message") do
56
+ print_help(opts)
57
+ end
58
+ end
59
+ opts.parse!(argv)
60
+
61
+ print_help(opts) if argv.empty?
62
+ rescue OptionParser::ParseError
63
+ print_help(opts)
64
+ end
65
+
66
+ private
67
+ def print_help(opts)
68
+ puts opts
69
+ exit
70
+ end
71
+
72
+ class << self
73
+ def run(argv)
74
+ new(argv).run
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,61 @@
1
+ module Csscss
2
+ class RedundancyAnalyzer
3
+ def initialize(raw_css)
4
+ @raw_css = raw_css
5
+ end
6
+
7
+ def redundancies(minimum = nil)
8
+ rule_sets = CSSPool.CSS(@raw_css).rule_sets
9
+ matches = {}
10
+ rule_sets.each do |rule_set|
11
+ rule_set.declarations.each do |dec|
12
+ dec.property.downcase!
13
+ dec.expressions do |exp|
14
+ exp.value.downcase!
15
+ end
16
+
17
+ dec_key = Declaration.from_csspool(dec)
18
+ sel = Selector.new(rule_set.selectors.map(&:to_s))
19
+ matches[dec_key] ||= []
20
+ matches[dec_key] << sel
21
+ end
22
+ end
23
+
24
+ inverted_matches = {}
25
+ matches.each do |declaration, selector_groups|
26
+ if selector_groups.size > 1
27
+ selector_groups.combination(2).each do |two_selectors|
28
+ inverted_matches[two_selectors] ||= []
29
+ inverted_matches[two_selectors] << declaration
30
+ end
31
+ end
32
+ end
33
+
34
+ if minimum
35
+ inverted_matches.delete_if do |key, declarations|
36
+ declarations.size < minimum
37
+ end
38
+ end
39
+
40
+ final_inverted_matches = inverted_matches.dup
41
+ inverted_matches.to_a[0..-2].each_with_index do |(selector_group1, declarations1), index|
42
+ inverted_matches.to_a[(index + 1)..-1].each do |selector_group2, declarations2|
43
+ if declarations1 == declarations2
44
+ final_inverted_matches.delete(selector_group1)
45
+ final_inverted_matches.delete(selector_group2)
46
+ key = (selector_group1 + selector_group2).sort.uniq
47
+ final_inverted_matches[key] ||= []
48
+ final_inverted_matches[key].concat(declarations1 + declarations2).uniq!
49
+ end
50
+ end
51
+ end
52
+
53
+ sorted_array = final_inverted_matches.sort {|(_, v1), (_, v2)| v2.size <=> v1.size }
54
+ {}.tap do |sorted_hash|
55
+ sorted_array.each do |key, value|
56
+ sorted_hash[key.sort] = value.sort
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ module Csscss
2
+ class Reporter
3
+ def initialize(redundancies)
4
+ @redundancies = redundancies
5
+ end
6
+
7
+ def report(verbose = false)
8
+ io = StringIO.new
9
+ @redundancies.each do |selector_groups, declarations|
10
+ selector_groups = selector_groups.map {|selectors| "{#{selectors.selectors.join(", ")}}" }
11
+ last_selector = selector_groups.pop
12
+ count = declarations.size
13
+ io.puts %Q(#{selector_groups.join(", ")} and #{last_selector} share #{count} rule#{"s" if count > 1})
14
+ if verbose
15
+ declarations.each do |dec|
16
+ io.puts " - #{dec.property}: #{dec.value}"
17
+ end
18
+ end
19
+ end
20
+
21
+ io.rewind
22
+ io.read
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Csscss
2
+ class Declaration < Struct.new(:property, :value)
3
+ def self.from_csspool(dec)
4
+ new(dec.property.to_s, dec.expressions.join(" "))
5
+ end
6
+
7
+ def <=>(other)
8
+ property <=> other.property
9
+ end
10
+ end
11
+
12
+ class Selector < Struct.new(:selectors)
13
+ def <=>(other)
14
+ selectors <=> other.selectors
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Csscss
2
+ VERSION = "0.0.1"
3
+ end
data/lib/csscss.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "stringio"
2
+ require "optparse"
3
+
4
+ require "parslet"
5
+ require "csspool"
6
+
7
+ require "csscss/version"
8
+ require "csscss/cli"
9
+ require "csscss/types"
10
+ require "csscss/redundancy_analyzer"
11
+ require "csscss/reporter"
@@ -0,0 +1,83 @@
1
+ require "test_helper"
2
+
3
+ module Csscss
4
+ describe RedundancyAnalyzer do
5
+ it "finds and trims redundant rule_sets" do
6
+ css = %$
7
+ h1, h2 { display: none; position: relative; outline:none}
8
+ .foo { display: none; width: 1px }
9
+ .bar { position: relative; width: 1px; outline: none }
10
+ .baz { display: none }
11
+ $
12
+
13
+ RedundancyAnalyzer.new(css).redundancies.must_equal({
14
+ [sel(".bar"), sel(%w(h1 h2))] => [dec("outline", "none"), dec("position", "relative")],
15
+ [sel(".bar"), sel(".foo")] => [dec("width", "1px")],
16
+ [sel(".baz"), sel(".foo"), sel(%w(h1 h2))] => [dec("display", "none")]
17
+ })
18
+
19
+ RedundancyAnalyzer.new(css).redundancies.first.must_equal [
20
+ [sel(".bar"), sel(%w(h1 h2))] , [dec("outline", "none"), dec("position", "relative")]
21
+ ]
22
+
23
+ RedundancyAnalyzer.new(css).redundancies(2).must_equal({
24
+ [sel(".bar"), sel(%w(h1 h2))] => [dec("outline", "none"), dec("position", "relative")]
25
+ })
26
+ end
27
+
28
+ it "finds ignores case with rule_sets" do
29
+ css = %$
30
+ .foo { WIDTH: 1px }
31
+ .bar { width: 1px }
32
+ $
33
+
34
+ RedundancyAnalyzer.new(css).redundancies.must_equal({
35
+ [sel(".bar"), sel(".foo")] => [dec("width", "1px")]
36
+ })
37
+ end
38
+
39
+ it "doesn't return solo selectors" do
40
+ css = %$
41
+ .foo {
42
+ -webkit-border-radius: 4px;
43
+ -moz-border-radius: 4px;
44
+ }
45
+ $
46
+ RedundancyAnalyzer.new(css).redundancies.must_equal({})
47
+ end
48
+
49
+ it "correctly finds counts" do
50
+ css = %$
51
+ .foo {
52
+ -webkit-border-radius: 4px;
53
+ -moz-border-radius: 4px;
54
+ }
55
+
56
+ .bar {
57
+ background: white;
58
+
59
+ -webkit-border-radius: 4px;
60
+ -moz-border-radius: 4px;
61
+ box-shadow: 1px 1px 10px #CCCCCC;
62
+ -moz-box-shadow: 1px 1px 10px #CCCCCC;
63
+ -webkit-box-shadow: 1px 1px 10px #CCCCCC;
64
+ }
65
+
66
+ .baz {
67
+ margin: 3px 3px 30px 3px;
68
+ padding: 10px 30px;
69
+ background: white url(images/bg-bolt-inactive.png) no-repeat 99% 5px;
70
+
71
+ -webkit-border-radius: 4px;
72
+ -moz-border-radius: 4px;
73
+ box-shadow: 1px 1px 10px #CCCCCC;
74
+ -moz-box-shadow: 1px 1px 10px #CCCCCC;
75
+ -webkit-box-shadow: 1px 1px 10px #CCCCCC;
76
+ }
77
+ $
78
+
79
+ redundancies = RedundancyAnalyzer.new(css).redundancies(3)
80
+ redundancies[[sel(".bar"), sel(".baz")]].size.must_equal(5)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,31 @@
1
+ require "test_helper"
2
+
3
+ module Csscss
4
+ describe Reporter do
5
+ it "formats string result" do
6
+ reporter = Reporter.new({
7
+ [sel(".foo"), sel(".bar")] => [dec("width", "1px"), dec("border", "black")],
8
+ [sel(%w(h1 h2)), sel(".foo"), sel(".baz")] => [dec("display", "none")],
9
+ [sel(%w(h1 h2)), sel(".bar")] => [dec("position", "relative")]
10
+ })
11
+
12
+ expected =<<-EXPECTED
13
+ {.foo} and {.bar} share 2 rules
14
+ {h1, h2}, {.foo} and {.baz} share 1 rule
15
+ {h1, h2} and {.bar} share 1 rule
16
+ EXPECTED
17
+ reporter.report.must_equal expected
18
+
19
+ expected =<<-EXPECTED
20
+ {.foo} and {.bar} share 2 rules
21
+ - width: 1px
22
+ - border: black
23
+ {h1, h2}, {.foo} and {.baz} share 1 rule
24
+ - display: none
25
+ {h1, h2} and {.bar} share 1 rule
26
+ - position: relative
27
+ EXPECTED
28
+ reporter.report(true).must_equal expected
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ require "minitest/autorun"
5
+ require "minitest/rg"
6
+ require "debugger"
7
+
8
+ require "csscss"
9
+
10
+ MiniTest::Spec.add_setup_hook do
11
+ def sel(s)
12
+ Csscss::Selector.new(Array(s))
13
+ end
14
+
15
+ def dec(p, v)
16
+ Csscss::Declaration.new(p, v)
17
+ end
18
+
19
+ def cmatch(selectors, decs)
20
+ Csscss::Match.new(selectors, decs)
21
+ end
22
+ end
23
+
24
+ Debugger.settings[:autoeval] = true
25
+ Debugger.settings[:autolist] = 1
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: csscss
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Zach Moazeni
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: csspool
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: parslet
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.5'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.5'
46
+ description: A CSS redundancy analyzer that analyzes redundancy.
47
+ email:
48
+ - zach.moazeni@gmail.com
49
+ executables:
50
+ - csscss
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .rspec.default
56
+ - .travis.yml
57
+ - Gemfile
58
+ - LICENSE.txt
59
+ - README.md
60
+ - Rakefile
61
+ - bin/csscss
62
+ - csscss.gemspec
63
+ - lib/csscss.rb
64
+ - lib/csscss/cli.rb
65
+ - lib/csscss/redundancy_analyzer.rb
66
+ - lib/csscss/reporter.rb
67
+ - lib/csscss/types.rb
68
+ - lib/csscss/version.rb
69
+ - test/csscss/redundancy_analyzer_test.rb
70
+ - test/csscss/reporter_test.rb
71
+ - test/test_helper.rb
72
+ homepage: ''
73
+ licenses: []
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ segments:
85
+ - 0
86
+ hash: 4262444302933628003
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ segments:
94
+ - 0
95
+ hash: 4262444302933628003
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 1.8.25
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: A CSS redundancy analyzer that analyzes redundancy.
102
+ test_files:
103
+ - test/csscss/redundancy_analyzer_test.rb
104
+ - test/csscss/reporter_test.rb
105
+ - test/test_helper.rb