inline-style 0.4.2 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +52 -0
- data/Manifest.txt +9 -1
- data/{README.rdoc → README.txt} +15 -2
- data/Rakefile +9 -26
- data/inline-style.gemspec +30 -0
- data/lib/inline-style.rb +20 -20
- data/lib/inline-style/css_parsers.rb +21 -0
- data/lib/inline-style/css_parsers/css_parser.rb +56 -0
- data/lib/inline-style/css_parsers/csspool.rb +34 -0
- data/lib/inline-style/mail/interceptor.rb +37 -0
- data/lib/inline-style/rack/middleware.rb +3 -3
- data/lib/inline-style/version.rb +5 -0
- data/spec/css_parsers_spec.rb +55 -0
- data/spec/factory_spec.rb +38 -0
- data/spec/fixtures/inline.html +2 -2
- data/spec/interceptor_spec.rb +63 -0
- data/spec/{as_middleware_spec.rb → rack_middleware_spec.rb} +5 -2
- data/spec/spec_helper.rb +2 -3
- metadata +111 -48
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
inline-style (0.4.6)
|
5
|
+
css_parser
|
6
|
+
maca-fork-csspool
|
7
|
+
nokogiri
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (3.0.4)
|
13
|
+
css_parser (1.1.5)
|
14
|
+
diff-lcs (1.1.2)
|
15
|
+
ffi (1.0.6)
|
16
|
+
rake (>= 0.8.7)
|
17
|
+
i18n (0.5.0)
|
18
|
+
maca-fork-csspool (2.0.2)
|
19
|
+
ffi
|
20
|
+
mail (2.2.15)
|
21
|
+
activesupport (>= 2.3.6)
|
22
|
+
i18n (>= 0.4.0)
|
23
|
+
mime-types (~> 1.16)
|
24
|
+
treetop (~> 1.4.8)
|
25
|
+
mime-types (1.16)
|
26
|
+
nokogiri (1.4.4)
|
27
|
+
polyglot (0.3.1)
|
28
|
+
rack (1.2.1)
|
29
|
+
rake (0.8.7)
|
30
|
+
rspec (2.4.0)
|
31
|
+
rspec-core (~> 2.4.0)
|
32
|
+
rspec-expectations (~> 2.4.0)
|
33
|
+
rspec-mocks (~> 2.4.0)
|
34
|
+
rspec-core (2.4.0)
|
35
|
+
rspec-expectations (2.4.0)
|
36
|
+
diff-lcs (~> 1.1.2)
|
37
|
+
rspec-mocks (2.4.0)
|
38
|
+
treetop (1.4.9)
|
39
|
+
polyglot (>= 0.3.1)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
css_parser
|
46
|
+
inline-style!
|
47
|
+
maca-fork-csspool
|
48
|
+
mail
|
49
|
+
nokogiri
|
50
|
+
rack
|
51
|
+
rspec
|
52
|
+
rspec-core
|
data/Manifest.txt
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
History.txt
|
2
2
|
Manifest.txt
|
3
|
-
README.
|
3
|
+
README.txt
|
4
4
|
Rakefile
|
5
5
|
example.rb
|
6
|
+
inline-style.gemspec
|
6
7
|
lib/inline-style.rb
|
8
|
+
lib/inline-style/css_parsers.rb
|
9
|
+
lib/inline-style/css_parsers/css_parser.rb
|
10
|
+
lib/inline-style/css_parsers/csspool.rb
|
11
|
+
lib/inline-style/mail/interceptor.rb
|
7
12
|
lib/inline-style/rack/middleware.rb
|
8
13
|
spec/as_middleware_spec.rb
|
9
14
|
spec/css_inlining_spec.rb
|
15
|
+
spec/css_parsers_spec.rb
|
16
|
+
spec/factory_spec.rb
|
10
17
|
spec/fixtures/all.css
|
11
18
|
spec/fixtures/boletin.html
|
12
19
|
spec/fixtures/box-model.html
|
@@ -15,4 +22,5 @@ spec/fixtures/none.css
|
|
15
22
|
spec/fixtures/print.css
|
16
23
|
spec/fixtures/selectors.html
|
17
24
|
spec/fixtures/style.css
|
25
|
+
spec/interceptor_spec.rb
|
18
26
|
spec/spec_helper.rb
|
data/{README.rdoc → README.txt}
RENAMED
@@ -10,6 +10,8 @@ each refered element taking selector specificity and declarator order.
|
|
10
10
|
Useful for html email: some clients (gmail, et all) won't render non inline styles.
|
11
11
|
|
12
12
|
* Includes a Rack middleware for using with Rails, Sinatra, etc...
|
13
|
+
* Includes a interceptor for the mail gem which allows automatic
|
14
|
+
inline processing for both mail as well as ActionMailer.
|
13
15
|
* It takes into account selector specificity.
|
14
16
|
|
15
17
|
== USAGE
|
@@ -92,12 +94,23 @@ Will become:
|
|
92
94
|
|
93
95
|
# Restrict processing to some routes:
|
94
96
|
use InlineStyle::Rack::Middleware, :paths => [%r(/mails/.*), "/somepath"]
|
97
|
+
|
98
|
+
== MAIL INTERCEPTOR
|
99
|
+
|
100
|
+
If using the mail library the following code will work:
|
101
|
+
|
102
|
+
Mail.register_interceptor \
|
103
|
+
InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
|
104
|
+
|
105
|
+
If using ActionMailer (which wraps mail):
|
106
|
+
|
107
|
+
ActionMailer::Base.register_interceptor \
|
108
|
+
InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
|
95
109
|
|
96
110
|
== ISSUES:
|
97
111
|
|
98
112
|
* It supports pseudo classes according to W3C specification for style in style attribute: http://www.w3.org/TR/css-style-attr, although browsers
|
99
113
|
doesn't seems to.
|
100
|
-
* It strips any numeric character (Rails may add) at the end of the stylesheet file name, anyway stylesheets should end with .css extension
|
101
114
|
|
102
115
|
== REQUIREMENTS:
|
103
116
|
|
@@ -130,4 +143,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
130
143
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
131
144
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
132
145
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
133
|
-
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
146
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,26 +1,9 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
['nokogiri','>= 1.3.3'],
|
11
|
-
['maca-fork-csspool', '>= 2.0.2']
|
12
|
-
]
|
13
|
-
p.extra_dev_deps = [
|
14
|
-
['newgem', ">= #{::Newgem::VERSION}"]
|
15
|
-
]
|
16
|
-
|
17
|
-
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
18
|
-
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
19
|
-
p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
|
20
|
-
p.rsync_args = '-av --delete --ignore-errors'
|
21
|
-
end
|
22
|
-
|
23
|
-
require 'newgem/tasks'
|
24
|
-
Dir['tasks/**/*.rake'].each { |t| load t }
|
25
|
-
|
26
|
-
|
1
|
+
require 'bundler'
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
task :default => [:spec]
|
5
|
+
|
6
|
+
desc "Run specs"
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
Bundler::GemHelper.install_tasks
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "inline-style/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "inline-style"
|
7
|
+
s.version = Inline::Style::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Macario Ortega", "Eric Anderson"]
|
10
|
+
s.email = ["macarui@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Inlines CSS for html email delivery}
|
13
|
+
s.description = %q{Inlines CSS for html email delivery}
|
14
|
+
|
15
|
+
# s.rubyforge_project = "inline-style"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency 'rspec'
|
23
|
+
s.add_development_dependency 'rack'
|
24
|
+
s.add_development_dependency 'rspec-core'
|
25
|
+
s.add_development_dependency 'mail'
|
26
|
+
|
27
|
+
s.add_dependency 'nokogiri'
|
28
|
+
s.add_dependency 'css_parser'
|
29
|
+
s.add_dependency 'maca-fork-csspool'
|
30
|
+
end
|
data/lib/inline-style.rb
CHANGED
@@ -1,14 +1,11 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
require 'open-uri'
|
3
3
|
|
4
|
-
|
5
|
-
require 'csspool'
|
6
|
-
|
4
|
+
require "#{ File.dirname( __FILE__ ) }/inline-style/css_parsers"
|
7
5
|
require "#{ File.dirname( __FILE__ ) }/inline-style/rack/middleware"
|
6
|
+
require "#{ File.dirname( __FILE__ ) }/inline-style/mail/interceptor"
|
8
7
|
|
9
8
|
module InlineStyle
|
10
|
-
VERSION = '0.4.2'
|
11
|
-
|
12
9
|
# Options:
|
13
10
|
# +:stylesheets_path+
|
14
11
|
# Stylesheets root path, can also be a URL
|
@@ -27,41 +24,44 @@ module InlineStyle
|
|
27
24
|
css = extract_css html, stylesheets_path
|
28
25
|
nodes = {}
|
29
26
|
|
30
|
-
css.
|
31
|
-
rule_set.
|
32
|
-
|
27
|
+
css.each_rule_set do |rule_set|
|
28
|
+
rule_set.each_selector do |css_selector, declarations, specificity|
|
29
|
+
orig_selector = css_selector.dup
|
33
30
|
css_selector = "#{ 'body ' unless /^body/ === css_selector }#{ css_selector.gsub /:.*/, '' }"
|
34
31
|
|
35
32
|
html.css(css_selector).each do |node|
|
36
33
|
nodes[node] ||= []
|
37
|
-
nodes[node].push
|
34
|
+
nodes[node].push [css_selector, declarations, specificity, orig_selector]
|
38
35
|
|
39
36
|
next unless node['style']
|
40
37
|
|
41
38
|
path = node.css_path
|
42
39
|
path << "##{ node['id'] }" if node['id']
|
43
40
|
path << ".#{ node['class'].scan(/\S+/).join('.') }" if node['class']
|
44
|
-
|
45
|
-
|
41
|
+
|
42
|
+
InlineStyle::CssParsers.parser.new("#{ path }{#{ node['style'] }}").each_rule_set do |rule|
|
43
|
+
rule.each_selector do |css_selector_inner, declarations_inner, specificity_inner|
|
44
|
+
nodes[node].push [css_selector_inner, declarations_inner, specificity_inner, css_selector_inner]
|
45
|
+
end
|
46
|
+
end
|
46
47
|
end
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
50
51
|
nodes.each_pair do |node, style|
|
51
|
-
style = style.sort_by{ |sel| "#{
|
52
|
-
sets = style.partition{ |sel| not /:\w+/ ===
|
52
|
+
style = style.sort_by{ |(sel, dec, spe, orig)| "#{ spe }%03d" % style.index([sel, dec, spe, orig]) }
|
53
|
+
sets = style.partition{ |(sel, dec, spe, orig)| not /:\w+/ === orig }
|
53
54
|
|
54
55
|
sets.pop if not pseudo or sets.last.empty?
|
55
56
|
|
56
57
|
node['style'] = sets.collect do |selectors|
|
57
58
|
index = sets.index selectors
|
58
59
|
|
59
|
-
set = selectors.map do |
|
60
|
-
declarations
|
61
|
-
index == 0 ? declarations : "\n#{ selector.to_s.gsub /\w(?=:)/, '' } {#{ declarations }}"
|
60
|
+
set = selectors.map do |(css_selector, declarations, specificity, orig_selector)|
|
61
|
+
index == 0 ? declarations : "\n#{ orig_selector.gsub /\w(?=:)/, '' } {#{ declarations }}"
|
62
62
|
end
|
63
63
|
|
64
|
-
index == 0 && sets.size > 1 ? "{#{ set }}" : set.join
|
64
|
+
index == 0 && sets.size > 1 ? "{#{ set }}" : set.collect(&:strip).join(' ')
|
65
65
|
end.join.strip
|
66
66
|
end
|
67
67
|
|
@@ -70,8 +70,8 @@ module InlineStyle
|
|
70
70
|
|
71
71
|
# Returns CSSPool::Document
|
72
72
|
def self.extract_css html, stylesheets_path = ''
|
73
|
-
|
74
|
-
next unless e['media'].nil?
|
73
|
+
InlineStyle::CssParsers.parser.new html.css('style, link').collect { |e|
|
74
|
+
next unless e['media'].nil? || ['screen', 'all'].include?(e['media'])
|
75
75
|
next(e.remove and e.content) if e.name == 'style'
|
76
76
|
next unless e['rel'] == 'stylesheet'
|
77
77
|
e.remove
|
@@ -80,4 +80,4 @@ module InlineStyle
|
|
80
80
|
open(uri).read rescue nil
|
81
81
|
}.join("\n")
|
82
82
|
end
|
83
|
-
end
|
83
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module InlineStyle
|
2
|
+
|
3
|
+
# A factory for returning a configured CSS parser. Defaults to
|
4
|
+
# :css_parser if not specified. Will also use ENV['CSS_PARSER'].
|
5
|
+
module CssParsers
|
6
|
+
|
7
|
+
# Allows you to specify the CSS parser to use.
|
8
|
+
def self.parser=(parser)
|
9
|
+
@parser = nil
|
10
|
+
@parser_name = parser
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.parser
|
14
|
+
return @parser if @parser
|
15
|
+
@parser_name = ENV['CSS_PARSER'] || :css_parser unless @parser_name
|
16
|
+
require "inline-style/css_parsers/#{@parser_name}"
|
17
|
+
@parser = const_get(@parser_name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'css_parser'
|
2
|
+
|
3
|
+
module InlineStyle::CssParsers
|
4
|
+
class CssParser
|
5
|
+
|
6
|
+
def initialize(css_code)
|
7
|
+
@parser = ::CssParser::Parser.new
|
8
|
+
@parser.add_block! css_code
|
9
|
+
end
|
10
|
+
|
11
|
+
def each_rule_set(&blk)
|
12
|
+
@parser.each_rule_set do |rule_set|
|
13
|
+
yield Ruleset.new(rule_set)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Ruleset
|
18
|
+
|
19
|
+
def initialize(ruleset)
|
20
|
+
@ruleset = ruleset
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_selector
|
24
|
+
@ruleset.each_selector do |sel, dec, spe|
|
25
|
+
normalize_for_test! dec
|
26
|
+
yield sel, dec, spe
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# To make testings work for both parsers we need to conform to
|
33
|
+
# csspool normalizing attributes. This means all numbers have a
|
34
|
+
# decimal place. It also means that urls do not have quotes.
|
35
|
+
#
|
36
|
+
# NOTE: This really doesn't have anything to do with the
|
37
|
+
# correctness of the parser. It just makes testing easier since
|
38
|
+
# each parser can run against the same tests.
|
39
|
+
def normalize_for_test!(dec)
|
40
|
+
|
41
|
+
# Give all numbers a decimal if they do not already have it
|
42
|
+
dec.gsub! '0;', '0.0;'
|
43
|
+
dec.gsub! ' 0 ', ' 0.0 '
|
44
|
+
dec.gsub! /([^\.0-9]\d+)px/, '\1.0px'
|
45
|
+
dec.gsub! /([^\.0-9]\d+)%/, '\1.0%'
|
46
|
+
|
47
|
+
# Remove any quotes in url()
|
48
|
+
dec.gsub! "url('", 'url('
|
49
|
+
dec.gsub! "')", ')'
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
gem 'maca-fork-csspool'
|
2
|
+
require 'csspool'
|
3
|
+
|
4
|
+
module InlineStyle::CssParsers
|
5
|
+
class Csspool
|
6
|
+
|
7
|
+
def initialize(css_code)
|
8
|
+
@parser = CSSPool.CSS css_code
|
9
|
+
end
|
10
|
+
|
11
|
+
def each_rule_set
|
12
|
+
@parser.rule_sets.each do |rule_set|
|
13
|
+
yield Ruleset.new(rule_set)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Ruleset
|
18
|
+
|
19
|
+
def initialize(ruleset)
|
20
|
+
@ruleset = ruleset
|
21
|
+
end
|
22
|
+
|
23
|
+
def each_selector(&blk)
|
24
|
+
@ruleset.selectors.each do |selector|
|
25
|
+
yield selector.to_s,
|
26
|
+
selector.declarations.map{ |d| d.to_s.squeeze(' ') }.join.strip,
|
27
|
+
selector.specificity.inject(0) {|t, s| t+s}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# A interceptor for +mail+ (https://github.com/mikel/mail) to
|
2
|
+
# automatically inline the styles of outgoing e-mails. To use:
|
3
|
+
#
|
4
|
+
# Mail.register_interceptor \
|
5
|
+
# InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
|
6
|
+
#
|
7
|
+
# Rails 3's ActionMailer wraps around the +mail+ and also supports
|
8
|
+
# interceptors. Example usage:
|
9
|
+
#
|
10
|
+
# ActionMailer::Base.register_interceptor \
|
11
|
+
# InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
|
12
|
+
module InlineStyle
|
13
|
+
module Mail
|
14
|
+
class Interceptor
|
15
|
+
# The mime types we should inline. Basically HTML and XHTML.
|
16
|
+
# If you have something else you can just push it onto the list
|
17
|
+
INLINE_MIME_TYPES = %w(text/html application/xhtml+xml)
|
18
|
+
|
19
|
+
# Save the options to later pass to InlineStyle.process
|
20
|
+
def initialize(options={})
|
21
|
+
@options = options
|
22
|
+
end
|
23
|
+
|
24
|
+
# Mail callback where we actually inline the styles
|
25
|
+
def delivering_email(part)
|
26
|
+
if part.multipart?
|
27
|
+
for part in part.parts
|
28
|
+
delivering_email part
|
29
|
+
end
|
30
|
+
elsif INLINE_MIME_TYPES.any? {|m| part.content_type.starts_with? m}
|
31
|
+
part.body = InlineStyle.process(part.body.to_s, @options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -3,8 +3,8 @@ module InlineStyle
|
|
3
3
|
class Middleware
|
4
4
|
#
|
5
5
|
# Options:
|
6
|
-
# +
|
7
|
-
#
|
6
|
+
# +stylesheets_path+
|
7
|
+
# Stylesheets root path or app's public directory where the stylesheets are to be found, defaults to
|
8
8
|
# env['DOCUMENT_ROOT']
|
9
9
|
#
|
10
10
|
# +paths+
|
@@ -33,7 +33,7 @@ module InlineStyle
|
|
33
33
|
response = ::Rack::Response.new '', status, headers
|
34
34
|
body = content.respond_to?(:body) ? content.body : content
|
35
35
|
|
36
|
-
response.write InlineStyle.process(body, {:
|
36
|
+
response.write InlineStyle.process(body, {:stylesheets_path => env['DOCUMENT_ROOT']}.merge(@opts))
|
37
37
|
response.finish
|
38
38
|
end
|
39
39
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "#{ File.dirname __FILE__ }/spec_helper"
|
2
|
+
require "#{ DIR }/../lib/inline-style/css_parsers/css_parser"
|
3
|
+
require "#{ DIR }/../lib/inline-style/css_parsers/csspool"
|
4
|
+
|
5
|
+
describe InlineStyle::CssParsers::CssParser do
|
6
|
+
|
7
|
+
it 'should wrap css_parser' do
|
8
|
+
rs_count = 0
|
9
|
+
sel_count = 0
|
10
|
+
selectors = %w(p b i)
|
11
|
+
decs = ['color: black;', 'color: black;', 'color: green; text-decoration: none;']
|
12
|
+
spes = [1, 1, 1]
|
13
|
+
p = InlineStyle::CssParsers::CssParser.new "p, b {color: black}\ni {color: green; text-decoration: none}"
|
14
|
+
|
15
|
+
p.each_rule_set do |rs|
|
16
|
+
rs_count += 1
|
17
|
+
rs.each_selector do |sel, dec, spe|
|
18
|
+
sel_count += 1
|
19
|
+
sel.should == selectors.shift
|
20
|
+
dec.should == decs.shift
|
21
|
+
spe.should == spes.shift
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
rs_count.should == 2
|
26
|
+
sel_count.should == 3
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
describe InlineStyle::CssParsers::Csspool do
|
32
|
+
|
33
|
+
it 'should wrap csspool' do
|
34
|
+
rs_count = 0
|
35
|
+
sel_count = 0
|
36
|
+
selectors = %w(p b i)
|
37
|
+
decs = ['color: black;', 'color: black;', 'color: green; text-decoration: none;']
|
38
|
+
spes = [1, 1, 1]
|
39
|
+
p = InlineStyle::CssParsers::Csspool.new "p, b {color: black}\ni {color: green; text-decoration: none}"
|
40
|
+
|
41
|
+
p.each_rule_set do |rs|
|
42
|
+
rs_count += 1
|
43
|
+
rs.each_selector do |sel, dec, spe|
|
44
|
+
sel_count += 1
|
45
|
+
sel.should == selectors.shift
|
46
|
+
dec.should == decs.shift
|
47
|
+
spe.should == spes.shift
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
rs_count.should == 2
|
52
|
+
sel_count.should == 3
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "#{ File.dirname __FILE__ }/spec_helper"
|
2
|
+
|
3
|
+
describe InlineStyle::CssParsers do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@orig_env = ENV['CSS_PARSER']
|
7
|
+
ENV['CSS_PARSER'] = nil
|
8
|
+
InlineStyle::CssParsers.parser = nil # Clear out any cached value
|
9
|
+
end
|
10
|
+
after do
|
11
|
+
ENV['CSS_PARSER'] = @orig_env
|
12
|
+
InlineStyle::CssParsers.parser = nil # Clear out any cached value
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should load css_parser by default' do
|
16
|
+
InlineStyle::CssParsers.parser.name.
|
17
|
+
should == 'InlineStyle::CssParsers::CssParser'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should obey ENV['CSS_PARSER']" do
|
21
|
+
ENV['CSS_PARSER'] = 'css_parser'
|
22
|
+
InlineStyle::CssParsers.parser.name.
|
23
|
+
should == 'InlineStyle::CssParsers::CssParser'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should be able to load csspool' do
|
27
|
+
InlineStyle::CssParsers.parser = :csspool
|
28
|
+
InlineStyle::CssParsers.parser.name.
|
29
|
+
should == 'InlineStyle::CssParsers::Csspool'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should be able to load css_parser' do
|
33
|
+
InlineStyle::CssParsers.parser = :css_parser
|
34
|
+
InlineStyle::CssParsers.parser.name.
|
35
|
+
should == 'InlineStyle::CssParsers::CssParser'
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/spec/fixtures/inline.html
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
2
2
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
3
3
|
<head>
|
4
|
-
<meta
|
4
|
+
<meta content="text/html; charset=utf-8" http-equiv="Content-type">
|
5
5
|
<title> Boletín CENART - November
|
6
6
|
</title>
|
7
7
|
<link href="/print.css?1248460539" rel="stylesheet" media="print" type="text/css">
|
@@ -96,7 +96,7 @@
|
|
96
96
|
contacto <br style="margin: 0.0; padding: 0.0;"><a href="mailto:mail@host.org" style="margin: 0.0; padding: 0.0; color: #185d6b; font-weight: bold; font-size: 1.2em;">mail@host.org</a>
|
97
97
|
</div>
|
98
98
|
<div id="logos" style="margin: 0.0; padding: 0.0; width: 100.0%; margin-top: 40.0px; background-image: url(/images/boletines/Noviembre/logos_fondo.jpg);">
|
99
|
-
<img src="A" id="izq" alt="A" style="margin: 0.0; padding: 0.0; float: left;"><img src="B" id="der" alt="B" style="margin: 0.0; padding: 0.0; float: right;"><div class="clearer" style="margin: 0.0; padding: 0.0; clear: both;">
|
99
|
+
<img src="A" id="izq" alt="A" style="margin: 0.0; padding: 0.0; float: left; margin: 30.0px; padding: 10.0px; color: red;"><img src="B" id="der" alt="B" style="margin: 0.0; padding: 0.0; float: right;"><div class="clearer" style="margin: 0.0; padding: 0.0; clear: both;">
|
100
100
|
</div>
|
101
101
|
</div>
|
102
102
|
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "#{ File.dirname __FILE__ }/spec_helper"
|
2
|
+
|
3
|
+
require 'mail'
|
4
|
+
Mail.defaults do
|
5
|
+
delivery_method :test
|
6
|
+
end
|
7
|
+
Mail.register_interceptor \
|
8
|
+
InlineStyle::Mail::Interceptor.new(:stylesheets_path => FIXTURES)
|
9
|
+
|
10
|
+
describe InlineStyle::Mail::Interceptor do
|
11
|
+
|
12
|
+
before do
|
13
|
+
Mail::TestMailer.deliveries.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should inline html e-mail' do
|
17
|
+
Mail.deliver do
|
18
|
+
to 'joe@example.com'
|
19
|
+
from 'joe@example.com'
|
20
|
+
subject 'HTML e-mail'
|
21
|
+
content_type 'text/html'
|
22
|
+
body File.read("#{ FIXTURES }/boletin.html")
|
23
|
+
end
|
24
|
+
|
25
|
+
Mail::TestMailer.deliveries.first.body.to_s.
|
26
|
+
should == File.read("#{ FIXTURES }/inline.html")
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not inline non-html' do
|
30
|
+
Mail.deliver do
|
31
|
+
to 'joe@example.com'
|
32
|
+
from 'joe@example.com'
|
33
|
+
subject 'Plain text e-mail (with HTML looking content)'
|
34
|
+
content_type 'text/plain'
|
35
|
+
body File.read("#{ FIXTURES }/boletin.html")
|
36
|
+
end
|
37
|
+
|
38
|
+
Mail::TestMailer.deliveries.first.body.to_s.
|
39
|
+
should == File.read("#{ FIXTURES }/boletin.html")
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should inline html part of multipart/alternative' do
|
43
|
+
Mail.deliver do
|
44
|
+
to 'joe@example.com'
|
45
|
+
from 'joe@example.com'
|
46
|
+
subject 'Multipart/alternative e-mail'
|
47
|
+
text_part do
|
48
|
+
body File.read("#{ FIXTURES }/boletin.html")
|
49
|
+
end
|
50
|
+
html_part do
|
51
|
+
content_type 'text/html; charset=UTF-8'
|
52
|
+
body File.read("#{ FIXTURES }/boletin.html")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Mail::TestMailer.deliveries.first.parts[0].body.to_s.
|
57
|
+
should == File.read("#{ FIXTURES }/boletin.html")
|
58
|
+
|
59
|
+
Mail::TestMailer.deliveries.first.parts[1].body.to_s.
|
60
|
+
should == File.read("#{ FIXTURES }/inline.html")
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -19,8 +19,11 @@ describe InlineStyle::Rack::Middleware do
|
|
19
19
|
get_response('/', @html, :stylesheets_path => FIXTURES).should have_inline_style_for('#A')
|
20
20
|
end
|
21
21
|
|
22
|
+
it "should use external css" do
|
23
|
+
get_response('/', Nokogiri.HTML(@html), :stylesheets_path => FIXTURES).css('#izq').first['style'].should =~ /margin: 30.0px/
|
24
|
+
end
|
25
|
+
|
22
26
|
describe 'Path inclusion' do
|
23
|
-
|
24
27
|
it "should inline style for string path" do
|
25
28
|
paths = "/some/path"
|
26
29
|
get_response('/some/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('#A')
|
@@ -54,4 +57,4 @@ describe InlineStyle::Rack::Middleware do
|
|
54
57
|
get_response('/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('#A')
|
55
58
|
end
|
56
59
|
end
|
57
|
-
end
|
60
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
2
|
+
require 'rspec'
|
3
3
|
require "#{ DIR = File.dirname(__FILE__) }/../lib/inline-style"
|
4
4
|
require 'rack'
|
5
5
|
require 'rack/mock'
|
6
6
|
|
7
7
|
FIXTURES = "#{ DIR }/fixtures"
|
8
8
|
|
9
|
-
|
10
9
|
module HaveInlineStyleMatcher
|
11
10
|
class HaveInlineStyle
|
12
11
|
def initialize selector
|
@@ -32,4 +31,4 @@ module HaveInlineStyleMatcher
|
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
35
|
-
|
34
|
+
RSpec.configure { |config| config.include HaveInlineStyleMatcher }
|
metadata
CHANGED
@@ -1,85 +1,143 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inline-style
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 4
|
8
|
+
- 6
|
9
|
+
version: 0.4.6
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Macario Ortega
|
13
|
+
- Eric Anderson
|
8
14
|
autorequire:
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2011-03-07 00:00:00 -06:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
25
34
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
27
|
-
|
28
|
-
|
29
|
-
|
35
|
+
name: rack
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
30
39
|
requirements:
|
31
40
|
- - ">="
|
32
41
|
- !ruby/object:Gem::Version
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
name: newgem
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
37
45
|
type: :development
|
38
|
-
|
39
|
-
|
46
|
+
version_requirements: *id002
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec-core
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
40
52
|
requirements:
|
41
53
|
- - ">="
|
42
54
|
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id003
|
45
60
|
- !ruby/object:Gem::Dependency
|
46
|
-
name:
|
61
|
+
name: mail
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
47
71
|
type: :development
|
48
|
-
|
49
|
-
|
72
|
+
version_requirements: *id004
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: nokogiri
|
75
|
+
prerelease: false
|
76
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
50
78
|
requirements:
|
51
79
|
- - ">="
|
52
80
|
- !ruby/object:Gem::Version
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
type: :runtime
|
85
|
+
version_requirements: *id005
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: css_parser
|
88
|
+
prerelease: false
|
89
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
type: :runtime
|
98
|
+
version_requirements: *id006
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: maca-fork-csspool
|
101
|
+
prerelease: false
|
102
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
type: :runtime
|
111
|
+
version_requirements: *id007
|
112
|
+
description: Inlines CSS for html email delivery
|
63
113
|
email:
|
64
114
|
- macarui@gmail.com
|
65
115
|
executables: []
|
66
116
|
|
67
117
|
extensions: []
|
68
118
|
|
69
|
-
extra_rdoc_files:
|
70
|
-
|
71
|
-
- Manifest.txt
|
72
|
-
- README.rdoc
|
119
|
+
extra_rdoc_files: []
|
120
|
+
|
73
121
|
files:
|
122
|
+
- .gitignore
|
123
|
+
- Gemfile
|
124
|
+
- Gemfile.lock
|
74
125
|
- History.txt
|
75
126
|
- Manifest.txt
|
76
|
-
- README.
|
127
|
+
- README.txt
|
77
128
|
- Rakefile
|
78
129
|
- example.rb
|
130
|
+
- inline-style.gemspec
|
79
131
|
- lib/inline-style.rb
|
132
|
+
- lib/inline-style/css_parsers.rb
|
133
|
+
- lib/inline-style/css_parsers/css_parser.rb
|
134
|
+
- lib/inline-style/css_parsers/csspool.rb
|
135
|
+
- lib/inline-style/mail/interceptor.rb
|
80
136
|
- lib/inline-style/rack/middleware.rb
|
81
|
-
-
|
137
|
+
- lib/inline-style/version.rb
|
82
138
|
- spec/css_inlining_spec.rb
|
139
|
+
- spec/css_parsers_spec.rb
|
140
|
+
- spec/factory_spec.rb
|
83
141
|
- spec/fixtures/all.css
|
84
142
|
- spec/fixtures/boletin.html
|
85
143
|
- spec/fixtures/box-model.html
|
@@ -88,35 +146,40 @@ files:
|
|
88
146
|
- spec/fixtures/print.css
|
89
147
|
- spec/fixtures/selectors.html
|
90
148
|
- spec/fixtures/style.css
|
149
|
+
- spec/interceptor_spec.rb
|
150
|
+
- spec/rack_middleware_spec.rb
|
91
151
|
- spec/spec_helper.rb
|
92
152
|
has_rdoc: true
|
93
|
-
homepage:
|
153
|
+
homepage: ""
|
94
154
|
licenses: []
|
95
155
|
|
96
156
|
post_install_message:
|
97
|
-
rdoc_options:
|
98
|
-
|
99
|
-
- README.rdoc
|
157
|
+
rdoc_options: []
|
158
|
+
|
100
159
|
require_paths:
|
101
160
|
- lib
|
102
161
|
required_ruby_version: !ruby/object:Gem::Requirement
|
162
|
+
none: false
|
103
163
|
requirements:
|
104
164
|
- - ">="
|
105
165
|
- !ruby/object:Gem::Version
|
166
|
+
segments:
|
167
|
+
- 0
|
106
168
|
version: "0"
|
107
|
-
version:
|
108
169
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
109
171
|
requirements:
|
110
172
|
- - ">="
|
111
173
|
- !ruby/object:Gem::Version
|
174
|
+
segments:
|
175
|
+
- 0
|
112
176
|
version: "0"
|
113
|
-
version:
|
114
177
|
requirements: []
|
115
178
|
|
116
|
-
rubyforge_project:
|
117
|
-
rubygems_version: 1.3.
|
179
|
+
rubyforge_project:
|
180
|
+
rubygems_version: 1.3.7
|
118
181
|
signing_key:
|
119
182
|
specification_version: 3
|
120
|
-
summary:
|
183
|
+
summary: Inlines CSS for html email delivery
|
121
184
|
test_files: []
|
122
185
|
|