plain-david 0.0.1

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.
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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module PlainDavid
2
+ VERSION = "0.0.1"
3
+ end
@@ -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
@@ -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: []