postmortem 0.1.0 → 0.2.1
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 +4 -4
- data/.gitignore +2 -1
- data/.rubocop.yml +1 -0
- data/.ruby-version +1 -0
- data/README.md +61 -10
- data/doc/screenshot.png +0 -0
- data/layout/default.html.erb +69 -41
- data/layout/dependencies.css +11 -0
- data/layout/dependencies.js +14 -0
- data/layout/headers_template.html +35 -0
- data/layout/index.html.erb +28 -0
- data/layout/layout.css +160 -0
- data/layout/layout.js +248 -0
- data/lib/postmortem.rb +28 -15
- data/lib/postmortem/adapters.rb +2 -0
- data/lib/postmortem/adapters/action_mailer.rb +46 -7
- data/lib/postmortem/adapters/base.rb +32 -6
- data/lib/postmortem/adapters/mail.rb +21 -0
- data/lib/postmortem/adapters/pony.rb +31 -0
- data/lib/postmortem/configuration.rb +35 -0
- data/lib/postmortem/delivery.rb +22 -11
- data/lib/postmortem/index.rb +60 -0
- data/lib/postmortem/layout.rb +87 -4
- data/lib/postmortem/{action_mailer.rb → plugins/action_mailer.rb} +0 -0
- data/lib/postmortem/plugins/mail.rb +11 -0
- data/lib/postmortem/plugins/pony.rb +17 -0
- data/lib/postmortem/version.rb +1 -1
- data/postmortem.gemspec +1 -0
- metadata +36 -8
- data/Gemfile.lock +0 -126
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Postmortem
|
4
|
+
module Adapters
|
5
|
+
# Mail adapter.
|
6
|
+
class Mail < Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def adapted
|
10
|
+
%i[from reply_to to cc bcc subject]
|
11
|
+
.map { |field| [field, @data.public_send(field)] }
|
12
|
+
.to_h
|
13
|
+
.merge({ text_body: @data.text_part, html_body: @data.html_part })
|
14
|
+
end
|
15
|
+
|
16
|
+
def mail
|
17
|
+
@mail ||= @data
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Postmortem
|
4
|
+
module Adapters
|
5
|
+
# Pony adapter.
|
6
|
+
class Pony < Base
|
7
|
+
private
|
8
|
+
|
9
|
+
def adapted
|
10
|
+
{
|
11
|
+
from: mail.from,
|
12
|
+
reply_to: mail.reply_to,
|
13
|
+
to: mail.to,
|
14
|
+
cc: mail.cc,
|
15
|
+
bcc: mail.bcc,
|
16
|
+
subject: mail.subject,
|
17
|
+
text_body: @data[:body],
|
18
|
+
html_body: @data[:html_body]
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def mail
|
23
|
+
@mail ||= ::Mail.new(@data.select { |key| keys.include?(key) })
|
24
|
+
end
|
25
|
+
|
26
|
+
def keys
|
27
|
+
%i[from reply_to to cc bcc subject text_body html_body]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Postmortem
|
4
|
+
# Provides interface for configuring Postmortem and implements sensible defaults.
|
5
|
+
class Configuration
|
6
|
+
attr_writer :colorize, :mail_skip_delivery
|
7
|
+
attr_accessor :log_path
|
8
|
+
|
9
|
+
def colorize
|
10
|
+
defined?(@colorize) ? @colorize : true
|
11
|
+
end
|
12
|
+
|
13
|
+
def preview_directory=(val)
|
14
|
+
@preview_directory = Pathname.new(val)
|
15
|
+
end
|
16
|
+
|
17
|
+
def layout=(val)
|
18
|
+
@layout = Pathname.new(val)
|
19
|
+
end
|
20
|
+
|
21
|
+
def layout
|
22
|
+
default = File.expand_path(File.join(__dir__, '..', '..', 'layout', 'default'))
|
23
|
+
path = Pathname.new(@layout || default)
|
24
|
+
path.extname.empty? ? path.sub_ext('.html.erb') : path
|
25
|
+
end
|
26
|
+
|
27
|
+
def preview_directory
|
28
|
+
@preview_directory ||= Pathname.new(File.join(Dir.tmpdir, 'postmortem'))
|
29
|
+
end
|
30
|
+
|
31
|
+
def mail_skip_delivery
|
32
|
+
defined?(@mail_skip_delivery) ? @mail_skip_delivery : true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/postmortem/delivery.rb
CHANGED
@@ -3,32 +3,43 @@
|
|
3
3
|
module Postmortem
|
4
4
|
# Abstraction of an email delivery. Capable of writing email HTML body to disk.
|
5
5
|
class Delivery
|
6
|
-
attr_reader :path
|
6
|
+
attr_reader :path, :index_path
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@path = Postmortem.
|
8
|
+
def initialize(mail)
|
9
|
+
@mail = mail
|
10
|
+
@path = Postmortem.config.preview_directory.join('emails.html')
|
11
|
+
@index_path = Postmortem.config.preview_directory.join('index.html')
|
11
12
|
end
|
12
13
|
|
13
14
|
def record
|
14
15
|
path.parent.mkpath
|
15
|
-
|
16
|
+
content = Layout.new(@mail).content
|
17
|
+
path.write(content)
|
18
|
+
index_path.write(index.content)
|
16
19
|
end
|
17
20
|
|
18
21
|
private
|
19
22
|
|
20
|
-
def
|
21
|
-
|
23
|
+
def index
|
24
|
+
@index ||= Index.new(index_path, path, timestamp, @mail)
|
22
25
|
end
|
23
26
|
|
24
27
|
def timestamp
|
25
|
-
Time.now
|
28
|
+
@timestamp ||= Time.now
|
26
29
|
end
|
27
30
|
|
28
|
-
def
|
29
|
-
|
31
|
+
def token
|
32
|
+
SecureRandom.hex(4)
|
33
|
+
end
|
34
|
+
|
35
|
+
def subject
|
36
|
+
return 'no-subject' if @mail.subject.nil? || @mail.subject.empty?
|
30
37
|
|
31
|
-
@
|
38
|
+
@mail.subject
|
39
|
+
end
|
40
|
+
|
41
|
+
def safe_subject
|
42
|
+
subject.tr(' ', '_').split('').select { |char| safe_chars.include?(char) }.join
|
32
43
|
end
|
33
44
|
|
34
45
|
def safe_chars
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Postmortem
|
4
|
+
# Generates and parses an index of previously-sent emails.
|
5
|
+
class Index
|
6
|
+
def initialize(index_path, mail_path, timestamp, mail)
|
7
|
+
@index_path = index_path
|
8
|
+
@mail_path = mail_path
|
9
|
+
@timestamp = timestamp.iso8601
|
10
|
+
@mail = mail
|
11
|
+
end
|
12
|
+
|
13
|
+
def content
|
14
|
+
mail_path = @mail_path
|
15
|
+
ERB.new(File.read(template_path), nil, '-').result(binding)
|
16
|
+
end
|
17
|
+
|
18
|
+
def size
|
19
|
+
encoded_index.size
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def encoded_index
|
25
|
+
return [encoded_mail] unless @index_path.file?
|
26
|
+
|
27
|
+
@encoded_index ||= [encoded_mail] + lines[index(:start)..index(:end)]
|
28
|
+
end
|
29
|
+
|
30
|
+
def encoded_mail
|
31
|
+
Base64.encode64(mail_data.to_json).split("\n").join
|
32
|
+
end
|
33
|
+
|
34
|
+
def mail_data
|
35
|
+
{
|
36
|
+
subject: @mail.subject || '(no subject)',
|
37
|
+
timestamp: @timestamp,
|
38
|
+
path: @mail_path,
|
39
|
+
content: @mail.serializable
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def lines
|
44
|
+
@lines ||= @index_path.read.split("\n")
|
45
|
+
end
|
46
|
+
|
47
|
+
def index(position)
|
48
|
+
offset = { start: 1, end: -1 }.fetch(position)
|
49
|
+
lines.index(marker(position)) + offset
|
50
|
+
end
|
51
|
+
|
52
|
+
def marker(position)
|
53
|
+
"### INDEX #{position.to_s.upcase}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def template_path
|
57
|
+
File.expand_path(File.join(__dir__, '..', '..', 'layout', 'index.html.erb'))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/postmortem/layout.rb
CHANGED
@@ -3,13 +3,96 @@
|
|
3
3
|
module Postmortem
|
4
4
|
# Wraps provided body in an enclosing layout for presentation purposes.
|
5
5
|
class Layout
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(mail)
|
7
|
+
@mail = mail
|
8
|
+
end
|
9
|
+
|
10
|
+
def format_email_array(array)
|
11
|
+
array&.map { |email| %(<a href="mailto:#{email}">#{email}</a>) }&.join(', ')
|
8
12
|
end
|
9
13
|
|
10
14
|
def content
|
11
|
-
mail = @
|
12
|
-
|
15
|
+
mail = @mail
|
16
|
+
mail.html_body = with_inlined_images(mail.html_body) if defined?(Nokogiri)
|
17
|
+
ERB.new(Postmortem.config.layout.read).result(binding)
|
18
|
+
end
|
19
|
+
|
20
|
+
def styles
|
21
|
+
default_layout_directory.join('layout.css').read
|
22
|
+
end
|
23
|
+
|
24
|
+
def javascript
|
25
|
+
default_layout_directory.join('layout.js').read
|
26
|
+
end
|
27
|
+
|
28
|
+
def css_dependencies
|
29
|
+
default_layout_directory.join('dependencies.css').read
|
30
|
+
end
|
31
|
+
|
32
|
+
def javascript_dependencies
|
33
|
+
default_layout_directory.join('dependencies.js').read
|
34
|
+
end
|
35
|
+
|
36
|
+
def headers_template
|
37
|
+
default_layout_directory.join('headers_template.html').read
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def default_layout_directory
|
43
|
+
Postmortem.root.join('layout')
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_inlined_images(body)
|
47
|
+
parsed = Nokogiri::HTML.parse(body)
|
48
|
+
parsed.css('img').each do |img|
|
49
|
+
uri = try_uri(img['src'])
|
50
|
+
next unless local_file?(uri)
|
51
|
+
|
52
|
+
path = located_image(uri)
|
53
|
+
img['src'] = encoded_image(path) unless path.nil?
|
54
|
+
end
|
55
|
+
parsed.to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
def local_file?(uri)
|
59
|
+
return false if uri.nil?
|
60
|
+
return true if uri.host.nil?
|
61
|
+
return true if /^www\.example\.[a-z]+$/.match(uri.host)
|
62
|
+
return true if %w[127.0.0.1 localhost].include?(uri.host)
|
63
|
+
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
def try_uri(uri)
|
68
|
+
URI(uri)
|
69
|
+
rescue URI::InvalidURIError
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def located_image(uri)
|
74
|
+
path = uri.path.partition('/').last
|
75
|
+
common_locations.each do |location|
|
76
|
+
full_path = location.join(path)
|
77
|
+
next unless full_path.file?
|
78
|
+
|
79
|
+
return full_path
|
80
|
+
end
|
81
|
+
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def encoded_image(path)
|
86
|
+
"data:#{mime_type(path)};base64,#{Base64.encode64(path.read)}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def common_locations
|
90
|
+
['public/assets', 'app/assets/images'].map { |path| Pathname.new(path) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def mime_type(path)
|
94
|
+
extension = path.extname.partition('.').last
|
95
|
+
extension == 'jpg' ? 'jpeg' : extension
|
13
96
|
end
|
14
97
|
end
|
15
98
|
end
|
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Mail::SMTP.class_eval do
|
4
|
+
alias_method :_original_deliver!, :deliver!
|
5
|
+
|
6
|
+
def deliver!(mail)
|
7
|
+
result = _original_deliver!(mail) unless Postmortem.config.mail_skip_delivery
|
8
|
+
Postmortem.record_delivery(Postmortem::Adapters::Mail.new(mail))
|
9
|
+
result
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Pony monkey-patch.
|
4
|
+
module Pony
|
5
|
+
class << self
|
6
|
+
alias _original_mail mail
|
7
|
+
|
8
|
+
def mail(options)
|
9
|
+
# SMTP delivery is handled by Mail plugin further down the stack
|
10
|
+
return _original_mail(options) if options[:via].to_s == 'smtp'
|
11
|
+
|
12
|
+
result = _original_mail(options) unless Postmortem.config.mail_skip_delivery
|
13
|
+
Postmortem.record_delivery(Postmortem::Adapters::Pony.new(options))
|
14
|
+
result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/postmortem/version.rb
CHANGED
data/postmortem.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_runtime_dependency 'mail', '~> 2.7'
|
29
29
|
|
30
30
|
spec.add_development_dependency 'actionmailer', '~> 6.0'
|
31
|
+
spec.add_development_dependency 'pony', '~> 1.13'
|
31
32
|
spec.add_development_dependency 'rspec', '~> 3.9'
|
32
33
|
spec.add_development_dependency 'rspec-its', '~> 1.3'
|
33
34
|
spec.add_development_dependency 'rubocop', '~> 0.88.0'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: postmortem
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Farrell
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mail
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '6.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pony
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.13'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,22 +132,35 @@ files:
|
|
118
132
|
- ".gitignore"
|
119
133
|
- ".rspec"
|
120
134
|
- ".rubocop.yml"
|
135
|
+
- ".ruby-version"
|
121
136
|
- Gemfile
|
122
|
-
- Gemfile.lock
|
123
137
|
- LICENSE.txt
|
124
138
|
- Makefile
|
125
139
|
- README.md
|
126
140
|
- Rakefile
|
127
141
|
- bin/console
|
128
142
|
- bin/setup
|
143
|
+
- doc/screenshot.png
|
129
144
|
- layout/default.html.erb
|
145
|
+
- layout/dependencies.css
|
146
|
+
- layout/dependencies.js
|
147
|
+
- layout/headers_template.html
|
148
|
+
- layout/index.html.erb
|
149
|
+
- layout/layout.css
|
150
|
+
- layout/layout.js
|
130
151
|
- lib/postmortem.rb
|
131
|
-
- lib/postmortem/action_mailer.rb
|
132
152
|
- lib/postmortem/adapters.rb
|
133
153
|
- lib/postmortem/adapters/action_mailer.rb
|
134
154
|
- lib/postmortem/adapters/base.rb
|
155
|
+
- lib/postmortem/adapters/mail.rb
|
156
|
+
- lib/postmortem/adapters/pony.rb
|
157
|
+
- lib/postmortem/configuration.rb
|
135
158
|
- lib/postmortem/delivery.rb
|
159
|
+
- lib/postmortem/index.rb
|
136
160
|
- lib/postmortem/layout.rb
|
161
|
+
- lib/postmortem/plugins/action_mailer.rb
|
162
|
+
- lib/postmortem/plugins/mail.rb
|
163
|
+
- lib/postmortem/plugins/pony.rb
|
137
164
|
- lib/postmortem/version.rb
|
138
165
|
- postmortem.gemspec
|
139
166
|
homepage: https://github.com/bobf/postmortem
|
@@ -143,7 +170,7 @@ metadata:
|
|
143
170
|
homepage_uri: https://github.com/bobf/postmortem
|
144
171
|
source_code_uri: https://github.com/bobf/postmortem
|
145
172
|
changelog_uri: https://github.com/bobf/postmortem/blob/master/README.md
|
146
|
-
post_install_message:
|
173
|
+
post_install_message:
|
147
174
|
rdoc_options: []
|
148
175
|
require_paths:
|
149
176
|
- lib
|
@@ -158,8 +185,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
185
|
- !ruby/object:Gem::Version
|
159
186
|
version: '0'
|
160
187
|
requirements: []
|
161
|
-
|
162
|
-
|
188
|
+
rubyforge_project:
|
189
|
+
rubygems_version: 2.7.6
|
190
|
+
signing_key:
|
163
191
|
specification_version: 4
|
164
192
|
summary: Development HTML Email Inspection Tool
|
165
193
|
test_files: []
|