premailer 1.5.6 → 1.5.7
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.
- data/bin/premailer +89 -65
- data/lib/premailer/premailer.rb +57 -40
- metadata +4 -4
data/bin/premailer
CHANGED
@@ -1,76 +1,100 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
require 'optparse'
|
4
4
|
require 'rubygems'
|
5
|
-
require
|
6
|
-
require
|
7
|
-
|
8
|
-
opts = Trollop::options do
|
9
|
-
version "Premailer #{Premailer::VERSION} (c) 2008-2009 Alex Dunae"
|
10
|
-
banner <<-EOS
|
11
|
-
Improve the rendering of HTML emails by making CSS inline, converting links and warning about unsupported code.
|
12
|
-
|
13
|
-
Usage:
|
14
|
-
premailer [options] inputfile [outputfile] [warningsfile]
|
15
|
-
|
16
|
-
Example
|
17
|
-
premailer http://example.com/
|
18
|
-
premailer http://example.com/ out.html out.txt warnings.txt
|
19
|
-
premailer --base-url=http://example.com/ src.html out.html
|
20
|
-
|
21
|
-
Options:
|
22
|
-
EOS
|
23
|
-
opt :base_url, "Manually set the base URL, useful for local files", :type => String
|
24
|
-
opt :query_string, "Query string to append to links", :type => String, :short => 'q'
|
25
|
-
opt :line_length, "Length of lines when creating plaintext version", :type => :int, :default => 65, :short => 'l'
|
26
|
-
opt :remove_classes, "Remove classes from the HTML document?", :default => false
|
27
|
-
opt :css, "Manually specify css stylesheets", :type => String, :multi => true
|
28
|
-
opt :verbose, '', :default => false, :short => 'v'
|
29
|
-
opt :io_exceptions, "Abort on I/O errors loading style resources", :default => false, :short => 'e'
|
30
|
-
end
|
5
|
+
require 'premailer'
|
6
|
+
require 'fcntl'
|
31
7
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
premailer_opts = {
|
40
|
-
:base_url => opts[:base_url],
|
41
|
-
:query_string => opts[:query_string],
|
42
|
-
:show_warnings => opts[:show_warnings] ? Premailer::Warnings::SAFE : Premailer::Warnings::NONE,
|
43
|
-
:line_length => opts[:line_length],
|
44
|
-
:remove_classes => opts[:remove_classes],
|
45
|
-
:css => opts[:css],
|
46
|
-
:verbose => opts[:verbose],
|
47
|
-
:io_exceptions => opts[:io_exceptions],
|
8
|
+
# defaults
|
9
|
+
options = {
|
10
|
+
:base_url => nil,
|
11
|
+
:link_query_string => nil,
|
12
|
+
:remove_classes => false,
|
13
|
+
:verbose => false,
|
14
|
+
:line_length => 65
|
48
15
|
}
|
49
16
|
|
50
|
-
|
17
|
+
mode = :html
|
51
18
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
19
|
+
opts = OptionParser.new do |opts|
|
20
|
+
opts.banner = "Improve the rendering of HTML emails by making CSS inline, converting links and warning about unsupported code."
|
21
|
+
opts.define_head "Usage: premailer <uri|path> [options]"
|
22
|
+
opts.separator ""
|
23
|
+
opts.separator "Examples:"
|
24
|
+
opts.separator " premailer http://example.com/ > out.html"
|
25
|
+
opts.separator " premailer http://example.com/ --mode txt > out.txt"
|
26
|
+
opts.separator " cat input.html | premailer -q src=email > out.html"
|
27
|
+
opts.separator " premailer ./public/index.html"
|
28
|
+
opts.separator ""
|
29
|
+
opts.separator "Options:"
|
30
|
+
|
31
|
+
opts.on("--mode [MODE]", [:html, :txt], "Output type: either html or txt") do |v|
|
32
|
+
mode = v
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-b", "--base-url", String, "Manually set the base URL, useful for local files") do |v|
|
36
|
+
options[:base_url] = v
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("-q", "--query-string STRING", String, "Query string to append to links (do not include the ?)") do |v|
|
40
|
+
options[:link_query_string] = v
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("--css FILE,FILE", Array, "Additional CSS stylesheets") do |v|
|
44
|
+
options[:css] = v
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("-r", "--remove-classes", "Remove classes from the HTML document?") do |v|
|
48
|
+
options[:remove_classes] = v
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-l", "--line-length N", Integer, "Length of lines when creating plaintext version (default: #{options[:line_length].to_s})") do |v|
|
52
|
+
options[:line_length] = v
|
53
|
+
end
|
61
54
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
55
|
+
opts.on("-d", "--io-exceptions", "Abort on I/O errors") do |v|
|
56
|
+
options[:io_exceptions] = v
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on("-v", "--verbose", "Print additional information at runtime") do |v|
|
60
|
+
options[:verbose] = v
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on_tail("-?", "--help", "Show this message") do
|
64
|
+
puts opts
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on_tail("-V", "--version", "Show version") do
|
69
|
+
puts "Premailer #{Premailer::VERSION} (c) 2008-2010 Alex Dunae"
|
70
|
+
exit
|
71
|
+
end
|
67
72
|
end
|
73
|
+
opts.parse!
|
74
|
+
|
75
|
+
$stderr.puts "Processing in #{mode} mode with options #{options.inspect}" if options[:verbose]
|
76
|
+
|
77
|
+
premailer = nil
|
68
78
|
|
69
|
-
#
|
70
|
-
if
|
71
|
-
|
72
|
-
premailer.
|
73
|
-
|
79
|
+
# check for input from STDIN
|
80
|
+
if STDIN.fcntl(Fcntl::F_GETFL, 0) == 0
|
81
|
+
io = STDIN.to_io
|
82
|
+
premailer = Premailer.new(io, options)
|
83
|
+
else
|
84
|
+
uri = ARGV.shift
|
85
|
+
|
86
|
+
if uri.to_s.strip.empty?
|
87
|
+
puts opts
|
88
|
+
exit 1
|
74
89
|
end
|
75
|
-
|
90
|
+
|
91
|
+
premailer = Premailer.new(uri, options)
|
92
|
+
end
|
93
|
+
|
94
|
+
if mode == :txt
|
95
|
+
p premailer.to_plain_text
|
96
|
+
else
|
97
|
+
p premailer.to_inline_css
|
76
98
|
end
|
99
|
+
|
100
|
+
exit
|
data/lib/premailer/premailer.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
#
|
3
|
-
# Premailer by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-
|
3
|
+
# Premailer by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2008-10
|
4
4
|
#
|
5
5
|
# Premailer processes HTML and CSS to improve e-mail deliverability.
|
6
6
|
#
|
@@ -33,7 +33,7 @@ class Premailer
|
|
33
33
|
include HtmlToPlainText
|
34
34
|
include CssParser
|
35
35
|
|
36
|
-
VERSION = '1.5.
|
36
|
+
VERSION = '1.5.7'
|
37
37
|
|
38
38
|
CLIENT_SUPPORT_FILE = File.dirname(__FILE__) + '/../../misc/client_support.yaml'
|
39
39
|
|
@@ -96,11 +96,12 @@ class Premailer
|
|
96
96
|
# ==== Options
|
97
97
|
# [+line_length+] Line length used by to_plain_text. Boolean, default is 65.
|
98
98
|
# [+warn_level+] What level of CSS compatibility warnings to show (see Warnings).
|
99
|
-
# [+link_query_string+] A string to append to every <a href=""> link. Do not include the initial
|
99
|
+
# [+link_query_string+] A string to append to every <tt>a href=""</tt> link. Do not include the initial <tt>?</tt>.
|
100
100
|
# [+base_url+] Used to calculate absolute URLs for local files.
|
101
101
|
# [+css+] Manually specify a CSS stylesheet.
|
102
102
|
# [+css_to_attributes+] Copy related CSS attributes into HTML attributes (e.g. +background-color+ to +bgcolor+)
|
103
103
|
# [+with_html_string+] Whether the +html+ param should be treated as a raw string.
|
104
|
+
# [+verbose+] Whether to print errors and warnings to <tt>$stderr</tt>. Default is +false+.
|
104
105
|
def initialize(html, options = {})
|
105
106
|
@options = {:warn_level => Warnings::SAFE,
|
106
107
|
:line_length => 65,
|
@@ -153,10 +154,10 @@ class Premailer
|
|
153
154
|
@css_warnings = check_client_support if @css_warnings.empty?
|
154
155
|
@css_warnings
|
155
156
|
end
|
156
|
-
|
157
|
+
|
157
158
|
# Returns the original HTML as a string.
|
158
159
|
def to_s
|
159
|
-
@doc.to_html
|
160
|
+
is_xhtml? ? @doc.to_xhtml : @doc.to_html
|
160
161
|
end
|
161
162
|
|
162
163
|
# Converts the HTML document to a format suitable for plain-text e-mail.
|
@@ -188,21 +189,24 @@ class Premailer
|
|
188
189
|
# Iterate through the rules and merge them into the HTML
|
189
190
|
@css_parser.each_selector(:all) do |selector, declaration, specificity|
|
190
191
|
# Save un-mergable rules separately
|
191
|
-
selector.gsub!(/:link([\s]
|
192
|
+
selector.gsub!(/:link([\s]*)+/i) {|m| $1 }
|
192
193
|
|
193
194
|
# Convert element names to lower case
|
194
195
|
selector.gsub!(/([\s]|^)([\w]+)/) {|m| $1.to_s + $2.to_s.downcase }
|
195
|
-
|
196
|
+
|
196
197
|
if selector =~ RE_UNMERGABLE_SELECTORS
|
197
198
|
unmergable_rules.add_rule_set!(RuleSet.new(selector, declaration))
|
198
199
|
else
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
200
|
+
begin
|
201
|
+
doc.css(selector).each do |el|
|
202
|
+
if el.elem?
|
203
|
+
# Add a style attribute or append to the existing one
|
204
|
+
block = "[SPEC=#{specificity}[#{declaration}]]"
|
205
|
+
el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
|
206
|
+
end
|
205
207
|
end
|
208
|
+
rescue Nokogiri::SyntaxError, RuntimeError
|
209
|
+
$stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
|
206
210
|
end
|
207
211
|
end
|
208
212
|
end
|
@@ -254,28 +258,30 @@ class Premailer
|
|
254
258
|
doc.to_html
|
255
259
|
end
|
256
260
|
|
261
|
+
# Check for an XHTML doctype
|
262
|
+
def is_xhtml?
|
263
|
+
intro = @doc.to_s.strip.split("\n")[0..2].join(' ')
|
264
|
+
intro =~ /w3c\/\/[\s]*dtd[\s]+xhtml/i
|
265
|
+
end
|
257
266
|
|
258
267
|
protected
|
259
268
|
# Load the HTML file and convert it into an Nokogiri document.
|
260
269
|
#
|
261
270
|
# Returns an Nokogiri document.
|
262
|
-
def load_html(
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
271
|
+
def load_html(input) # :nodoc:
|
272
|
+
thing = nil
|
273
|
+
|
274
|
+
# TODO: duplicate options
|
275
|
+
if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
|
276
|
+
thing = input
|
277
|
+
elsif @is_local_file
|
278
|
+
@base_dir = File.dirname(input)
|
279
|
+
thing = File.open(input, 'r')
|
267
280
|
else
|
268
|
-
|
269
|
-
if path.is_a?(IO) || path.is_a?(StringIO)
|
270
|
-
Nokogiri::HTML(path.read)
|
271
|
-
else
|
272
|
-
@base_dir = File.dirname(path)
|
273
|
-
Nokogiri::HTML(File.open(path, "r") {|f| f.read })
|
274
|
-
end
|
275
|
-
else
|
276
|
-
Nokogiri::HTML(open(path))
|
277
|
-
end
|
281
|
+
thing = open(input)
|
278
282
|
end
|
283
|
+
|
284
|
+
thing ? Nokogiri::HTML(thing) : nil
|
279
285
|
end
|
280
286
|
|
281
287
|
def load_css_from_local_file!(path)
|
@@ -308,10 +314,10 @@ protected
|
|
308
314
|
|
309
315
|
link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
|
310
316
|
if Premailer.local_data?(link_uri)
|
311
|
-
puts "Loading css from local file: " + link_uri if @options[:verbose]
|
317
|
+
$stderr.puts "Loading css from local file: " + link_uri if @options[:verbose]
|
312
318
|
load_css_from_local_file!(link_uri)
|
313
319
|
else
|
314
|
-
puts "Loading css from uri: " + link_uri if @options[:verbose]
|
320
|
+
$stderr.puts "Loading css from uri: " + link_uri if @options[:verbose]
|
315
321
|
@css_parser.load_uri!(link_uri)
|
316
322
|
end
|
317
323
|
|
@@ -350,19 +356,30 @@ protected
|
|
350
356
|
end
|
351
357
|
|
352
358
|
def append_query_string(doc, qs)
|
359
|
+
return doc if qs.nil?
|
360
|
+
|
361
|
+
qs.to_s.strip!
|
362
|
+
return doc if qs.empty?
|
363
|
+
|
364
|
+
$stderr.puts "Attempting to append_query_string: #{qs}" if @options[:verbose]
|
365
|
+
|
353
366
|
doc.search('a').each do|el|
|
354
|
-
href = el.attributes['href'].to_s
|
355
|
-
next if href.nil? or href.empty?
|
356
|
-
|
357
|
-
|
367
|
+
href = el.attributes['href'].to_s.strip
|
368
|
+
next if href.nil? or href.empty?
|
369
|
+
|
370
|
+
begin
|
371
|
+
href = URI.parse(href)
|
372
|
+
if href.query
|
373
|
+
href.query = href.query + '&' + qs
|
374
|
+
else
|
375
|
+
href.query = qs
|
376
|
+
end
|
358
377
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
378
|
+
el['href'] = href.to_s
|
379
|
+
rescue URI::Error => e
|
380
|
+
$stderr.puts "Skipping append_query_string for: #{href.to_s} (#{e.message})" if @options[:verbose]
|
381
|
+
next
|
363
382
|
end
|
364
|
-
|
365
|
-
el['href'] = href.to_s
|
366
383
|
|
367
384
|
end
|
368
385
|
doc
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: premailer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 5
|
9
|
-
-
|
10
|
-
version: 1.5.
|
9
|
+
- 7
|
10
|
+
version: 1.5.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alex Dunae
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-11-
|
18
|
+
date: 2010-11-05 00:00:00 -07:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|