mail_style 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +75 -0
- data/lib/mail_style.rb +6 -0
- data/lib/mail_style/inline_styles.rb +167 -0
- data/lib/mail_style/sass_support.rb +15 -0
- data/readme.textile +82 -0
- data/spec/inline_styles_spec.rb +190 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +30 -0
- metadata +109 -0
data/Rakefile
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
rescue LoadError
|
8
|
+
begin
|
9
|
+
gem 'rspec-rails', '>= 1.0.0'
|
10
|
+
require 'spec/rake/spectask'
|
11
|
+
rescue LoadError
|
12
|
+
puts "RSpec - or one of its dependencies - is not available. Install it with: sudo gem install rspec-rails"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
NAME = "mail_style"
|
17
|
+
SUMMARY = %{Making HTML emails a little less painful. Writes css inline and corrects image urls.}
|
18
|
+
HOMEPAGE = "http://github.com/purify/mail_style"
|
19
|
+
AUTHOR = "Jim Neath"
|
20
|
+
EMAIL = "jimneath@googlemail.com"
|
21
|
+
SUPPORT_FILES = %w(readme.textile)
|
22
|
+
|
23
|
+
begin
|
24
|
+
gem 'jeweler', '>= 1.0.0'
|
25
|
+
require 'jeweler'
|
26
|
+
|
27
|
+
Jeweler::Tasks.new do |t|
|
28
|
+
t.name = NAME
|
29
|
+
t.summary = SUMMARY
|
30
|
+
t.email = EMAIL
|
31
|
+
t.homepage = HOMEPAGE
|
32
|
+
t.description = SUMMARY
|
33
|
+
t.author = AUTHOR
|
34
|
+
|
35
|
+
t.require_path = 'lib'
|
36
|
+
t.files = SUPPORT_FILES << %w(Rakefile) << Dir.glob(File.join(*%w[{lib,spec} ** *]).to_s)
|
37
|
+
t.extra_rdoc_files = SUPPORT_FILES
|
38
|
+
|
39
|
+
t.add_dependency 'action_mailer', '>= 1.2.3'
|
40
|
+
t.add_dependency 'nokogiri', '>= 1.0.0'
|
41
|
+
t.add_dependency 'css_parser', '>= 1.0.0'
|
42
|
+
|
43
|
+
t.add_development_dependency 'rspec-rails', '>= 1.2.6'
|
44
|
+
end
|
45
|
+
|
46
|
+
Jeweler::GemcutterTasks.new
|
47
|
+
rescue LoadError
|
48
|
+
puts "Jeweler - or one of its dependencies - is not available. Install it with: sudo gem install jeweler -s http://gemcutter.org"
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Default: Run specs."
|
52
|
+
task :default => :spec
|
53
|
+
|
54
|
+
desc "Generate documentation for the #{NAME} plugin."
|
55
|
+
Rake::RDocTask.new(:rdoc) do |t|
|
56
|
+
t.rdoc_dir = 'rdoc'
|
57
|
+
t.title = NAME
|
58
|
+
t.options << '--line-numbers' << '--inline-source'
|
59
|
+
t.rdoc_files.include(SUPPORT_FILES)
|
60
|
+
t.rdoc_files.include('lib/**/*.rb')
|
61
|
+
end
|
62
|
+
|
63
|
+
if defined?(Spec)
|
64
|
+
desc "Run plugin specs for #{NAME}."
|
65
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
66
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
67
|
+
t.spec_opts = ["-c"]
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "Run plugin specs for #{NAME} with specdoc formatting and colors"
|
71
|
+
Spec::Rake::SpecTask.new('specdoc') do |t|
|
72
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
73
|
+
t.spec_opts = ["--format specdoc", "-c"]
|
74
|
+
end
|
75
|
+
end
|
data/lib/mail_style.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'css_parser'
|
4
|
+
|
5
|
+
module MailStyle
|
6
|
+
module InlineStyles
|
7
|
+
DOCTYPE = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
8
|
+
|
9
|
+
module InstanceMethods
|
10
|
+
def create_mail_with_inline_styles
|
11
|
+
write_inline_styles if @css.present?
|
12
|
+
create_mail_without_inline_styles
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def write_inline_styles
|
18
|
+
# Parse only text/html parts
|
19
|
+
@parts.select{|p| p.content_type == 'text/html'}.each do |part|
|
20
|
+
part.body = parse_html(part.body)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Parse single part emails if the body is html
|
24
|
+
real_content_type, ctype_attrs = parse_content_type
|
25
|
+
self.body = parse_html(body) if body.is_a?(String) && real_content_type == 'text/html'
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_html(html)
|
29
|
+
# Parse original html
|
30
|
+
html_document = create_html_document(html)
|
31
|
+
html_document = absolutize_image_sources(html_document)
|
32
|
+
|
33
|
+
# Write inline styles
|
34
|
+
element_styles = {}
|
35
|
+
|
36
|
+
css_parser.each_selector do |selector, declaration, specificity|
|
37
|
+
html_document.css(selector).each do |element|
|
38
|
+
declaration.to_s.split(';').each do |style|
|
39
|
+
# Split style in attribute and value
|
40
|
+
attribute, value = style.split(':').map(&:strip)
|
41
|
+
|
42
|
+
# Set element style defaults
|
43
|
+
element_styles[element] ||= {}
|
44
|
+
element_styles[element][attribute] ||= { :specificity => 0, :value => '' }
|
45
|
+
|
46
|
+
# Update attribute value if specificity is higher than previous values
|
47
|
+
if element_styles[element][attribute][:specificity] <= specificity
|
48
|
+
element_styles[element][attribute] = { :specificity => specificity, :value => value }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Loop through element styles
|
55
|
+
element_styles.each_pair do |element, attributes|
|
56
|
+
# Elements current styles
|
57
|
+
current_style = element['style'].to_s.split(';').sort
|
58
|
+
|
59
|
+
# Elements new styles
|
60
|
+
new_style = attributes.map{|attribute, style| "#{attribute}: #{update_image_urls(style[:value])}"}
|
61
|
+
|
62
|
+
# Concat styles
|
63
|
+
style = (current_style + new_style).compact.uniq.map(&:strip).sort
|
64
|
+
|
65
|
+
# Set new styles
|
66
|
+
element['style'] = style.join(';')
|
67
|
+
end
|
68
|
+
|
69
|
+
# Strip all references to classes.
|
70
|
+
html_document.css('*').remove_class
|
71
|
+
html_document.to_html
|
72
|
+
end
|
73
|
+
|
74
|
+
def absolutize_image_sources(document)
|
75
|
+
document.css('img').each do |img|
|
76
|
+
src = img['src']
|
77
|
+
img['src'] = src.gsub(src, absolutize_url(src))
|
78
|
+
end
|
79
|
+
|
80
|
+
document
|
81
|
+
end
|
82
|
+
|
83
|
+
# Create Nokogiri html document from part contents and add/amend certain elements.
|
84
|
+
# Reference: http://www.creativeglo.co.uk/email-design/html-email-design-and-coding-tips-part-2/
|
85
|
+
def create_html_document(body)
|
86
|
+
# Add doctype to html along with body
|
87
|
+
document = Nokogiri::HTML.parse(DOCTYPE + body)
|
88
|
+
|
89
|
+
# Set some meta stuff
|
90
|
+
html = document.at_css('html')
|
91
|
+
html['xmlns'] = 'http://www.w3.org/1999/xhtml'
|
92
|
+
|
93
|
+
# Create <head> element if missing
|
94
|
+
head = document.at_css('head')
|
95
|
+
|
96
|
+
unless head.present?
|
97
|
+
head = Nokogiri::XML::Node.new('head', document)
|
98
|
+
document.at_css('body').add_previous_sibling(head)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Add utf-8 content type meta tag
|
102
|
+
meta = Nokogiri::XML::Node.new('meta', document)
|
103
|
+
meta['http-equiv'] = 'Content-Type'
|
104
|
+
meta['content'] = 'text/html; charset=utf-8'
|
105
|
+
head.add_child(meta)
|
106
|
+
|
107
|
+
# Return document
|
108
|
+
document
|
109
|
+
end
|
110
|
+
|
111
|
+
# Update image urls
|
112
|
+
def update_image_urls(style)
|
113
|
+
if default_url_options[:host].present?
|
114
|
+
# Replace urls in stylesheets
|
115
|
+
style.gsub!($1, absolutize_url($1, 'stylesheets')) if style[/url\(['"]?(.*)['"]?\)/i]
|
116
|
+
end
|
117
|
+
|
118
|
+
style
|
119
|
+
end
|
120
|
+
|
121
|
+
# Absolutize URL (Absolutize? Seriously?)
|
122
|
+
def absolutize_url(url, base_path = '')
|
123
|
+
original_url = url
|
124
|
+
|
125
|
+
unless original_url[URI::regexp(%w[http https])]
|
126
|
+
# Calculate new path
|
127
|
+
host = default_url_options[:host]
|
128
|
+
url = URI.join("http://#{host}/", File.join(base_path, original_url)).to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
url
|
132
|
+
end
|
133
|
+
|
134
|
+
# Css Parser
|
135
|
+
def css_parser
|
136
|
+
parser = CssParser::Parser.new
|
137
|
+
parser.add_block!(css_rules)
|
138
|
+
parser
|
139
|
+
end
|
140
|
+
|
141
|
+
# Css Rules
|
142
|
+
def css_rules
|
143
|
+
File.read(css_file)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Find the css file
|
147
|
+
def css_file
|
148
|
+
if @css.present?
|
149
|
+
css = @css.to_s
|
150
|
+
css = css[/\.css$/] ? css : "#{css}.css"
|
151
|
+
path = File.join(RAILS_ROOT, 'public', 'stylesheets', css)
|
152
|
+
File.exist?(path) ? path : raise(CSSFileNotFound)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.included(receiver)
|
158
|
+
receiver.send :include, InstanceMethods
|
159
|
+
receiver.class_eval do
|
160
|
+
adv_attr_accessor :css
|
161
|
+
alias_method_chain :create_mail, :inline_styles
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
ActionMailer::Base.send :include, MailStyle::InlineStyles
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MailStyle
|
2
|
+
module InlineStyles
|
3
|
+
module InstanceMethods
|
4
|
+
def css_file_with_sass
|
5
|
+
if !Sass::Plugin.checked_for_updates || Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
|
6
|
+
Sass::Plugin.update_stylesheets
|
7
|
+
end
|
8
|
+
|
9
|
+
css_file_without_sass
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method_chain :css_file, :sass
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/readme.textile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
h1. MailStyle
|
2
|
+
|
3
|
+
MailStyle tries to make sending HTML emails a little less painful.
|
4
|
+
|
5
|
+
For a more in depth introduction check out the "Blog Post":http://blog.purifyapp.com/2009/12/30/mailstyle-a-html-email-plugin-for-ruby-on-rails/
|
6
|
+
|
7
|
+
It was developed for use on "Purify: Awesome Bug & Issue Tracking":http://purifyapp.com
|
8
|
+
|
9
|
+
h2. Install
|
10
|
+
|
11
|
+
First install the dependencies:
|
12
|
+
|
13
|
+
<pre><code>sudo gem install nokogiri css_parser</code></pre>
|
14
|
+
|
15
|
+
Then install MailStyle to your rails app, either as a plugin:
|
16
|
+
|
17
|
+
<pre><code>script/plugin install http://github.com/purify/mail_style</code></pre>
|
18
|
+
|
19
|
+
Or you can install the gem:
|
20
|
+
|
21
|
+
<pre><code>sudo gem install mail_style</code></pre>
|
22
|
+
|
23
|
+
Then in your <code>environment.rb</code> file add:
|
24
|
+
|
25
|
+
<pre><code>config.gem "mail_style"</code></pre>
|
26
|
+
|
27
|
+
h2. Usage
|
28
|
+
|
29
|
+
Simply add the <code>css</code> method to your deliver actions:
|
30
|
+
|
31
|
+
<pre><code>class Notifier < ActionMailer::Base
|
32
|
+
def welcome_email
|
33
|
+
css :email
|
34
|
+
|
35
|
+
subject 'Welcome Aboard'
|
36
|
+
recipients 'someone@example.com'
|
37
|
+
from 'jimneath@googlemail.com'
|
38
|
+
sent_on Time.now
|
39
|
+
end
|
40
|
+
end</code></pre>
|
41
|
+
|
42
|
+
This will look for a css file called _email.css_ in your _public/stylesheets_ folder. The <code>css</code> method can take either a string or a symbol. You can also pass the css file name with or without the .css extension.
|
43
|
+
|
44
|
+
h2. Image URL Correcting
|
45
|
+
|
46
|
+
If you have _default_url_options[:host]_ set in your mailer, then MailStyle will do it's best to make the urls of images absolute.
|
47
|
+
|
48
|
+
h4. In your mailer
|
49
|
+
|
50
|
+
<pre><code>ActionMailer::Base.default_url_options[:host] = "example.com"</code></pre>
|
51
|
+
|
52
|
+
h2. Features
|
53
|
+
|
54
|
+
* Write CSS styles inline
|
55
|
+
* Corrects image urls
|
56
|
+
* SASS Support
|
57
|
+
|
58
|
+
h2. Bugs / Todo
|
59
|
+
|
60
|
+
* Improve overall performance
|
61
|
+
* Add support for less
|
62
|
+
* Allow users to swap between css_parser and csspool
|
63
|
+
|
64
|
+
h2. Contributors
|
65
|
+
|
66
|
+
* "Jim Neath":http://jimneath.org
|
67
|
+
* "Lars Klevans":http://tastybyte.blogspot.com/
|
68
|
+
* "Jonas Grimfelt":http://github.com/grimen
|
69
|
+
|
70
|
+
h2. License
|
71
|
+
|
72
|
+
(The MIT License)
|
73
|
+
|
74
|
+
Copyright © 2009-2010
|
75
|
+
|
76
|
+
* "Jim Neath":http://jimneath.org
|
77
|
+
|
78
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
79
|
+
|
80
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
81
|
+
|
82
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
3
|
+
|
4
|
+
RAILS_ROOT = File.join(File.dirname(__FILE__), '../../../../')
|
5
|
+
|
6
|
+
# Set ActionMailer stuff
|
7
|
+
ActionMailer::Base.template_root = '.'
|
8
|
+
ActionMailer::Base.delivery_method = :test
|
9
|
+
ActionMailer::Base.perform_deliveries = true
|
10
|
+
ActionMailer::Base.deliveries = []
|
11
|
+
ActionMailer::Base.default_url_options[:host] = "example.com"
|
12
|
+
|
13
|
+
# Test Mailer
|
14
|
+
class TestMailer < ActionMailer::Base
|
15
|
+
def test_multipart(css_file = nil)
|
16
|
+
setup_email(css_file)
|
17
|
+
content_type 'multipart/alternative'
|
18
|
+
part :content_type => 'text/html', :body => '<p class="text">Hello World</p>'
|
19
|
+
part :content_type => 'text/plain', :body => 'Hello World'
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_singlepart_html(css_file = nil)
|
23
|
+
setup_email(css_file)
|
24
|
+
content_type 'text/html'
|
25
|
+
body '<p class="text">Hello World</p>'
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_singlepart_plain(css_file = nil)
|
29
|
+
setup_email(css_file)
|
30
|
+
content_type 'text/plain'
|
31
|
+
body '<p class="text">Hello World</p>'
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_image_urls(css_file = nil)
|
35
|
+
setup_email(css_file)
|
36
|
+
content_type 'multipart/alternative'
|
37
|
+
part :content_type => 'text/html', :body => '<p id="image">Hello World</p><img src="/images/test.jpg" />'
|
38
|
+
part :content_type => 'text/plain', :body => 'Hello World'
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def setup_email(css_file = nil)
|
44
|
+
css css_file unless css_file.nil?
|
45
|
+
|
46
|
+
subject 'Test Multipart Email'
|
47
|
+
recipients 'jimneath@googlemail.com'
|
48
|
+
from 'jimneath@googlemail.com'
|
49
|
+
sent_on Time.now
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'Inline styles' do
|
54
|
+
describe 'singlepart' do
|
55
|
+
before(:each) do
|
56
|
+
css_rules <<-EOF
|
57
|
+
body { background: #000 }
|
58
|
+
p { color: #f00; line-height: 1.5 }
|
59
|
+
.text { font-size: 14px }
|
60
|
+
EOF
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should apply styles for text/html" do
|
64
|
+
@email = TestMailer.deliver_test_singlepart_html(:real)
|
65
|
+
@email.body.should match(/<body style="background: #000">/)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should do nothing for text/plain" do
|
69
|
+
@email = TestMailer.deliver_test_singlepart_plain(:real)
|
70
|
+
@email.body.should eql('<p class="text">Hello World</p>')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'multipart' do
|
75
|
+
describe 'image urls' do
|
76
|
+
before(:each) do
|
77
|
+
# CSS rules
|
78
|
+
css_rules <<-EOF
|
79
|
+
p#image { background: url(../images/test-image.png)}
|
80
|
+
EOF
|
81
|
+
|
82
|
+
# Generate email
|
83
|
+
@email = TestMailer.deliver_test_image_urls(:real)
|
84
|
+
@html = html_part(@email)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should make the css urls absolute" do
|
88
|
+
@html.should match(/<p.*style="background: url\(http:\/\/example\.com\/images\/test\-image\.png\)">/)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should make image sources absolute" do
|
92
|
+
# Note: Nokogiri loses the closing slash from the <img> tag for some reason.
|
93
|
+
@html.should match(/<img src="http:\/\/example\.com\/images\/test\.jpg\">/)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe 'rendering inline styles' do
|
98
|
+
before(:each) do
|
99
|
+
css_rules <<-EOF
|
100
|
+
body { background: #000 }
|
101
|
+
.text { color: #0f0; font-size: 14px }
|
102
|
+
p { color: #f00; line-height: 1.5 }
|
103
|
+
EOF
|
104
|
+
|
105
|
+
# Generate email
|
106
|
+
@email = TestMailer.deliver_test_multipart(:real)
|
107
|
+
@html = html_part(@email)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should add the correct xml namespace" do
|
111
|
+
@html.should match(/<html xmlns="http:\/\/www\.w3\.org\/1999\/xhtml">/)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should write the xhtml 1.0 doctype" do
|
115
|
+
@html.should match(/<!DOCTYPE html PUBLIC "-\/\/W3C\/\/DTD XHTML 1\.0 Transitional\/\/EN" "http:\/\/www.w3.org\/TR\/xhtml1\/DTD\/xhtml1-transitional\.dtd">/mi)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should write utf-8 content type meta tag" do
|
119
|
+
@html.should match(/<head>.*<meta http\-equiv="Content\-Type" content="text\/html; charset=utf\-8">.*<\/head>/mi)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should wrap with html and body tag if missing" do
|
123
|
+
@html.should match(/<html.*>.*<body.*>.*<\/body>.*<\/html>/m)
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should add style to body" do
|
127
|
+
@html.should match(/<body style="background: #000">/)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should remove classes from html" do
|
131
|
+
@html.should_not match(/<p.*class="text".*>/)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should add both styles to paragraph" do
|
135
|
+
@html.should match(/<p style="color: #0f0;font-size: 14px;line-height: 1.5">/)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'combining styles' do
|
140
|
+
it "should select the most specific style" do
|
141
|
+
css_rules <<-EOF
|
142
|
+
.text { color: #0f0; }
|
143
|
+
p { color: #f00; }
|
144
|
+
EOF
|
145
|
+
@email = TestMailer.deliver_test_multipart(:real)
|
146
|
+
@html = html_part(@email)
|
147
|
+
@html.should match(/<p style="color: #0f0">/)
|
148
|
+
end
|
149
|
+
it "should combine different properties for one element" do
|
150
|
+
css_rules <<-EOF
|
151
|
+
.text { font-size: 14px; }
|
152
|
+
p { color: #f00; }
|
153
|
+
EOF
|
154
|
+
@email = TestMailer.deliver_test_multipart(:real)
|
155
|
+
@html = html_part(@email)
|
156
|
+
@html.should match(/<p style="color: #f00;font-size: 14px">/)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe 'css file' do
|
161
|
+
it "should do nothing if no css file is set" do
|
162
|
+
@email = TestMailer.deliver_test_multipart(nil)
|
163
|
+
html_part(@email).should eql('<p class="text">Hello World</p>')
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should raise MailStyle::CSSFileNotFound if css file does not exist" do
|
167
|
+
lambda {
|
168
|
+
TestMailer.deliver_test_multipart(:fake)
|
169
|
+
}.should raise_error(MailStyle::CSSFileNotFound)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should support inline styles without deliver' do
|
174
|
+
css_rules <<-EOF
|
175
|
+
body { background: #000 }
|
176
|
+
p { color: #f00; line-height: 1.5 }
|
177
|
+
.text { font-size: 14px }
|
178
|
+
EOF
|
179
|
+
|
180
|
+
# Generate email
|
181
|
+
@email = TestMailer.create_test_multipart(:real)
|
182
|
+
html_part(@email).should match(/<body style="background: #000">/)
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should have two parts" do
|
186
|
+
@email = TestMailer.deliver_test_multipart
|
187
|
+
@email.parts.length.should eql(2)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'spec'
|
5
|
+
require 'action_mailer'
|
6
|
+
require 'mail_style'
|
7
|
+
|
8
|
+
# Extract HTML Part
|
9
|
+
def html_part(email)
|
10
|
+
email.parts.select{|part| part.content_type == 'text/html'}.first.body
|
11
|
+
end
|
12
|
+
|
13
|
+
def css_rules(css)
|
14
|
+
@css_rules = css
|
15
|
+
|
16
|
+
# Stubs
|
17
|
+
File.stub(:exist?).and_return(true)
|
18
|
+
File.stub(:read).and_return(@css_rules)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Debugging helper
|
22
|
+
module Kernel
|
23
|
+
if ENV.keys.find {|env_var| env_var.match(/^TM_/)}
|
24
|
+
def rputs(*args)
|
25
|
+
puts( *["<pre>", args.collect {|a| CGI.escapeHTML(a.to_s)}, "</pre>"])
|
26
|
+
end
|
27
|
+
else
|
28
|
+
alias_method :rputs, :puts
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mail_style
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jim Neath
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-02 00:00:00 +00:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: action_mailer
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: nokogiri
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.0
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: css_parser
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.0.0
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: rspec-rails
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.2.6
|
54
|
+
version:
|
55
|
+
description: Making HTML emails a little less painful. Writes css inline and corrects image urls.
|
56
|
+
email: jimneath@googlemail.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- Rakefile
|
63
|
+
- lib/mail_style.rb
|
64
|
+
- lib/mail_style/inline_styles.rb
|
65
|
+
- lib/mail_style/sass_support.rb
|
66
|
+
- readme.textile
|
67
|
+
- spec/inline_styles_spec.rb
|
68
|
+
- spec/spec.opts
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
files:
|
71
|
+
- Rakefile
|
72
|
+
- lib/mail_style.rb
|
73
|
+
- lib/mail_style/inline_styles.rb
|
74
|
+
- lib/mail_style/sass_support.rb
|
75
|
+
- readme.textile
|
76
|
+
- spec/inline_styles_spec.rb
|
77
|
+
- spec/spec.opts
|
78
|
+
- spec/spec_helper.rb
|
79
|
+
has_rdoc: true
|
80
|
+
homepage: http://github.com/purify/mail_style
|
81
|
+
licenses: []
|
82
|
+
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options:
|
85
|
+
- --charset=UTF-8
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: "0"
|
93
|
+
version:
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: "0"
|
99
|
+
version:
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.3.5
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Making HTML emails a little less painful. Writes css inline and corrects image urls.
|
107
|
+
test_files:
|
108
|
+
- spec/inline_styles_spec.rb
|
109
|
+
- spec/spec_helper.rb
|