premailer 1.19.0 → 1.30.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.
@@ -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
  # NokogiriFast adapter
6
7
  module NokogiriFast
8
+ WIDTH_AND_HEIGHT = ['width', 'height'].freeze
7
9
 
8
10
  include AdapterHelper::RgbToHex
9
11
  # Merge CSS into the HTML document.
@@ -25,34 +27,33 @@ class Premailer
25
27
 
26
28
  # Iterate through the rules and merge them into the HTML
27
29
  @css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
28
-
29
30
  # Save un-mergable rules separately
30
- selector.gsub!(/:link([\s]*)+/i) { |m| $1 }
31
+ selector.gsub!(/:link([\s]*)+/i) { |_m| $1 }
31
32
 
32
33
  # Convert element names to lower case
33
- selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
34
+ selector.gsub!(/([\s]|^)([\w]+)/) { |_m| $1.to_s + $2.to_s.downcase }
34
35
 
35
- if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
36
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
36
+ if Premailer.media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
37
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
37
38
  else
38
39
  begin
39
- if selector =~ Premailer::RE_RESET_SELECTORS
40
+ if Premailer::RE_RESET_SELECTORS.match?(selector) && !!@options[:preserve_reset]
40
41
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
41
42
  # however, this doesn't mean for testing pur
42
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
43
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
43
44
  end
44
45
 
45
46
  # Try the new index based technique. If not supported, fall back to the old brute force one.
46
47
  nodes = match_selector(index, all_nodes, descendants, selector) || doc.search(selector)
47
48
  nodes.each do |el|
48
- if el.elem? and (el.name != 'head' and el.parent.name != 'head')
49
+ if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
49
50
  # Add a style attribute or append to the existing one
50
51
  block = "[SPEC=#{specificity}[#{declaration}]]"
51
52
  el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
52
53
  end
53
54
  end
54
55
  rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
55
- $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
56
+ warn "CSS syntax error with selector: #{selector}" if @options[:verbose]
56
57
  next
57
58
  end
58
59
  end
@@ -66,19 +67,25 @@ class Premailer
66
67
  style = el.attributes['style'].to_s
67
68
 
68
69
  declarations = []
69
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
70
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
70
+ style.scan(/\[SPEC=([\d]+)\[(.[^\]]*)\]\]/m).each do |declaration|
71
+ rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
71
72
  declarations << rs
73
+ rescue ArgumentError => e
74
+ raise e if @options[:rule_set_exceptions]
72
75
  end
73
76
 
74
77
  # Perform style folding
75
78
  merged = CssParser.merge(declarations)
76
- merged.expand_shorthand!
79
+ begin
80
+ merged.expand_shorthand!
81
+ rescue ArgumentError => e
82
+ raise e if @options[:rule_set_exceptions]
83
+ end
77
84
 
78
85
  # Duplicate CSS attributes as HTML attributes
79
- if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
86
+ if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
80
87
  Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
81
- if el[html_attr].nil? and not merged[css_attr].empty?
88
+ if el[html_attr].nil? && !merged[css_attr].empty?
82
89
  new_val = merged[css_attr].dup
83
90
 
84
91
  # Remove url() function wrapper
@@ -88,7 +95,7 @@ class Premailer
88
95
  new_val.gsub!(/;$|\s*!important/, '').strip!
89
96
 
90
97
  # For width and height tags, remove px units
91
- new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr)
98
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HEIGHT.include?(html_attr)
92
99
 
93
100
  # For color-related tags, convert RGB to hex if specified by options
94
101
  new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
@@ -97,7 +104,7 @@ class Premailer
97
104
  end
98
105
 
99
106
  unless @options[:preserve_style_attribute]
100
- merged.instance_variable_get("@declarations").tap do |declarations|
107
+ merged.instance_variable_get(:@declarations).tap do |declarations|
101
108
  declarations.delete(css_attr)
102
109
  end
103
110
  end
@@ -113,9 +120,9 @@ class Premailer
113
120
 
114
121
  doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
115
122
 
116
- if @options[:remove_classes] or @options[:remove_comments]
123
+ if @options[:remove_classes] || @options[:remove_comments]
117
124
  doc.traverse do |el|
118
- if el.comment? and @options[:remove_comments]
125
+ if el.comment? && @options[:remove_comments]
119
126
  el.remove
120
127
  elsif el.element?
121
128
  el.remove_attribute('class') if @options[:remove_classes]
@@ -127,7 +134,7 @@ class Premailer
127
134
  # find all anchor's targets and hash them
128
135
  targets = []
129
136
  doc.search("a[@href^='#']").each do |el|
130
- target = el.get_attribute('href')[1..-1]
137
+ target = el.get_attribute('href')[1..]
131
138
  targets << target
132
139
  el.set_attribute('href', "#" + Digest::SHA256.hexdigest(target))
133
140
  end
@@ -149,7 +156,7 @@ class Premailer
149
156
  end
150
157
 
151
158
  @processed_doc = doc
152
- if is_xhtml?
159
+ if xhtml?
153
160
  # we don't want to encode carriage returns
154
161
  @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
155
162
  else
@@ -173,15 +180,14 @@ class Premailer
173
180
  else
174
181
  style_tag = doc.create_element "style", styles
175
182
  head = doc.at_css('head')
176
- head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child
177
- head ||= doc.add_child(doc.create_element "head")
183
+ head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element("head")) if doc.root&.first_element_child
184
+ head ||= doc.add_child(doc.create_element("head"))
178
185
  head << style_tag
179
186
  end
180
187
  end
181
188
  doc
182
189
  end
183
190
 
184
-
185
191
  # Converts the HTML document to a format suitable for plain-text e-mail.
186
192
  #
187
193
  # If present, uses the <body> element as its base; otherwise uses the whole document.
@@ -191,17 +197,17 @@ class Premailer
191
197
  html_src = ''
192
198
  begin
193
199
  html_src = @doc.at("body").inner_html
194
- rescue;
200
+ rescue StandardError
195
201
  end
196
202
 
197
- html_src = @doc.to_html unless html_src and not html_src.empty?
203
+ html_src = @doc.to_html unless html_src && !html_src.empty?
198
204
  convert_to_text(html_src, @options[:line_length], @html_encoding)
199
205
  end
200
206
 
201
207
  # Gets the original HTML as a string.
202
208
  # @return [String] HTML.
203
209
  def to_s
204
- if is_xhtml?
210
+ if xhtml?
205
211
  @doc.to_xhtml(:encoding => nil)
206
212
  else
207
213
  @doc.to_html(:encoding => nil)
@@ -215,13 +221,13 @@ class Premailer
215
221
  thing = nil
216
222
 
217
223
  # TODO: duplicate options
218
- if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
224
+ if @options[:with_html_string] || @options[:inline] || input.respond_to?(:read)
219
225
  thing = input
220
226
  elsif @is_local_file
221
227
  @base_dir = File.dirname(input)
222
228
  thing = File.open(input, 'r')
223
229
  else
224
- thing = URI.open(input)
230
+ thing = URI.parse(input).open
225
231
  end
226
232
 
227
233
  if thing.respond_to?(:read)
@@ -232,7 +238,7 @@ class Premailer
232
238
  doc = nil
233
239
 
234
240
  # Handle HTML entities
235
- if @options[:replace_html_entities] == true and thing.is_a?(String)
241
+ if (@options[:replace_html_entities] == true) && thing.is_a?(String)
236
242
  HTML_ENTITIES.map do |entity, replacement|
237
243
  thing.gsub! entity, replacement
238
244
  end
@@ -241,15 +247,15 @@ class Premailer
241
247
  doc = if @options[:html_fragment]
242
248
  ::Nokogiri::HTML.fragment(thing, encoding)
243
249
  else
244
- ::Nokogiri::HTML(thing, nil, encoding) { |c| c.recover }
250
+ ::Nokogiri::HTML(thing, nil, encoding, &:recover)
245
251
  end
246
252
 
247
253
  # Fix for removing any CDATA tags from both style and script tags inserted per
248
254
  # https://github.com/sparklemotion/nokogiri/issues/311 and
249
255
  # https://github.com/premailer/premailer/issues/199
250
- %w(style script).each do |tag|
256
+ ['style', 'script'].each do |tag|
251
257
  doc.search(tag).children.each do |child|
252
- child.swap(child.text()) if child.cdata?
258
+ child.swap(child.text) if child.cdata?
253
259
  end
254
260
  end
255
261
 
@@ -273,7 +279,7 @@ class Premailer
273
279
  page.traverse do |node|
274
280
  all_nodes.push(node)
275
281
 
276
- if node != page then
282
+ if node != page
277
283
  index_ancestry(page, node, node.parent, descendants)
278
284
  end
279
285
 
@@ -284,7 +290,7 @@ class Premailer
284
290
  # Index the node by all class attributes it possesses.
285
291
  # Classes are modestly selective. Usually more than tag names
286
292
  # but less selective than ids.
287
- if node.has_attribute?("class") then
293
+ if node.has_attribute?("class")
288
294
  node.get_attribute("class").split(/\s+/).each do |c|
289
295
  c = '.' + c
290
296
  index[c] = (index[c] || Set.new).add(node)
@@ -293,7 +299,7 @@ class Premailer
293
299
 
294
300
  # Index the node by its "id" attribute if it has one.
295
301
  # This is usually the most selective of the three.
296
- if node.has_attribute?("id") then
302
+ if node.has_attribute?("id")
297
303
  id = '#' + node.get_attribute("id")
298
304
  index[id] = (index[id] || Set.new).add(node)
299
305
  end
@@ -306,7 +312,7 @@ class Premailer
306
312
  index.default = Set.new
307
313
  descendants.default = Set.new
308
314
 
309
- return index, Set.new(all_nodes), descendants
315
+ [index, Set.new(all_nodes), descendants]
310
316
  end
311
317
 
312
318
  # @param doc The top level document
@@ -315,9 +321,9 @@ class Premailer
315
321
  # @param descendants The running hash map of node -> set of nodes that maps descendants of a node.
316
322
  # @return The descendants argument after updating it.
317
323
  def index_ancestry(doc, elem, parent, descendants)
318
- if parent then
324
+ if parent
319
325
  descendants[parent] = (descendants[parent] || Set.new).add(elem)
320
- if doc != parent then
326
+ if doc != parent
321
327
  index_ancestry(doc, elem, parent.parent, descendants)
322
328
  end
323
329
  end
@@ -346,14 +352,14 @@ class Premailer
346
352
  # It will return nil when such a selector is passed, so you can take
347
353
  # action on the falsity of the return value.
348
354
  def match_selector(index, all_nodes, descendants, selector)
349
- if /[^-a-zA-Z0-9_\s.#]/.match(selector) then
355
+ if /[^-a-zA-Z0-9_\s.#]/.match?(selector)
350
356
  return nil
351
357
  end
352
358
 
353
359
  take_children = false
354
360
  selector.split(/\s+/).reduce(all_nodes) do |base, spec|
355
361
  desc = base
356
- if take_children then
362
+ if take_children
357
363
  desc = Set.new
358
364
  base.each do |n|
359
365
  desc.merge(descendants[n])
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  class Premailer
2
3
  module Adapter
3
4
  # Nokogiri adapter
4
5
  module Nokogumbo
6
+ WIDTH_AND_HEIGHT = ['width', 'height'].freeze
5
7
 
6
8
  include AdapterHelper::RgbToHex
7
9
  # Merge CSS into the HTML document.
@@ -19,19 +21,19 @@ class Premailer
19
21
  # Iterate through the rules and merge them into the HTML
20
22
  @css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
21
23
  # Save un-mergable rules separately
22
- selector.gsub!(/:link([\s]*)+/i) { |m| $1 }
24
+ selector.gsub!(/:link([\s]*)+/i) { |_m| $1 }
23
25
 
24
26
  # Convert element names to lower case
25
- selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
27
+ selector.gsub!(/([\s]|^)([\w]+)/) { |_m| $1.to_s + $2.to_s.downcase }
26
28
 
27
- if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
28
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
29
+ if Premailer.media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
30
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration), media_types) unless @options[:preserve_styles]
29
31
  else
30
32
  begin
31
- if selector =~ Premailer::RE_RESET_SELECTORS
33
+ if Premailer::RE_RESET_SELECTORS.match?(selector) && !!@options[:preserve_reset]
32
34
  # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
33
35
  # however, this doesn't mean for testing pur
34
- @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
36
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
35
37
  end
36
38
 
37
39
  # Change single ID CSS selectors into xpath so that we can match more
@@ -39,14 +41,14 @@ class Premailer
39
41
  selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
40
42
 
41
43
  doc.search(selector).each do |el|
42
- if el.elem? and (el.name != 'head' and el.parent.name != 'head')
44
+ if el.elem? && ((el.name != 'head') && (el.parent.name != 'head'))
43
45
  # Add a style attribute or append to the existing one
44
46
  block = "[SPEC=#{specificity}[#{declaration}]]"
45
47
  el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
46
48
  end
47
49
  end
48
50
  rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
49
- $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
51
+ warn "CSS syntax error with selector: #{selector}" if @options[:verbose]
50
52
  next
51
53
  end
52
54
  end
@@ -60,19 +62,25 @@ class Premailer
60
62
  style = el.attributes['style'].to_s
61
63
 
62
64
  declarations = []
63
- style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
64
- rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
65
+ style.scan(/\[SPEC=([\d]+)\[(.[^\]]*)\]\]/m).each do |declaration|
66
+ rs = CssParser::RuleSet.new(block: declaration[1].to_s, specificity: declaration[0].to_i)
65
67
  declarations << rs
68
+ rescue ArgumentError => e
69
+ raise e if @options[:rule_set_exceptions]
66
70
  end
67
71
 
68
72
  # Perform style folding
69
73
  merged = CssParser.merge(declarations)
70
- merged.expand_shorthand!
74
+ begin
75
+ merged.expand_shorthand!
76
+ rescue ArgumentError => e
77
+ raise e if @options[:rule_set_exceptions]
78
+ end
71
79
 
72
80
  # Duplicate CSS attributes as HTML attributes
73
- if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
81
+ if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
74
82
  Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
75
- if el[html_attr].nil? and not merged[css_attr].empty?
83
+ if el[html_attr].nil? && !merged[css_attr].empty?
76
84
  new_val = merged[css_attr].dup
77
85
 
78
86
  # Remove url() function wrapper
@@ -82,7 +90,7 @@ class Premailer
82
90
  new_val.gsub!(/;$|\s*!important/, '').strip!
83
91
 
84
92
  # For width and height tags, remove px units
85
- new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr)
93
+ new_val.gsub!(/(\d+)px/, '\1') if WIDTH_AND_HEIGHT.include?(html_attr)
86
94
 
87
95
  # For color-related tags, convert RGB to hex if specified by options
88
96
  new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]
@@ -91,7 +99,7 @@ class Premailer
91
99
  end
92
100
 
93
101
  unless @options[:preserve_style_attribute]
94
- merged.instance_variable_get("@declarations").tap do |declarations|
102
+ merged.instance_variable_get(:@declarations).tap do |declarations|
95
103
  declarations.delete(css_attr)
96
104
  end
97
105
  end
@@ -107,9 +115,9 @@ class Premailer
107
115
 
108
116
  doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]
109
117
 
110
- if @options[:remove_classes] or @options[:remove_comments]
118
+ if @options[:remove_classes] || @options[:remove_comments]
111
119
  doc.traverse do |el|
112
- if el.comment? and @options[:remove_comments]
120
+ if el.comment? && @options[:remove_comments]
113
121
  el.remove
114
122
  elsif el.element?
115
123
  el.remove_attribute('class') if @options[:remove_classes]
@@ -121,7 +129,7 @@ class Premailer
121
129
  # find all anchor's targets and hash them
122
130
  targets = []
123
131
  doc.search("a[@href^='#']").each do |el|
124
- target = el.get_attribute('href')[1..-1]
132
+ target = el.get_attribute('href')[1..]
125
133
  targets << target
126
134
  el.set_attribute('href', "#" + Digest::SHA256.hexdigest(target))
127
135
  end
@@ -143,7 +151,7 @@ class Premailer
143
151
  end
144
152
 
145
153
  @processed_doc = doc
146
- if is_xhtml?
154
+ if xhtml?
147
155
  # we don't want to encode carriage returns
148
156
  @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
149
157
  else
@@ -167,15 +175,14 @@ class Premailer
167
175
  else
168
176
  style_tag = doc.create_element "style", styles
169
177
  head = doc.at_css('head')
170
- head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child
171
- head ||= doc.add_child(doc.create_element "head")
178
+ head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element("head")) if doc.root&.first_element_child
179
+ head ||= doc.add_child(doc.create_element("head"))
172
180
  head << style_tag
173
181
  end
174
182
  end
175
183
  doc
176
184
  end
177
185
 
178
-
179
186
  # Converts the HTML document to a format suitable for plain-text e-mail.
180
187
  #
181
188
  # If present, uses the <body> element as its base; otherwise uses the whole document.
@@ -185,17 +192,17 @@ class Premailer
185
192
  html_src = ''
186
193
  begin
187
194
  html_src = @doc.at("body").inner_html
188
- rescue;
195
+ rescue StandardError
189
196
  end
190
197
 
191
- html_src = @doc.to_html unless html_src and not html_src.empty?
198
+ html_src = @doc.to_html unless html_src && !html_src.empty?
192
199
  convert_to_text(html_src, @options[:line_length], @html_encoding)
193
200
  end
194
201
 
195
202
  # Gets the original HTML as a string.
196
203
  # @return [String] HTML.
197
204
  def to_s
198
- if is_xhtml?
205
+ if xhtml?
199
206
  @doc.to_xhtml(:encoding => nil)
200
207
  else
201
208
  @doc.to_html(:encoding => nil)
@@ -209,13 +216,13 @@ class Premailer
209
216
  thing = nil
210
217
 
211
218
  # TODO: duplicate options
212
- if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
219
+ if @options[:with_html_string] || @options[:inline] || input.respond_to?(:read)
213
220
  thing = input
214
221
  elsif @is_local_file
215
222
  @base_dir = File.dirname(input)
216
223
  thing = File.open(input, 'r')
217
224
  else
218
- thing = URI.open(input)
225
+ thing = URI.parse(input).open
219
226
  end
220
227
 
221
228
  if thing.respond_to?(:read)
@@ -226,7 +233,7 @@ class Premailer
226
233
  doc = nil
227
234
 
228
235
  # Handle HTML entities
229
- if @options[:replace_html_entities] == true and thing.is_a?(String)
236
+ if (@options[:replace_html_entities] == true) && thing.is_a?(String)
230
237
  HTML_ENTITIES.map do |entity, replacement|
231
238
  thing.gsub! entity, replacement
232
239
  end
@@ -240,15 +247,14 @@ class Premailer
240
247
  # Fix for removing any CDATA tags from both style and script tags inserted per
241
248
  # https://github.com/sparklemotion/nokogiri/issues/311 and
242
249
  # https://github.com/premailer/premailer/issues/199
243
- %w(style script).each do |tag|
250
+ ['style', 'script'].each do |tag|
244
251
  doc.search(tag).children.each do |child|
245
- child.swap(child.text()) if child.cdata?
252
+ child.swap(child.text) if child.cdata?
246
253
  end
247
254
  end
248
255
 
249
256
  doc
250
257
  end
251
-
252
258
  end
253
259
  end
254
260
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # RGB helper for adapters, currently only nokogiri supported
2
3
 
3
4
  module AdapterHelper
@@ -6,23 +7,24 @@ module AdapterHelper
6
7
  str.to_i.to_s(16).rjust(2, '0').upcase
7
8
  end
8
9
 
9
- def is_rgb?(color)
10
+ def rgb?(color)
10
11
  pattern = %r{
11
12
  rgb
12
- \(\s* # literal open, with optional whitespace
13
- (\d{1,3}) # capture 1-3 digits
14
- \s*,\s* # comma, with optional whitespace
15
- (\d{1,3}) # capture 1-3 digits
16
- \s*,\s* # comma, with optional whitespace
17
- (\d{1,3}) # capture 1-3 digits
18
- \s*\) # literal close, with optional whitespace
13
+ \(\s* # literal open, with optional whitespace
14
+ (\d{1,3}) # capture 1-3 digits
15
+ (?:\s*,\s*|\s+) # comma or whitespace
16
+ (\d{1,3}) # capture 1-3 digits
17
+ (?:\s*,\s*|\s+) # comma or whitespacee
18
+ (\d{1,3}) # capture 1-3 digits
19
+ \s*(?:/\s*\d*\.?\d*%?)? # optional alpha modifier
20
+ \s*\) # literal close, with optional whitespace
19
21
  }x
20
22
 
21
23
  pattern.match(color)
22
24
  end
23
25
 
24
26
  def ensure_hex(color)
25
- match_data = is_rgb?(color)
27
+ match_data = rgb?(color)
26
28
  if match_data
27
29
  "#{to_hex(match_data[1])}#{to_hex(match_data[2])}#{to_hex(match_data[3])}"
28
30
  else
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class Premailer
2
3
  # Manages the adapter classes. Currently supports:
3
4
  #
@@ -5,7 +6,6 @@ class Premailer
5
6
  # * nokogiri_fast
6
7
  # * nokogumbo
7
8
  module Adapter
8
-
9
9
  autoload :Nokogiri, 'premailer/adapter/nokogiri'
10
10
  autoload :NokogiriFast, 'premailer/adapter/nokogiri_fast'
11
11
  autoload :Nokogumbo, 'premailer/adapter/nokogumbo'
@@ -14,13 +14,13 @@ class Premailer
14
14
  REQUIREMENT_MAP = [
15
15
  ["nokogiri", :nokogiri],
16
16
  ["nokogiri", :nokogiri_fast],
17
- ["nokogumbo", :nokogumbo],
18
- ]
17
+ ["nokogumbo", :nokogumbo]
18
+ ].freeze
19
19
 
20
20
  # Returns the adapter to use.
21
21
  def self.use
22
22
  return @use if @use
23
- self.use = self.default
23
+ self.use = default
24
24
  @use
25
25
  end
26
26
 
@@ -34,15 +34,13 @@ class Premailer
34
34
  return :nokogumbo if defined?(::Nokogumbo)
35
35
 
36
36
  REQUIREMENT_MAP.each do |(library, adapter)|
37
- begin
38
- require library
39
- return adapter
40
- rescue LoadError
41
- next
42
- end
37
+ require library
38
+ return adapter
39
+ rescue LoadError
40
+ next
43
41
  end
44
42
 
45
- raise RuntimeError.new("No suitable adapter for Premailer was found, please install nokogiri or nokogumbo")
43
+ raise "No suitable adapter for Premailer was found, please install nokogiri or nokogumbo"
46
44
  end
47
45
 
48
46
  # Sets the adapter to use.
@@ -56,10 +54,9 @@ class Premailer
56
54
  def self.find(adapter)
57
55
  return adapter if adapter.is_a?(Module)
58
56
 
59
- Premailer::Adapter.const_get("#{adapter.to_s.split('_').map{|s| s.capitalize}.join('')}")
57
+ Premailer::Adapter.const_get(adapter.to_s.split('_').map(&:capitalize).join.to_s)
60
58
  rescue NameError
61
59
  raise ArgumentError, "Invalid adapter: #{adapter}"
62
60
  end
63
-
64
61
  end
65
62
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ class Premailer
3
+ class CachedRuleSet < CssParser::RuleSet
4
+ # we call this early to find errors but css-parser calls it in .merge again
5
+ # so to prevent slowdown and bugs we refuse to run it twice on the same ruleset
6
+ # ideally should be upstreamed into css-parser
7
+ def expand_shorthand!
8
+ super unless @expand_shorthand
9
+ ensure
10
+ @expand_shorthand = true
11
+ end
12
+ end
13
+ end
@@ -1,19 +1,20 @@
1
+ # frozen_string_literal: true
1
2
  require 'optparse'
2
3
  require 'premailer'
3
4
 
4
5
  # defaults
5
6
  options = {
6
- :base_url => nil,
7
+ :base_url => nil,
7
8
  :link_query_string => nil,
8
- :remove_classes => false,
9
- :verbose => false,
10
- :line_length => 65,
11
- :adapter => :nokogiri,
9
+ :remove_classes => false,
10
+ :verbose => false,
11
+ :line_length => 65,
12
+ :adapter => :nokogiri
12
13
  }
13
14
 
14
15
  mode = :html
15
16
 
16
- opts = OptionParser.new do |opts|
17
+ parser = OptionParser.new do |opts|
17
18
  opts.banner = "Improve the rendering of HTML emails by making CSS inline among other things. Takes a path to a local file, a URL or a pipe as input.\n\n"
18
19
  opts.define_head "Usage: premailer <optional uri|optional path> [options]"
19
20
  opts.separator ""
@@ -53,11 +54,11 @@ opts = OptionParser.new do |opts|
53
54
  options[:remove_scripts] = true
54
55
  end
55
56
 
56
- opts.on("-l", "--line-length N", Integer, "Line length for plaintext (default: #{options[:line_length].to_s})") do |v|
57
+ opts.on("-l", "--line-length N", Integer, "Line length for plaintext (default: #{options[:line_length]})") do |v|
57
58
  options[:line_length] = v
58
59
  end
59
60
 
60
- opts.on("-e", "--entities", "Output HTML entities instead of UTF-8 when using Nokogiri") do |v|
61
+ opts.on("-e", "--entities", "Output HTML entities instead of UTF-8 when using Nokogiri") do |_v|
61
62
  options[:output_encoding] = "US-ASCII"
62
63
  end
63
64
 
@@ -79,9 +80,9 @@ opts = OptionParser.new do |opts|
79
80
  exit
80
81
  end
81
82
  end
82
- opts.parse!
83
+ parser.parse!
83
84
 
84
- $stderr.puts "Processing in #{mode} mode with options #{options.inspect}" if options[:verbose]
85
+ warn "Processing in #{mode} mode with options #{options.inspect}" if options[:verbose]
85
86
 
86
87
  premailer = nil
87
88
  input = nil
@@ -98,7 +99,7 @@ end
98
99
  if input
99
100
  premailer = Premailer.new(input, options)
100
101
  else
101
- puts opts
102
+ puts parser
102
103
  exit 1
103
104
  end
104
105