grimen-awesome_email 0.1.0
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.
- data/MIT-LICENSE +20 -0
- data/README.textile +90 -0
- data/Rakefile +50 -0
- data/lib/awesome_email/convert_entities.rb +55 -0
- data/lib/awesome_email/helpers.rb +49 -0
- data/lib/awesome_email/inline_styles.rb +92 -0
- data/lib/awesome_email/layouts.rb +81 -0
- data/lib/awesome_email.rb +4 -0
- data/rails/init.rb +1 -0
- data/test/awesome_email_test.rb +199 -0
- data/test/test_helper.rb +24 -0
- metadata +64 -0
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2008 imedo GmbH
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
h2. For the impatient
|
|
2
|
+
|
|
3
|
+
Check out the demo application:
|
|
4
|
+
|
|
5
|
+
"http://opensource.imedo.de/htmlmail":http://opensource.imedo.de/htmlmail
|
|
6
|
+
|
|
7
|
+
Install as plugin:
|
|
8
|
+
|
|
9
|
+
<pre>script/plugin install git://github.com/grimen/awesome_email.git</pre>
|
|
10
|
+
|
|
11
|
+
Install as gem:
|
|
12
|
+
|
|
13
|
+
<pre>sudo install grimen-awesome_email</pre>
|
|
14
|
+
|
|
15
|
+
Learn how to use it below.
|
|
16
|
+
|
|
17
|
+
h2. Introduction
|
|
18
|
+
|
|
19
|
+
Have you ever tried sending HTML emails to your users? If you did, you know for sure that it sucks big time: none of the usual ActionView helpers want to work, URL routing is disabled, layouts don't work, and last but not least, the CSS you wrote for your email "simply won't work in any e-mail client":http://www.sitepoint.com/blogs/2007/01/10/microsoft-breaks-html-email-rendering-in-outlook except maybe Apple Mail. To solve all of the above problems, the <code>awesome_email</code> plugin comes to the rescue. Just install it into your <code>vendor/plugins</code> folder, and the rest comes by itself.
|
|
20
|
+
If you are interested in what works in which Email client check this link: "A guide to css support in Email":http://www.campaignmonitor.com/css/
|
|
21
|
+
|
|
22
|
+
h2. What does it do?
|
|
23
|
+
|
|
24
|
+
There are a few interesting components in <code>awesome_email</code>:
|
|
25
|
+
|
|
26
|
+
* awesome_email adds layout support to emails. That means that you can use templates for e-mails just like you would with normal Rails Views.
|
|
27
|
+
* The HTML Mail's CSS is automatcally inlined. That means that your designer and/or CSS guy can design the email in a web browser without worrying about how it might look like in excotic email clients. Yes, it works in Outlook, too, and no, it "doesn't work in Outlook 2007 without tweaking":http://www.sitepoint.com/blogs/2007/01/10/microsoft-breaks-html-email-rendering-in-outlook. The reason is a "stupid decision from Microsoft about Outlook 2007", but we're working on that one.
|
|
28
|
+
* ConvertEntities replaces Umlauts and other crazy symbols like ä, Ö etc. with their HTML Entitiy counterparts e.g. <code>&auml;</code> and so on.
|
|
29
|
+
* HelperMethods allow you to dump the content of the CSS file right into a style tag inside the header of your HTML mail.
|
|
30
|
+
|
|
31
|
+
h2. How to use it
|
|
32
|
+
|
|
33
|
+
In your Mailer.delivery_xxx methods you can use
|
|
34
|
+
|
|
35
|
+
<macro:code lang="ruby">
|
|
36
|
+
layout "template_filename"
|
|
37
|
+
css "css_filename"
|
|
38
|
+
</macro:code>
|
|
39
|
+
|
|
40
|
+
to define which layout should be used and which css file should be used to create inline styles
|
|
41
|
+
|
|
42
|
+
h3. CSS inlining
|
|
43
|
+
|
|
44
|
+
The cummulated style of each DOM element will be set as an style attribute when using css inlining.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
|
|
48
|
+
your css file:
|
|
49
|
+
<macro:code lang="css">
|
|
50
|
+
#some-id { font-size:2em; }
|
|
51
|
+
.some-class { color:red; }
|
|
52
|
+
</macro:code>
|
|
53
|
+
|
|
54
|
+
your template:
|
|
55
|
+
<macro:code lang="html">
|
|
56
|
+
<p id="some-id" class="some-class">Hello World!</p>
|
|
57
|
+
</macro:code>
|
|
58
|
+
|
|
59
|
+
will result in the following code:
|
|
60
|
+
<macro:code lang="html">
|
|
61
|
+
<p id="some-id" class="some-class" style="color:red; font-size:2em;">Hello World!</p>
|
|
62
|
+
</macro:code>
|
|
63
|
+
|
|
64
|
+
h2. Important!
|
|
65
|
+
|
|
66
|
+
Be sure to follow these simple conventions or otherwise awesome_emails's magic will fail:
|
|
67
|
+
|
|
68
|
+
* The layout must be located inside <code>app/views/layouts/{mailer_name}</code>
|
|
69
|
+
* If you send mutlipart mails, check out the conventions on how to name your files: "http://rails.rubyonrails.com/classes/ActionMailer/Base.html":http://rails.rubyonrails.com/classes/ActionMailer/Base.html
|
|
70
|
+
** So if you have these files inside of /app/views/{mailer_name}: *signup_notification.text.plain.erb*, *signup_notification.text.html.erb* ActionMailer will send a multipart mail with two parts: *text/plain* and *text/html*
|
|
71
|
+
* Your CSS file must be inside of <code>/public/stylesheets</code>
|
|
72
|
+
|
|
73
|
+
h2. Dependencies
|
|
74
|
+
|
|
75
|
+
Gems:
|
|
76
|
+
|
|
77
|
+
* "rails 2.0.1+":http://github.com/rails/rails
|
|
78
|
+
* "nokogiri 1.3.3+":http://github.com/tenderlove/nokogiri
|
|
79
|
+
* "csspool 2.0.0+":http://github.com/tenderlove/csspool
|
|
80
|
+
|
|
81
|
+
h2. Getting it, License and Patches
|
|
82
|
+
|
|
83
|
+
Get the original source code through "http://github.com/imedo/awesome_email":http://github.com/imedo/awesome_email. License is MIT. That means that you can do whatever you want with the software, as long as the copyright statement stays intact. Please be a kind open source citizen, and give back your patches and extensions. Just fork the code on Github, and after you're done, send us a pull request. Thanks for your help!
|
|
84
|
+
|
|
85
|
+
h2. ToDo
|
|
86
|
+
|
|
87
|
+
* More test coverage (as usual) - especially testing multiple rules (!)
|
|
88
|
+
* Make it more flexible with view paths
|
|
89
|
+
|
|
90
|
+
Copyright (c) 2008 imedo GmbH, released under the MIT license
|
data/Rakefile
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'rake'
|
|
2
|
+
require 'rake/testtask'
|
|
3
|
+
require 'rake/rdoctask'
|
|
4
|
+
|
|
5
|
+
NAME = "awesome_email"
|
|
6
|
+
SUMMARY = %Q{Rails ActionMailer with HTML layouts, inline CSS and entity substitution.}
|
|
7
|
+
HOMEPAGE = "http://github.com/grimen/#{NAME}/tree/master"
|
|
8
|
+
AUTHORS = ["imedo GmbH"]
|
|
9
|
+
EMAIL = "entwickler@imedo.de"
|
|
10
|
+
|
|
11
|
+
require 'rubygems'
|
|
12
|
+
gem 'technicalpickles-jeweler', '1.2.1'
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
require 'jeweler'
|
|
16
|
+
Jeweler::Tasks.new do |gem|
|
|
17
|
+
gem.name = NAME
|
|
18
|
+
gem.summary = SUMMARY
|
|
19
|
+
gem.description = SUMMARY
|
|
20
|
+
gem.homepage = HOMEPAGE
|
|
21
|
+
gem.authors = AUTHORS
|
|
22
|
+
gem.email = EMAIL
|
|
23
|
+
|
|
24
|
+
gem.require_paths = %w{lib}
|
|
25
|
+
gem.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob(File.join('{lib,rails,test}', '**', '*'))
|
|
26
|
+
gem.executables = %w()
|
|
27
|
+
gem.extra_rdoc_files = %w{README.textile}
|
|
28
|
+
end
|
|
29
|
+
rescue LoadError
|
|
30
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc %Q{Run unit tests for "#{NAME}".}
|
|
34
|
+
task :default => :test
|
|
35
|
+
|
|
36
|
+
desc %Q{Run unit tests for "#{NAME}".}
|
|
37
|
+
Rake::TestTask.new(:test) do |test|
|
|
38
|
+
test.libs << ['lib', 'test']
|
|
39
|
+
test.pattern = File.join('test', '**', '*_test.rb')
|
|
40
|
+
test.verbose = true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc %Q{Generate documentation for "#{NAME}".}
|
|
44
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
46
|
+
rdoc.title = NAME
|
|
47
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--charset=UTF-8'
|
|
48
|
+
rdoc.rdoc_files.include('README.textile')
|
|
49
|
+
rdoc.rdoc_files.include(File.join('lib', '**', '*.rb'))
|
|
50
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
$KCODE = 'u' unless RUBY_VERSION >= '1.9'
|
|
3
|
+
|
|
4
|
+
module ActionMailer
|
|
5
|
+
module ConvertEntities
|
|
6
|
+
|
|
7
|
+
# Add more if replacements you need
|
|
8
|
+
UMLAUTS = {
|
|
9
|
+
'ä' => 'ä',
|
|
10
|
+
'ö' => 'ö',
|
|
11
|
+
'ü' => 'ü',
|
|
12
|
+
'Ä' => 'Ä',
|
|
13
|
+
'Ö' => 'Ö',
|
|
14
|
+
'Ü' => 'Ü',
|
|
15
|
+
'ß' => 'ß'
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
module ClassMethods
|
|
19
|
+
# none
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module InstanceMethods
|
|
23
|
+
|
|
24
|
+
# Replace all umlauts
|
|
25
|
+
# Add more if replacements you need them
|
|
26
|
+
def convert_to_entities(text)
|
|
27
|
+
text.gsub(/[#{UMLAUTS.keys.join}]/u) { |match| UMLAUTS[match] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Convert entities only when rendering html
|
|
31
|
+
def render_message_with_converted_entities(method_name, body)
|
|
32
|
+
message = render_message_without_converted_entities(method_name, body)
|
|
33
|
+
html_part?(method_name) ? convert_to_entities(message) : message
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Check if the part we are rendering is html
|
|
37
|
+
def html_part?(method_name)
|
|
38
|
+
method_name.to_s.gsub('.', '/') =~ /#{Mime::EXTENSION_LOOKUP['html']}/
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.included(receiver)
|
|
44
|
+
receiver.class_eval do
|
|
45
|
+
extend ClassMethods
|
|
46
|
+
include InstanceMethods
|
|
47
|
+
|
|
48
|
+
alias_method_chain :render_message, :converted_entities
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
ActionMailer::Base.send :include, ActionMailer::ConvertEntities
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
$KCODE = 'u' unless RUBY_VERSION >= '1.9'
|
|
3
|
+
|
|
4
|
+
module AwesomeEmail
|
|
5
|
+
module Helpers
|
|
6
|
+
|
|
7
|
+
# helper methods for ActionView::Base
|
|
8
|
+
module Views
|
|
9
|
+
|
|
10
|
+
# prints the contents of a file to the page
|
|
11
|
+
# default file_name is 'html-mail.css'
|
|
12
|
+
def render_css_file(file_name = 'html-mail.css')
|
|
13
|
+
file_name = "#{file_name}.css" unless file_name.end_with?('.css')
|
|
14
|
+
relative_path = File.join('public', 'stylesheets', 'mails', file_name)
|
|
15
|
+
files = Dir.glob(File.join(RAILS_ROOT, '**', relative_path))
|
|
16
|
+
full_path = files.blank? ? File.join(RAILS_ROOT, relative_path) : files.first
|
|
17
|
+
File.read(full_path) rescue ''
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# outputs style sheet information into the header of a webpage
|
|
21
|
+
# to link the stylesheet absolute, we have to pass in the server_url like: "http://localhost" or "https://localhost:3001"
|
|
22
|
+
def mail_header_styles(server_url, file_name)
|
|
23
|
+
return '' if file_name.blank?
|
|
24
|
+
%Q{<link rel="stylesheet" href="#{File.join(server_url, file_name)}" />\n<style type="text/css"><!-- #{render_css_file(file_name)} --></style>}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# helper methods for ActionMailer::Base
|
|
30
|
+
module Mailer
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
# sets a few variables that ensure good delivery of the mail
|
|
35
|
+
def setup_multipart_mail
|
|
36
|
+
headers 'Content-transfer-encoding' => '8bit'
|
|
37
|
+
sent_on Time.now
|
|
38
|
+
content_type 'text/html'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
ActionView::Base.class_eval do
|
|
47
|
+
include AwesomeEmail::Helpers::Views
|
|
48
|
+
include AwesomeEmail::Helpers::Mailer
|
|
49
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
$KCODE = 'u' unless RUBY_VERSION >= '1.9'
|
|
3
|
+
|
|
4
|
+
require 'rubygems'
|
|
5
|
+
|
|
6
|
+
gem 'nokogiri', '>= 1.3.3'
|
|
7
|
+
gem 'csspool', '>= 2.0.0'
|
|
8
|
+
|
|
9
|
+
require 'nokogiri'
|
|
10
|
+
require 'csspool'
|
|
11
|
+
|
|
12
|
+
module ActionMailer
|
|
13
|
+
module InlineStyles
|
|
14
|
+
|
|
15
|
+
STYLE_ATTR = (RUBY_VERSION >= '1.9') ? :style : 'style'
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
# none
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module InstanceMethods
|
|
22
|
+
|
|
23
|
+
def inline(html)
|
|
24
|
+
css_doc = parse_css_doc(build_css_file_name_from_css_setting)
|
|
25
|
+
html_doc = parse_html_doc(html)
|
|
26
|
+
render_inline(css_doc, html_doc)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def render_message_with_inline_styles(method_name, body)
|
|
30
|
+
message = render_message_without_inline_styles(method_name, body)
|
|
31
|
+
return message if @css.blank?
|
|
32
|
+
inline(message)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
protected
|
|
36
|
+
|
|
37
|
+
def render_inline(css_doc, html_doc)
|
|
38
|
+
css_doc.rule_sets.each do |rule_set|
|
|
39
|
+
inline_css = css_for_rule(rule_set)
|
|
40
|
+
|
|
41
|
+
html_doc.css(rule_set.selectors.first.to_s).each do |element|
|
|
42
|
+
element[STYLE_ATTR] = [inline_css, element[STYLE_ATTR]].compact.join('').strip
|
|
43
|
+
element[STYLE_ATTR] << ';' unless element[STYLE_ATTR] =~ /;$/
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
html_doc.to_html
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def css_for_rule(rule_set)
|
|
50
|
+
rule_set.declarations.map do |declaration|
|
|
51
|
+
declaration.to_s.strip
|
|
52
|
+
end.join
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def parse_html_doc(html)
|
|
56
|
+
html_doc = Nokogiri::HTML.parse(html)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def parse_css_doc(file_name)
|
|
60
|
+
css_doc = CSSPool.CSS(parse_css_from_file(file_name))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parse_css_from_file(file_name)
|
|
64
|
+
files = Dir.glob(File.join(RAILS_ROOT, '**', file_name))
|
|
65
|
+
files.blank? ? '' : File.read(files.first)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def build_css_file_name_from_css_setting
|
|
69
|
+
@css.blank? ? '' : build_css_file_name(@css)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_css_file_name(css_name)
|
|
73
|
+
file_name = "#{css_name}.css"
|
|
74
|
+
Dir.glob(File.join(RAILS_ROOT, '**', file_name)).first || File.join(RAILS_ROOT, 'public', 'stylesheets', 'mails', file_name)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.included(receiver)
|
|
80
|
+
receiver.class_eval do
|
|
81
|
+
extend ClassMethods
|
|
82
|
+
include InstanceMethods
|
|
83
|
+
|
|
84
|
+
adv_attr_accessor :css
|
|
85
|
+
alias_method_chain :render_message, :inline_styles
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
ActionMailer::Base.send :include, ActionMailer::InlineStyles
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
$KCODE = 'u' unless RUBY_VERSION >= '1.9'
|
|
3
|
+
|
|
4
|
+
module ActionMailer
|
|
5
|
+
module Layouts
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
# none
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module InstanceMethods
|
|
12
|
+
|
|
13
|
+
# render with layout, if it is set through the "layout" accessor method and a corresponding file is found
|
|
14
|
+
def render_message_with_layouts(method_name, body)
|
|
15
|
+
return render_message_without_layouts(method_name, body) if @layout.blank?
|
|
16
|
+
# template was set, now render with layout
|
|
17
|
+
template = initialize_template_class body
|
|
18
|
+
template = render_content method_name, template
|
|
19
|
+
render_layout_template template, method_name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
protected
|
|
23
|
+
|
|
24
|
+
# tries to find a matching template and renders the inner content back to the template
|
|
25
|
+
def render_content(method_name, template)
|
|
26
|
+
template.instance_variable_set(:@content_for_layout, render_content_for_layout(method_name, template))
|
|
27
|
+
template
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# builds the filename from the method_name, then renders the inner content
|
|
31
|
+
def render_content_for_layout(method_name, template)
|
|
32
|
+
file_name = extend_with_mailer_name(method_name)
|
|
33
|
+
template.render(:file => file_name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# finds the layout file and renders it, if the file is not found an exception is raised
|
|
37
|
+
# default path for all mailer layouts is layouts/mailers below app/views/
|
|
38
|
+
# you can pass in another layout path as 3rd arguments
|
|
39
|
+
def render_layout_template(template, method_name, layout_path = File.join('layouts', 'mailers'))
|
|
40
|
+
extension_parts = method_name.to_s.split('.')[1..-1]
|
|
41
|
+
while !extension_parts.blank?
|
|
42
|
+
file_name = File.join(layout_path, ([@layout.to_s] + extension_parts).join('.'))
|
|
43
|
+
return render_layout(file_name, template) if template_exists?(file_name)
|
|
44
|
+
extension_parts.shift
|
|
45
|
+
end
|
|
46
|
+
# nothing found, complain
|
|
47
|
+
raise "Layout '#{@layout}' not found"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render_layout(file_name, template)
|
|
51
|
+
template.render(:file => file_name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# check if a the given view exists within the app/views folder
|
|
55
|
+
def template_exists?(file_name)
|
|
56
|
+
full_path = File.join(RAILS_ROOT, '**', 'views', file_name)
|
|
57
|
+
files = Dir.glob(full_path)
|
|
58
|
+
!files.blank?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def extend_with_mailer_name(template_name)
|
|
62
|
+
template_name.to_s =~ /\// ? template_name : File.join(mailer_name, template_name)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# create "layout" method to define the layout name
|
|
68
|
+
def self.included(receiver)
|
|
69
|
+
receiver.class_eval do
|
|
70
|
+
extend ClassMethods
|
|
71
|
+
include InstanceMethods
|
|
72
|
+
|
|
73
|
+
adv_attr_accessor :layout
|
|
74
|
+
alias_method_chain :render_message, :layouts
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
ActionMailer::Base.send :include, ActionMailer::Layouts
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'awesome_email', 'layouts.rb')
|
|
2
|
+
require File.join(File.dirname(__FILE__), 'awesome_email', 'inline_styles.rb')
|
|
3
|
+
require File.join(File.dirname(__FILE__), 'awesome_email', 'convert_entities.rb')
|
|
4
|
+
require File.join(File.dirname(__FILE__), 'awesome_email', 'helpers.rb')
|
data/rails/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'lib', '..', 'awesome_email.rb'))
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
$KCODE = 'u' unless RUBY_VERSION >= '1.9'
|
|
3
|
+
|
|
4
|
+
require 'rubygems'
|
|
5
|
+
|
|
6
|
+
gem 'actionmailer', '>= 2.0.1'
|
|
7
|
+
gem 'actionpack', '>= 2.0.1'
|
|
8
|
+
|
|
9
|
+
require 'action_mailer'
|
|
10
|
+
require 'action_view'
|
|
11
|
+
|
|
12
|
+
require 'awesome_email'
|
|
13
|
+
|
|
14
|
+
require 'test/unit'
|
|
15
|
+
require 'test_helper'
|
|
16
|
+
|
|
17
|
+
ActionMailer::Base.delivery_method = :test
|
|
18
|
+
|
|
19
|
+
RAILS_ROOT = '/' << File.join('some', 'dir')
|
|
20
|
+
CSS_TEST_FILE = File.join(RAILS_ROOT, 'public', 'stylesheets', 'mails', 'test.css')
|
|
21
|
+
|
|
22
|
+
#################################################################
|
|
23
|
+
|
|
24
|
+
# Do some mocking, use Mocha here?
|
|
25
|
+
class SimpleMailer < ActionMailer::Base
|
|
26
|
+
|
|
27
|
+
def test
|
|
28
|
+
setup_multipart_mail
|
|
29
|
+
layout 'test'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
def setup_multipart_mail
|
|
35
|
+
headers 'Content-transfer-encoding' => '8bit'
|
|
36
|
+
sent_on Time.now
|
|
37
|
+
content_type 'text/html'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def html_part?(method_name)
|
|
41
|
+
true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def render_content_for_layout(method_name, template)
|
|
45
|
+
'test inner content'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# mock rendering
|
|
49
|
+
def render_layout_template(template, method_name, layout_path = File.join('layouts', 'mailers'))
|
|
50
|
+
return template.render(:inline => "<html><body><h1>Fäncy</h1><p><%= yield %></p></body></html>")
|
|
51
|
+
html = %{
|
|
52
|
+
<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">
|
|
53
|
+
<html>
|
|
54
|
+
<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"></head>
|
|
55
|
+
<body><p><%= yield %></p></body>
|
|
56
|
+
</html>
|
|
57
|
+
}
|
|
58
|
+
return template.render(:inline => html)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
###############################################################
|
|
64
|
+
|
|
65
|
+
# test mailer
|
|
66
|
+
class MyMailer
|
|
67
|
+
def render_message(method_name, body)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def parse_css_from_file(file_name)
|
|
71
|
+
'h1 {font-size: 140.0% !important}'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def mailer_name
|
|
75
|
+
'my_mailer'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# include neccessary mixins
|
|
79
|
+
include ActionMailer::AdvAttrAccessor
|
|
80
|
+
include ActionMailer::ConvertEntities
|
|
81
|
+
include ActionMailer::InlineStyles
|
|
82
|
+
include ActionMailer::Layouts
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
MyMailer.send(:public, *MyMailer.protected_instance_methods)
|
|
86
|
+
MyMailer.send(:public, *MyMailer.private_instance_methods)
|
|
87
|
+
|
|
88
|
+
###############################################################
|
|
89
|
+
|
|
90
|
+
# not so great actually, please do help improve this
|
|
91
|
+
class AwesomeEmailTest < Test::Unit::TestCase
|
|
92
|
+
|
|
93
|
+
def setup
|
|
94
|
+
ActionMailer::Base.delivery_method = :test
|
|
95
|
+
ActionMailer::Base.perform_deliveries = true
|
|
96
|
+
ActionMailer::Base.deliveries = []
|
|
97
|
+
@css = 'h1 {font-size: 140.0% !important}'
|
|
98
|
+
@mailer = MyMailer.new
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#######################
|
|
102
|
+
# inline styles tests #
|
|
103
|
+
#######################
|
|
104
|
+
|
|
105
|
+
def test_should_build_correct_find_css_file_name
|
|
106
|
+
|
|
107
|
+
assert_equal CSS_TEST_FILE, @mailer.build_css_file_name("test")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_should_build_correct_file_name_from_set_css
|
|
111
|
+
@mailer.css 'test'
|
|
112
|
+
assert_equal CSS_TEST_FILE, @mailer.build_css_file_name_from_css_setting
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_should_build_no_file_name_if_css_not_set
|
|
116
|
+
assert_equal '', @mailer.build_css_file_name_from_css_setting
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_should_not_change_html_if_no_styles_were_found
|
|
120
|
+
html = build_html('', '')
|
|
121
|
+
result = render_inline(html)
|
|
122
|
+
assert_not_nil result
|
|
123
|
+
assert_equal html.gsub(/\n/, ''), result.gsub(/\n/, '')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_should_add_style_information_found_in_css_file
|
|
127
|
+
html = build_html('<h1>bla</h1>')
|
|
128
|
+
result = render_inline(html)
|
|
129
|
+
assert_not_nil result
|
|
130
|
+
assert_not_equal html, result
|
|
131
|
+
assert result =~ /<h1 style="(.*)font-size:/
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def test_should_find_matching_rules
|
|
135
|
+
rules = find_rules(build_html('', '<h1>bla</h1>'))
|
|
136
|
+
assert rules.size > 0
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_should_create_css_for_h1
|
|
140
|
+
rules = find_rules(build_html('<h1>bla</h1>'))
|
|
141
|
+
css = @mailer.css_for_rule(rules.first)
|
|
142
|
+
assert_not_nil css
|
|
143
|
+
assert_equal 'font-size: 140.0% !important;', css
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_should_cummulate_style_information
|
|
147
|
+
html = build_html(%Q{<h1 id="oh-hai" class="green-thing" style="border-bottom: 1px solid black;">u haz a flavor</h1>})
|
|
148
|
+
inlined = render_inline(html)
|
|
149
|
+
assert inlined =~ /border-bottom/
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
##########################
|
|
153
|
+
# convert entities tests #
|
|
154
|
+
##########################
|
|
155
|
+
|
|
156
|
+
def test_should_replace_entities
|
|
157
|
+
expected = 'ä Ä'
|
|
158
|
+
result = @mailer.convert_to_entities('ä Ä')
|
|
159
|
+
assert_equal expected, result
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
################
|
|
163
|
+
# layout tests #
|
|
164
|
+
################
|
|
165
|
+
|
|
166
|
+
def test_should_extend_with_mailer_name
|
|
167
|
+
template_name = 'some_mail'
|
|
168
|
+
result = @mailer.extend_with_mailer_name(template_name)
|
|
169
|
+
assert_equal "my_mailer/#{template_name}", result
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# make sure the accessors are available
|
|
173
|
+
def test_should_have_awesome_email_accessor_methods
|
|
174
|
+
if RUBY_VERSION >= '1.9'
|
|
175
|
+
assert ActionMailer::Base.instance_methods.include?(:'css')
|
|
176
|
+
assert ActionMailer::Base.instance_methods.include?(:'css=')
|
|
177
|
+
assert ActionMailer::Base.instance_methods.include?(:'layout')
|
|
178
|
+
assert ActionMailer::Base.instance_methods.include?(:'layout=')
|
|
179
|
+
else
|
|
180
|
+
assert ActionMailer::Base.instance_methods.include?('css')
|
|
181
|
+
assert ActionMailer::Base.instance_methods.include?('css=')
|
|
182
|
+
assert ActionMailer::Base.instance_methods.include?('layout')
|
|
183
|
+
assert ActionMailer::Base.instance_methods.include?('layout=')
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# check for delivery errors
|
|
188
|
+
def test_should_deliver
|
|
189
|
+
SimpleMailer.deliver_test
|
|
190
|
+
assert SimpleMailer.deliveries.size > 0
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# test all of the awesomeness
|
|
194
|
+
def test_should_render_layout_convert_entities_and_apply_css
|
|
195
|
+
SimpleMailer.deliver_test
|
|
196
|
+
assert SimpleMailer.deliveries.last.body =~ /<h1>Fäncy<\/h1><p>test inner content<\/p>/
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
end
|
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
$KCODE = 'u' unless RUBY_VERSION >= '1.9'
|
|
3
|
+
|
|
4
|
+
class Test::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def find_rules(html)
|
|
9
|
+
css_doc = @mailer.parse_css_doc(@css)
|
|
10
|
+
html_doc = @mailer.parse_html_doc(html)
|
|
11
|
+
css_doc.rule_sets
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def render_inline(html)
|
|
15
|
+
css_doc = @mailer.parse_css_doc(@css)
|
|
16
|
+
html_doc = @mailer.parse_html_doc(html)
|
|
17
|
+
@mailer.render_inline(css_doc, html_doc)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def build_html(content = '', head = '')
|
|
21
|
+
%Q{<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\"><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">#{head}</head><body>#{content}</body></html>}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: grimen-awesome_email
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- imedo GmbH
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-08-31 00:00:00 -07:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
16
|
+
description: Rails ActionMailer with HTML layouts, inline CSS and entity substitution.
|
|
17
|
+
email: entwickler@imedo.de
|
|
18
|
+
executables: []
|
|
19
|
+
|
|
20
|
+
extensions: []
|
|
21
|
+
|
|
22
|
+
extra_rdoc_files:
|
|
23
|
+
- README.textile
|
|
24
|
+
files:
|
|
25
|
+
- MIT-LICENSE
|
|
26
|
+
- README.textile
|
|
27
|
+
- Rakefile
|
|
28
|
+
- lib/awesome_email.rb
|
|
29
|
+
- lib/awesome_email/convert_entities.rb
|
|
30
|
+
- lib/awesome_email/helpers.rb
|
|
31
|
+
- lib/awesome_email/inline_styles.rb
|
|
32
|
+
- lib/awesome_email/layouts.rb
|
|
33
|
+
- rails/init.rb
|
|
34
|
+
- test/awesome_email_test.rb
|
|
35
|
+
- test/test_helper.rb
|
|
36
|
+
has_rdoc: true
|
|
37
|
+
homepage: http://github.com/grimen/awesome_email/tree/master
|
|
38
|
+
post_install_message:
|
|
39
|
+
rdoc_options:
|
|
40
|
+
- --charset=UTF-8
|
|
41
|
+
require_paths:
|
|
42
|
+
- lib
|
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: "0"
|
|
48
|
+
version:
|
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: "0"
|
|
54
|
+
version:
|
|
55
|
+
requirements: []
|
|
56
|
+
|
|
57
|
+
rubyforge_project:
|
|
58
|
+
rubygems_version: 1.2.0
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 2
|
|
61
|
+
summary: Rails ActionMailer with HTML layouts, inline CSS and entity substitution.
|
|
62
|
+
test_files:
|
|
63
|
+
- test/awesome_email_test.rb
|
|
64
|
+
- test/test_helper.rb
|