premailer 1.5.6 → 1.5.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|