pakyow-mailer 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dd01a34dce235ce0d202baa48d429678dad59dd4
4
- data.tar.gz: b503b8a7f8b607e5fe8a73774dcaf217fd99587d
3
+ metadata.gz: d0b74faa0bb8adc9f4f7016b3b5fc9973c3450a8
4
+ data.tar.gz: 0ed46bc6a8dc4ac35434b6deb381aea87e7dd3d3
5
5
  SHA512:
6
- metadata.gz: 4740d3b49e9af8d7358f6091a547179ff208f2698bb5f080c722ae03175d4fb3d04d55a1e7104837d5413ace94541a04edba4ced83e79315a6143a1c0f54ae7c
7
- data.tar.gz: 58d6509129fe30e199d51660407fb2525f7841cc0f81db601ea976ec5de4acea95ca8ae73782a6361d714fae1e6cd661d2fe8b4d82ec9cf1a91606f71c04c0d8
6
+ metadata.gz: 16c2a60c86fd4f10ce099359b7667412fc691e9c3c234e948619912cc06a6a3de5c58f4df9b65113c1875284dccd624eb2b86d48c9cdeab40832c52c364a65e5
7
+ data.tar.gz: 505ed535070e22021d6aad5dc4e8a6660a7fde7c2da6015d8871cb202c509c0f45cc0bc1d446284ca43b545a9c4c98759f1aa256e6814a3fdfbd60b7a629b1af
@@ -1,3 +1,8 @@
1
+ # 0.11.0
2
+
3
+ * Adds support to use mutators on a mailer view
4
+ * Moves everything into the Pakyow namespace
5
+
1
6
  # 0.10.0 / 2015-10-19
2
7
 
3
8
  * Prevent styles from being mixed into plaintext emails
@@ -1,11 +1 @@
1
- libdir = File.dirname(__FILE__)
2
- $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
-
4
- # Gems
5
- require 'mail'
6
- require 'premailer'
7
-
8
- # Base
9
- require 'mailer/mailer'
10
- require 'mailer/config/mailer'
11
- require 'mailer/helpers'
1
+ require 'pakyow/mailer'
@@ -0,0 +1,9 @@
1
+ require 'mail'
2
+ require 'premailer'
3
+
4
+ require 'pakyow/mailer/mailer'
5
+ require 'pakyow/mailer/config/mailer'
6
+ require 'pakyow/mailer/helpers'
7
+ require 'pakyow/mailer/ext/premailer/adapter/oga'
8
+
9
+ Premailer::Adapter.use = :oga
@@ -1,9 +1,9 @@
1
- Pakyow::Config.register(:mailer) { |config|
1
+ Pakyow::Config.register :mailer do |config|
2
2
  # the default sender name
3
3
  config.opt :default_sender, 'Pakyow'
4
4
 
5
5
  # the default mimetype to use
6
- config.opt :default_content_type, lambda { 'text/html; charset=' + Pakyow::Config.mailer.encoding }
6
+ config.opt :default_content_type, -> { 'text/html; charset=' + Pakyow::Config.mailer.encoding }
7
7
 
8
8
  # the delivery method to use for sending mail
9
9
  config.opt :delivery_method, :sendmail
@@ -13,4 +13,4 @@ Pakyow::Config.register(:mailer) { |config|
13
13
 
14
14
  # the default encoding to use
15
15
  config.opt :encoding, 'UTF-8'
16
- }
16
+ end
@@ -0,0 +1,303 @@
1
+ require 'oga'
2
+
3
+ module Pakyow
4
+ class OgaAttributes
5
+ def initialize(attributes)
6
+ @attributes = attributes
7
+ end
8
+
9
+ def [](key)
10
+ if key.is_a?(Integer)
11
+ super
12
+ else
13
+ @attributes.find { |a| a.name.to_sym == key.to_sym }
14
+ end
15
+ end
16
+
17
+ def method_missing(*args, &block)
18
+ @attributes.send(*args, &block)
19
+ end
20
+ end
21
+ end
22
+
23
+ module Oga
24
+ module XML
25
+ class Document
26
+ # Premailer builds its css searches with `@` prepended to an attribute,
27
+ # which breaks Oga. Since Premailer also expects `search` to be defined
28
+ # on a document, we define it and just strip the `@` out before calling
29
+ # Oga's build-in #css method.
30
+ def search(query)
31
+ css(query.gsub('@', ''))
32
+ end
33
+ end
34
+
35
+ class Element
36
+ def attributes
37
+ Pakyow::OgaAttributes.new(@attributes)
38
+ end
39
+
40
+ def []=(name, value)
41
+ set(name, value)
42
+ end
43
+
44
+ def [](name)
45
+ get(name)
46
+ end
47
+ end
48
+
49
+ class Attribute
50
+ def to_str
51
+ value
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ class Premailer
58
+ # This method was tied to a specific type of document rather than being
59
+ # adapter-agnostic (honestly it's a stretch to call them adapters). So,
60
+ # we just pulled this method in and made it work w/ Oga.
61
+ def load_css_from_html! # :nodoc:
62
+ tags = @doc.css("link[rel='stylesheet']").reject { |tag|
63
+ tag.attribute('data-premailer') == 'ignore'
64
+ }
65
+
66
+ if tags
67
+ tags.each do |tag|
68
+ if tag.to_s.strip =~ /^\<link/i && tag.attribute('href') && media_type_ok?(tag.attribute('media')) && @options[:include_link_tags]
69
+ # A user might want to <link /> to a local css file that is also mirrored on the site
70
+ # but the local one is different (e.g. newer) than the live file, premailer will now choose the local file
71
+
72
+ if tag.attribute('href').to_s.include? @base_url.to_s and @html_file.kind_of?(String)
73
+ if @options[:with_html_string]
74
+ link_uri = tag.attribute('href').to_s.sub(@base_url.to_s, '')
75
+ else
76
+ link_uri = File.join(File.dirname(@html_file), tag.attribute('href').to_s.sub!(@base_url.to_s, ''))
77
+ # if the file does not exist locally, try to grab the remote reference
78
+ unless File.exists?(link_uri)
79
+ link_uri = Premailer.resolve_link(tag.attribute('href').to_s, @html_file)
80
+ end
81
+ end
82
+ else
83
+ link_uri = tag.attribute('href').to_s
84
+ end
85
+
86
+ if Premailer.local_data?(link_uri)
87
+ $stderr.puts "Loading css from local file: " + link_uri if @options[:verbose]
88
+ load_css_from_local_file!(link_uri)
89
+ else
90
+ $stderr.puts "Loading css from uri: " + link_uri if @options[:verbose]
91
+ @css_parser.load_uri!(link_uri, {:only_media_types => [:screen, :handheld]})
92
+ end
93
+
94
+ elsif tag.to_s.strip =~ /^\<style/i && @options[:include_style_tags]
95
+ @css_parser.add_block!(tag.inner_html, :base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld])
96
+ end
97
+ end
98
+ unless @options[:preserve_styles]
99
+ tags.each do |tag|
100
+ tag.remove
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ module Adapter
107
+ # Oga adapter
108
+ module Oga
109
+
110
+ # Merge CSS into the HTML document.
111
+ #
112
+ # @return [String] an HTML.
113
+ def to_inline_css
114
+ doc = @processed_doc
115
+ @unmergable_rules = CssParser::Parser.new
116
+
117
+ # Give all styles already in style attributes a specificity of 1000
118
+ # per http://www.w3.org/TR/CSS21/cascade.html#specificity
119
+ doc.css("*[style]").each do |el|
120
+ el['style'] = '[SPEC=1000[' + el.attribute('style') + ']]'
121
+ end
122
+ # Iterate through the rules and merge them into the HTML
123
+ @css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
124
+ # Save un-mergable rules separately
125
+ selector.gsub!(/:link([\s]*)+/i) { |m| $1 }
126
+
127
+ # Convert element names to lower case
128
+ selector.gsub!(/([\s]|^)([\w]+)/) { |m| $1.to_s + $2.to_s.downcase }
129
+
130
+ if Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
131
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
132
+ else
133
+ begin
134
+ if selector =~ Premailer::RE_RESET_SELECTORS
135
+ # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
136
+ # however, this doesn't mean for testing pur
137
+ @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
138
+ end
139
+
140
+ # Change single ID CSS selectors into xpath so that we can match more
141
+ # than one element. Added to work around dodgy generated code.
142
+ selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
143
+
144
+ doc.css(selector).each do |el|
145
+ if el.elem? and (el.name != 'head' and el.parent.name != 'head')
146
+ # Add a style attribute or append to the existing one
147
+ block = "[SPEC=#{specificity}[#{declaration}]]"
148
+ el['style'] = (el.attribute('style').to_s ||= '') + ' ' + block
149
+ end
150
+ end
151
+ rescue RuntimeError, ArgumentError
152
+ $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
153
+ next
154
+ end
155
+ end
156
+ end
157
+
158
+ # Remove script tags
159
+ if @options[:remove_scripts]
160
+ doc.css("script").remove
161
+ end
162
+
163
+ # Read STYLE attributes and perform folding
164
+ doc.css("*[style]").each do |el|
165
+ style = el.attribute('style').to_s
166
+
167
+ declarations = []
168
+ style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
169
+ rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
170
+ declarations << rs
171
+ end
172
+
173
+ # Perform style folding
174
+ merged = CssParser.merge(declarations)
175
+ merged.expand_shorthand!
176
+
177
+ # Duplicate CSS attributes as HTML attributes
178
+ if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes]
179
+ Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
180
+ el[html_att] = merged[css_att].gsub(/url\(['|"](.*)['|"]\)/, '\1').gsub(/;$|\s*!important/, '').strip if el[html_att].nil? and not merged[css_att].empty?
181
+ merged.instance_variable_get("@declarations").tap do |declarations|
182
+ declarations.delete(css_att)
183
+ end
184
+ end
185
+ end
186
+ # Collapse multiple rules into one as much as possible.
187
+ merged.create_shorthand! if @options[:create_shorthands]
188
+
189
+ # write the inline STYLE attribute
190
+ attributes = Premailer.escape_string(merged.declarations_to_s).split(';').map(&:strip)
191
+ attributes = attributes.map { |attr| [attr.split(':').first, attr] }.sort_by { |pair| pair.first }.map { |pair| pair[1] }
192
+ el['style'] = attributes.join('; ')
193
+ end
194
+
195
+ doc = write_unmergable_css_rules(doc, @unmergable_rules)
196
+
197
+ if @options[:remove_classes] or @options[:remove_comments]
198
+ doc.traverse do |el|
199
+ if el.comment? and @options[:remove_comments]
200
+ el.remove
201
+ elsif el.element?
202
+ el.unset('class') if @options[:remove_classes]
203
+ end
204
+ end
205
+ end
206
+
207
+ if @options[:remove_ids]
208
+ # find all anchor's targets and hash them
209
+ targets = []
210
+ doc.css("a[@href^='#']").each do |el|
211
+ target = el.get_attribute('href')[1..-1]
212
+ targets << target
213
+ el.set('href', "#" + Digest::MD5.hexdigest(target))
214
+ end
215
+ # hash ids that are links target, delete others
216
+ doc.css("*[@id]").each do |el|
217
+ id = el.get_attribute('id')
218
+ if targets.include?(id)
219
+ el.set('id', Digest::MD5.hexdigest(id))
220
+ else
221
+ el.unset('id')
222
+ end
223
+ end
224
+ end
225
+
226
+ if @options[:reset_contenteditable]
227
+ doc.css('*[contenteditable]').each do |el|
228
+ el.unset('contenteditable')
229
+ end
230
+ end
231
+
232
+ @processed_doc = doc
233
+ @processed_doc.to_xml
234
+ end
235
+
236
+ # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
237
+ # and write it into the <tt>body</tt>.
238
+ #
239
+ # <tt>doc</tt> is an Oga document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
240
+ #
241
+ # @return [::Oga::XML::Document] a document.
242
+ def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
243
+ styles = unmergable_rules.to_s
244
+
245
+ unless styles.empty?
246
+ style_tag = "<style type=\"text/css\">\n#{styles}</style>"
247
+ unless (body = doc.css('body')).empty?
248
+ if doc.at_css('body').children && !doc.at_css('body').children.empty?
249
+ doc.at_css('body').children.before(::Oga.parse_html(style_tag))
250
+ else
251
+ doc.at_css('body').add_child(::Oga.parse_html(style_tag))
252
+ end
253
+ else
254
+ doc.inner_html = style_tag += doc.inner_html
255
+ end
256
+ end
257
+ doc
258
+ end
259
+
260
+
261
+ # Converts the HTML document to a format suitable for plain-text e-mail.
262
+ #
263
+ # If present, uses the <body> element as its base; otherwise uses the whole document.
264
+ #
265
+ # @return [String] a plain text.
266
+ def to_plain_text
267
+ html_src = ''
268
+ begin
269
+ html_src = @doc.at("body").inner_html
270
+ rescue;
271
+ end
272
+
273
+ html_src = @doc.to_xml unless html_src and not html_src.empty?
274
+ convert_to_text(html_src, @options[:line_length], @html_encoding)
275
+ end
276
+
277
+ # Gets the original HTML as a string.
278
+ # @return [String] HTML.
279
+ def to_s
280
+ @doc.to_xml
281
+ end
282
+
283
+ # Load the HTML file and convert it into an Oga document.
284
+ #
285
+ # @return [::Oga::XML::Document] a document.
286
+ def load_html(input) # :nodoc:
287
+ thing = nil
288
+
289
+ # TODO: duplicate options
290
+ if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
291
+ thing = input
292
+ elsif @is_local_file
293
+ @base_dir = File.dirname(input)
294
+ thing = File.open(input, 'r')
295
+ else
296
+ thing = open(input)
297
+ end
298
+
299
+ ::Oga.parse_html(thing)
300
+ end
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,7 @@
1
+ module Pakyow
2
+ module Helpers
3
+ def mailer(view_path)
4
+ Mailer.from_store(view_path, @presenter.store, self)
5
+ end
6
+ end
7
+ end
@@ -2,8 +2,9 @@ module Pakyow
2
2
  class Mailer
3
3
  attr_accessor :view, :message, :processed
4
4
 
5
- def self.from_store(view_path, view_store)
6
- new(view: view_store.view(view_path))
5
+ def self.from_store(view_path, view_store, context = nil)
6
+ view = view_store.view(view_path)
7
+ new(view: Pakyow::Presenter::ViewContext.new(view, context))
7
8
  end
8
9
 
9
10
  def initialize(view: nil, content: nil)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pakyow-mailer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-11-16 00:00:00.000000000 Z
12
+ date: 2016-04-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pakyow-support
@@ -17,42 +17,42 @@ dependencies:
17
17
  requirements:
18
18
  - - '='
19
19
  - !ruby/object:Gem::Version
20
- version: 0.10.2
20
+ version: 0.11.0
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '='
26
26
  - !ruby/object:Gem::Version
27
- version: 0.10.2
27
+ version: 0.11.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: pakyow-core
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - '='
33
33
  - !ruby/object:Gem::Version
34
- version: 0.10.2
34
+ version: 0.11.0
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - '='
40
40
  - !ruby/object:Gem::Version
41
- version: 0.10.2
41
+ version: 0.11.0
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: pakyow-presenter
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - '='
47
47
  - !ruby/object:Gem::Version
48
- version: 0.10.2
48
+ version: 0.11.0
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - '='
54
54
  - !ruby/object:Gem::Version
55
- version: 0.10.2
55
+ version: 0.11.0
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: mail
58
58
  requirement: !ruby/object:Gem::Requirement
@@ -104,10 +104,12 @@ files:
104
104
  - pakyow-mailer/CHANGELOG.md
105
105
  - pakyow-mailer/LICENSE
106
106
  - pakyow-mailer/README.md
107
- - pakyow-mailer/lib/mailer/config/mailer.rb
108
- - pakyow-mailer/lib/mailer/helpers.rb
109
- - pakyow-mailer/lib/mailer/mailer.rb
110
107
  - pakyow-mailer/lib/pakyow-mailer.rb
108
+ - pakyow-mailer/lib/pakyow/mailer.rb
109
+ - pakyow-mailer/lib/pakyow/mailer/config/mailer.rb
110
+ - pakyow-mailer/lib/pakyow/mailer/ext/premailer/adapter/oga.rb
111
+ - pakyow-mailer/lib/pakyow/mailer/helpers.rb
112
+ - pakyow-mailer/lib/pakyow/mailer/mailer.rb
111
113
  homepage: http://pakyow.org
112
114
  licenses:
113
115
  - MIT
@@ -128,9 +130,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
130
  version: '0'
129
131
  requirements: []
130
132
  rubyforge_project:
131
- rubygems_version: 2.4.5
133
+ rubygems_version: 2.5.1
132
134
  signing_key:
133
135
  specification_version: 4
134
136
  summary: Pakyow Mailer
135
137
  test_files: []
136
- has_rdoc:
@@ -1,7 +0,0 @@
1
- module Pakyow
2
- module AppHelpers
3
- def mailer(view_path)
4
- Mailer.from_store(view_path, @presenter.store)
5
- end
6
- end
7
- end