see-less-ess-ess 0.0.1

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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ..gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Glen Mailer
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.
@@ -0,0 +1,13 @@
1
+ # See-less-ess-ess
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your compass config.rb
8
+
9
+ gem 'see-less-ess-ess'
10
+
11
+ ## Usage
12
+
13
+ TODO: Write usage instructions here
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,169 @@
1
+ require 'sass'
2
+ require 'nokogiri'
3
+
4
+ module SeeLessEssEss
5
+ VERSION = '0.0.1'
6
+
7
+ class Extractor
8
+ def initialize(location)
9
+ @location = location
10
+ end
11
+
12
+ def css_classes
13
+ @css_classes ||= extract_css_classes!
14
+ end
15
+
16
+ protected
17
+
18
+ def files
19
+ Dir.glob("#{@location}/*.html")
20
+ end
21
+ def extract_css_classes!
22
+ files.map do |file|
23
+ collector = Collector.new()
24
+ parser = Nokogiri::HTML::SAX::Parser.new(collector)
25
+ contents = open(file, 'rb').read
26
+ parser.parse(contents)
27
+ collector.css_classes
28
+ end.flatten
29
+ end
30
+
31
+ class Collector < Nokogiri::XML::SAX::Document
32
+ def css_classes
33
+ @css_classes ||= []
34
+ end
35
+ def start_element(name, attrs = [])
36
+ attrs.each do |name, value|
37
+ if name == "class"
38
+ css_classes << value.split
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ class Checker
46
+
47
+ def initialize(extractor, used_classes)
48
+ @extractor = extractor
49
+ @used_classes = used_classes
50
+ end
51
+
52
+ def does_not_use(sequence)
53
+ selectors = sequence.members.reject { |s| s.is_a?(String) }
54
+ selectors.any? do |selector|
55
+ selector.members.any? do |simple|
56
+ unused(simple)
57
+ end
58
+ end
59
+ end
60
+
61
+ def unused(simple)
62
+ if simple.is_a?(Sass::Selector::Class)
63
+ !css_classes_whitelist.include?(simple.name[0].to_s)
64
+ else
65
+ false
66
+ end
67
+ end
68
+
69
+ def css_classes_whitelist
70
+ @whitelist ||= @extractor.css_classes + @used_classes
71
+ end
72
+ end
73
+
74
+ class RemoveUnusedRules < Sass::Tree::Visitors::Base
75
+
76
+ def initialize(checker)
77
+ @checker = checker
78
+ end
79
+
80
+ # If an exception is raised, this adds proper metadata to the backtrace.
81
+ def visit(node)
82
+ super(node)
83
+ rescue Sass::SyntaxError => e
84
+ e.modify_backtrace(:filename => node.filename, :line => node.line)
85
+ raise e
86
+ end
87
+
88
+ def remove_unused_children(node)
89
+ # Strip out any unused rules
90
+ node.children.reject! do |child|
91
+ if child.invisible?
92
+ # Already wont be shown
93
+ false
94
+ elsif not child.respond_to? :resolved_rules
95
+ # Only mess with RuleNodes
96
+ false
97
+ else
98
+ child.resolved_rules.members.reject! do |sequence|
99
+ @checker.does_not_use(sequence)
100
+ end
101
+
102
+ # Remove if there's nothing left
103
+ child.resolved_rules.members.empty?
104
+ end
105
+ end
106
+
107
+ yield
108
+ end
109
+
110
+ def visit_root(node, &block)
111
+ remove_unused_children(node, &block)
112
+ end
113
+ def visit_media(node, &block)
114
+ remove_unused_children(node, &block)
115
+ end
116
+
117
+ end
118
+
119
+ def self.initialised?
120
+ @@initialised ||= false
121
+ end
122
+ def self.init
123
+ if not defined? ::Compass or initialised?
124
+ return
125
+ end
126
+ @@initialised = true
127
+
128
+ Compass::Configuration.add_configuration_property(
129
+ :templates_location, "Directory to scan for template files") do
130
+
131
+ project_path
132
+ end
133
+ Compass::Configuration.add_configuration_property(
134
+ :used_css_classes, "CSS classes to explicity declare as 'used'") do
135
+
136
+ []
137
+ end
138
+
139
+ Sass::Engine.class_eval do
140
+ alias_method :_slss_to_tree, :_to_tree
141
+ def _to_tree
142
+ conf = Compass.configuration
143
+ extractor = SeeLessEssEss::Extractor.new(conf.templates_location)
144
+ checker = SeeLessEssEss::Checker.new(extractor, conf.used_css_classes)
145
+
146
+ tree = _slss_to_tree
147
+ if @options[:filename] == @options[:original_filename]
148
+ meta = class << tree; self; end
149
+ meta.send(:define_method, :render) do
150
+ # Taken verbatim from real #render
151
+ Sass::Tree::Visitors::CheckNesting.visit(self)
152
+ result = Sass::Tree::Visitors::Perform.visit(self)
153
+ # Check again to validate mixins
154
+ Sass::Tree::Visitors::CheckNesting.visit(result)
155
+ result, extends = Sass::Tree::Visitors::Cssize.visit(result)
156
+ Sass::Tree::Visitors::Extend.visit(result, extends)
157
+ # The next line is our modification
158
+ SeeLessEssEss::RemoveUnusedRules.new(checker).visit(result)
159
+ result.to_s
160
+ end
161
+ end
162
+ tree
163
+ end
164
+ end
165
+
166
+ end
167
+ end
168
+
169
+ SeeLessEssEss.init
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "see-less-ess-ess"
5
+ gem.version = '0.0.1'
6
+ gem.authors = ["Glen Mailer"]
7
+ gem.email = ["glenjamin@gmail.com"]
8
+ gem.description = %q{Remove unused CSS rules from a compass project by scanning template files}
9
+ gem.summary = %q{Remove unused CSS rules from a compass project}
10
+ gem.homepage = "https://github.com/glenjamin/see-less-ess-ess"
11
+
12
+ gem.files = `git ls-files`.split($/)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.require_paths = ["lib"]
16
+
17
+ gem.add_dependency('sass')
18
+ gem.add_dependency('nokogiri')
19
+
20
+ gem.add_development_dependency('rspec')
21
+ end
@@ -0,0 +1,78 @@
1
+ require File.expand_path('../../lib/see-less-ess-ess.rb', __FILE__)
2
+
3
+ module SeeLessEssEss
4
+ describe "Checker" do
5
+ let(:extractor) do
6
+ mock("Extractor").tap do |m|
7
+ m.stub!(:css_classes).and_return(parsed_css_classes)
8
+ end
9
+ end
10
+ let(:parsed_css_classes) { %w(row columns one two top-bar) }
11
+ let(:used_css_classes) { %w(zebra) }
12
+
13
+ let(:subject) { Checker.new(extractor, used_css_classes) }
14
+
15
+ describe "does_not_use" do
16
+
17
+ it "should keep .row" do
18
+ subject.does_not_use(
19
+ sequence(simple_sequence(className('row')))
20
+ ).should be_false
21
+ end
22
+ it "should keep body.row" do
23
+ subject.does_not_use(
24
+ sequence(simple_sequence(tag('body'), className('row')))
25
+ ).should be_false
26
+ end
27
+ it "should keep .zebra td" do
28
+ subject.does_not_use(
29
+ sequence(
30
+ simple_sequence(className('zebra')),
31
+ simple_sequence(tag('td'))
32
+ )
33
+ ).should be_false
34
+ end
35
+ it "should keep body" do
36
+ subject.does_not_use(
37
+ sequence(simple_sequence(tag('body')))
38
+ ).should be_false
39
+ end
40
+ it "should keep #id" do
41
+ subject.does_not_use(
42
+ sequence(simple_sequence(id('id')))
43
+ ).should be_false
44
+ end
45
+
46
+ it "should reject .class" do
47
+ subject.does_not_use(
48
+ sequence(simple_sequence(className('class')))
49
+ ).should be_true
50
+ end
51
+ it "should reject .row .class" do
52
+ subject.does_not_use(
53
+ sequence(
54
+ simple_sequence(className('row')),
55
+ simple_sequence(className('class'))
56
+ )
57
+ ).should be_true
58
+ end
59
+
60
+ def sequence(*args)
61
+ Sass::Selector::Sequence.new(args)
62
+ end
63
+ def simple_sequence(*args)
64
+ Sass::Selector::SimpleSequence.new(args, false)
65
+ end
66
+ def className(name)
67
+ Sass::Selector::Class.new([name])
68
+ end
69
+ def tag(name)
70
+ Sass::Selector::Element.new([name], nil)
71
+ end
72
+ def id(id)
73
+ Sass::Selector::Id.new([id])
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../../lib/see-less-ess-ess.rb', __FILE__)
2
+
3
+ module SeeLessEssEss
4
+ describe "Extractor" do
5
+
6
+ let(:subject) { Extractor.new(location) }
7
+ let(:location) { File.expand_path('../fixtures', __FILE__) }
8
+
9
+ it "should read all css classes from html files in location" do
10
+ subject.css_classes.should include(*%w(body1 body2 title main left lead))
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html>
4
+ <head>
5
+ <title>Page Title</title>
6
+ </head>
7
+
8
+ <body class="body1">
9
+ <h1 class="title main left">
10
+ Title!
11
+ </h1>
12
+ <p class="lead" data-something="not used">And some body text</p>
13
+ </body>
14
+ </html>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html>
4
+ <head>
5
+ <title>Page Title</title>
6
+ </head>
7
+
8
+ <body class="body2">
9
+ <h1 class="title main left">
10
+ Title!
11
+ </h1>
12
+ <p class="lead" data-something="not used">And some body text</p>
13
+ </body>
14
+ </html>
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: see-less-ess-ess
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Glen Mailer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sass
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '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: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
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: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Remove unused CSS rules from a compass project by scanning template files
63
+ email:
64
+ - glenjamin@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .rspec
71
+ - Gemfile
72
+ - LICENSE.txt
73
+ - README.md
74
+ - Rakefile
75
+ - lib/see-less-ess-ess.rb
76
+ - see-less-ess-ess.gemspec
77
+ - spec/checker_spec.rb
78
+ - spec/extractor_spec.rb
79
+ - spec/fixtures/test.html
80
+ - spec/fixtures/test2.html
81
+ - spec/spec_helper.rb
82
+ homepage: https://github.com/glenjamin/see-less-ess-ess
83
+ licenses: []
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 1.8.23
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Remove unused CSS rules from a compass project
106
+ test_files:
107
+ - spec/checker_spec.rb
108
+ - spec/extractor_spec.rb
109
+ - spec/fixtures/test.html
110
+ - spec/fixtures/test2.html
111
+ - spec/spec_helper.rb