premailer-rails 1.8.0 → 1.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +4 -0
- data/.travis.yml +10 -15
- data/CHANGELOG.md +74 -0
- data/Gemfile +10 -4
- data/README.md +49 -39
- data/VERSION +1 -1
- data/example/Gemfile +1 -3
- data/example/bin/rails +4 -0
- data/example/config.ru +1 -1
- data/example/config/application.rb +1 -0
- data/example/config/boot.rb +1 -2
- data/example/config/environments/development.rb +4 -0
- data/example/config/environments/production.rb +9 -0
- data/example/config/initializers/assets.rb +2 -0
- data/lib/premailer/rails.rb +4 -2
- data/lib/premailer/rails/css_helper.rb +45 -16
- data/lib/premailer/rails/css_loaders.rb +0 -1
- data/lib/premailer/rails/css_loaders/asset_pipeline_loader.rb +19 -10
- data/lib/premailer/rails/css_loaders/file_system_loader.rb +24 -2
- data/lib/premailer/rails/css_loaders/network_loader.rb +15 -8
- data/lib/premailer/rails/customized_premailer.rb +6 -6
- data/lib/premailer/rails/hook.rb +16 -8
- data/premailer-rails.gemspec +4 -4
- data/spec/integration/css_helper_spec.rb +92 -42
- data/spec/integration/delivery_spec.rb +13 -0
- data/spec/integration/hook_spec.rb +53 -1
- data/spec/rails_app/app/assets/config/manifest.js +3 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +3 -0
- data/spec/rails_app/app/mailers/application_mailer.rb +4 -0
- data/spec/rails_app/app/mailers/welcome_mailer.rb +6 -0
- data/spec/rails_app/app/views/layouts/mailer.html.erb +11 -0
- data/spec/rails_app/app/views/welcome_mailer/welcome_email.html.erb +1 -0
- data/spec/rails_app/config.ru +5 -0
- data/spec/rails_app/config/application.rb +13 -0
- data/spec/rails_app/config/boot.rb +5 -0
- data/spec/rails_app/config/environment.rb +2 -0
- data/spec/rails_app/config/environments/test.rb +10 -0
- data/spec/rails_app/config/initializers/assets.rb +1 -0
- data/spec/rails_app/config/routes.rb +3 -0
- data/spec/spec_helper.rb +3 -4
- data/spec/support/fixtures/html.rb +8 -4
- data/spec/support/fixtures/message.rb +56 -0
- data/spec/unit/css_loaders/asset_pipeline_loader_spec.rb +19 -4
- data/spec/unit/css_loaders/file_system_loader_spec.rb +37 -0
- data/spec/unit/css_loaders/network_loader_spec.rb +58 -0
- data/spec/unit/customized_premailer_spec.rb +32 -40
- metadata +63 -34
- data/lib/premailer/rails/css_loaders/cache_loader.rb +0 -19
- data/spec/integration/hook_registration_spec.rb +0 -11
- data/spec/support/stubs/action_mailer.rb +0 -5
- data/spec/support/stubs/rails.rb +0 -51
@@ -5,21 +5,30 @@ class Premailer
|
|
5
5
|
extend self
|
6
6
|
|
7
7
|
def load(url)
|
8
|
-
|
9
|
-
file = file_name(url)
|
10
|
-
asset = ::Rails.application.assets.find_asset(file)
|
11
|
-
asset.to_s if asset
|
12
|
-
end
|
13
|
-
end
|
8
|
+
return unless asset_pipeline_present?
|
14
9
|
|
15
|
-
|
16
|
-
|
10
|
+
file = file_name(url)
|
11
|
+
::Rails.application.assets_manifest.find_sources(file).first
|
12
|
+
rescue Errno::ENOENT, TypeError => _error
|
17
13
|
end
|
18
14
|
|
19
15
|
def file_name(url)
|
16
|
+
prefix = [
|
17
|
+
::Rails.configuration.relative_url_root,
|
18
|
+
::Rails.configuration.assets.prefix,
|
19
|
+
'/'
|
20
|
+
].join
|
20
21
|
URI(url).path
|
21
|
-
.sub(
|
22
|
-
.sub(
|
22
|
+
.sub(/\A#{prefix}/, '')
|
23
|
+
.sub(/-(\h{32}|\h{64})\.css\z/, '.css')
|
24
|
+
end
|
25
|
+
|
26
|
+
def asset_pipeline_present?
|
27
|
+
defined?(::Rails) &&
|
28
|
+
::Rails.respond_to?(:application) &&
|
29
|
+
::Rails.application &&
|
30
|
+
::Rails.application.respond_to?(:assets_manifest) &&
|
31
|
+
::Rails.application.assets_manifest
|
23
32
|
end
|
24
33
|
end
|
25
34
|
end
|
@@ -5,9 +5,31 @@ class Premailer
|
|
5
5
|
extend self
|
6
6
|
|
7
7
|
def load(url)
|
8
|
+
file = file_name(url)
|
9
|
+
File.read(file) if File.file?(file)
|
10
|
+
end
|
11
|
+
|
12
|
+
def file_name(url)
|
8
13
|
path = URI(url).path
|
9
|
-
|
10
|
-
|
14
|
+
if relative_url_root
|
15
|
+
path = path.sub(/\A#{relative_url_root.chomp('/')}/, '')
|
16
|
+
end
|
17
|
+
asset_filename(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def asset_filename(filename)
|
21
|
+
if defined?(::Rails) && ::Rails.respond_to?(:root)
|
22
|
+
File.join(::Rails.root, 'public', filename)
|
23
|
+
else
|
24
|
+
File.join('public', filename)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def relative_url_root
|
29
|
+
defined?(::Rails) &&
|
30
|
+
::Rails.respond_to?(:configuration) &&
|
31
|
+
::Rails.configuration.respond_to?(:relative_url_root) &&
|
32
|
+
::Rails.configuration.relative_url_root
|
11
33
|
end
|
12
34
|
end
|
13
35
|
end
|
@@ -12,19 +12,26 @@ class Premailer
|
|
12
12
|
def uri_for_url(url)
|
13
13
|
uri = URI(url)
|
14
14
|
|
15
|
-
if
|
16
|
-
|
17
|
-
|
15
|
+
if uri.host.present?
|
16
|
+
return uri if uri.scheme.present?
|
17
|
+
URI("http:#{uri}")
|
18
|
+
elsif asset_host_present?
|
19
|
+
scheme, host = asset_host(url).split(%r{:?//})
|
20
|
+
scheme, host = host, scheme if host.nil?
|
18
21
|
scheme = 'http' if scheme.blank?
|
19
|
-
|
20
|
-
|
22
|
+
path = url
|
23
|
+
URI(File.join("#{scheme}://#{host}", path))
|
21
24
|
end
|
25
|
+
end
|
22
26
|
|
23
|
-
|
27
|
+
def asset_host_present?
|
28
|
+
::Rails.respond_to?(:configuration) &&
|
29
|
+
::Rails.configuration.action_controller.asset_host.present?
|
24
30
|
end
|
25
31
|
|
26
|
-
def
|
27
|
-
|
32
|
+
def asset_host(url)
|
33
|
+
config = ::Rails.configuration.action_controller.asset_host
|
34
|
+
config.respond_to?(:call) ? config.call(url) : config
|
28
35
|
end
|
29
36
|
end
|
30
37
|
end
|
@@ -4,16 +4,16 @@ class Premailer
|
|
4
4
|
def initialize(html)
|
5
5
|
# In order to pass the CSS as string to super it is necessary to access
|
6
6
|
# the parsed HTML beforehand. To do so, the adapter needs to be
|
7
|
-
# initialized. The ::Premailer::
|
8
|
-
# suitable
|
9
|
-
#
|
10
|
-
#
|
7
|
+
# initialized. The ::Premailer::Adapter handles the discovery of
|
8
|
+
# a suitable adapter. To make load_html work, an adapter needs to be
|
9
|
+
# included and @options[:with_html_string] needs to be set. For further
|
10
|
+
# information, refer to ::Premailer#initialize.
|
11
11
|
@options = Rails.config.merge(with_html_string: true)
|
12
12
|
Premailer.send(:include, Adapter.find(Adapter.use))
|
13
13
|
doc = load_html(html)
|
14
|
-
|
15
14
|
options = @options.merge(css_string: CSSHelper.css_for_doc(doc))
|
16
|
-
|
15
|
+
|
16
|
+
super(doc.to_s, options)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/premailer/rails/hook.rb
CHANGED
@@ -42,7 +42,7 @@ class Premailer
|
|
42
42
|
# Returns true if the message itself has a content type of text/html, thus
|
43
43
|
# it does not contain other parts such as alternatives and attachments.
|
44
44
|
def pure_html_message?
|
45
|
-
message.content_type.include?('text/html')
|
45
|
+
message.content_type && message.content_type.include?('text/html')
|
46
46
|
end
|
47
47
|
|
48
48
|
def generate_html_part_replacement
|
@@ -68,17 +68,25 @@ class Premailer
|
|
68
68
|
def generate_html_part
|
69
69
|
# Make sure that the text part is generated first. Otherwise the text
|
70
70
|
# can end up containing CSS rules.
|
71
|
-
generate_text_part
|
71
|
+
generate_text_part if generate_text_part?
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
part = html_part
|
74
|
+
html = premailer.to_inline_css
|
75
|
+
Mail::Part.new do
|
76
|
+
content_type "text/html; charset=#{html.encoding}"
|
77
|
+
body html
|
78
|
+
end
|
76
79
|
end
|
77
80
|
|
78
81
|
def generate_text_part
|
79
|
-
@text_part ||=
|
80
|
-
|
81
|
-
|
82
|
+
@text_part ||= begin
|
83
|
+
part = html_part
|
84
|
+
text = premailer.to_plain_text
|
85
|
+
Mail::Part.new do
|
86
|
+
content_type "text/plain; charset=#{text.encoding}"
|
87
|
+
body text
|
88
|
+
end
|
89
|
+
end
|
82
90
|
end
|
83
91
|
|
84
92
|
def premailer
|
data/premailer-rails.gemspec
CHANGED
@@ -6,6 +6,7 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = "premailer-rails"
|
7
7
|
s.version = Premailer::Rails::VERSION
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
|
+
s.license = 'MIT'
|
9
10
|
s.authors = ["Philipe Fatio"]
|
10
11
|
s.email = ["philipe.fatio@gmail.com"]
|
11
12
|
s.homepage = "https://github.com/fphilipe/premailer-rails"
|
@@ -16,15 +17,14 @@ Gem::Specification.new do |s|
|
|
16
17
|
premailer will inline the included CSS.}
|
17
18
|
|
18
19
|
s.files = `git ls-files`.split("\n")
|
19
|
-
s.test_files = `git ls-files -- {
|
20
|
+
s.test_files = `git ls-files -- {example,spec}/*`.split("\n")
|
20
21
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
22
|
s.require_paths = ["lib"]
|
22
23
|
|
23
24
|
s.add_dependency 'premailer', '~> 1.7', '>= 1.7.9'
|
24
|
-
s.add_dependency 'actionmailer', '>= 3'
|
25
|
+
s.add_dependency 'actionmailer', '>= 3'
|
25
26
|
|
26
|
-
s.add_development_dependency 'rspec', '~> 3.
|
27
|
+
s.add_development_dependency 'rspec', '~> 3.3'
|
27
28
|
s.add_development_dependency 'nokogiri'
|
28
|
-
s.add_development_dependency 'hpricot' unless RUBY_PLATFORM == 'java'
|
29
29
|
s.add_development_dependency 'coveralls' if RUBY_ENGINE == 'ruby'
|
30
30
|
end
|
@@ -3,11 +3,11 @@ require 'spec_helper'
|
|
3
3
|
describe Premailer::Rails::CSSHelper do
|
4
4
|
# Reset the CSS cache:
|
5
5
|
after do
|
6
|
-
Premailer::Rails::CSSHelper.
|
6
|
+
Premailer::Rails::CSSHelper.cache = {}
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
Premailer::Rails::CSSHelper.
|
9
|
+
def css_for_url(path)
|
10
|
+
Premailer::Rails::CSSHelper.css_for_url(path)
|
11
11
|
end
|
12
12
|
|
13
13
|
def css_for_doc(doc)
|
@@ -15,7 +15,8 @@ describe Premailer::Rails::CSSHelper do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def expect_file(path, content='file content')
|
18
|
-
|
18
|
+
path = "#{Rails.root}/#{path}"
|
19
|
+
allow(File).to receive(:file?).with(path).and_return(true)
|
19
20
|
expect(File).to receive(:read).with(path).and_return(content)
|
20
21
|
end
|
21
22
|
|
@@ -28,63 +29,79 @@ describe Premailer::Rails::CSSHelper do
|
|
28
29
|
|
29
30
|
it 'returns the content of both files concatenated' do
|
30
31
|
allow(Premailer::Rails::CSSHelper).to \
|
31
|
-
receive(:
|
32
|
+
receive(:css_for_url)
|
32
33
|
.with('http://example.com/stylesheets/base.css')
|
33
34
|
.and_return('content of base.css')
|
34
35
|
allow(Premailer::Rails::CSSHelper).to \
|
35
|
-
receive(:
|
36
|
+
receive(:css_for_url)
|
36
37
|
.with('http://example.com/stylesheets/font.css')
|
37
38
|
.and_return('content of font.css')
|
38
39
|
|
39
40
|
expect(css_for_doc(doc)).to eq("content of base.css\ncontent of font.css")
|
40
41
|
end
|
41
42
|
end
|
43
|
+
|
44
|
+
context 'when HTML contains ignored links' do
|
45
|
+
let(:files) { ['ignore.css', 'data-premailer' => 'ignore'] }
|
46
|
+
|
47
|
+
it 'ignores links' do
|
48
|
+
expect(Premailer::Rails::CSSHelper).to_not receive(:css_for_url)
|
49
|
+
css_for_doc(doc)
|
50
|
+
end
|
51
|
+
end
|
42
52
|
end
|
43
53
|
|
44
|
-
describe '#
|
54
|
+
describe '#css_for_url' do
|
45
55
|
context 'when path is a url' do
|
46
56
|
it 'loads the CSS at the local path' do
|
47
57
|
expect_file('public/stylesheets/base.css')
|
48
58
|
|
49
|
-
|
59
|
+
css_for_url('http://example.com/stylesheets/base.css?test')
|
50
60
|
end
|
51
61
|
end
|
52
62
|
|
53
63
|
context 'when path is a relative url' do
|
54
64
|
it 'loads the CSS at the local path' do
|
55
65
|
expect_file('public/stylesheets/base.css')
|
56
|
-
|
66
|
+
css_for_url('/stylesheets/base.css?test')
|
57
67
|
end
|
58
68
|
end
|
59
69
|
|
60
|
-
context 'when
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
cache['http://example.com/stylesheets/base.css'] = 'content of base.css'
|
70
|
+
context 'when cache is enabled' do
|
71
|
+
before do
|
72
|
+
allow(Premailer::Rails::CSSHelper).to receive(:cache_enabled?).and_return(true)
|
73
|
+
end
|
65
74
|
|
66
|
-
|
67
|
-
|
75
|
+
context 'when file is cached' do
|
76
|
+
it 'returns the cached value' do
|
77
|
+
Premailer::Rails::CSSHelper.cache['http://example.com/stylesheets/base.css'] = 'content of base.css'
|
78
|
+
|
79
|
+
expect(css_for_url('http://example.com/stylesheets/base.css')).to \
|
80
|
+
eq('content of base.css')
|
81
|
+
end
|
68
82
|
end
|
69
83
|
end
|
70
84
|
|
71
|
-
context 'when
|
85
|
+
context 'when cache is disabled' do
|
86
|
+
before do
|
87
|
+
allow(Premailer::Rails::CSSHelper).to receive(:cache_enabled?).and_return(false)
|
88
|
+
end
|
89
|
+
|
72
90
|
it 'does not return cached values' do
|
73
|
-
cache =
|
74
|
-
Premailer::Rails::CSSHelper.send(:instance_variable_get, '@cache')
|
75
|
-
cache['http://example.com/stylesheets/base.css'] =
|
76
|
-
'cached content of base.css'
|
91
|
+
Premailer::Rails::CSSHelper.cache['http://example.com/stylesheets/base.css'] = 'cached content'
|
77
92
|
content = 'new content of base.css'
|
78
93
|
expect_file('public/stylesheets/base.css', content)
|
79
|
-
allow(Rails.env).to receive(:development?).and_return(true)
|
80
94
|
|
81
|
-
expect(
|
95
|
+
expect(css_for_url('http://example.com/stylesheets/base.css')).to eq(content)
|
82
96
|
end
|
83
97
|
end
|
84
98
|
|
85
99
|
context 'when Rails asset pipeline is used' do
|
86
100
|
before do
|
87
|
-
allow(Rails.configuration)
|
101
|
+
allow(Rails.configuration)
|
102
|
+
.to receive(:assets).and_return(double(prefix: '/assets'))
|
103
|
+
allow(Rails.configuration)
|
104
|
+
.to receive(:relative_url_root).and_return(nil)
|
88
105
|
end
|
89
106
|
|
90
107
|
context 'and a precompiled file exists' do
|
@@ -92,27 +109,61 @@ describe Premailer::Rails::CSSHelper do
|
|
92
109
|
path = '/assets/email-digest.css'
|
93
110
|
content = 'read from file'
|
94
111
|
expect_file("public#{path}", content)
|
95
|
-
expect(
|
112
|
+
expect(css_for_url(path)).to eq(content)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "when find_sources raises TypeError" do
|
117
|
+
let(:response) { 'content of base.css' }
|
118
|
+
let(:uri) { URI('http://example.com/assets/base.css') }
|
119
|
+
|
120
|
+
it "falls back to Net::HTTP" do
|
121
|
+
expect(Rails.application.assets_manifest).to \
|
122
|
+
receive(:find_sources)
|
123
|
+
.with('base.css')
|
124
|
+
.and_raise(TypeError)
|
125
|
+
|
126
|
+
allow(Net::HTTP).to \
|
127
|
+
receive(:get).with(uri).and_return(response)
|
128
|
+
expect(css_for_url('http://example.com/assets/base.css')).to \
|
129
|
+
eq(response)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when find_sources raises Errno::ENOENT" do
|
134
|
+
let(:response) { 'content of base.css' }
|
135
|
+
let(:uri) { URI('http://example.com/assets/base.css') }
|
136
|
+
|
137
|
+
it "falls back to Net::HTTP" do
|
138
|
+
expect(Rails.application.assets_manifest).to \
|
139
|
+
receive(:find_sources)
|
140
|
+
.with('base.css')
|
141
|
+
.and_raise(Errno::ENOENT)
|
142
|
+
|
143
|
+
allow(Net::HTTP).to \
|
144
|
+
receive(:get).with(uri).and_return(response)
|
145
|
+
expect(css_for_url('http://example.com/assets/base.css')).to \
|
146
|
+
eq(response)
|
96
147
|
end
|
97
148
|
end
|
98
149
|
|
99
150
|
it 'returns the content of the file compiled by Rails' do
|
100
|
-
expect(Rails.application.
|
101
|
-
receive(:
|
151
|
+
expect(Rails.application.assets_manifest).to \
|
152
|
+
receive(:find_sources)
|
102
153
|
.with('base.css')
|
103
|
-
.and_return(
|
154
|
+
.and_return(['content of base.css'])
|
104
155
|
|
105
|
-
expect(
|
156
|
+
expect(css_for_url('http://example.com/assets/base.css')).to \
|
106
157
|
eq('content of base.css')
|
107
158
|
end
|
108
159
|
|
109
160
|
it 'returns same file when path contains file fingerprint' do
|
110
|
-
expect(Rails.application.
|
111
|
-
receive(:
|
161
|
+
expect(Rails.application.assets_manifest).to \
|
162
|
+
receive(:find_sources)
|
112
163
|
.with('base.css')
|
113
|
-
.and_return(
|
164
|
+
.and_return(['content of base.css'])
|
114
165
|
|
115
|
-
expect(
|
166
|
+
expect(css_for_url(
|
116
167
|
'http://example.com/assets/base-089e35bd5d84297b8d31ad552e433275.css'
|
117
168
|
)).to eq('content of base.css')
|
118
169
|
end
|
@@ -124,43 +175,42 @@ describe Premailer::Rails::CSSHelper do
|
|
124
175
|
let(:asset_host) { 'http://assets.example.com' }
|
125
176
|
|
126
177
|
before do
|
127
|
-
allow(Rails.application.
|
128
|
-
receive(:
|
178
|
+
allow(Rails.application.assets_manifest).to \
|
179
|
+
receive(:find_sources).and_return([])
|
129
180
|
|
130
181
|
config = double(asset_host: asset_host)
|
131
182
|
allow(Rails.configuration).to \
|
132
183
|
receive(:action_controller).and_return(config)
|
133
184
|
|
134
|
-
uri_satisfaction = satisfy { |uri| uri.to_s == url }
|
135
185
|
allow(Net::HTTP).to \
|
136
|
-
receive(:get).with(
|
186
|
+
receive(:get).with(URI(url)).and_return(response)
|
137
187
|
end
|
138
188
|
|
139
189
|
it 'requests the file' do
|
140
|
-
expect(
|
190
|
+
expect(css_for_url(url)).to eq('content of base.css')
|
141
191
|
end
|
142
192
|
|
143
193
|
context 'when file url does not include the host' do
|
144
194
|
it 'requests the file using the asset host as host' do
|
145
|
-
expect(
|
195
|
+
expect(css_for_url(path)).to eq('content of base.css')
|
146
196
|
end
|
147
197
|
|
148
198
|
context 'and the asset host uses protocol relative scheme' do
|
149
199
|
let(:asset_host) { '//assets.example.com' }
|
150
200
|
|
151
201
|
it 'requests the file using http as the scheme' do
|
152
|
-
expect(
|
202
|
+
expect(css_for_url(path)).to eq('content of base.css')
|
153
203
|
end
|
154
204
|
end
|
155
205
|
end
|
156
|
-
|
206
|
+
end
|
157
207
|
end
|
158
208
|
|
159
209
|
context 'when static stylesheets are used' do
|
160
210
|
it 'returns the content of the static file' do
|
161
211
|
content = 'content of base.css'
|
162
212
|
expect_file('public/stylesheets/base.css', content)
|
163
|
-
loaded_content =
|
213
|
+
loaded_content = css_for_url('http://example.com/stylesheets/base.css')
|
164
214
|
expect(loaded_content).to eq(content)
|
165
215
|
end
|
166
216
|
end
|