muddle 1.0.0rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ # Changes
2
+
3
+ ## 1.0.0rc1
4
+
5
+ * Initial Release.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in muddle.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Ben Hamill
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,220 @@
1
+ # Muddle
2
+
3
+ Email clients are not web browsers. They render html all funny, to put it
4
+ politely. In general, the best practices for writing HTML that will look good
5
+ in an email are the exact inverse from those that you should use for a web
6
+ page. Remembering all those differences sucks.
7
+
8
+ With muddle, we're trying to make it so that the only thing you have to know is
9
+ to **use tables in your emails**. Muddle will take care of the rest. It uses
10
+ ideas from [HTML Email Boilerplate](http://htmlemailboilerplate.com/) to help
11
+ you get your emails in line without having to know tons about how clients
12
+ render it.
13
+
14
+ * CSS will be inlined using premailer, so you can use external style sheets as
15
+ you normally would.
16
+ * HTML elements will be augmented with all the attributes they need for email,
17
+ so you don't need to worry about ensuring all your anchor tags have `_target`
18
+ set, etc.
19
+ * The resulting html document will be checked for tags that don't play well in
20
+ email (like `div`).
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's `Gemfile`:
25
+
26
+ gem 'muddle'
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself with:
33
+
34
+ $ gem install muddle
35
+
36
+ ## Usage
37
+
38
+ ### The Basics
39
+
40
+ However you're sending email, you'll want to get what you intend to be the html
41
+ body of your email into a variable. How you do that is up to you. Say you have
42
+ a [SLIM](http://slim-lang.com) template and you're using
43
+ [Mail](https://github.com/mikel/mail) to build and send your emails:
44
+
45
+ ```ruby
46
+ require 'muddle'
47
+ require 'slim'
48
+ require 'mail'
49
+
50
+ body = Slim::Template('path/to/welcome_email.html.slim').render
51
+
52
+ # This is really the only thing Muddle is involved in.
53
+ muddled_body = Muddle.parse(body)
54
+
55
+ email = Mail.new do
56
+ to 'some_new_customer@gmail.com'
57
+ from 'welcome@awesome_web_service.com'
58
+ subject 'Welcome!!!!!!!!!!1!!!!one!!!'
59
+
60
+ html_part do
61
+ body muddled_body
62
+ content_type 'text/html; charset=UTF-8'
63
+ end
64
+ end
65
+ ```
66
+
67
+ If you're using `ActionMailer`, you could do like this:
68
+
69
+ ``` ruby
70
+ class UserMailer < ActionMailer::Base
71
+ def welcome_email
72
+ mail(
73
+ to: 'some_new_customer@gmail.com',
74
+ from: 'welcome@awesome_web_service.com',
75
+ subject: 'Welcome!!!!!!!!!!1!!!!one!!!'
76
+ ) do |format|
77
+ format.html { Muddle.parse(render) }
78
+ end
79
+ end
80
+ end
81
+ ```
82
+
83
+ ### Configuration
84
+
85
+ You can configure Muddle with a block. Maybe throw this in an
86
+ initializer of some sort. Here are all the defaults:
87
+
88
+ ```ruby
89
+ Muddle.configure do |config|
90
+ config.parse_with_premailer = true
91
+ config.insert_boilerplate_styles = true
92
+ config.insert_boilerplate_css = true
93
+ config.insert_boilerplate_attributes = true
94
+ config.validate_html = true
95
+ config.generate_plain_text = false
96
+ config.logger = nil
97
+
98
+ config.premailer_options = {
99
+ :remove_comments => true,
100
+ :with_html_string => true,
101
+ :adapter => :hpricot
102
+ }
103
+ end
104
+ ```
105
+
106
+ ### Writing An Email
107
+
108
+ For best results, just start writing your email with a table tag and move in
109
+ from there. Muddler will handle putting the `xmlns` and `DOCTYPE` and a bunch
110
+ of stuff into the `<head>`, then open the `<body>` for you. It will also close
111
+ these tags at the end.
112
+
113
+ For example, if you have a template the ends up like this:
114
+
115
+ ```html
116
+ <html>
117
+ <body>
118
+ <table>
119
+ <tbody>
120
+ <tr>
121
+ <td><h1>Welcome to our AWESOME NEW WEB SERVICE!</h1></td>
122
+ </tr>
123
+ <tr>
124
+ <td><p>You should come <a href="http://awesome_web_service.com">check us out</a>.</p></td>
125
+ </tr>
126
+ </tbody>
127
+ </table>
128
+ </body>
129
+ </html>
130
+ ```
131
+
132
+ Muddle will spit out this:
133
+
134
+ ```html
135
+ <html xmlns="http://www.w3.org/1999/xhtml">
136
+ <head>
137
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
138
+ </head>
139
+ <body style="width: 100% !important; margin: 0; padding: 0; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;">
140
+ <table cellpadding="0" cellspacing="0" border="0" align="center">
141
+ <tbody>
142
+ <tr>
143
+ <td valign="top"><h1 style="color: black !important;">Welcome to our AWESOME NEW WEB SERVICE!</h1></td>
144
+ </tr>
145
+ <tr>
146
+ <td valign="top"><p style="margin: 1em 0;">You should <a href="http://awesome_web_service.com" style="color: blue;" target="_blank">check us out</a>.</p></td>
147
+ </tr>
148
+ </tbody>
149
+ </table>
150
+
151
+ <style type="text/css">
152
+ /* Boilerplate CSS for BODY */
153
+
154
+ #outlook a {padding:0;}
155
+ #backgroundTable {margin:0; padding:0; width:100% !important; line-height: 100% !important;}
156
+
157
+ .ExternalClass {width:100%;}
158
+ .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
159
+ .image_fix {display:block;}
160
+
161
+ h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: red !important;}
162
+ h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: purple !important;}
163
+
164
+ @media only screen and (max-device-width: 480px) {
165
+ a[href^="tel"], a[href^="sms"] {
166
+ text-decoration: none;
167
+ color: blue;
168
+ pointer-events: none;
169
+ cursor: default;
170
+ }
171
+ .mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
172
+ text-decoration: default;
173
+ color: orange !important;
174
+ pointer-events: auto;
175
+ cursor: default;
176
+ }
177
+ }
178
+ @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
179
+ a[href^="tel"], a[href^="sms"] {
180
+ text-decoration: none;
181
+ color: blue;
182
+ pointer-events: none;
183
+ cursor: default;
184
+ }
185
+ .mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
186
+ text-decoration: default;
187
+ color: orange !important;
188
+ pointer-events: auto;
189
+ cursor: default;
190
+ }
191
+ }
192
+ </style>
193
+ <style type="text/css">
194
+ body { width: 100% !important; -webkit-text-size-adjust: 100% !important; -ms-text-size-adjust: 100% !important; margin: 0 !important; padding: 0 !important; }
195
+ img { outline: none !important; text-decoration: none !important; -ms-interpolation-mode: bicubic !important; }
196
+ </style>
197
+ </body>
198
+ </html>
199
+ ```
200
+
201
+
202
+ ## To Do
203
+
204
+ * naughty tag warnings
205
+ * performance tests
206
+ * test external CSS resource handling
207
+ * test if premailer is making image URI's absolute where possible
208
+ * complain about images with relative urls
209
+ * complain about image not having alt, height, width
210
+ * create background attribute from css where relevant
211
+ * check for lines starting with a period
212
+
213
+
214
+ ## Contributing
215
+
216
+ 1. Fork it
217
+ 2. Create your feature branch (`$ git checkout -b my-new-feature`)
218
+ 3. Commit your changes (`$ git commit -am 'Added some feature'`)
219
+ 4. Push to the branch (`$ git push origin my-new-feature`)
220
+ 5. Create new Pull Request
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
3
+
4
+ require "muddle/version"
5
+
6
+ require "muddle/filter"
7
+ require "muddle/parser"
8
+ require "muddle/configuration"
9
+ require 'muddle/logger'
10
+
11
+ module Muddle
12
+ # Top-level configuration function
13
+ #
14
+ # Pass it a block and the configuration object will yield 'self'
15
+ #
16
+ def self.configure(&block)
17
+ config.configure(&block)
18
+ end
19
+
20
+ def self.config
21
+ @config ||= Muddle::Configuration.new
22
+ end
23
+
24
+ # Top-level parser function
25
+ #
26
+ # body_string should be an email body in string form
27
+ #
28
+ # returns body_string after passing it through the filters defined in Parser.filters
29
+ #
30
+ def self.parse(body_string)
31
+ parser.parse body_string
32
+ end
33
+
34
+ def self.parser
35
+ @parser ||= Muddle::Parser.new
36
+ end
37
+ end
@@ -0,0 +1,43 @@
1
+ class Muddle::Configuration
2
+ attr_accessor :parse_with_premailer
3
+ attr_accessor :insert_boilerplate_styles
4
+ attr_accessor :insert_boilerplate_css
5
+ attr_accessor :insert_boilerplate_attributes
6
+ attr_accessor :validate_html
7
+ attr_accessor :generate_plain_text
8
+ attr_accessor :logger
9
+
10
+ attr_accessor :premailer_options
11
+
12
+ # Initialize the configuration object with default values
13
+ #
14
+ # if a block is passed, we'll yield 'this' to it so you can set config values
15
+ #
16
+ def initialize(options = {})
17
+ @parse_with_premailer = options[:parse_with_premailer] || true
18
+ @insert_boilerplate_styles = options[:insert_boilerplate_styles] || true
19
+ @insert_boilerplate_css = options[:insert_boilerplate_css] || true
20
+ @insert_boilerplate_attributes = options[:insert_boilerplate_attributes] || true
21
+ @validate_html = options[:validate_html] || true
22
+ @generate_plain_text = options[:generate_plain_text] || false
23
+ @logger = options[:logger]
24
+
25
+ # NOTE: when this tries to inline CSS, all it sees is a stylesheet URL
26
+ # This may require that we download css from the interwebs @ each render
27
+ # pass of a mailer ?!?!?
28
+ @premailer_options = {
29
+ :remove_comments => true, # Env-dependent?
30
+ :with_html_string => true,
31
+ :adapter => :hpricot
32
+ }
33
+ end
34
+
35
+ # Set config vars
36
+ #
37
+ # Pass it a block, will yield the config object
38
+ #
39
+ def configure
40
+ yield self
41
+ self
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ module Muddle::Filter
2
+ # Prepend the content if child nodes exist, otherwise insert it
3
+ #
4
+ def prepend_or_insert(doc, content)
5
+ unless doc.empty?
6
+ doc.children.first.before(content)
7
+ else
8
+ doc.inner_html(content)
9
+ end
10
+ end
11
+
12
+ # Append the content if child nodes exist, otherwise insert it
13
+ #
14
+ def append_or_insert(doc, content)
15
+ unless doc.empty?
16
+ doc.children.last.after(content)
17
+ else
18
+ doc.inner_html(content)
19
+ end
20
+ end
21
+
22
+
23
+ # Find `selector` within `doc`. If not found, create using `with` as the
24
+ # last child of `doc`
25
+ #
26
+ def find_or_append(doc, selector, opts, &block)
27
+ append_or_insert(doc, opts[:with]) if doc.search(selector).empty?
28
+ yield doc.search(selector)
29
+ end
30
+
31
+ # Find `selector` within `doc` if not found, create using `with` as the
32
+ # first child of `doc`
33
+ #
34
+ # yields to `block` and passes the found/created element
35
+ #
36
+ def find_or_prepend(doc, selector, opts, &block)
37
+ prepend_or_insert(doc, opts[:with]) if doc.search(selector).empty?
38
+ yield doc.search(selector)
39
+ end
40
+ end
41
+
42
+ require 'muddle/filter/boilerplate_attributes'
43
+ require 'muddle/filter/boilerplate_css'
44
+ require 'muddle/filter/boilerplate_style_element'
45
+ require 'muddle/filter/premailer'
46
+ require 'muddle/filter/schema_validation'
@@ -0,0 +1,24 @@
1
+ require 'hpricot'
2
+
3
+ module Muddle::Filter::BoilerplateAttributes
4
+ def self.filter(body_string)
5
+ doc = Hpricot(body_string)
6
+
7
+ ensure_node_includes(doc, 'table', 'cellpadding', '0')
8
+ ensure_node_includes(doc, 'table', 'cellspacing', '0')
9
+ ensure_node_includes(doc, 'table', 'border', '0')
10
+ ensure_node_includes(doc, 'table', 'align', 'center')
11
+
12
+ ensure_node_includes(doc, 'td', 'valign', 'top')
13
+
14
+ ensure_node_includes(doc, 'a', 'target', '_blank')
15
+
16
+ doc.to_html
17
+ end
18
+
19
+ def self.ensure_node_includes(doc, element_selector, attribute, default_value)
20
+ doc.search("#{element_selector}:not([@#{attribute}])").each do |node|
21
+ node.attributes[attribute] = default_value
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ require 'hpricot'
2
+
3
+ module Muddle::Filter::BoilerplateCSS
4
+ extend Muddle::Filter
5
+
6
+ # Boilerplate CSS Filter
7
+ #
8
+ # Inserts a style tag containing the boilerplate CSS - we assume that this will
9
+ # later be filtered with the Premailer filter and inlined at that time.
10
+ #
11
+ # If the body_string doesn't look like an HTML file, we'll build as much structure
12
+ # as makes sense in the context of a style tag (ie at least enclosing <html> and <head> tags)
13
+ #
14
+ # The style tag will be inserted before any existing style tags so that any user-supplied
15
+ # CSS will over-write our boilerplate stuff
16
+ #
17
+ def self.filter(body_string)
18
+ doc = Hpricot(body_string)
19
+
20
+ insert_styles_to_inline(doc)
21
+
22
+ doc.to_html
23
+ end
24
+
25
+ def self.insert_styles_to_inline(doc)
26
+ find_or_append(doc, 'html', :with => '<html></html>') do |html|
27
+ find_or_prepend(html.first, 'head', :with => '<head></head>') do |head|
28
+ if node = head.search('style:first-of-type()').first
29
+ node.before('<style type="text/css"></style>')
30
+ else
31
+ prepend_or_insert(head.first, '<style type="text/css"></style>')
32
+ end
33
+
34
+ head.search('style:first-of-type()').inner_html(boilerplate_css)
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.boilerplate_css
40
+ @boilerplate_css ||= File.read(File.join(File.dirname(__FILE__), '..', 'resources', 'boilerplate_inline.css'))
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ require 'hpricot'
2
+
3
+ module Muddle::Filter::BoilerplateStyleElement
4
+ extend Muddle::Filter
5
+
6
+ def self.filter(body_string)
7
+ doc = Hpricot(body_string)
8
+
9
+ insert_style_block(doc)
10
+
11
+ doc.to_html
12
+ end
13
+
14
+ def self.insert_style_block(doc)
15
+ find_or_append(doc, 'html', :with => '<html></html>') do |html|
16
+ find_or_prepend(html.first, 'body', :with => '<body></body>') do |body|
17
+ if node = body.search('style:first-of-type()').first
18
+ node.before('<style type="text/css"></style>')
19
+ else
20
+ append_or_insert(body.first, '<style type="text/css"></style>')
21
+ end
22
+
23
+ body.search('style:first-of-type()').inner_html(boilerplate_css)
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.boilerplate_css
29
+ @boilerplate_css ||= File.read(File.join(File.dirname(__FILE__), '..', 'resources', 'boilerplate_style.css'))
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ require 'premailer'
2
+
3
+ module Muddle::Filter::Premailer
4
+ def self.filter(body_string)
5
+ premailer = Premailer.new(body_string, Muddle.config.premailer_options)
6
+
7
+ warn "Premailer generated #{premailer.warnings.length.to_s} warnings:" unless premailer.warnings.empty?
8
+
9
+ premailer.warnings.each do |w|
10
+ warn "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
11
+ end
12
+
13
+ premailer.to_inline_css
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ require 'nokogiri'
2
+
3
+ module Muddle::Filter::SchemaValidation
4
+ def self.filter(body_string)
5
+ doc = Nokogiri::XML(body_string)
6
+
7
+ if doc.internal_subset.nil?
8
+ doc.create_internal_subset("html", "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd")
9
+ end
10
+
11
+ doc.to_xhtml
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Muddle::Logger
2
+ def self.log(level, message)
3
+ return false unless logger
4
+ logger.send(level, message)
5
+ end
6
+
7
+ private
8
+
9
+ def self.logger
10
+ Muddle.config.logger
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ class Muddle::Parser
2
+ # Set up the parser
3
+ #
4
+ # The default filters do the following:
5
+ #
6
+ # CSS adds a style block containing boilerplate CSS attributes to be
7
+ # inlined. This is based on Email Boilerplate's 'Inline: YES' portions
8
+ #
9
+ # Premailer passes the email through the Premailer gem, which inlines the CSS
10
+ # it can, then appends a style block to the body containing the rest (since
11
+ # some email clients strip out the <head> tag).
12
+ #
13
+ # Style Element adds another style block, but this one is intended to be left
14
+ # as a style declaration (rather than being inlined). This is based on
15
+ # EMail Boilerplate's 'Inline: NO' portions
16
+ #
17
+ # Attributes adds attributes to HTML elements where helpful, such as table cellpadding
18
+ # and such. Based on Email Boilerplate's example element declarations
19
+ #
20
+ # Schema Validation currently just adds the XHTML Strict DTD and outputs to a string,
21
+ # however it's intended that this will eventually validate against an XSD and emit
22
+ # warnings about potentially troublesome tags (like <div>)
23
+ #
24
+ def initialize
25
+ @filters = []
26
+
27
+ @filters << Muddle::Filter::BoilerplateCSS if Muddle.config.insert_boilerplate_css
28
+ @filters << Muddle::Filter::Premailer if Muddle.config.parse_with_premailer
29
+ @filters << Muddle::Filter::BoilerplateStyleElement if Muddle.config.insert_boilerplate_styles
30
+ @filters << Muddle::Filter::BoilerplateAttributes if Muddle.config.insert_boilerplate_attributes
31
+ @filters << Muddle::Filter::SchemaValidation if Muddle.config.validate_html
32
+ end
33
+
34
+ # Parse an email body
35
+ #
36
+ # body_string is the email body to be parsed in string form
37
+ #
38
+ # Returns the parsed body string
39
+ #
40
+ def parse(body_string)
41
+ @filters.inject(body_string) do |filtered_string, filter|
42
+ s = filter.filter(filtered_string)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+
2
+ /* Boilerplate CSS for Inlining */
3
+
4
+ body{width:100%; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
5
+
6
+ img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
7
+ a img {border:none;}
8
+
9
+ p {margin: 1em 0;}
10
+
11
+ h1, h2, h3, h4, h5, h6 {color: black;}
12
+
13
+ h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color: blue;}
14
+
15
+ a {color: blue;}
16
+
@@ -0,0 +1,41 @@
1
+
2
+ /* Boilerplate CSS for HEAD */
3
+
4
+ #outlook a {padding:0;}
5
+ #backgroundTable {margin:0; padding:0; width:100%; line-height: 100%;}
6
+
7
+ .ExternalClass {width:100%;}
8
+ .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
9
+ .image_fix {display:block;}
10
+
11
+ h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: red;}
12
+ h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: purple;}
13
+
14
+ @media only screen and (max-device-width: 480px) {
15
+ a[href^="tel"], a[href^="sms"] {
16
+ text-decoration: none;
17
+ color: blue;
18
+ pointer-events: none;
19
+ cursor: default;
20
+ }
21
+ .mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
22
+ text-decoration: default;
23
+ color: orange;
24
+ pointer-events: auto;
25
+ cursor: default;
26
+ }
27
+ }
28
+ @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
29
+ a[href^="tel"], a[href^="sms"] {
30
+ text-decoration: none;
31
+ color: blue;
32
+ pointer-events: none;
33
+ cursor: default;
34
+ }
35
+ .mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
36
+ text-decoration: default;
37
+ color: orange;
38
+ pointer-events: auto;
39
+ cursor: default;
40
+ }
41
+ }
@@ -0,0 +1,7 @@
1
+ module Muddle
2
+ VERSION = "1.0.0rc1"
3
+
4
+ def self.version
5
+ VERSION
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,196 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: muddle
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0rc1
5
+ prerelease: 5
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Michael
9
+ - Ben Hamill
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-07-10 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: premailer
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 1.7.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 1.7.3
31
+ - !ruby/object:Gem::Dependency
32
+ name: nokogiri
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: 1.5.0
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 1.5.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: hpricot
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.6'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '0.6'
63
+ - !ruby/object:Gem::Dependency
64
+ name: css_parser
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ version: 1.2.6
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: 1.2.6
79
+ - !ruby/object:Gem::Dependency
80
+ name: rspec
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: email_spec
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: mail
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: pry
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ description: Email clients are not web browsers. They render html all funny, to put
144
+ it politely. In general, the best practices for writing HTML that will look good
145
+ in an email are the exact inverse from those that you should use for a web page.
146
+ Remembering all those differences sucks.
147
+ email:
148
+ - benhamill@otherinbox.com
149
+ executables: []
150
+ extensions: []
151
+ extra_rdoc_files: []
152
+ files:
153
+ - lib/muddle.rb
154
+ - lib/muddle/configuration.rb
155
+ - lib/muddle/filter.rb
156
+ - lib/muddle/filter/boilerplate_attributes.rb
157
+ - lib/muddle/filter/boilerplate_css.rb
158
+ - lib/muddle/filter/boilerplate_style_element.rb
159
+ - lib/muddle/filter/premailer.rb
160
+ - lib/muddle/filter/schema_validation.rb
161
+ - lib/muddle/logger.rb
162
+ - lib/muddle/parser.rb
163
+ - lib/muddle/resources/boilerplate_inline.css
164
+ - lib/muddle/resources/boilerplate_style.css
165
+ - lib/muddle/version.rb
166
+ - Gemfile
167
+ - LICENSE
168
+ - Rakefile
169
+ - README.md
170
+ - Changes.md
171
+ homepage: http://github.com/otherinbox/muddle
172
+ licenses: []
173
+ post_install_message:
174
+ rdoc_options: []
175
+ require_paths:
176
+ - lib
177
+ required_ruby_version: !ruby/object:Gem::Requirement
178
+ none: false
179
+ requirements:
180
+ - - ! '>='
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ none: false
185
+ requirements:
186
+ - - ! '>'
187
+ - !ruby/object:Gem::Version
188
+ version: 1.3.1
189
+ requirements: []
190
+ rubyforge_project:
191
+ rubygems_version: 1.8.24
192
+ signing_key:
193
+ specification_version: 3
194
+ summary: Never type all the annoying markup that emails demand again.
195
+ test_files: []
196
+ has_rdoc: