deadweight 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +123 -0
- data/lib/deadweight.rb +37 -11
- data/lib/deadweight/hijack.rb +13 -0
- data/lib/deadweight/hijack/rails.rb +35 -0
- data/lib/deadweight/rack/capturing_middleware.rb +26 -0
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/lib/css_parser.rb +25 -12
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/lib/css_parser/parser.rb +120 -54
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/lib/css_parser/regexps.rb +1 -1
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/lib/css_parser/rule_set.rb +24 -13
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/fixtures/import-circular-reference.css +0 -0
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/fixtures/import-with-media-types.css +0 -0
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/fixtures/import1.css +0 -0
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/fixtures/simple.css +0 -0
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/fixtures/subdir/import2.css +0 -0
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/test_css_parser_basic.rb +4 -1
- data/vendor/gems/css_parser-1.1.5/test/test_css_parser_loading.rb +139 -0
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/test_css_parser_media_types.rb +39 -4
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/test_css_parser_misc.rb +1 -2
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/test_css_parser_regexps.rb +1 -1
- data/vendor/gems/css_parser-1.1.5/test/test_helper.rb +6 -0
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/test_merging.rb +16 -1
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/test_rule_set.rb +16 -1
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/test_rule_set_creating_shorthand.rb +1 -1
- data/vendor/gems/{css_parser-1.0.0 → css_parser-1.1.5}/test/test_rule_set_expanding_shorthand.rb +1 -1
- metadata +79 -54
- data/.document +0 -5
- data/.gitignore +0 -6
- data/README.rdoc +0 -85
- data/Rakefile +0 -58
- data/VERSION +0 -1
- data/deadweight.gemspec +0 -84
- data/test/cli_test.rb +0 -21
- data/test/deadweight_test.rb +0 -72
- data/test/fixtures/index.html +0 -16
- data/test/fixtures/index2.html +0 -21
- data/test/fixtures/style.css +0 -24
- data/test/rake_task_test.rb +0 -26
- data/test/test_helper.rb +0 -47
- data/vendor/gems/css_parser-1.0.0/test/test_css_parser_downloading.rb +0 -81
- data/vendor/gems/css_parser-1.0.0/test/test_helper.rb +0 -8
data/README.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
Deadweight
|
2
|
+
==========
|
3
|
+
|
4
|
+
Deadweight is a CSS coverage tool. Given a set of stylesheets and a set of URLs, it determines which selectors are actually used and reports which can be "safely" deleted.
|
5
|
+
|
6
|
+
Screencast!
|
7
|
+
-----------
|
8
|
+
|
9
|
+
Ryan Bates has worked his magic once again. [Head over here for an excellent introduction to Deadweight](http://railscasts.com/episodes/180-finding-unused-css).
|
10
|
+
|
11
|
+
How to Use It
|
12
|
+
-------------
|
13
|
+
|
14
|
+
There are multiple ways to use Deadweight. It's designed to be completely agnostic of whatever framework (or, indeed language) your website is built on, but optimised for Ruby and Rails.
|
15
|
+
|
16
|
+
I'm going to tell you about the coolest way to use it first.
|
17
|
+
|
18
|
+
### The Coolest Way to Use It ###
|
19
|
+
|
20
|
+
Deadweight can hijack your Rails integration tests (both the spartan Test::Unit type and the refreshing Cucumber variety), capturing every page that is returned by your app during testing and saving you the trouble of manually specifying a ton of URLs and login processses. THIS IS PRETTY COOL YOU SHOULD TRY IT. (It's also somewhat new and untested, but you're the kind of person who just _loves_ it out here on the edge, I can tell.)
|
21
|
+
|
22
|
+
Here's how it works. First, put this in your `Gemfile`:
|
23
|
+
|
24
|
+
group :test do
|
25
|
+
gem 'colored' # optional, in the same way that dressing respectably when you leave the house is 'optional'
|
26
|
+
gem 'deadweight', :require => 'deadweight/hijack/rails'
|
27
|
+
end
|
28
|
+
|
29
|
+
Then, run your integration tests with the environment variable `DEADWEIGHT` set to `true`:
|
30
|
+
|
31
|
+
rake test DEADWEIGHT=true
|
32
|
+
rake cucumber DEADWEIGHT=true
|
33
|
+
|
34
|
+
Let me know how it goes. It's not terribly customisable at the moment (you can't specify what exact stylesheets to look at, or what selectors to ignore). I'm looking for your feedback on how you'd like to be able to do that.
|
35
|
+
|
36
|
+
### Or Make a Rake Task ###
|
37
|
+
|
38
|
+
# lib/tasks/deadweight.rake
|
39
|
+
|
40
|
+
require 'deadweight'
|
41
|
+
|
42
|
+
Deadweight::RakeTask.new do |dw|
|
43
|
+
dw.stylesheets = %w( /stylesheets/style.css )
|
44
|
+
dw.pages = %w( / /page/1 /about )
|
45
|
+
end
|
46
|
+
|
47
|
+
Running `rake deadweight` will output all unused rules, one per line. Note that it looks at `http://localhost:3000` by default, so you'll need to have `script/server` (or whatever your server command looks like) running.
|
48
|
+
|
49
|
+
### Or Run it From the Command Line ###
|
50
|
+
|
51
|
+
$ deadweight -s styles.css -s ie.css index.html about.html
|
52
|
+
$ deadweight -s http://www.tigerbloodwins.com/index.css http://www.tigerbloodwins.com/
|
53
|
+
$ deadweight --root http://kottke.org/ -s '/templates/2009/css.php?p=mac' / /everfresh /about
|
54
|
+
|
55
|
+
You can pipe in CSS rules from STDIN:
|
56
|
+
|
57
|
+
$ cat styles.css | deadweight index.html
|
58
|
+
|
59
|
+
And you can use it as an HTTP proxy:
|
60
|
+
|
61
|
+
$ deadweight -l deadweight.log -s styles.css -w http://github.com/ -P
|
62
|
+
|
63
|
+
### Or Call it Directly ###
|
64
|
+
|
65
|
+
require 'deadweight'
|
66
|
+
|
67
|
+
dw = Deadweight.new
|
68
|
+
dw.stylesheets = %w( /stylesheets/style.css )
|
69
|
+
dw.pages = %w( / /page/1 /about )
|
70
|
+
puts dw.run
|
71
|
+
|
72
|
+
Setting the Root URL
|
73
|
+
--------------------
|
74
|
+
|
75
|
+
By default, Deadweight uses `http://localhost:3000` as the base URL for all paths. To change it, set `root`:
|
76
|
+
|
77
|
+
dw.root = "http://staging.example.com" # staging server
|
78
|
+
dw.root = "http://example.com/staging-area" # urls can have paths in
|
79
|
+
dw.root = "/path/to/some/html" # local paths work too
|
80
|
+
|
81
|
+
What About Stuff Added by Javascript?
|
82
|
+
-------------------------------------
|
83
|
+
|
84
|
+
Deadweight is completely dumb about any classes, IDs or tags that are only added by your Javascript layer, but you can filter them out by setting `ignore_selectors`:
|
85
|
+
|
86
|
+
dw.ignore_selectors = /hover|lightbox|superimposed_kittens/
|
87
|
+
|
88
|
+
The command-line tool also has basic support for [Lyndon](http://github.com/defunkt/lyndon) with the `-L` flag, which simply pipes all HTML through the `lyndon` executable.
|
89
|
+
|
90
|
+
You Can Use Mechanize for Complex Stuff
|
91
|
+
---------------------------------------
|
92
|
+
|
93
|
+
Set `mechanize` to `true` and add a Proc to `pages` (rather than a String), and Deadweight will execute it using [Mechanize](http://mechanize.rubyforge.org/mechanize):
|
94
|
+
|
95
|
+
dw.mechanize = true
|
96
|
+
|
97
|
+
# go through the login form to get to a protected URL
|
98
|
+
dw.pages << proc {
|
99
|
+
fetch('/login')
|
100
|
+
form = agent.page.forms.first
|
101
|
+
form.username = 'username'
|
102
|
+
form.password = 'password'
|
103
|
+
agent.submit(form)
|
104
|
+
fetch('/secret-page')
|
105
|
+
}
|
106
|
+
|
107
|
+
# use HTTP basic auth
|
108
|
+
dw.pages << proc {
|
109
|
+
agent.auth('username', 'password')
|
110
|
+
fetch('/other-secret-page')
|
111
|
+
}
|
112
|
+
|
113
|
+
The `agent` method returns the Mechanize instance. The `fetch` method is a wrapper around `agent.get` that will abort in the event of an HTTP error status.
|
114
|
+
|
115
|
+
If You Install `colored`, It'll Look Nicer
|
116
|
+
-------------------------------------------------
|
117
|
+
|
118
|
+
gem install colored
|
119
|
+
|
120
|
+
Copyright
|
121
|
+
---------
|
122
|
+
|
123
|
+
Copyright (c) 2009 Aanand Prasad. See LICENSE for details.
|
data/lib/deadweight.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup
|
4
|
+
|
1
5
|
$LOAD_PATH.concat Dir.glob(File.expand_path('../../vendor/gems/*/lib', __FILE__))
|
2
6
|
|
3
7
|
require 'css_parser'
|
4
|
-
require '
|
8
|
+
require 'nokogiri'
|
5
9
|
require 'open-uri'
|
6
10
|
|
7
11
|
begin
|
@@ -30,12 +34,16 @@ class Deadweight
|
|
30
34
|
end
|
31
35
|
|
32
36
|
def analyze(html)
|
33
|
-
doc =
|
37
|
+
doc = Nokogiri::HTML(html)
|
34
38
|
|
35
39
|
@unused_selectors.collect do |selector, declarations|
|
36
40
|
# We test against the selector stripped of any pseudo classes,
|
37
41
|
# but we report on the selector with its pseudo classes.
|
38
|
-
|
42
|
+
stripped_selector = strip(selector)
|
43
|
+
|
44
|
+
next if stripped_selector.empty?
|
45
|
+
|
46
|
+
if doc.search(stripped_selector).any?
|
39
47
|
log.puts(" #{selector.green}")
|
40
48
|
selector
|
41
49
|
end
|
@@ -62,8 +70,7 @@ class Deadweight
|
|
62
70
|
selector_count
|
63
71
|
end
|
64
72
|
|
65
|
-
|
66
|
-
def run
|
73
|
+
def reset!
|
67
74
|
@parsed_rules = {}
|
68
75
|
@unused_selectors = []
|
69
76
|
|
@@ -78,7 +85,18 @@ class Deadweight
|
|
78
85
|
log.puts("Added #{new_selector_count} extra selectors".yellow)
|
79
86
|
end
|
80
87
|
|
81
|
-
total_selectors = @unused_selectors.size
|
88
|
+
@total_selectors = @unused_selectors.size
|
89
|
+
end
|
90
|
+
|
91
|
+
def report
|
92
|
+
log.puts
|
93
|
+
log.puts "found #{@unused_selectors.size} unused selectors out of #{@total_selectors} total".yellow
|
94
|
+
log.puts
|
95
|
+
end
|
96
|
+
|
97
|
+
# Find all unused CSS selectors and return them as an array.
|
98
|
+
def run
|
99
|
+
reset!
|
82
100
|
|
83
101
|
pages.each do |page|
|
84
102
|
log.puts
|
@@ -106,9 +124,7 @@ class Deadweight
|
|
106
124
|
process!(html)
|
107
125
|
end
|
108
126
|
|
109
|
-
|
110
|
-
log.puts "found #{@unused_selectors.size} unused selectors out of #{total_selectors} total".yellow
|
111
|
-
log.puts
|
127
|
+
report
|
112
128
|
|
113
129
|
@unused_selectors
|
114
130
|
end
|
@@ -139,7 +155,7 @@ class Deadweight
|
|
139
155
|
|
140
156
|
begin
|
141
157
|
page = agent.get(loc)
|
142
|
-
rescue
|
158
|
+
rescue Mechanize::ResponseCodeError => e
|
143
159
|
raise FetchError.new("#{loc} returned a response code of #{e.response_code}")
|
144
160
|
end
|
145
161
|
|
@@ -178,7 +194,17 @@ private
|
|
178
194
|
def initialize_agent
|
179
195
|
begin
|
180
196
|
require 'mechanize'
|
181
|
-
|
197
|
+
|
198
|
+
unless defined?(Mechanize::VERSION) and Mechanize::VERSION >= "1.0.0"
|
199
|
+
log.puts %{
|
200
|
+
=================================================================
|
201
|
+
A mechanize version of 1.0.0 or above is required.
|
202
|
+
Install it like so: gem install mechanize
|
203
|
+
=================================================================
|
204
|
+
}
|
205
|
+
end
|
206
|
+
|
207
|
+
return Mechanize.new
|
182
208
|
rescue LoadError
|
183
209
|
log.puts %{
|
184
210
|
=================================================================
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Deadweight
|
2
|
+
module Hijack
|
3
|
+
def self.redirect_output(log_file_prefix)
|
4
|
+
original_stdout, original_stderr = STDOUT.clone, STDERR.clone
|
5
|
+
|
6
|
+
STDOUT.reopen(File.open("#{log_file_prefix}stdout.log", 'w'))
|
7
|
+
STDERR.reopen(File.open("#{log_file_prefix}stderr.log", 'w'))
|
8
|
+
|
9
|
+
[original_stdout, original_stderr]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
if ENV['DEADWEIGHT'] == 'true'
|
2
|
+
require 'deadweight'
|
3
|
+
require 'deadweight/hijack'
|
4
|
+
require 'deadweight/rack/capturing_middleware'
|
5
|
+
|
6
|
+
class Deadweight
|
7
|
+
module Hijack
|
8
|
+
module Rails
|
9
|
+
class Railtie < ::Rails::Railtie
|
10
|
+
initializer "deadweight.hijack" do |app|
|
11
|
+
root = ::Rails.root
|
12
|
+
|
13
|
+
original_stdout, original_stderr = Deadweight::Hijack.redirect_output(root + 'log/test_')
|
14
|
+
|
15
|
+
dw = Deadweight.new
|
16
|
+
|
17
|
+
dw.root = root + 'public'
|
18
|
+
dw.stylesheets = Dir.chdir(dw.root) { Dir.glob("stylesheets/*.css") }
|
19
|
+
dw.log_file = original_stderr
|
20
|
+
|
21
|
+
dw.reset!
|
22
|
+
|
23
|
+
at_exit do
|
24
|
+
dw.report
|
25
|
+
dw.dump(original_stdout)
|
26
|
+
end
|
27
|
+
|
28
|
+
app.middleware.insert(0, Deadweight::Rack::CapturingMiddleware, dw)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Deadweight
|
2
|
+
module Rack
|
3
|
+
class CapturingMiddleware
|
4
|
+
def initialize(app, dw)
|
5
|
+
@app = app
|
6
|
+
@dw = dw
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
response = @app.call(env)
|
11
|
+
process(response)
|
12
|
+
response
|
13
|
+
end
|
14
|
+
|
15
|
+
def process(rack_response)
|
16
|
+
status, headers, response = rack_response
|
17
|
+
|
18
|
+
if response.respond_to?(:body)
|
19
|
+
html = response.body
|
20
|
+
@dw.process!(html)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -1,13 +1,14 @@
|
|
1
|
-
$:.unshift File.dirname(__FILE__)
|
2
1
|
require 'uri'
|
2
|
+
require 'net/https'
|
3
|
+
require 'open-uri'
|
3
4
|
require 'digest/md5'
|
4
5
|
require 'zlib'
|
6
|
+
require 'stringio'
|
5
7
|
require 'iconv'
|
6
|
-
require 'css_parser/rule_set'
|
7
|
-
require 'css_parser/regexps'
|
8
|
-
require 'css_parser/parser'
|
9
8
|
|
10
9
|
module CssParser
|
10
|
+
VERSION = '1.1.5'
|
11
|
+
|
11
12
|
# Merge multiple CSS RuleSets by cascading according to the CSS 2.1 cascading rules
|
12
13
|
# (http://www.w3.org/TR/REC-CSS2/cascade.html#cascading-order).
|
13
14
|
#
|
@@ -74,20 +75,28 @@ module CssParser
|
|
74
75
|
|
75
76
|
rule_set.each_declaration do |property, value, is_important|
|
76
77
|
# Add the property to the list to be folded per http://www.w3.org/TR/CSS21/cascade.html#cascading-order
|
77
|
-
if not properties.has_key?(property)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
78
|
+
if not properties.has_key?(property)
|
79
|
+
properties[property] = {:value => value, :specificity => specificity, :is_important => is_important}
|
80
|
+
elsif properties[property][:specificity] < specificity or properties[property][:specificity] == specificity
|
81
|
+
unless properties[property][:is_important]
|
82
|
+
properties[property] = {:value => value, :specificity => specificity, :is_important => is_important}
|
83
|
+
end
|
82
84
|
end
|
85
|
+
|
86
|
+
if is_important
|
87
|
+
properties[property] = {:value => value, :specificity => specificity, :is_important => is_important}
|
88
|
+
end
|
83
89
|
end
|
84
90
|
end
|
85
91
|
|
86
92
|
merged = RuleSet.new(nil, nil)
|
87
93
|
|
88
|
-
# TODO: what about important
|
89
94
|
properties.each do |property, details|
|
90
|
-
|
95
|
+
if details[:is_important]
|
96
|
+
merged[property.strip] = details[:value].strip.gsub(/\;\Z/, '') + '!important'
|
97
|
+
else
|
98
|
+
merged[property.strip] = details[:value].strip
|
99
|
+
end
|
91
100
|
end
|
92
101
|
|
93
102
|
merged.create_shorthand!
|
@@ -146,4 +155,8 @@ module CssParser
|
|
146
155
|
end
|
147
156
|
out
|
148
157
|
end
|
149
|
-
end
|
158
|
+
end
|
159
|
+
|
160
|
+
require File.dirname(__FILE__) + '/css_parser/rule_set'
|
161
|
+
require File.dirname(__FILE__) + '/css_parser/regexps'
|
162
|
+
require File.dirname(__FILE__) + '/css_parser/parser'
|
@@ -15,19 +15,15 @@ module CssParser
|
|
15
15
|
# [<tt>import</tt>] Follow <tt>@import</tt> rules. Boolean, default is <tt>true</tt>.
|
16
16
|
# [<tt>io_exceptions</tt>] Throw an exception if a link can not be found. Boolean, default is <tt>true</tt>.
|
17
17
|
class Parser
|
18
|
-
USER_AGENT = "Ruby CSS Parser/#{
|
18
|
+
USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (http://github.com/alexdunae/css_parser)"
|
19
19
|
|
20
20
|
STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
|
21
21
|
STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
|
22
22
|
|
23
23
|
# Initial parsing
|
24
|
-
RE_AT_IMPORT_RULE = /\@import
|
24
|
+
RE_AT_IMPORT_RULE = /\@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s\)]*)["']?\)?([\w\s\,^\])]*)\)?[;\n]?/
|
25
25
|
|
26
|
-
|
27
|
-
# RE_AT_IMPORT_RULE = Regexp.new('@import[\s]*(' + RE_STRING.to_s + ')([\w\s\,]*)[;]?', Regexp::IGNORECASE) -- should handle url() even though it is not allowed
|
28
|
-
#++
|
29
|
-
|
30
|
-
# Array of CSS files that have been loaded.
|
26
|
+
# Array of CSS files that have been loaded.
|
31
27
|
attr_reader :loaded_uris
|
32
28
|
|
33
29
|
#attr_reader :rules
|
@@ -82,6 +78,13 @@ module CssParser
|
|
82
78
|
|
83
79
|
# Add a raw block of CSS.
|
84
80
|
#
|
81
|
+
# In order to follow +@import+ rules you must supply either a
|
82
|
+
# +:base_dir+ or +:base_uri+ option.
|
83
|
+
#
|
84
|
+
# Use the +:media_types+ option to set the media type(s) for this block. Takes an array of symbols.
|
85
|
+
#
|
86
|
+
# Use the +:only_media_types+ option to selectively follow +@import+ rules. Takes an array of symbols.
|
87
|
+
#
|
85
88
|
# ==== Example
|
86
89
|
# css = <<-EOT
|
87
90
|
# body { font-size: 10pt }
|
@@ -92,21 +95,43 @@ module CssParser
|
|
92
95
|
# EOT
|
93
96
|
#
|
94
97
|
# parser = CssParser::Parser.new
|
95
|
-
# parser.
|
96
|
-
#--
|
97
|
-
# TODO: add media_type
|
98
|
-
#++
|
98
|
+
# parser.add_block!(css)
|
99
99
|
def add_block!(block, options = {})
|
100
|
-
options = {:base_uri => nil, :charset => nil, :media_types => :all}.merge(options)
|
101
|
-
|
100
|
+
options = {:base_uri => nil, :base_dir => nil, :charset => nil, :media_types => :all, :only_media_types => :all}.merge(options)
|
101
|
+
options[:media_types] = [options[:media_types]].flatten
|
102
|
+
options[:only_media_types] = [options[:only_media_types]].flatten
|
103
|
+
|
102
104
|
block = cleanup_block(block)
|
103
105
|
|
104
106
|
if options[:base_uri] and @options[:absolute_paths]
|
105
107
|
block = CssParser.convert_uris(block, options[:base_uri])
|
106
108
|
end
|
109
|
+
|
110
|
+
# Load @imported CSS
|
111
|
+
block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
|
112
|
+
media_types = []
|
113
|
+
if media_string = import_rule[-1]
|
114
|
+
media_string.split(/\s|\,/).each do |t|
|
115
|
+
media_types << t.to_sym unless t.empty?
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
next unless options[:only_media_types].include?(:all) or media_types.length < 1 or (media_types & options[:only_media_types]).length > 0
|
120
|
+
|
121
|
+
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
|
122
|
+
|
123
|
+
if options[:base_uri]
|
124
|
+
import_uri = URI.parse(options[:base_uri].to_s).merge(import_path)
|
125
|
+
load_uri!(import_uri, options[:base_uri], media_types)
|
126
|
+
elsif options[:base_dir]
|
127
|
+
load_file!(import_path, options[:base_dir], media_types)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Remove @import declarations
|
132
|
+
block.gsub!(RE_AT_IMPORT_RULE, '')
|
107
133
|
|
108
134
|
parse_block_into_rule_sets!(block, options)
|
109
|
-
|
110
135
|
end
|
111
136
|
|
112
137
|
# Add a CSS rule by setting the +selectors+, +declarations+ and +media_types+.
|
@@ -175,7 +200,7 @@ module CssParser
|
|
175
200
|
options = {:media_types => :all}.merge(options)
|
176
201
|
media_types = options[:media_types]
|
177
202
|
|
178
|
-
in_declarations =
|
203
|
+
in_declarations = 0
|
179
204
|
|
180
205
|
block_depth = 0
|
181
206
|
|
@@ -195,13 +220,25 @@ module CssParser
|
|
195
220
|
in_string = !in_string
|
196
221
|
end
|
197
222
|
|
198
|
-
if in_declarations
|
223
|
+
if in_declarations > 0
|
224
|
+
|
225
|
+
# too deep, malformed declaration block
|
226
|
+
if in_declarations > 1
|
227
|
+
in_declarations -= 1 if token =~ /\}/
|
228
|
+
next
|
229
|
+
end
|
230
|
+
|
231
|
+
if token =~ /\{/
|
232
|
+
in_declarations += 1
|
233
|
+
next
|
234
|
+
end
|
235
|
+
|
199
236
|
current_declarations += token
|
200
237
|
|
201
238
|
if token =~ /\}/ and not in_string
|
202
239
|
current_declarations.gsub!(/\}[\s]*$/, '')
|
203
240
|
|
204
|
-
in_declarations
|
241
|
+
in_declarations -= 1
|
205
242
|
|
206
243
|
unless current_declarations.strip.empty?
|
207
244
|
#puts "SAVING #{current_selectors} -> #{current_declarations}"
|
@@ -233,7 +270,7 @@ module CssParser
|
|
233
270
|
if token =~ /\{/ and not in_string
|
234
271
|
current_selectors.gsub!(/^[\s]*/, '')
|
235
272
|
current_selectors.gsub!(/[\s]*$/, '')
|
236
|
-
in_declarations
|
273
|
+
in_declarations += 1
|
237
274
|
else
|
238
275
|
current_selectors += token
|
239
276
|
end
|
@@ -243,35 +280,35 @@ module CssParser
|
|
243
280
|
end
|
244
281
|
|
245
282
|
# Load a remote CSS file.
|
283
|
+
#
|
284
|
+
# You can also pass in file://test.css
|
246
285
|
def load_uri!(uri, base_uri = nil, media_types = :all)
|
286
|
+
uri = URI.parse(uri) unless uri.respond_to? :scheme
|
287
|
+
if uri.scheme == 'file' or uri.scheme.nil?
|
288
|
+
uri.path = File.expand_path(uri.path)
|
289
|
+
uri.scheme = 'file'
|
290
|
+
end
|
247
291
|
base_uri = uri if base_uri.nil?
|
248
|
-
src, charset = read_remote_file(uri)
|
249
|
-
|
250
|
-
# Load @imported CSS
|
251
|
-
src.scan(RE_AT_IMPORT_RULE).each do |import_rule|
|
252
|
-
import_path = import_rule[1].to_s.gsub(/['"]*/, '').strip
|
253
|
-
import_uri = URI.parse(base_uri.to_s).merge(import_path)
|
254
|
-
#puts import_uri.to_s
|
255
292
|
|
256
|
-
|
257
|
-
if media_string = import_rule[import_rule.length-1]
|
258
|
-
media_string.split(/\s|\,/).each do |t|
|
259
|
-
media_types << t.to_sym unless t.empty?
|
260
|
-
end
|
261
|
-
end
|
293
|
+
src, charset = read_remote_file(uri)
|
262
294
|
|
263
|
-
|
264
|
-
|
295
|
+
if src
|
296
|
+
add_block!(src, {:media_types => media_types, :base_uri => base_uri})
|
265
297
|
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Load a local CSS file.
|
301
|
+
def load_file!(file_name, base_dir = nil, media_types = :all)
|
302
|
+
file_name = File.expand_path(file_name, base_dir)
|
303
|
+
return unless File.readable?(file_name)
|
266
304
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
# Relative paths need to be converted here
|
271
|
-
src = CssParser.convert_uris(src, base_uri) if base_uri and @options[:absolute_paths]
|
305
|
+
src = IO.read(file_name)
|
306
|
+
base_dir = File.dirname(file_name)
|
272
307
|
|
273
|
-
add_block!(src, {:media_types => media_types})
|
308
|
+
add_block!(src, {:media_types => media_types, :base_dir => base_dir})
|
274
309
|
end
|
310
|
+
|
311
|
+
|
275
312
|
|
276
313
|
protected
|
277
314
|
# Strip comments and clean up blank lines from a block of CSS.
|
@@ -298,30 +335,59 @@ module CssParser
|
|
298
335
|
# TODO: add option to fail silently or throw and exception on a 404
|
299
336
|
#++
|
300
337
|
def read_remote_file(uri) # :nodoc:
|
301
|
-
|
338
|
+
if @loaded_uris.include?(uri.to_s)
|
339
|
+
raise CircularReferenceError, "can't load #{uri.to_s} more than once" if @options[:io_exceptions]
|
340
|
+
return '', nil
|
341
|
+
end
|
342
|
+
|
302
343
|
@loaded_uris << uri.to_s
|
303
344
|
|
304
|
-
|
305
|
-
#fh = open(uri, 'rb')
|
306
|
-
fh = open(uri, 'rb', 'User-Agent' => USER_AGENT, 'Accept-Encoding' => 'gzip')
|
345
|
+
src = '', charset = nil
|
307
346
|
|
308
|
-
|
309
|
-
|
347
|
+
begin
|
348
|
+
uri = URI.parse(uri.to_s)
|
349
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
350
|
+
|
351
|
+
if uri.scheme == 'file'
|
352
|
+
# local file
|
353
|
+
fh = open(uri.path, 'rb')
|
354
|
+
src = fh.read
|
355
|
+
fh.close
|
310
356
|
else
|
311
|
-
|
312
|
-
|
357
|
+
# remote file
|
358
|
+
if uri.scheme == 'https'
|
359
|
+
http.use_ssl = true
|
360
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
361
|
+
end
|
313
362
|
|
314
|
-
|
363
|
+
res, src = http.get(uri.path, {'User-Agent' => USER_AGENT, 'Accept-Encoding' => 'gzip'})
|
364
|
+
charset = fh.respond_to?(:charset) ? fh.charset : 'utf-8'
|
315
365
|
|
316
|
-
|
317
|
-
|
366
|
+
if res.code.to_i >= 400
|
367
|
+
raise RemoteFileError if @options[:io_exceptions]
|
368
|
+
return '', nil
|
369
|
+
end
|
370
|
+
|
371
|
+
case res['content-encoding']
|
372
|
+
when 'gzip'
|
373
|
+
io = Zlib::GzipReader.new(StringIO.new(res.body))
|
374
|
+
src = io.read
|
375
|
+
when 'deflate'
|
376
|
+
io = Zlib::Inflate.new
|
377
|
+
src = io.inflate(res.body)
|
378
|
+
end
|
379
|
+
end
|
318
380
|
|
319
|
-
|
320
|
-
|
381
|
+
if charset
|
382
|
+
ic = Iconv.new('UTF-8//IGNORE', charset)
|
383
|
+
src = ic.iconv(src)
|
384
|
+
end
|
321
385
|
rescue
|
322
386
|
raise RemoteFileError if @options[:io_exceptions]
|
323
|
-
return
|
387
|
+
return nil, nil
|
324
388
|
end
|
389
|
+
|
390
|
+
return src, charset
|
325
391
|
end
|
326
392
|
|
327
393
|
private
|
@@ -342,4 +408,4 @@ module CssParser
|
|
342
408
|
@css_warnings = []
|
343
409
|
end
|
344
410
|
end
|
345
|
-
end
|
411
|
+
end
|