actionmailer-text 0.1.1 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fb51e8701b903d56ac693f378e34d38039b1cb09
4
- data.tar.gz: 3a9f8751dc64409876f495f453f2af443d8cb096
2
+ SHA256:
3
+ metadata.gz: a131c593f2e132ffc762a6b548ea188ad7312ed543d13bab0f6006aa1228559c
4
+ data.tar.gz: 5b653dc3f0ee2f1bc955452efa414c5cd79aa409b1377c78db21d0c203ab63c1
5
5
  SHA512:
6
- metadata.gz: 6e31e763f210563c237399ea3dc4a4ef7d928c977111dbd27ff2dc1fa4cc48987f3ab1c1085320d6b550a940fe293f28e206ccd144917d1801cad345fa6304ad
7
- data.tar.gz: 316873f7a5805e270ffc9bea597af3e60d54f62d04842947df66066b831dee11b5266ab113ea5899a925489ef5491aae44a0aee7ec98d3053b936d42b7f1212d
6
+ metadata.gz: ded5d23fc8c3824f93b5c0aa9e7757a24acda261241a91db9829503a3cc892f149476dd896e2d2105ff9a9d401733eecf3740e6777f6a0c25050b28c087e7483
7
+ data.tar.gz: 0ab108ace5b96f31c5f98afc72d758a9047b7f9d5f3e4b0b9592c320129d94dfcadcfff547ca03c99480c0598e4a5c2ea0bbad7d527b20b0d01bf3be78d4d26b
data/CHANGELOG.md CHANGED
@@ -1,7 +1,18 @@
1
- ### 0.1.1 (Next)
1
+ ### 0.2.0 (2026-10-19)
2
2
 
3
- * Your contribution here.
4
- * [#1](https://github.com/dblock/actionmailer-text/pull/1) Ignore elements outside the body - [@woahdae](https://github.com/woahdae).
3
+ * [#17](https://github.com/dblock/actionmailer-text/pull/17): Add a maintainer release guide - [@dblock](https://github.com/dblock).
4
+ * [#12](https://github.com/dblock/actionmailer-text/pull/12): Avoid backtracking when wrapping long tokens and preserve unbroken URLs, ported from [Kit/actionmailer-text](https://github.com/Kit/actionmailer-text) - [@scanferla](https://github.com/scanferla).
5
+ * [#14](https://github.com/dblock/actionmailer-text/pull/14): Support `generate_text only:` and `except:` to select mailer actions for automatic plaintext generation - [@dblock](https://github.com/dblock).
6
+ * [#13](https://github.com/dblock/actionmailer-text/pull/13): Parse HTML with Nokogiri while preserving the converter API and core text formatting, inspired by [NomNomCameron/actionmailer-textgiri](https://github.com/NomNomCameron/actionmailer-textgiri) - [@NomNomCameron](https://github.com/NomNomCameron).
7
+ * [#15](https://github.com/dblock/actionmailer-text/pull/15): Require Ruby 3.1 or newer and RuboCop 1.92, disable Metrics and line-length cops, and regenerate the lint baseline after checking safe autocorrections - [@dblock](https://github.com/dblock).
8
+ * [#16](https://github.com/dblock/actionmailer-text/pull/16): Prepare version 0.2.0 and add agent contribution guidance - [@dblock](https://github.com/dblock).
9
+ * [#11](https://github.com/dblock/actionmailer-text/pull/11): Replace Travis CI with GitHub Actions coverage for Rails 6.1 through 8.1, modernize development dependencies, and expand mailer regression tests - [@dblock](https://github.com/dblock).
10
+ * [#4](https://github.com/dblock/actionmailer-text/pull/4): Ignore empty links - [@apalancat](https://github.com/apalancat).
11
+ * [#8](https://github.com/dblock/actionmailer-text/pull/8): Decode HTML entities in newer Rails versions - [@mthurman](https://github.com/mthurman).
12
+
13
+ ### 0.1.1 (2015-04-25)
14
+
15
+ * [#1](https://github.com/dblock/actionmailer-text/pull/1): Ignore elements outside the body - [@woahdae](https://github.com/woahdae).
5
16
 
6
17
  ### 0.1.0 (2015-04-07)
7
18
 
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  ActionMailer::Text
2
2
  ==================
3
3
 
4
- [![Build Status](https://travis-ci.org/dblock/actionmailer-text.png)](https://travis-ci.org/dblock/actionmailer-text)
4
+ [![CI](https://github.com/dblock/actionmailer-text/actions/workflows/ci.yml/badge.svg)](https://github.com/dblock/actionmailer-text/actions/workflows/ci.yml)
5
5
  [![Gem Version](https://badge.fury.io/rb/actionmailer-text.svg)](http://badge.fury.io/rb/actionmailer-text)
6
6
 
7
7
  Automatically insert a text/plain part into your HTML multipart e-mails.
8
8
 
9
9
  ## Installation
10
10
 
11
+ Requires Ruby 3.1 or newer. Use a Ruby version supported by your selected Rails release.
12
+
11
13
  ```ruby
12
14
  gem 'actionmailer-text'
13
15
  ```
@@ -30,10 +32,30 @@ end
30
32
 
31
33
  ```
32
34
 
35
+ ### Selecting actions
36
+
37
+ Including `ActionMailer::Text` generates plaintext for every action by default. To limit generation to specific actions, declare `generate_text` in your mailer:
38
+
39
+ ```ruby
40
+ class WelcomeMailer < ActionMailer::Base
41
+ include ActionMailer::Text
42
+
43
+ generate_text only: [:welcome]
44
+ end
45
+ ```
46
+
47
+ Use `generate_text except: [:receipt]` to generate plaintext for every action except those listed. Action names accept strings or symbols, individually or in arrays. Use either `only:` or `except:`, not both. An empty `only: []` disables generation for all actions; an empty `except: []` excludes none.
48
+
49
+ Filters are inherited by subclasses. A subclass can replace the inherited filter with its own `generate_text` declaration, or call `generate_text` without arguments to restore generation for every action. Filtering only controls automatic generation: explicitly rendered text parts are always preserved.
50
+
33
51
  ## Details
34
52
 
35
53
  The MIME standard allows systems to send e-mail with multiple parts: plain/text for business-efficient devices such as the Blackberry, and text/html for web-based e-mail readers, such as GMail. Furthermore, ActionMailer supports multiple template formats: create an .html.haml template along with a .txt.haml template to generate both. We also know that text/plain email helps deliverability, but we believe a disproportionately small amount of text e-mails are actually read - the vast majority of devices are capable of parsing some HTML. This gem lets you get the text/plain part for free.
36
54
 
55
+ HTML is parsed with Nokogiri. Generated text preserves repeated content, includes image alt text and link destinations, and formats headings, lists, paragraphs, and line breaks. Existing explicit text parts are left unchanged.
56
+
57
+ Escaped markup remains literal text, image alt text does not require a self-closing tag, comments and script/style content are omitted, and malformed HTML is repaired before conversion. Fragment text surrounding paragraphs may gain paragraph breaks as Nokogiri normalizes the document. Nested list items appear on separate lines; ordered lists use `*` bullets, and nesting is flattened rather than indented.
58
+
37
59
  See [this blog post](http://artsy.github.io/blog/2012/05/16/generating-automatic-plain-text-mime-parts-with-rails-actionmailer) for details.
38
60
 
39
61
  ## Contributing
@@ -1,113 +1,103 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- # Extracted from the premailer gem.
4
- #
5
- # https://raw.github.com/alexdunae/premailer/master/lib/premailer/html_to_plain_text.rb
6
- #
7
-
8
- require 'htmlentities'
3
+ require 'nokogiri'
9
4
 
10
5
  module ActionMailer
11
6
  module Text
7
+ # Converts HTML into plaintext with link destinations and basic structural formatting.
12
8
  module HtmlToPlainText
13
9
  def convert_to_text(html, line_length = 65, from_charset = 'UTF-8')
14
10
  HtmlToPlainText.convert_to_text(html, line_length, from_charset)
15
11
  end
16
12
 
17
- # Returns the text in UTF-8 format with all HTML tags removed
18
- #
19
- # TODO: add support for DL, OL
13
+ # Returns UTF-8 text, preserving the converter's paragraph, link, and heading formatting.
20
14
  def self.convert_to_text(html, line_length = 65, _from_charset = 'UTF-8')
21
- txt = html
22
-
23
- # decode HTML entities
24
- he = HTMLEntities.new
25
- txt = he.decode(txt)
26
-
27
- # Ignore things that come outside the body
28
- txt.gsub!(/.*?(<body.+?\/body>).*?/im, '\1')
29
-
30
- # replace image by their alt attribute
31
- txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\/>/i, '\1')
32
- txt.gsub!(/<img.+?alt='([^\']*)\'[^>]*\/>/i, '\1')
15
+ document = Nokogiri::HTML.parse(html.to_str, nil, 'UTF-8')
16
+ txt = render_children(document.at_css('body') || document, line_length)
17
+ txt = custom_word_wrap(txt, line_length) if line_length
18
+ txt.gsub!(/\r\n?/, "\n")
19
+ txt.tr!("\u00a0", ' ')
20
+ txt.gsub!(/\n[ \t]+/, "\n")
21
+ txt.gsub!(/[ \t]+\n/, "\n")
22
+ txt.gsub!(/\n{3,}/, "\n\n")
23
+ txt.gsub!(/ {2,}/, ' ')
24
+ txt.strip
25
+ end
33
26
 
34
- # links
35
- txt.gsub!(/<a.+?href=\"([^\"]*)\"[^>]*>(.+?)<\/a>/mi) do |_s|
36
- Regexp.last_match[2].strip + ' ( ' + Regexp.last_match[1].strip + ' )'
27
+ def self.render_children(node, line_length)
28
+ node.children.each_with_object(''.dup) do |child, text|
29
+ if child.element? && child.name == 'li'
30
+ text.rstrip!
31
+ text << "\n" unless text.empty?
32
+ end
33
+ text << render_node(child, line_length)
37
34
  end
35
+ end
38
36
 
39
- txt.gsub!(/<a.+?href='([^\']*)\'[^>]*>(.+?)<\/a>/mi) do |_s|
40
- Regexp.last_match[2].strip + ' ( ' + Regexp.last_match[1].strip + ' )'
37
+ def self.render_node(node, line_length)
38
+ return render_text(node) if node.text?
39
+ return '' unless node.element?
40
+ return '' if %w[script style].include?(node.name)
41
+ return node['alt'].to_s if node.name == 'img'
42
+ return "\n" if node.name == 'br'
43
+
44
+ text = render_children(node, line_length)
45
+ case node.name
46
+ when 'a'
47
+ node['href'] && !text.strip.empty? ? "#{text.strip} ( #{node['href'].strip} )" : text
48
+ when 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
49
+ render_heading(text, node.name, line_length)
50
+ when 'li'
51
+ "* #{text.lstrip}\n"
52
+ when 'ul', 'ol'
53
+ "\n#{text.strip}\n"
54
+ when 'p'
55
+ "#{text}\n\n"
56
+ else
57
+ text
41
58
  end
59
+ end
42
60
 
43
- # handle headings (H1-H6)
44
- txt.gsub!(/(<\/h[1-6]>)/i, "\n\\1") # move closing tags to new lines
45
- txt.gsub!(/[\s]*<h([1-6]+)[^>]*>[\s]*(.*)[\s]*<\/h[1-6]+>/i) do |_s|
46
- hlevel = Regexp.last_match[1].to_i
61
+ def self.render_text(node)
62
+ previous = node.previous_sibling
63
+ following = node.next_sibling
64
+ return ' ' if node.content.strip.empty? && !previous.nil? && !following.nil? &&
65
+ previous.name == 'span' && following.name == 'span'
47
66
 
48
- htext = Regexp.last_match[2]
49
- htext.gsub!(/<br[\s]*\/?>/i, "\n") # handle <br>s
50
- htext.gsub!(/<\/?[^>]*>/i, '') # strip tags
67
+ node.content
68
+ end
51
69
 
52
- # determine maximum line length
53
- hlength = 0
54
- htext.each_line do |l|
55
- llength = l.strip.length
56
- hlength = llength if llength > hlength
57
- end
58
- hlength = line_length if line_length && hlength > line_length
70
+ def self.render_heading(text, tag, line_length)
71
+ text = text.strip
72
+ length = text.lines.map { |line| line.strip.length }.max || 0
73
+ length = [length, line_length].min if line_length
74
+ rule = (tag == 'h1' ? '*' : '-') * length
75
+ heading = %w[h1 h2].include?(tag) ? "#{rule}\n#{text}\n#{rule}" : "#{text}\n#{rule}"
76
+ "\n\n#{heading}\n\n"
77
+ end
78
+
79
+ def self.custom_word_wrap(txt, line_length)
80
+ txt.split("\n").map { |line| wrap_long_line(line, line_length) }.join("\n")
81
+ end
59
82
 
60
- case hlevel
61
- when 1 # H1, asterisks above and below
62
- htext = ('*' * hlength) + "\n" + htext + "\n" + ('*' * hlength)
63
- when 2 # H1, dashes above and below
64
- htext = ('-' * hlength) + "\n" + htext + "\n" + ('-' * hlength)
65
- else # H3-H6, dashes below
66
- htext = htext + "\n" + ('-' * hlength)
83
+ def self.wrap_long_line(line, line_length)
84
+ return line if line.length <= line_length
85
+
86
+ wrapped = []
87
+ while line.length > line_length
88
+ index = line[0, line_length + 1].rindex(/\s/)
89
+ index = line.index(/\s/, line_length) if index.nil? || index.zero?
90
+ unless index
91
+ wrapped << line
92
+ line = ''
93
+ break
67
94
  end
68
95
 
69
- "\n\n" + htext + "\n\n"
96
+ wrapped << line[0, index]
97
+ line = line[index, line.length - index].lstrip
70
98
  end
71
-
72
- # wrap spans
73
- txt.gsub!(/(<\/span>)[\s]+(<span)/mi, '\1 \2')
74
-
75
- # lists -- TODO: should handle ordered lists
76
- txt.gsub!(/[\s]*(<li[^>]*>)[\s]*/i, '* ')
77
- # list not followed by a newline
78
- txt.gsub!(/<\/li>[\s]*(?![\n])/i, "\n")
79
-
80
- # paragraphs and line breaks
81
- txt.gsub!(/<\/p>/i, "\n\n")
82
- txt.gsub!(/<br[\/ ]*>/i, "\n")
83
-
84
- # strip remaining tags
85
- txt.gsub!(/<\/?[^>]*>/, '')
86
-
87
- txt = custom_word_wrap(txt, line_length) if line_length
88
-
89
- # remove linefeeds (\r\n and \r -> \n)
90
- txt.gsub!(/\r\n?/, "\n")
91
-
92
- # strip extra spaces
93
- txt.gsub!(/\302\240+/, ' ') # non-breaking spaces -> spaces
94
- txt.gsub!(/\n[ \t]+/, "\n") # space at start of lines
95
- txt.gsub!(/[ \t]+\n/, "\n") # space at end of lines
96
-
97
- # no more than two consecutive newlines
98
- txt.gsub!(/[\n]{3,}/, "\n\n")
99
-
100
- # no more than two consecutive spaces
101
- txt.gsub!(/ {2,}/, ' ')
102
-
103
- txt.strip
104
- end
105
-
106
- # Taken from Rails' word_wrap helper (http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-word_wrap)
107
- def self.custom_word_wrap(txt, line_length)
108
- txt.split("\n").collect do |line|
109
- line.length > line_length ? line.gsub(/(.{1,#{line_length}})(\s+|$)/, "\\1\n").strip : line
110
- end * "\n"
99
+ wrapped << line unless line.empty?
100
+ wrapped.join("\n").strip
111
101
  end
112
102
  end
113
103
  end
@@ -1,20 +1,65 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionMailer
4
+ # Adds a generated plaintext alternative to HTML mail unless an explicit text part exists.
2
5
  module Text
3
6
  def self.included(klass)
4
7
  klass.class_eval do
5
8
  include ActionMailer::Text::HtmlToPlainText
9
+
10
+ class_attribute :text_generation_filter, instance_writer: false
11
+ extend ClassMethods
12
+ end
13
+ end
14
+
15
+ # Configures inherited action filters for automatic plaintext generation.
16
+ module ClassMethods
17
+ def generate_text(only: nil, except: nil)
18
+ raise ArgumentError, 'Specify only or except, not both' unless only.nil? || except.nil?
19
+
20
+ self.text_generation_filter = {
21
+ only: normalize_text_actions(only),
22
+ except: normalize_text_actions(except)
23
+ }.freeze
24
+ end
25
+
26
+ private
27
+
28
+ def normalize_text_actions(actions)
29
+ return if actions.nil?
30
+
31
+ Array(actions).map do |action|
32
+ raise ArgumentError, 'Action names must be strings or symbols' unless action.is_a?(String) || action.is_a?(Symbol)
33
+
34
+ action.to_s.dup.freeze
35
+ end.freeze
6
36
  end
7
37
  end
8
38
 
9
39
  def collect_responses(headers)
10
- responses = super headers
40
+ responses = super
41
+ return responses unless generate_text_for_action?
42
+
11
43
  html_part = responses.detect { |response| response[:content_type] == 'text/html' }
12
44
  text_part = responses.detect { |response| response[:content_type] == 'text/plain' }
13
45
  if html_part && !text_part
14
46
  responses.insert 0, content_type: 'text/plain', body: convert_to_text(html_part[:body], nil)
15
- headers[:parts_order] = ['text/plain'] + headers[:parts_order] unless headers[:parts_order].include?('text/plain')
47
+ unless headers[:parts_order].include?('text/plain')
48
+ headers[:parts_order] =
49
+ ['text/plain'] + headers[:parts_order]
50
+ end
16
51
  end
17
52
  responses
18
53
  end
54
+
55
+ private
56
+
57
+ def generate_text_for_action?
58
+ filter = self.class.text_generation_filter
59
+ return true unless filter
60
+ return filter[:only].include?(action_name) if filter[:only]
61
+
62
+ !filter[:except] || !filter[:except].include?(action_name)
63
+ end
19
64
  end
20
65
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionMailer
2
4
  module Text
3
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'action_mailer'
2
4
  require 'actionmailer-text/version'
3
5
  require 'actionmailer-text/html_to_plain_text'
@@ -1 +1,3 @@
1
- require 'email-example-spec'
1
+ # frozen_string_literal: true
2
+
3
+ require 'actionmailer-text'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionmailer-text
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Doubrovkine
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-25 00:00:00.000000000 Z
11
+ date: 2026-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionmailer
@@ -25,50 +25,39 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: htmlentities
28
+ name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '1.6'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
41
- description:
40
+ version: '1.6'
41
+ description:
42
42
  email: dblock@dblock.org
43
43
  executables: []
44
44
  extensions: []
45
45
  extra_rdoc_files: []
46
46
  files:
47
47
  - CHANGELOG.md
48
- - CONTRIBUTING.md
49
- - Gemfile
50
- - Gemfile.lock
51
48
  - LICENSE.md
52
49
  - README.md
53
- - Rakefile
54
- - actionmailer-text.gemspec
55
50
  - lib/actionmailer-text.rb
56
51
  - lib/actionmailer-text/html_to_plain_text.rb
57
52
  - lib/actionmailer-text/text.rb
58
53
  - lib/actionmailer-text/version.rb
59
54
  - lib/actionmailer_text.rb
60
- - pkg/actionmailer-text-0.1.1.gem
61
- - spec/actionmailer-text/html_to_plain_text_spec.rb
62
- - spec/actionmailer-text/multipart_spec.rb
63
- - spec/actionmailer-text/version_spec.rb
64
- - spec/spec_helper.rb
65
- - spec/support/fixtures/welcome_email.multipart
66
- - spec/support/templates/welcome_email.html.erb
67
- homepage: http://github.com/dblock/actionmailer-text
55
+ homepage: https://github.com/dblock/actionmailer-text
68
56
  licenses:
69
57
  - MIT
70
- metadata: {}
71
- post_install_message:
58
+ metadata:
59
+ rubygems_mfa_required: 'true'
60
+ post_install_message:
72
61
  rdoc_options: []
73
62
  require_paths:
74
63
  - lib
@@ -76,16 +65,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
65
  requirements:
77
66
  - - ">="
78
67
  - !ruby/object:Gem::Version
79
- version: '0'
68
+ version: '3.1'
80
69
  required_rubygems_version: !ruby/object:Gem::Requirement
81
70
  requirements:
82
71
  - - ">="
83
72
  - !ruby/object:Gem::Version
84
73
  version: 1.3.6
85
74
  requirements: []
86
- rubyforge_project:
87
- rubygems_version: 2.2.2
88
- signing_key:
75
+ rubygems_version: 3.5.16
76
+ signing_key:
89
77
  specification_version: 4
90
78
  summary: Automatically insert a text/plain part into your HTML multipart e-mails.
91
79
  test_files: []
data/CONTRIBUTING.md DELETED
@@ -1,125 +0,0 @@
1
- # Contributing to ActionMailer::Text
2
-
3
- This project is work of [many contributors](https://github.com/dblock/actionmailer-text/graphs/contributors).
4
-
5
- You're encouraged to submit [pull requests](https://github.com/dblock/actionmailer-text/pulls), [propose features and discuss issues](https://github.com/dblock/actionmailer-text/issues).
6
-
7
- In the examples below, substitute your Github username for `contributor` in URLs.
8
-
9
- ## Fork the Project
10
-
11
- Fork the [project on Github](https://github.com/dblock/actionmailer-text) and check out your copy.
12
-
13
- ```
14
- git clone https://github.com/contributor/actionmailer-text.git
15
- cd actionmailer-text
16
- git remote add upstream https://github.com/dblock/actionmailer-text.git
17
- ```
18
-
19
- ## Create a Topic Branch
20
-
21
- Make sure your fork is up-to-date and create a topic branch for your feature or bug fix.
22
-
23
- ```
24
- git checkout master
25
- git pull upstream master
26
- git checkout -b my-feature-branch
27
- ```
28
-
29
- ## Bundle Install and Test
30
-
31
- Ensure that you can build the project and run tests.
32
-
33
- ```
34
- bundle install
35
- bundle exec rake
36
- ```
37
-
38
- ## Write Tests
39
-
40
- Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build.
41
- Add to [spec](spec).
42
-
43
- We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix.
44
-
45
- ## Write Code
46
-
47
- Implement your feature or bug fix.
48
-
49
- Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop).
50
- Run `bundle exec rubocop` and fix any style issues highlighted.
51
-
52
- Make sure that `bundle exec rake` completes without errors.
53
-
54
- ## Write Documentation
55
-
56
- Document any external behavior in the [README](README.md).
57
-
58
- ## Update Changelog
59
-
60
- Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*.
61
- Make it look like every other line, including your name and link to your Github account.
62
-
63
- ## Commit Changes
64
-
65
- Make sure git knows your name and email address:
66
-
67
- ```
68
- git config --global user.name "Your Name"
69
- git config --global user.email "contributor@example.com"
70
- ```
71
-
72
- Writing good commit logs is important. A commit log should describe what changed and why.
73
-
74
- ```
75
- git add ...
76
- git commit
77
- ```
78
-
79
- ## Push
80
-
81
- ```
82
- git push origin my-feature-branch
83
- ```
84
-
85
- ## Make a Pull Request
86
-
87
- Go to https://github.com/contributor/actionmailer-text and select your feature branch.
88
- Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days.
89
-
90
- ## Rebase
91
-
92
- If you've been working on a change for a while, rebase with upstream/master.
93
-
94
- ```
95
- git fetch upstream
96
- git rebase upstream/master
97
- git push origin my-feature-branch -f
98
- ```
99
-
100
- ## Update CHANGELOG Again
101
-
102
- Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows.
103
-
104
- ```
105
- * [#123](https://github.com/dblock/actionmailer-text/pull/123): Reticulated splines - [@contributor](https://github.com/contributor).
106
- ```
107
-
108
- Amend your previous commit and force push the changes.
109
-
110
- ```
111
- git commit --amend
112
- git push origin my-feature-branch -f
113
- ```
114
-
115
- ## Check on Your Pull Request
116
-
117
- Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above.
118
-
119
- ## Be Patient
120
-
121
- It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there!
122
-
123
- ## Thank You
124
-
125
- Please do know that we really appreciate and value your time and work. We love you, really.
data/Gemfile DELETED
@@ -1,8 +0,0 @@
1
- source 'http://rubygems.org'
2
-
3
- gemspec
4
-
5
- gem 'rake', '~> 10.4'
6
- gem 'rubocop', '0.30.0'
7
- gem 'rspec', '~> 3.2'
8
- gem 'email-example-spec', '~> 0.1.0'
data/Gemfile.lock DELETED
@@ -1,109 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- actionmailer-text (0.1.1)
5
- actionmailer
6
- htmlentities
7
-
8
- GEM
9
- remote: http://rubygems.org/
10
- specs:
11
- actionmailer (4.2.1)
12
- actionpack (= 4.2.1)
13
- actionview (= 4.2.1)
14
- activejob (= 4.2.1)
15
- mail (~> 2.5, >= 2.5.4)
16
- rails-dom-testing (~> 1.0, >= 1.0.5)
17
- actionpack (4.2.1)
18
- actionview (= 4.2.1)
19
- activesupport (= 4.2.1)
20
- rack (~> 1.6)
21
- rack-test (~> 0.6.2)
22
- rails-dom-testing (~> 1.0, >= 1.0.5)
23
- rails-html-sanitizer (~> 1.0, >= 1.0.1)
24
- actionview (4.2.1)
25
- activesupport (= 4.2.1)
26
- builder (~> 3.1)
27
- erubis (~> 2.7.0)
28
- rails-dom-testing (~> 1.0, >= 1.0.5)
29
- rails-html-sanitizer (~> 1.0, >= 1.0.1)
30
- activejob (4.2.1)
31
- activesupport (= 4.2.1)
32
- globalid (>= 0.3.0)
33
- activesupport (4.2.1)
34
- i18n (~> 0.7)
35
- json (~> 1.7, >= 1.7.7)
36
- minitest (~> 5.1)
37
- thread_safe (~> 0.3, >= 0.3.4)
38
- tzinfo (~> 1.1)
39
- ast (2.0.0)
40
- astrolabe (1.3.0)
41
- parser (>= 2.2.0.pre.3, < 3.0)
42
- builder (3.2.2)
43
- diff-lcs (1.2.5)
44
- email-example-spec (0.1.0)
45
- actionmailer
46
- erubis (2.7.0)
47
- globalid (0.3.5)
48
- activesupport (>= 4.1.0)
49
- htmlentities (4.3.3)
50
- i18n (0.7.0)
51
- json (1.8.2)
52
- loofah (2.0.2)
53
- nokogiri (>= 1.5.9)
54
- mail (2.6.3)
55
- mime-types (>= 1.16, < 3)
56
- mime-types (2.5)
57
- mini_portile (0.6.2)
58
- minitest (5.6.1)
59
- nokogiri (1.6.6.2)
60
- mini_portile (~> 0.6.0)
61
- parser (2.2.2.2)
62
- ast (>= 1.1, < 3.0)
63
- powerpack (0.1.1)
64
- rack (1.6.1)
65
- rack-test (0.6.3)
66
- rack (>= 1.0)
67
- rails-deprecated_sanitizer (1.0.3)
68
- activesupport (>= 4.2.0.alpha)
69
- rails-dom-testing (1.0.6)
70
- activesupport (>= 4.2.0.beta, < 5.0)
71
- nokogiri (~> 1.6.0)
72
- rails-deprecated_sanitizer (>= 1.0.1)
73
- rails-html-sanitizer (1.0.2)
74
- loofah (~> 2.0)
75
- rainbow (2.0.0)
76
- rake (10.4.2)
77
- rspec (3.2.0)
78
- rspec-core (~> 3.2.0)
79
- rspec-expectations (~> 3.2.0)
80
- rspec-mocks (~> 3.2.0)
81
- rspec-core (3.2.3)
82
- rspec-support (~> 3.2.0)
83
- rspec-expectations (3.2.1)
84
- diff-lcs (>= 1.2.0, < 2.0)
85
- rspec-support (~> 3.2.0)
86
- rspec-mocks (3.2.1)
87
- diff-lcs (>= 1.2.0, < 2.0)
88
- rspec-support (~> 3.2.0)
89
- rspec-support (3.2.2)
90
- rubocop (0.30.0)
91
- astrolabe (~> 1.3)
92
- parser (>= 2.2.0.1, < 3.0)
93
- powerpack (~> 0.1)
94
- rainbow (>= 1.99.1, < 3.0)
95
- ruby-progressbar (~> 1.4)
96
- ruby-progressbar (1.7.5)
97
- thread_safe (0.3.5)
98
- tzinfo (1.2.2)
99
- thread_safe (~> 0.1)
100
-
101
- PLATFORMS
102
- ruby
103
-
104
- DEPENDENCIES
105
- actionmailer-text!
106
- email-example-spec (~> 0.1.0)
107
- rake (~> 10.4)
108
- rspec (~> 3.2)
109
- rubocop (= 0.30.0)
data/Rakefile DELETED
@@ -1,16 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler/gem_tasks'
3
-
4
- Bundler.setup :default, :development
5
-
6
- require 'rspec/core'
7
- require 'rspec/core/rake_task'
8
-
9
- RSpec::Core::RakeTask.new(:spec) do |spec|
10
- spec.pattern = FileList['spec/**/*_spec.rb']
11
- end
12
-
13
- require 'rubocop/rake_task'
14
- RuboCop::RakeTask.new
15
-
16
- task default: [:rubocop, :spec]
@@ -1,18 +0,0 @@
1
- $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
- require 'actionmailer-text/version'
3
-
4
- Gem::Specification.new do |s|
5
- s.name = 'actionmailer-text'
6
- s.version = ActionMailer::Text::VERSION
7
- s.authors = ['Daniel Doubrovkine']
8
- s.email = 'dblock@dblock.org'
9
- s.platform = Gem::Platform::RUBY
10
- s.required_rubygems_version = '>= 1.3.6'
11
- s.files = Dir['**/*']
12
- s.require_paths = ['lib']
13
- s.homepage = 'http://github.com/dblock/actionmailer-text'
14
- s.licenses = ['MIT']
15
- s.summary = 'Automatically insert a text/plain part into your HTML multipart e-mails.'
16
- s.add_dependency 'actionmailer'
17
- s.add_dependency 'htmlentities'
18
- end
Binary file
@@ -1,183 +0,0 @@
1
- # encoding: utf-8
2
- require 'spec_helper'
3
-
4
- describe ActionMailer::Text::HtmlToPlainText do
5
- it 'converts a fragment' do
6
- expect(subject.convert_to_text('<p>Test</p>')).to match(/Test/)
7
- end
8
-
9
- it 'converts a body' do
10
- expect(subject.convert_to_text('
11
- <html>
12
- <title>Ignore me</title>
13
- <body>
14
- <p>Test</p>
15
- </body>
16
- </html>
17
- ')).to match(/Test/)
18
- end
19
-
20
- it 'ignores titles' do
21
- expect(subject.convert_to_text('
22
- <html>
23
- <title>Ignore me</title>
24
- <body>
25
- <p>Test</p>
26
- </body>
27
- </html>
28
- ')).not_to match(/Ignore me/)
29
- end
30
-
31
- it 'ignores header links' do
32
- expect(subject.convert_to_text('
33
- <html>
34
- <head>
35
- <link href="http://example.com/should/be/ignored.css" rel="stylesheet" />
36
- </head>
37
- <body>
38
- <p>Test</p>
39
- </body>
40
- </html>
41
- ')).not_to match(/\*/)
42
- end
43
-
44
- it 'ignores header titles' do
45
- expect(subject.convert_to_text('
46
- <html>
47
- <head>
48
- <title>Ignore me</title>
49
- </head>
50
- <body>
51
- <p>Test</p>
52
- </body>
53
- </html>
54
- ')).not_to match(/Ignore me/)
55
- end
56
-
57
- it 'converts a malformed body' do
58
- expect(subject.convert_to_text('
59
- <html>
60
- <title>Ignore me</title>
61
- <body>
62
- <p>Test
63
- ')).to match(/Test/)
64
- end
65
-
66
- it 'dencodes html entities' do
67
- expect(subject.convert_to_text('
68
- c&eacute;dille gar&#231;on &amp; &agrave; &ntilde;
69
- ')).to eq('cédille garçon & à ñ')
70
- end
71
-
72
- it 'strips whitespace' do
73
- expect(subject.convert_to_text(" \ttext\ntext\n")).to eq("text\ntext")
74
- expect(subject.convert_to_text(" \na \n a \t")).to eq("a\na")
75
- expect(subject.convert_to_text(" \na \n\t \n \n a \t")).to eq("a\n\na")
76
- expect(subject.convert_to_text('test text&nbsp;')).to eq('test text')
77
- expect(subject.convert_to_text('test text')).to eq('test text')
78
- end
79
-
80
- it 'leaves spaces for spans' do
81
- expect(subject.convert_to_text('
82
- <html>
83
- <body>
84
- <p><span>Test</span>
85
- <span>line 2</span>
86
- </p>
87
- ')).to match(/Test line 2/)
88
- end
89
-
90
- it 'normalizes line breaks' do
91
- expect(subject.convert_to_text("Test text\r\nTest text")).to eq("Test text\nTest text")
92
- expect(subject.convert_to_text("Test text\nTest text")).to eq("Test text\nTest text")
93
- end
94
-
95
- it 'formats lists' do
96
- expect(subject.convert_to_text("<li class='123'>item 1</li> <li>item 2</li>\n")).to eq("* item 1\n* item 2")
97
- expect(subject.convert_to_text("<li>item 1</li> \t\n <li>item 2</li> <li> item 3</li>\n")).to eq("* item 1\n* item 2\n* item 3")
98
- end
99
-
100
- it 'strips html' do
101
- expect(subject.convert_to_text("<p class=\"123'45 , att\" att=tester>test <span class='te\"st'>text</span>\n")).to eq('test text')
102
- end
103
-
104
- it 'creates line breaks for p and br tags' do
105
- expect(subject.convert_to_text('<p>Test text</p><p>Test text</p>')).to eq("Test text\n\nTest text")
106
- expect(subject.convert_to_text("\n<p>Test text</p>\n\n\n\t<p>Test text</p>\n")).to eq("Test text\n\nTest text")
107
- expect(subject.convert_to_text("\n<p>Test text<br/>Test text</p>\n")).to eq("Test text\nTest text")
108
- expect(subject.convert_to_text("\n<p>Test text<br> \tTest text<br></p>\n")).to eq("Test text\nTest text")
109
- expect(subject.convert_to_text('Test text<br><BR />Test text')).to eq("Test text\n\nTest text")
110
- end
111
-
112
- it 'converts headings' do
113
- expect(subject.convert_to_text('<h1>Test</h1>')).to eq("****\nTest\n****")
114
- expect(subject.convert_to_text("\t<h1>\nTest</h1> ")).to eq("****\nTest\n****")
115
- expect(subject.convert_to_text("\t<h1>\nTest line 1<br>Test 2</h1> ")).to eq("***********\nTest line 1\nTest 2\n***********")
116
- expect(subject.convert_to_text('<h1>Test</h1> <h1>Test</h1>')).to eq("****\nTest\n****\n\n****\nTest\n****")
117
- expect(subject.convert_to_text('<h2>Test</h2>')).to eq("----\nTest\n----")
118
- expect(subject.convert_to_text("<h3> <span class='a'>Test </span></h3>")).to eq("Test\n----")
119
- end
120
-
121
- it 'wraps lines' do
122
- raw = ''
123
- 100.times { raw += 'test ' }
124
-
125
- txt = subject.convert_to_text(raw, 20)
126
-
127
- lens = []
128
- txt.each_line { |l| lens << l.length }
129
- expect(lens.max).to be <= 20
130
- end
131
-
132
- it 'converts links' do
133
- # basic
134
- expect(subject.convert_to_text('<a href="http://example.com/">Link</a>')).to eq('Link ( http://example.com/ )')
135
-
136
- # nested html
137
- expect(subject
138
- .convert_to_text('<a href="http://example.com/"><span class="a">Link</span></a>'))
139
- .to eq('Link ( http://example.com/ )')
140
-
141
- # complex link
142
- expect(subject
143
- .convert_to_text('<a href="http://example.com:80/~user?aaa=bb&amp;c=d,e,f#foo">Link</a>'))
144
- .to eq('Link ( http://example.com:80/~user?aaa=bb&c=d,e,f#foo )')
145
-
146
- # attributes
147
- expect(subject
148
- .convert_to_text('<a title=\'title\' href="http://example.com/">Link</a>'))
149
- .to eq('Link ( http://example.com/ )')
150
-
151
- # spacing
152
- expect(subject
153
- .convert_to_text('<a href=" http://example.com/ "> Link </a>'))
154
- .to eq('Link ( http://example.com/ )')
155
-
156
- # multiple
157
- expect(subject
158
- .convert_to_text('<a href="http://example.com/a/">Link A</a> <a href="http://example.com/b/">Link B</a>'))
159
- .to eq('Link A ( http://example.com/a/ ) Link B ( http://example.com/b/ )')
160
-
161
- # merge links
162
- expect(subject.convert_to_text('<a href="%%LINK%%">Link</a>')).to eq('Link ( %%LINK%% )')
163
- expect(subject.convert_to_text('<a href="[LINK]">Link</a>')).to eq('Link ( [LINK] )')
164
- expect(subject.convert_to_text('<a href="{LINK}">Link</a>')).to eq('Link ( {LINK} )')
165
-
166
- # unsubscribe
167
- expect(subject.convert_to_text('<a href="[[!unsubscribe]]">Link</a>')).to eq('Link ( [[!unsubscribe]] )')
168
- end
169
-
170
- # see https://github.com/alexdunae/premailer/issues/72
171
- it 'converts multiple links per line' do
172
- expect(subject
173
- .convert_to_text('<p>This is <a href="http://www.google.com" >link1</a> and <a href="http://www.google.com" >link2 </a> is next.</p>', nil, 10_000))
174
- .to eq('This is link1 ( http://www.google.com ) and link2 ( http://www.google.com ) is next.')
175
- end
176
-
177
- # see https://github.com/alexdunae/premailer/issues/72
178
- it 'converts links within headings' do
179
- expect(subject
180
- .convert_to_text("<h1><a href='http://example.com/'>Test</a></h1>"))
181
- .to eq("****************************\nTest ( http://example.com/ )\n****************************")
182
- end
183
- end
@@ -1,33 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActionMailer::Text do
4
- let(:mailer) do
5
- Class.new(ActionMailer::Base) do
6
- include ActionMailer::Text
7
-
8
- default from: 'Joe User <joe@example.org>'
9
-
10
- def welcome_mail(username)
11
- @username = username
12
- mail to: 'user@example.org', subject: 'Welcome!' do |format|
13
- format.html { render 'templates/welcome_email' }
14
- end
15
- end
16
- end
17
- end
18
- subject do
19
- mailer.welcome_mail('username')
20
- end
21
- it 'generates a text part' do
22
- expect(subject.parts.size).to eq 2
23
- end
24
- it 'inserts text part first' do
25
- expect(subject.parts.first.content_type).to eq 'text/plain; charset=UTF-8'
26
- end
27
- it 'inserts html part last' do
28
- expect(subject.parts.last.content_type).to eq 'text/html; charset=UTF-8'
29
- end
30
- it 'matches output' do
31
- expect(subject).to match_email_example_in 'welcome_email.multipart'
32
- end
33
- end
@@ -1,7 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ActionMailer::Text do
4
- it 'has a version' do
5
- expect(ActionMailer::Text::VERSION).to_not be nil
6
- end
7
- end
data/spec/spec_helper.rb DELETED
@@ -1,12 +0,0 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
-
3
- require 'rubygems'
4
- require 'rspec'
5
- require 'actionmailer-text'
6
- require 'email-example-spec'
7
-
8
- ActionMailer::Base.view_paths = File.join(File.dirname(__FILE__), 'support')
9
-
10
- EmailExampleSpec.configure do |config|
11
- config.fixture_path = File.join(File.dirname(__FILE__), 'support/fixtures')
12
- end
@@ -1,32 +0,0 @@
1
- Date: Thu, 01 Jan 1970 00:00:00 +0000
2
- From: Joe User <joe@example.org>
3
- To: user@example.org
4
- Message-ID: 1
5
- Subject: Welcome!
6
- Mime-Version: 1.0
7
- Content-Type: multipart/alternative;
8
- boundary=boundary;
9
- charset=UTF-8
10
- Content-Transfer-Encoding: 7bit
11
-
12
-
13
- --boundary
14
- Content-Type: text/plain;
15
- charset=UTF-8
16
- Content-Transfer-Encoding: 7bit
17
-
18
- Hi username,
19
-
20
- Thanks for joining our service! Please check back often.
21
- --boundary
22
- Content-Type: text/html;
23
- charset=UTF-8
24
- Content-Transfer-Encoding: 7bit
25
-
26
- <body>
27
- <p>Hi username,</p>
28
- <p>Thanks for joining our service! Please check back often.</p>
29
- </body>
30
-
31
-
32
- --boundary--
@@ -1,5 +0,0 @@
1
- <body>
2
- <p>Hi <%= @username %>,</p>
3
- <p>Thanks for joining our service! Please check back often.</p>
4
- </body>
5
-