premailer 1.23.0 → 1.29.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
2
  SHA256:
3
- metadata.gz: ea6e9ef14eeb9cdffd7e718f976d9e71e50fb1f154c947c9ee9b97399f1aceeb
4
- data.tar.gz: 02d8f609acce2abb421f607e1937e35e4d36f12c609386bef808378f56e9753c
3
+ metadata.gz: fa4f38aaadca592e45c38537b54ad6e88bd15c0c43c07efed0beb25af8194766
4
+ data.tar.gz: cde54fad4ac9397bd09b88509aa2b5a22daaa1721172a7c98e0e78c94e8707ad
5
5
  SHA512:
6
- metadata.gz: d1e580add5528f240e2170bb2aed29171227d35ce8acc31d5c8ceed7ea3f211072f8db8257f9665bef5838c606c9f77a56a792482c3d5f730025a6448374a4bb
7
- data.tar.gz: 3afd5c1330b5b1ddff158e77260c0fc87876779e839f5f1beb641ea4c00d424db97c0b5410a566f08ac97a3615cd125c8d9198a77d5edf3ee5959abcc2058440
6
+ metadata.gz: bc0358601f237a6ec2cf74764dfb28e5d800cf1ca486d88ca44a7e4d3913ade8ff00b76b98017fa6f565f5812bd6f9c3e66be819708a2764a70fca051458f09f
7
+ data.tar.gz: 9ea81979f54bd5fd9d8edb2e390e5900d53b7462064bc1465999dba639218db5a479c74e93e6e179d6d6a51abbf066685a21b033d5003899ab037a021db385f7
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  For the best HTML e-mail delivery results, CSS should be inline. This is a
6
6
  huge pain and a simple newsletter becomes un-managable very quickly. This
7
- script is my solution.
7
+ gem is a solution.
8
8
 
9
9
  * CSS styles are converted to inline style attributes
10
10
  - Checks `style` and `link[rel=stylesheet]` tags and preserves existing inline attributes
@@ -12,35 +12,26 @@ script is my solution.
12
12
  - Checks links in `href`, `src` and CSS `url('')`
13
13
  * CSS properties are checked against e-mail client capabilities
14
14
  - Based on the Email Standards Project's guides
15
- * A [plain text version](https://premailer.github.io/premailer/HtmlToPlainText.html) is created (optional)
15
+ * A [plain text version](#plain-text-version) is created (optional)
16
16
 
17
17
  ## Installation
18
18
 
19
- Install the Premailer gem from RubyGems.
20
-
21
19
  ```bash
22
20
  gem install premailer
23
21
  ```
24
22
 
25
- or add it to your `Gemfile` and run `bundle`.
26
-
27
23
  ## Example
28
24
 
29
25
  ```ruby
30
26
  require 'premailer'
31
27
 
32
- premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE)
28
+ premailer = Premailer.new('http://example.com/myfile.html', warn_level: Premailer::Warnings::SAFE)
33
29
 
34
- # Write the plain-text output
35
- # This must come before to_inline_css (https://github.com/premailer/premailer/issues/201)
36
- File.open("output.txt", "w") do |fout|
37
- fout.puts premailer.to_plain_text
38
- end
30
+ # Write the plain-text output (must come before to_inline_css)
31
+ File.write "output.txt", premailer.to_plain_text
39
32
 
40
33
  # Write the HTML output
41
- File.open("output.html", "w") do |fout|
42
- fout.puts premailer.to_inline_css
43
- end
34
+ File.write "output.html", premailer.to_inline_css
44
35
 
45
36
  # Output any CSS warnings
46
37
  premailer.warnings.each do |w|
@@ -50,17 +41,13 @@ end
50
41
 
51
42
  ## Adapters
52
43
 
53
- Premailer's default adapter is nokogiri if both nokogiri and nokogumbo are included in the Gemfile list. However, if you want to use a different adapter, you can choose to.
54
-
55
- There are three adapters in total (as of premailer 1.10.0)
56
-
57
44
  1. nokogiri (default)
58
- 2. nokogiri_fast
45
+ 2. nokogiri_fast (20x speed, more memory)
59
46
  3. nokogumbo
60
47
 
61
- hpricot adapter removed due to its EOL, please use `~>1.9.0` version if You still need it..
48
+ (hpricot adapter removed, use `~>1.9.0` version if you need it)
62
49
 
63
- `NokogiriFast` adapter improves the Algorithmic complexity of the running time by 20x with a slight compensation on memory. To switch to any of these adapters, add the following line. For example, if you want to include the `NokogiriFast` adapter,
50
+ Picking an adapter:
64
51
 
65
52
  ```ruby
66
53
  Premailer::Adapter.use = :nokogiri_fast
@@ -97,22 +84,125 @@ will result in
97
84
  <table cellspacing='5' width='500'>
98
85
  ```
99
86
 
100
- ## Configuration options
87
+ ## Plain text version
88
+
89
+ Premailer can generate a plain text version of your HTML. Links and images will be inlined.
90
+
91
+ For example
92
+
93
+ ```html
94
+ <a href="https://example.com" >
95
+ <img src="https://github.com/premailer.png" alt="Premailer Logo" />
96
+ </a>
97
+ ```
98
+
99
+ will become
100
+
101
+ ```text
102
+ Premailer Logo ( https://example.com )
103
+ ```
104
+
105
+ To ignore/omit a section of HTML content from the plain text version, wrap it with the following comments.
101
106
 
102
- The behavior of Premailer can be configured by passing options in the initializer.
107
+ ```html
108
+ <!-- start text/html -->
109
+ <p>This will be omitted from the plain text version.</p>
110
+ <p>
111
+ This is extremely helpful for <strong>removing email headers and footers</strong>
112
+ that aren't needed in the text version.
113
+ </p>
114
+ <!-- end text/html -->
115
+ ```
103
116
 
104
- For example, the following will accept HTML from a string and will exclude unmergeable css from being added to the `<head>` of the output document.
117
+ ## Configuration options
105
118
 
119
+ For example:
106
120
  ```ruby
107
- premailer = Premailer.new(html_string, with_html_string: true, drop_unmergeable_css_rules: true)
121
+ Premailer.new(
122
+ html, # html as string
123
+ with_html_string: true,
124
+ drop_unmergeable_css_rules: true
125
+ )
126
+ ```
127
+
128
+ [available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method)
129
+
130
+ ## Support for CSS variables
131
+
132
+ The gem does not automatically replace CSS variables with their static values.
133
+
134
+ For example, if a variable is used to set the `font-weight` of an `h1` element, the result will be
135
+ ```html
136
+ <h1 style="
137
+ font-size:3rem;
138
+ font-weight:var(--bulma-content-heading-weight);">
139
+ Title</h1>
140
+ ```
141
+
142
+ This causes the `font-weight` value to be the CSS variable call `var(--bulma-content-heading-weight);` instead of its static value.
143
+
144
+ ### Replace CSS variable calls with their static values
145
+
146
+ The following section instructs how to replace CSS variables with their static value in the context of a Ruby on Rails application.
147
+
148
+ Install the `postcss-css-variables` plugin for PostCSS to process the CSS variables.
149
+
150
+ ```shell
151
+ yarn add postcss postcss-cli postcss-css-variables
152
+ ```
153
+
154
+ To configure the plugin, create the file `postcss.config.js` in the root directory with the content:
155
+
156
+ ```javascript
157
+ module.exports = {
158
+ plugins: [
159
+ // https://github.com/MadLittleMods/postcss-css-variables to transform the css
160
+ require("postcss-css-variables")({
161
+ preserve: false, // Set to false to replace variables with static values
162
+ }),
163
+ ],
164
+ };
165
+ ```
166
+
167
+ In the `package.json` file, add the new "build:emails" to the scripts.<br>Replace `./app/assets/stylesheets/emails.css` with your file path:
168
+ ```json
169
+ "scripts": {
170
+ "build:emails": "postcss ./app/assets/stylesheets/emails.css -o ./app/assets/builds/emails.css"
171
+ }
108
172
  ```
109
173
 
110
- [See here for a full list of the available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method).
174
+ The previous script processes and overwrites the file at `./app/assets/stylesheets/emails.css` with PostCSS using its `postcss-css-variables` plugin, replacing the CSS variables with their static value.
111
175
 
176
+ If the file to be processed is not `.css`, but `.scss`, it needs to be converted first to `.css`, then have its variables replaced. The script would then be
177
+
178
+ ```json
179
+ "scripts": {
180
+ "build:emails": "sass ./app/assets/stylesheets/emails.scss:./app/assets/builds/emails.css --no-source-map --load-path=node_modules && postcss ./app/assets/builds/emails.css -o ./app/assets/builds/emails.css"
181
+ }
182
+ ```
183
+
184
+ Next, to execute the script when running `bin/dev`, add the following line in the file `Procfile.dev`
185
+
186
+ ```
187
+ emails_css: yarn build:emails --watch
188
+ ```
189
+
190
+ The srcipt can also be executed separately with the command
191
+
192
+ ```shell
193
+ yarn build:emails
194
+ ```
195
+
196
+ ### Caveat
197
+
198
+ The variables must be declared before use. Otherwise, their values when called will be set to `undefined`.
112
199
 
113
200
  ## Contributions
114
201
 
115
- Contributions are most welcome. Premailer was rotting away in a private SVN repository for too long and could use some TLC. Fork and patch to your heart's content. Please don't increment the version numbers, though.
202
+ Contributions are most welcome.
203
+ Premailer was rotting away in a private SVN repository for too long and could use some TLC.
204
+ Fork and patch to your heart's content.
205
+ Please don't increment the version numbers.
116
206
 
117
207
  A few areas that are particularly in need of love:
118
208
 
@@ -129,4 +219,3 @@ and to [Campaign Monitor](https://www.campaignmonitor.com/) for supporting the w
129
219
  The source code can be found on [GitHub](https://github.com/premailer/premailer).
130
220
 
131
221
  Copyright by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2007-2017. See [LICENSE.md](https://github.com/premailer/premailer/blob/master/LICENSE.md) for license details.
132
-
data/bin/premailer CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # This binary used in rubygems environment only as part of installed gem
4
5
 
5
6
  require 'premailer/executor'
6
-
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  require 'nokogiri'
2
3
 
3
4
  class Premailer
4
5
  module Adapter
5
6
  # Nokogiri adapter
6
7
  module Nokogiri
8
+ WIDTH_AND_HIGHT = ['width', 'height'].freeze
7
9
 
8
10
  include AdapterHelper::RgbToHex
9
11
  # Merge CSS into the HTML document.
@@ -21,19 +23,19 @@ class Premailer
21
23
  # Iterate through the rules and merge them into the HTML
22
24
  @css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
23
25
  # Save un-mergable rules separately
24
- selector.gsub!(/:link([\s]*)+/i) { |m| $1 }
26
+ selector.gsub!(/:link([\s]*)+/i) { |_m| $1 }
25
27
 
26
28
  # Convert element names to lower case
27
- selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
29
+ selector.gsub!(/([\s]|^)([\w]+)/) { |_m| $1.to_s + $2.to_s.downcase }
28
30
 
29
- if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
30
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
31
+ if Premailer.media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
32
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
31
33
  else
32
34
  begin
33
- if selector =~ Premailer::RE_RESET_SELECTORS
35
+ if Premailer::RE_RESET_SELECTORS.match?(selector) && !!@options[:preserve_reset]
34
36
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
35
37
  # however, this doesn't mean for testing pur
36
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
38
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
37
39
  end
38
40
 
39
41
  # Change single ID CSS selectors into xpath so that we can match more
@@ -41,14 +43,14 @@ class Premailer
41
43
  selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
42
44
 
43
45
  doc.search(selector).each do |el|
44
- if el.elem? and (el.name != 'head' and el.parent.name != 'head')
46
+ if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
45
47
  # Add a style attribute or append to the existing one
46
48
  block = "[SPEC=#{specificity}[#{declaration}]]"
47
49
  el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
48
50
  end
49
51
  end
50
52
  rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
51
- $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
53
+ warn "CSS syntax error with selector: #{selector}" if @options[:verbose]
52
54
  next
53
55
  end
54
56
  end
@@ -61,8 +63,8 @@ class Premailer
61
63
  doc.search("*[@style]").each do |el|
62
64
  style = el.attributes['style'].to_s
63
65
 
64
- declarations = style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).filter_map do |declaration|
65
- rs = Premailer::CachedRuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
66
+ declarations = style.scan(/\[SPEC=([\d]+)\[(.[^\]]*)\]\]/m).filter_map do |declaration|
67
+ rs = Premailer::CachedRuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
66
68
  rs.expand_shorthand!
67
69
  rs
68
70
  rescue ArgumentError => e
@@ -78,9 +80,9 @@ class Premailer
78
80
  end
79
81
 
80
82
  # Duplicate CSS attributes as HTML attributes
81
- if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
83
+ if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
82
84
  Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
83
- if el[html_attr].nil? and not merged[css_attr].empty?
85
+ if el[html_attr].nil? && !merged[css_attr].empty?
84
86
  new_val = merged[css_attr].dup
85
87
 
86
88
  # Remove url() function wrapper
@@ -90,7 +92,7 @@ class Premailer
90
92
  new_val.gsub!(/;$|\s*!important/, '').strip!
91
93
 
92
94
  # For width and height tags, remove px units
93
- new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr)
95
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HIGHT.include?(html_attr)
94
96
 
95
97
  # For color-related tags, convert RGB to hex if specified by options
96
98
  new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
@@ -99,7 +101,7 @@ class Premailer
99
101
  end
100
102
 
101
103
  unless @options[:preserve_style_attribute]
102
- merged.instance_variable_get("@declarations").tap do |declarations|
104
+ merged.instance_variable_get(:@declarations).tap do |declarations|
103
105
  declarations.delete(css_attr)
104
106
  end
105
107
  end
@@ -115,9 +117,9 @@ class Premailer
115
117
 
116
118
  doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
117
119
 
118
- if @options[:remove_classes] or @options[:remove_comments]
120
+ if @options[:remove_classes] || @options[:remove_comments]
119
121
  doc.traverse do |el|
120
- if el.comment? and @options[:remove_comments]
122
+ if el.comment? && @options[:remove_comments]
121
123
  el.remove
122
124
  elsif el.element?
123
125
  el.remove_attribute('class') if @options[:remove_classes]
@@ -129,7 +131,7 @@ class Premailer
129
131
  # find all anchor's targets and hash them
130
132
  targets = []
131
133
  doc.search("a[@href^='#']").each do |el|
132
- target = el.get_attribute('href')[1..-1]
134
+ target = el.get_attribute('href')[1..]
133
135
  targets << target
134
136
  el.set_attribute('href', "#" + Digest::SHA256.hexdigest(target))
135
137
  end
@@ -151,7 +153,7 @@ class Premailer
151
153
  end
152
154
 
153
155
  @processed_doc = doc
154
- if is_xhtml?
156
+ if xhtml?
155
157
  # we don't want to encode carriage returns
156
158
  @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
157
159
  else
@@ -173,17 +175,16 @@ class Premailer
173
175
  style_tag.content = styles
174
176
  doc.add_child(style_tag)
175
177
  else
176
- style_tag = doc.create_element "style", "#{styles}"
178
+ style_tag = doc.create_element "style", styles.to_s
177
179
  head = doc.at_css('head')
178
- head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child
179
- head ||= doc.add_child(doc.create_element "head")
180
+ head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element("head")) if doc.root&.first_element_child
181
+ head ||= doc.add_child(doc.create_element("head"))
180
182
  head << style_tag
181
183
  end
182
184
  end
183
185
  doc
184
186
  end
185
187
 
186
-
187
188
  # Converts the HTML document to a format suitable for plain-text e-mail.
188
189
  #
189
190
  # If present, uses the <body> element as its base; otherwise uses the whole document.
@@ -193,17 +194,17 @@ class Premailer
193
194
  html_src = ''
194
195
  begin
195
196
  html_src = @doc.at("body").inner_html
196
- rescue;
197
+ rescue StandardError
197
198
  end
198
199
 
199
- html_src = @doc.to_html unless html_src and not html_src.empty?
200
+ html_src = @doc.to_html unless html_src && !html_src.empty?
200
201
  convert_to_text(html_src, @options[:line_length], @html_encoding)
201
202
  end
202
203
 
203
204
  # Gets the original HTML as a string.
204
205
  # @return [String] HTML.
205
206
  def to_s
206
- if is_xhtml?
207
+ if xhtml?
207
208
  @doc.to_xhtml(:encoding => nil)
208
209
  else
209
210
  @doc.to_html(:encoding => nil)
@@ -217,13 +218,13 @@ class Premailer
217
218
  thing = nil
218
219
 
219
220
  # TODO: duplicate options
220
- if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
221
+ if @options[:with_html_string] || @options[:inline] || input.respond_to?(:read)
221
222
  thing = input
222
223
  elsif @is_local_file
223
224
  @base_dir = File.dirname(input)
224
225
  thing = File.open(input, 'r')
225
226
  else
226
- thing = URI.open(input)
227
+ thing = URI.parse(input).open
227
228
  end
228
229
 
229
230
  if thing.respond_to?(:read)
@@ -234,7 +235,8 @@ class Premailer
234
235
  doc = nil
235
236
 
236
237
  # Handle HTML entities
237
- if @options[:replace_html_entities] == true and thing.is_a?(String)
238
+ if (@options[:replace_html_entities] == true) && thing.is_a?(String)
239
+ thing = +thing
238
240
  HTML_ENTITIES.map do |entity, replacement|
239
241
  thing.gsub! entity, replacement
240
242
  end
@@ -243,21 +245,20 @@ class Premailer
243
245
  doc = if @options[:html_fragment]
244
246
  ::Nokogiri::HTML.fragment(thing, encoding)
245
247
  else
246
- ::Nokogiri::HTML(thing, nil, encoding) { |c| c.recover }
248
+ ::Nokogiri::HTML(thing, nil, encoding, &:recover)
247
249
  end
248
250
 
249
251
  # Fix for removing any CDATA tags from both style and script tags inserted per
250
252
  # https://github.com/sparklemotion/nokogiri/issues/311 and
251
253
  # https://github.com/premailer/premailer/issues/199
252
- %w(style script).each do |tag|
254
+ ['style', 'script'].each do |tag|
253
255
  doc.search(tag).children.each do |child|
254
- child.swap(child.text()) if child.cdata?
256
+ child.swap(child.text) if child.cdata?
255
257
  end
256
258
  end
257
259
 
258
260
  doc
259
261
  end
260
-
261
262
  end
262
263
  end
263
264
  end