premailer-rails 1.8.0 → 1.11.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.
- 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
|