plain-david 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +51 -0
- data/lib/plain-david.rb +1 -0
- data/lib/plain_david/action_mailer_extensions.rb +52 -0
- data/lib/plain_david/railtie.rb +16 -0
- data/lib/plain_david/strategies/markdown_strategy.rb +17 -0
- data/lib/plain_david/strategies/plain_strategy.rb +127 -0
- data/lib/plain_david/version.rb +3 -0
- data/lib/plain_david.rb +19 -0
- data/plain-david.gemspec +22 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 64f568f2fbb872d1fff35baa76c1aca6cc500783
|
4
|
+
data.tar.gz: d4743a120e6ee5939099884b7a6f3efce990b5a1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 50cc8f751a1a7c4f96551d3ef3b21cfeb8ae5ffb8b6c9f0ff7e4302850672d8d85658cdf4645b3db4ed1414495016258c907a20994e2acd18a1745ba776c2ca2
|
7
|
+
data.tar.gz: 4ec7e5b0e52696f60daa561f4a99de99bb2e12b7b0a5cb9b07ec6fbb008d891ada7e6cf7bf0c51546d28258ec73d7ec3eef63f68ba5b07a7ed69c3ca3b27bf63
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright © 2013 Luca Spiller
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Plain David
|
2
|
+
|
3
|
+
Plain David automatically generatess a text part for your HTML emails.
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
Add the gem to Rails' Gemfile
|
8
|
+
|
9
|
+
gem 'plain-david', github: 'lucaspiller/plain-david'
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Nothing. Just send emails as normal, and a text part will be automatically generated. The email will be changed from HTML to multipart like magic!
|
14
|
+
|
15
|
+
It works seamlessly with css inliners like [Roadie](https://github.com/kandadaboggu/roadie).
|
16
|
+
|
17
|
+
## Conversion Strategies
|
18
|
+
|
19
|
+
You can set your own conversion strategy. The default `MarkdownStrategy` converts the HTML to Markdown. If you want to use your own you just need to set the `strategy` option.
|
20
|
+
|
21
|
+
For example, in your `application.rb`:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class AwesomeStrategy
|
25
|
+
attr_accessor :html
|
26
|
+
|
27
|
+
def initialize(html)
|
28
|
+
@html = html
|
29
|
+
end
|
30
|
+
|
31
|
+
def convert!
|
32
|
+
@html.awesomize!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
config.plain_david.strategy = AwesomeStrategy
|
37
|
+
```
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
|
41
|
+
* Fork the project.
|
42
|
+
* Make your feature addition or bug fix.
|
43
|
+
* Send me a pull request. Bonus points for topic branches.
|
44
|
+
|
45
|
+
## License
|
46
|
+
|
47
|
+
MIT License. See `LICENSE` for details.
|
48
|
+
|
49
|
+
## Copyright
|
50
|
+
|
51
|
+
Copyright (c) 2013 Luca Spiller.
|
data/lib/plain-david.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'plain_david'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module PlainDavid
|
2
|
+
module ActionMailerExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
if method_defined?(:collect_responses)
|
7
|
+
alias_method_chain :collect_responses, :text_part
|
8
|
+
else
|
9
|
+
alias_method_chain :collect_responses_and_parts_order, :text_part
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
# Rails 4
|
16
|
+
def collect_responses_with_text_part(headers, &block)
|
17
|
+
responses = collect_responses_without_text_part(headers, &block)
|
18
|
+
|
19
|
+
html_part, text_part = detect_parts(responses)
|
20
|
+
if html_part && !text_part
|
21
|
+
text_body = generate_text_body(html_part[:body])
|
22
|
+
responses.insert 0, { content_type: "text/plain", body: text_body }
|
23
|
+
end
|
24
|
+
|
25
|
+
responses
|
26
|
+
end
|
27
|
+
|
28
|
+
# Rails 3
|
29
|
+
def collect_responses_and_parts_order_with_text_part(headers, &block)
|
30
|
+
responses, order = collect_responses_and_parts_order_without_text_part(headers, &block)
|
31
|
+
|
32
|
+
html_part, text_part = detect_parts(responses)
|
33
|
+
if html_part && !text_part
|
34
|
+
text_body = generate_text_body(html_part[:body])
|
35
|
+
responses.insert 0, { content_type: "text/plain", body: text_body }
|
36
|
+
order && order.insert(0, "text/plain")
|
37
|
+
end
|
38
|
+
|
39
|
+
[responses, order]
|
40
|
+
end
|
41
|
+
|
42
|
+
def detect_parts(responses)
|
43
|
+
html_part = responses.detect { |response| response[:content_type] == "text/html" }
|
44
|
+
text_part = responses.detect { |response| response[:content_type] == "text/plain" }
|
45
|
+
return html_part, text_part
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_text_body(html)
|
49
|
+
PlainDavid.current_strategy.new(html.to_str).convert!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'action_mailer'
|
2
|
+
require 'plain_david'
|
3
|
+
require 'plain_david/action_mailer_extensions'
|
4
|
+
|
5
|
+
module PlainDavid
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
config.plain_david = ActiveSupport::OrderedOptions.new
|
8
|
+
config.plain_david.strategy = PlainDavid::Strategies::MarkdownStrategy
|
9
|
+
|
10
|
+
initializer "plain_david.extend_action_mailer" do
|
11
|
+
ActiveSupport.on_load(:action_mailer) do
|
12
|
+
include PlainDavid::ActionMailerExtensions
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'html2markdown'
|
2
|
+
|
3
|
+
module PlainDavid
|
4
|
+
module Strategies
|
5
|
+
class MarkdownStrategy
|
6
|
+
attr_accessor :html
|
7
|
+
|
8
|
+
def initialize(html)
|
9
|
+
@html = html
|
10
|
+
end
|
11
|
+
|
12
|
+
def convert!
|
13
|
+
HTMLPage.new(:contents => html).markdown!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'htmlentities'
|
3
|
+
|
4
|
+
# Taken from premailer
|
5
|
+
|
6
|
+
# Support functions for Premailer
|
7
|
+
module PlainDavid
|
8
|
+
module Strategies
|
9
|
+
class PlainStrategy
|
10
|
+
attr_accessor :html
|
11
|
+
|
12
|
+
def initialize(html)
|
13
|
+
@html = html
|
14
|
+
end
|
15
|
+
|
16
|
+
def convert!
|
17
|
+
convert_to_text(html)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
# Returns the text in UTF-8 format with all HTML tags removed
|
22
|
+
#
|
23
|
+
# TODO: add support for DL, OL
|
24
|
+
def convert_to_text(html, line_length = 65, from_charset = 'UTF-8')
|
25
|
+
txt = html
|
26
|
+
|
27
|
+
# decode HTML entities
|
28
|
+
he = HTMLEntities.new
|
29
|
+
txt = he.decode(txt)
|
30
|
+
|
31
|
+
# remove the head tag
|
32
|
+
txt.gsub!(/<head>.+?<\/head>/mi, "")
|
33
|
+
# remove style tags
|
34
|
+
txt.gsub!(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/mi, "")
|
35
|
+
# remove script tags
|
36
|
+
txt.gsub!(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi, "")
|
37
|
+
|
38
|
+
# replace image by their alt attribute
|
39
|
+
txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\/>/i, '\1')
|
40
|
+
|
41
|
+
# replace image by their alt attribute
|
42
|
+
txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\/>/i, '\1')
|
43
|
+
txt.gsub!(/<img.+?alt='([^\']*)\'[^>]*\/>/i, '\1')
|
44
|
+
|
45
|
+
# links
|
46
|
+
txt.gsub!(/<a.+?href=\"([^\"]*)\"[^>]*>(.+?)<\/a>/i) do |s|
|
47
|
+
$2.strip + ' ( ' + $1.strip + ' )'
|
48
|
+
end
|
49
|
+
|
50
|
+
txt.gsub!(/<a.+?href='([^\']*)\'[^>]*>(.+?)<\/a>/i) do |s|
|
51
|
+
$2.strip + ' ( ' + $1.strip + ' )'
|
52
|
+
end
|
53
|
+
|
54
|
+
# handle headings (H1-H6)
|
55
|
+
txt.gsub!(/(<\/h[1-6]>)/i, "\n\\1") # move closing tags to new lines
|
56
|
+
txt.gsub!(/[\s]*<h([1-6]+)[^>]*>[\s]*(.*)[\s]*<\/h[1-6]+>/i) do |s|
|
57
|
+
hlevel = $1.to_i
|
58
|
+
|
59
|
+
htext = $2
|
60
|
+
htext.gsub!(/<br[\s]*\/?>/i, "\n") # handle <br>s
|
61
|
+
htext.gsub!(/<\/?[^>]*>/i, '') # strip tags
|
62
|
+
|
63
|
+
# determine maximum line length
|
64
|
+
hlength = 0
|
65
|
+
htext.each_line { |l| llength = l.strip.length; hlength = llength if llength > hlength }
|
66
|
+
hlength = line_length if hlength > line_length
|
67
|
+
|
68
|
+
case hlevel
|
69
|
+
when 1 # H1, asterisks above and below
|
70
|
+
htext = ('*' * hlength) + "\n" + htext + "\n" + ('*' * hlength)
|
71
|
+
when 2 # H1, dashes above and below
|
72
|
+
htext = ('-' * hlength) + "\n" + htext + "\n" + ('-' * hlength)
|
73
|
+
else # H3-H6, dashes below
|
74
|
+
htext = htext + "\n" + ('-' * hlength)
|
75
|
+
end
|
76
|
+
|
77
|
+
"\n\n" + htext + "\n\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
# wrap spans
|
81
|
+
txt.gsub!(/(<\/span>)[\s]+(<span)/mi, '\1 \2')
|
82
|
+
|
83
|
+
# lists -- TODO: should handle ordered lists
|
84
|
+
txt.gsub!(/[\s]*(<li[^>]*>)[\s]*/i, '* ')
|
85
|
+
# list not followed by a newline
|
86
|
+
txt.gsub!(/<\/li>[\s]*(?![\n])/i, "\n")
|
87
|
+
|
88
|
+
# paragraphs and line breaks
|
89
|
+
txt.gsub!(/<\/p>/i, "\n\n")
|
90
|
+
txt.gsub!(/<br[\/ ]*>/i, "\n")
|
91
|
+
|
92
|
+
# strip remaining tags
|
93
|
+
txt.gsub!(/<\/?[^>]*>/, '')
|
94
|
+
|
95
|
+
txt = word_wrap(txt, line_length)
|
96
|
+
|
97
|
+
# remove linefeeds (\r\n and \r -> \n)
|
98
|
+
txt.gsub!(/\r\n?/, "\n")
|
99
|
+
|
100
|
+
# strip extra spaces
|
101
|
+
txt.gsub!(/\302\240+/, " ") # non-breaking spaces -> spaces
|
102
|
+
txt.gsub!(/\n[ \t]+/, "\n") # space at start of lines
|
103
|
+
txt.gsub!(/[ \t]+\n/, "\n") # space at end of lines
|
104
|
+
|
105
|
+
# no more than two consecutive newlines
|
106
|
+
txt.gsub!(/[\n]{3,}/, "\n\n")
|
107
|
+
|
108
|
+
# no more than two consecutive spaces
|
109
|
+
txt.gsub!(/ {2,}/, " ")
|
110
|
+
|
111
|
+
# the word messes up the parens
|
112
|
+
txt.gsub!(/\([ \n](http[^)]+)[\n ]\)/) do |s|
|
113
|
+
"( " + $1 + " )"
|
114
|
+
end
|
115
|
+
|
116
|
+
txt.strip
|
117
|
+
end
|
118
|
+
|
119
|
+
# Taken from Rails' word_wrap helper (http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-word_wrap)
|
120
|
+
def word_wrap(txt, line_length)
|
121
|
+
txt.split("\n").collect do |line|
|
122
|
+
line.length > line_length ? line.gsub(/(.{1,#{line_length}})(\s+|$)/, "\\1\n").strip : line
|
123
|
+
end * "\n"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/plain_david.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'plain_david/version'
|
2
|
+
require 'plain_david/strategies/markdown_strategy'
|
3
|
+
require 'plain_david/strategies/plain_strategy'
|
4
|
+
require 'plain_david/railtie' if defined?(Rails)
|
5
|
+
|
6
|
+
module PlainDavid
|
7
|
+
class << self
|
8
|
+
def current_strategy
|
9
|
+
return config.strategy if config.strategy
|
10
|
+
Strategies::MarkdownStrategy
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def config
|
16
|
+
Rails.application.config.plain_david
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/plain-david.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'plain_david/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "plain-david"
|
8
|
+
gem.version = PlainDavid::VERSION
|
9
|
+
gem.authors = ["Luca Spiller"]
|
10
|
+
gem.email = ["luca@stackednotion.com"]
|
11
|
+
gem.description = %q{Auto email plain text part generator}
|
12
|
+
gem.summary = %q{Auto email plain text part generator}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.add_dependency 'html2markdown'
|
16
|
+
gem.add_dependency 'htmlentities'
|
17
|
+
|
18
|
+
gem.files = `git ls-files`.split($/)
|
19
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
20
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
21
|
+
gem.require_paths = ["lib"]
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: plain-david
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luca Spiller
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: html2markdown
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: htmlentities
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Auto email plain text part generator
|
42
|
+
email:
|
43
|
+
- luca@stackednotion.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- LICENSE
|
49
|
+
- README.md
|
50
|
+
- lib/plain-david.rb
|
51
|
+
- lib/plain_david.rb
|
52
|
+
- lib/plain_david/action_mailer_extensions.rb
|
53
|
+
- lib/plain_david/railtie.rb
|
54
|
+
- lib/plain_david/strategies/markdown_strategy.rb
|
55
|
+
- lib/plain_david/strategies/plain_strategy.rb
|
56
|
+
- lib/plain_david/version.rb
|
57
|
+
- plain-david.gemspec
|
58
|
+
homepage: ''
|
59
|
+
licenses: []
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.0.7
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Auto email plain text part generator
|
81
|
+
test_files: []
|