muddle 1.0.0rc1

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.
@@ -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: