postmortem 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rubocop.yml +1 -0
- data/README.md +64 -10
- data/doc/screenshot.png +0 -0
- data/layout/default.html.erb +282 -41
- data/lib/postmortem.rb +20 -17
- data/lib/postmortem/adapters.rb +1 -0
- data/lib/postmortem/adapters/action_mailer.rb +46 -7
- data/lib/postmortem/adapters/base.rb +6 -6
- data/lib/postmortem/adapters/pony.rb +31 -0
- data/lib/postmortem/configuration.rb +39 -0
- data/lib/postmortem/delivery.rb +4 -2
- data/lib/postmortem/layout.rb +5 -1
- data/lib/postmortem/{action_mailer.rb → plugins/action_mailer.rb} +0 -0
- data/lib/postmortem/plugins/pony.rb +14 -0
- data/lib/postmortem/version.rb +1 -1
- data/postmortem.gemspec +1 -0
- metadata +21 -4
- data/Gemfile.lock +0 -126
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d675b984abb71e212b6c7f3e190c9bf8d17282ed0741b260cf06891cff5b70d
|
4
|
+
data.tar.gz: 408e888943fe644d76b686d3693588378c513138ea88d82da633bf0c2367f19e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6cddb8f06e21fd028b7531d79efabf8320e86a22670cfed45901b189d563249cd1a420859ff8cbf1a7a542a15d8c908018a6fb8477ec023407997e7d51338e9
|
7
|
+
data.tar.gz: cfda5e45b5302a36cbd7503b418142c13f5c0e458ca0ba1e705503513b9549e5c41d440af485bade2db280a1890fffef5df33bfd487250e38762ee7a811b4992
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
# Postmortem
|
2
2
|
|
3
|
-
|
3
|
+
_Postmortem_ provides a simple and clean preview of all outgoing mails sent by your _Ruby_ application to make email development a little less painful.
|
4
4
|
|
5
|
-
|
5
|
+
Every time your application sends an email a clearly-visible log entry will be written which provides a path to a temporary file containing your preview.
|
6
|
+
|
7
|
+
Take a look at a [live example](https://postmortem.surge.sh/) to see _Postmortem_ in action.
|
8
|
+
|
9
|
+
_Postmortem_ should only be enabled in test or development environments.
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
* Seamless integration with [_ActionMailer_](https://guides.rubyonrails.org/action_mailer_basics.html) and [_Pony_](https://github.com/benprew/pony) (with automatic delivery skipping when using _Pony_).
|
14
|
+
* Preview email content as well as typical email headers (recipients, subject, etc.).
|
15
|
+
* View rendered _HTML_, plaintext, or _HTML_ source with syntax highlighting (courtesy of [highlight.js](https://highlightjs.org/)).
|
16
|
+
* Dual or single column view to suit your requirements.
|
17
|
+
* Content is loaded inside an `<iframe>` to ensure document isolation and validity.
|
6
18
|
|
7
19
|
## Installation
|
8
20
|
|
9
|
-
Add
|
21
|
+
Add the gem to your application's Gemfile:
|
10
22
|
|
11
23
|
```ruby
|
12
|
-
|
24
|
+
group :development, :test do
|
25
|
+
gem 'postmortem', '~> 0.1.1'
|
26
|
+
end
|
13
27
|
```
|
14
28
|
|
15
29
|
And then execute:
|
@@ -22,18 +36,58 @@ Or install it yourself as:
|
|
22
36
|
|
23
37
|
## Usage
|
24
38
|
|
25
|
-
|
39
|
+
_Postmortem_ automatically integrates with _Rails ActionMailer_ and _Pony_. When an email is sent an entry will be visible in your application's log output.
|
40
|
+
|
41
|
+
The path to the preview file is based on the current time and the subject of the email. If you would prefer to use the same path for each email you can disable timestamps (see [configuration](#configuration)) and simply reload your browser every time an email is sent.
|
26
42
|
|
27
|
-
|
43
|
+
If you are using assets (images etc.) with _ActionMailer_ make sure to configure the asset host, e.g.:
|
28
44
|
|
29
|
-
|
45
|
+
```ruby
|
46
|
+
# config/environments/development.rb
|
47
|
+
Rails.application.configure do
|
48
|
+
config.action_mailer.asset_host = 'http://localhost:3000'
|
49
|
+
end
|
50
|
+
```
|
30
51
|
|
31
|
-
|
52
|
+
Load the provided file in your browser to preview your email.
|
32
53
|
|
33
|
-
|
54
|
+
![Screenshot](doc/screenshot.png)
|
55
|
+
|
56
|
+
|
57
|
+
## Configuration
|
58
|
+
<a name="configuration"></a>
|
34
59
|
|
35
|
-
|
60
|
+
Configure _Postmortem_ by calling `Postmortem.configure`, e.g. in a _Rails_ initializer.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# config/initializers/postmortem.rb
|
64
|
+
Postmortem.configure do |config|
|
65
|
+
# Colorize output in logs (path to preview HTML file) to improve visibility (default: true).
|
66
|
+
config.colorize = true
|
67
|
+
|
68
|
+
# Prefix all preview filenames with a timestamp (default: true).
|
69
|
+
# Setting to false allows refreshing the same path in your browser to view the latest version.
|
70
|
+
config.timestmap = true
|
71
|
+
|
72
|
+
# Path to the Postmortem log file, where preview paths are written (default: STDOUT).
|
73
|
+
config.log_path = '/path/to/postmortem.log'
|
74
|
+
|
75
|
+
# Path to save preview .html files (default: OS-provided temp directory).
|
76
|
+
# The directory will be created if it does not exist.
|
77
|
+
config.preview_directory = '/path/to/postmortem/directory'
|
78
|
+
|
79
|
+
# Provide a custom layout path if the default interface does not suit you.
|
80
|
+
# See `layout/default.html.erb` for implementation reference.
|
81
|
+
config.layout = '/path/to/layout'
|
82
|
+
|
83
|
+
# Skip delivery of emails when using Pony (default: true).
|
84
|
+
config.pony_skip_delivery = true
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
## Contributing
|
36
89
|
|
90
|
+
Feel free to make a pull request.
|
37
91
|
|
38
92
|
## License
|
39
93
|
|
data/doc/screenshot.png
ADDED
Binary file
|
data/layout/default.html.erb
CHANGED
@@ -1,49 +1,290 @@
|
|
1
1
|
<html>
|
2
2
|
<head>
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
<link rel="stylesheet"
|
4
|
+
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
5
|
+
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
|
6
|
+
crossorigin="anonymous" />
|
7
|
+
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
|
8
|
+
rel="stylesheet" />
|
9
|
+
<link rel="stylesheet"
|
10
|
+
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/styles/a11y-light.min.css"
|
11
|
+
integrity="sha512-zqEpZCxg7IVhGXy6EwdTb26cHl8IZzN/29Mj2/oSzkCKiLxHTi491mcC/K1NhShMWXu+WvfU5Z261XPc60lw7g=="
|
12
|
+
crossorigin="anonymous" />
|
13
|
+
<style>
|
14
|
+
.headers {
|
15
|
+
height: 100%;
|
16
|
+
max-width: 35rem;
|
17
|
+
display: none;
|
18
|
+
}
|
19
|
+
|
20
|
+
.content {
|
21
|
+
background-color: #efefef;
|
22
|
+
padding: 1rem 2rem;
|
23
|
+
height: 100%;
|
24
|
+
}
|
25
|
+
|
26
|
+
.headers table {
|
27
|
+
background-color: #fff;
|
28
|
+
}
|
29
|
+
|
30
|
+
.preview {
|
31
|
+
display: none;
|
32
|
+
background-color: #fff;
|
33
|
+
padding: 2rem;
|
34
|
+
height: 100%;
|
35
|
+
}
|
36
|
+
|
37
|
+
.preview.source-view {
|
38
|
+
padding: 1.5rem;
|
39
|
+
}
|
40
|
+
|
41
|
+
.preview iframe {
|
42
|
+
border-style: none;
|
43
|
+
width: 100%;
|
44
|
+
height: 100%;
|
45
|
+
}
|
46
|
+
|
47
|
+
.main-row {
|
48
|
+
height: 100%;
|
49
|
+
}
|
50
|
+
|
51
|
+
.container {
|
52
|
+
height: 90vh;
|
53
|
+
margin-top: 2.8rem;
|
54
|
+
}
|
55
|
+
|
56
|
+
.container.full-width {
|
57
|
+
max-width: 100%;
|
58
|
+
}
|
59
|
+
|
60
|
+
.visible {
|
61
|
+
display: block;
|
62
|
+
}
|
63
|
+
|
64
|
+
.toolbar {
|
65
|
+
background-color: #fff;
|
66
|
+
text-align: right;
|
67
|
+
padding: 0.5rem 1rem;
|
68
|
+
position: fixed;
|
69
|
+
width: 100%;
|
70
|
+
}
|
71
|
+
|
72
|
+
.toolbar i {
|
73
|
+
font-size: 1.2rem;
|
74
|
+
cursor: pointer;
|
75
|
+
margin: 0 0.2rem;
|
76
|
+
}
|
77
|
+
|
78
|
+
.toolbar i.disabled {
|
79
|
+
cursor: default;
|
80
|
+
color: #ddd !important;
|
81
|
+
}
|
82
|
+
|
83
|
+
.toolbar .separator {
|
84
|
+
border-right: 1px solid #ccc;
|
85
|
+
margin-right: 0.5rem;
|
86
|
+
margin-left: 0.2rem;
|
87
|
+
}
|
88
|
+
</style>
|
7
89
|
</head>
|
8
|
-
<body
|
9
|
-
<div
|
10
|
-
|
11
|
-
<
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
<td><%= mail.bcc&.join(', ') %></td>
|
30
|
-
</tr>
|
31
|
-
|
32
|
-
<tr>
|
33
|
-
<th>Subject:</th>
|
34
|
-
<td><%= mail.subject %></td>
|
35
|
-
</tr>
|
36
|
-
</tbody>
|
37
|
-
</table>
|
38
|
-
|
39
|
-
<div style="background-color: #ffffff; padding: 2rem; height: 100%; flex-grow: 1">
|
40
|
-
<iframe id="mail-iframe" src="" style="border-style: none; width: 100%; height: 100%"></iframe>
|
90
|
+
<body>
|
91
|
+
<div class="toolbar">
|
92
|
+
|
93
|
+
<i data-toggle="tooltip"
|
94
|
+
title="<%= mail.html_body.nil? ? 'View HTML Part (Unavailable)' : 'View HTML Part' %>"
|
95
|
+
class="fa fa-code html-view-switch"></i>
|
96
|
+
|
97
|
+
<i data-toggle="tooltip"
|
98
|
+
title="<%= mail.html_body.nil? ? 'View HTML Source (Unavailable)' : 'View HTML Source' %>"
|
99
|
+
class="fa fa-file-code-o source-view-switch"></i>
|
100
|
+
|
101
|
+
<i data-toggle="tooltip"
|
102
|
+
title="<%= mail.text_body.nil? ? 'View Plaintext Part (Unavailable)' : 'View Plaintext Part' %>"
|
103
|
+
class="fa fa-file-text-o text-view-switch"></i>
|
104
|
+
|
105
|
+
<span class="separator"></span>
|
106
|
+
|
107
|
+
<i data-toggle="tooltip"
|
108
|
+
title="Toggle Email Headers"
|
109
|
+
class="fa fa-columns column-switch"></i>
|
110
|
+
|
41
111
|
</div>
|
112
|
+
<div class="content">
|
113
|
+
<div class="container full-width">
|
114
|
+
<div class="row main-row">
|
115
|
+
<div class="col headers visible">
|
116
|
+
<table class="table table-hover table table-bordered">
|
117
|
+
<tbody>
|
118
|
+
<tr>
|
119
|
+
<th>Subject:</th>
|
120
|
+
<td><%= mail.subject %></td>
|
121
|
+
</tr>
|
122
|
+
|
123
|
+
<tr>
|
124
|
+
<th style="width: 7rem;">From:</th>
|
125
|
+
<td><%= format_email_array(mail.from) %></td>
|
126
|
+
</tr>
|
127
|
+
|
128
|
+
<tr>
|
129
|
+
<th>Reply-To:</th>
|
130
|
+
<td><%= format_email_array(mail.reply_to) %></td>
|
131
|
+
</tr>
|
132
|
+
|
133
|
+
<tr>
|
134
|
+
<th>To:</th>
|
135
|
+
<td><%= format_email_array(mail.to) %></td>
|
136
|
+
</tr>
|
137
|
+
|
138
|
+
<tr>
|
139
|
+
<th>Cc:</th>
|
140
|
+
<td><%= format_email_array(mail.cc) %></td>
|
141
|
+
</tr>
|
142
|
+
|
143
|
+
<tr>
|
144
|
+
<th>Bcc:</th>
|
145
|
+
<td><%= format_email_array(mail.bcc) %></td>
|
146
|
+
</tr>
|
147
|
+
</tbody>
|
148
|
+
</table>
|
149
|
+
</div>
|
150
|
+
<div class="preview col html-view">
|
151
|
+
<iframe id="html-iframe"></iframe>
|
152
|
+
</div>
|
153
|
+
<div class="preview col text-view">
|
154
|
+
<iframe id="text-iframe"></iframe>
|
155
|
+
</div>
|
156
|
+
<div class="preview col source-view">
|
157
|
+
<iframe id="source-iframe"></iframe>
|
158
|
+
</div>
|
159
|
+
</div>
|
160
|
+
</div>
|
161
|
+
</div>
|
162
|
+
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
|
163
|
+
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
|
164
|
+
crossorigin="anonymous"></script>
|
165
|
+
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
|
166
|
+
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
|
167
|
+
crossorigin="anonymous"></script>
|
168
|
+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
|
169
|
+
integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
|
170
|
+
crossorigin="anonymous"></script>
|
42
171
|
<script>
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
172
|
+
(function () {
|
173
|
+
var htmlIframeDocument = document.querySelector("#html-iframe").contentDocument;
|
174
|
+
htmlIframeDocument.write(<%= mail.html_body.to_json %>);
|
175
|
+
htmlIframeDocument.close();
|
176
|
+
|
177
|
+
var textIframeDocument = document.querySelector("#text-iframe").contentDocument;
|
178
|
+
textIframeDocument.write('<pre>' + <%= ERB::Util.html_escape(mail.text_body).to_json %> + '</pre>');
|
179
|
+
textIframeDocument.close();
|
180
|
+
|
181
|
+
var sourceHighlightBundle = [
|
182
|
+
'<link rel="stylesheet"',
|
183
|
+
' href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/styles/agate.min.css"',
|
184
|
+
' integrity="sha512-mMMMPADD4HAIogAWZbv+WjZTC0itafUZNI0jQm48PMBTApXt11omF5jhS7U1kp3R2Pr6oGJ+JwQKiUkUwCQaUQ=="',
|
185
|
+
' crossorigin="anonymous" />',
|
186
|
+
'<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/highlight.min.js"',
|
187
|
+
' integrity="sha512-tHQeqtcNWlZtEh8As/4MmZ5qpy0wj04svWFK7MIzLmUVIzaHXS8eod9OmHxyBL1UET5Rchvw7Ih4ZDv5JojZww=="',
|
188
|
+
' crossorigin="anonymous"></' + 'script>',
|
189
|
+
'<script>hljs.initHighlightingOnLoad();</' + 'script>',
|
190
|
+
].join('\n');
|
191
|
+
|
192
|
+
var sourceIframeDocument = document.querySelector("#source-iframe").contentDocument;
|
193
|
+
sourceIframeDocument.write('<pre><code style="padding: 1rem;" class="language-html">' + <%= ERB::Util.html_escape(mail.html_body).to_json %> + '</code></pre>');
|
194
|
+
sourceIframeDocument.write(sourceHighlightBundle);
|
195
|
+
sourceIframeDocument.close();
|
196
|
+
|
197
|
+
var twoColumnView;
|
198
|
+
var hasHtml = <%= (!mail.html_body.nil?).to_json %>;
|
199
|
+
var hasText = <%= (!mail.text_body.nil?).to_json %>;
|
200
|
+
var headers = document.querySelector('.headers');
|
201
|
+
var columnSwitch = document.querySelector('.column-switch');
|
202
|
+
|
203
|
+
var setColumnView = function (enableTwoColumnView) {
|
204
|
+
var container = document.querySelector('.container');
|
205
|
+
twoColumnView = enableTwoColumnView;
|
206
|
+
if (twoColumnView) {
|
207
|
+
setVisible(headers, true);
|
208
|
+
setOn(columnSwitch);
|
209
|
+
container.classList.add('full-width');
|
210
|
+
} else {
|
211
|
+
setVisible(headers, false);
|
212
|
+
setOff(columnSwitch);
|
213
|
+
container.classList.remove('full-width');
|
214
|
+
}
|
215
|
+
};
|
216
|
+
|
217
|
+
var contexts = ['source', 'text', 'html'];
|
218
|
+
|
219
|
+
var views = {
|
220
|
+
source: document.querySelector('.source-view'),
|
221
|
+
html: document.querySelector('.html-view'),
|
222
|
+
text: document.querySelector('.text-view'),
|
223
|
+
};
|
224
|
+
|
225
|
+
var toolbar = {
|
226
|
+
source: document.querySelector('.source-view-switch'),
|
227
|
+
html: document.querySelector('.html-view-switch'),
|
228
|
+
text: document.querySelector('.text-view-switch'),
|
229
|
+
};
|
230
|
+
|
231
|
+
var setOn = function(element) {
|
232
|
+
element.classList.add('text-primary');
|
233
|
+
element.classList.remove('text-secondary');
|
234
|
+
};
|
235
|
+
|
236
|
+
var setOff = function(element) {
|
237
|
+
element.classList.add('text-secondary');
|
238
|
+
element.classList.remove('text-primary');
|
239
|
+
};
|
240
|
+
|
241
|
+
var setDisabled = function(element) {
|
242
|
+
element.classList.add('disabled');
|
243
|
+
element.classList.remove('text-secondary');
|
244
|
+
element.onclick = function () {};
|
245
|
+
};
|
246
|
+
|
247
|
+
var setVisible = function(element, visible) {
|
248
|
+
if (visible) {
|
249
|
+
element.classList.add('visible');
|
250
|
+
} else {
|
251
|
+
element.classList.remove('visible');
|
252
|
+
}
|
253
|
+
};
|
254
|
+
|
255
|
+
var setView = function(context) {
|
256
|
+
var key;
|
257
|
+
for (i = 0; i < contexts.length; i++) {
|
258
|
+
key = contexts[i];
|
259
|
+
if (key === context) {
|
260
|
+
setOn(toolbar[key]);
|
261
|
+
setVisible(views[key], true);
|
262
|
+
} else {
|
263
|
+
setOff(toolbar[key]);
|
264
|
+
setVisible(views[key], false);
|
265
|
+
}
|
266
|
+
}
|
267
|
+
};
|
268
|
+
|
269
|
+
toolbar.html.onclick = function () { setView('html'); };
|
270
|
+
toolbar.text.onclick = function () { setView('text'); };
|
271
|
+
toolbar.source.onclick = function () { setView('source'); };
|
272
|
+
columnSwitch.onclick = function () { setColumnView(!twoColumnView); };
|
273
|
+
|
274
|
+
if (!hasText) setDisabled(toolbar.text);
|
275
|
+
|
276
|
+
if (hasHtml) {
|
277
|
+
setView('html');
|
278
|
+
} else {
|
279
|
+
setDisabled(toolbar.html);
|
280
|
+
setDisabled(toolbar.source);
|
281
|
+
setView('text');
|
282
|
+
}
|
283
|
+
|
284
|
+
setColumnView(true);
|
285
|
+
|
286
|
+
$('[data-toggle="tooltip"]').tooltip();
|
287
|
+
})();
|
47
288
|
</script>
|
48
289
|
</body>
|
49
290
|
</html>
|
data/lib/postmortem.rb
CHANGED
@@ -7,27 +7,20 @@ require 'fileutils'
|
|
7
7
|
require 'mail'
|
8
8
|
require 'erb'
|
9
9
|
require 'json'
|
10
|
+
require 'cgi'
|
10
11
|
|
11
12
|
require 'postmortem/version'
|
12
13
|
require 'postmortem/adapters'
|
13
14
|
require 'postmortem/delivery'
|
14
15
|
require 'postmortem/layout'
|
16
|
+
require 'postmortem/configuration'
|
15
17
|
|
16
18
|
# HTML email inspection tool.
|
17
19
|
module Postmortem
|
18
20
|
class Error < StandardError; end
|
19
21
|
|
20
22
|
class << self
|
21
|
-
attr_reader :
|
22
|
-
attr_accessor :output_file
|
23
|
-
|
24
|
-
def output_directory=(val)
|
25
|
-
@output_directory = Pathname.new(val)
|
26
|
-
end
|
27
|
-
|
28
|
-
def layout=(val)
|
29
|
-
@layout = Pathname.new(val)
|
30
|
-
end
|
23
|
+
attr_reader :config
|
31
24
|
|
32
25
|
def record_delivery(mail)
|
33
26
|
Delivery.new(mail)
|
@@ -35,15 +28,20 @@ module Postmortem
|
|
35
28
|
.tap { |delivery| log_delivery(delivery) }
|
36
29
|
end
|
37
30
|
|
38
|
-
def try_load(*args)
|
31
|
+
def try_load(*args, plugin:)
|
39
32
|
args.each { |arg| require arg }
|
40
33
|
rescue LoadError
|
41
34
|
false
|
42
35
|
else
|
43
|
-
|
36
|
+
require "postmortem/plugins/#{plugin}"
|
44
37
|
true
|
45
38
|
end
|
46
39
|
|
40
|
+
def configure
|
41
|
+
@config = Configuration.new
|
42
|
+
yield @config if block_given?
|
43
|
+
end
|
44
|
+
|
47
45
|
private
|
48
46
|
|
49
47
|
def log_delivery(delivery)
|
@@ -52,14 +50,19 @@ module Postmortem
|
|
52
50
|
end
|
53
51
|
|
54
52
|
def colorized(val)
|
55
|
-
return val unless output_file.tty?
|
53
|
+
return val unless output_file.tty? || !config.colorize
|
56
54
|
|
57
55
|
"\e[34m[postmortem]\e[36m #{val}\e[0m"
|
58
56
|
end
|
57
|
+
|
58
|
+
def output_file
|
59
|
+
return STDOUT if config.log_path.nil?
|
60
|
+
|
61
|
+
@output_file ||= File.open(config.log_path, mode: 'a')
|
62
|
+
end
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
62
|
-
Postmortem.
|
63
|
-
Postmortem.
|
64
|
-
Postmortem.
|
65
|
-
Postmortem.try_load('action_mailer', 'active_support') { require 'postmortem/action_mailer' }
|
66
|
+
Postmortem.configure
|
67
|
+
Postmortem.try_load('action_mailer', 'active_support', plugin: 'action_mailer')
|
68
|
+
Postmortem.try_load('pony', plugin: 'pony')
|
data/lib/postmortem/adapters.rb
CHANGED
@@ -6,16 +6,55 @@ module Postmortem
|
|
6
6
|
class ActionMailer < Base
|
7
7
|
private
|
8
8
|
|
9
|
-
def adapted
|
9
|
+
def adapted
|
10
10
|
{
|
11
|
-
from:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
from: mail.from,
|
12
|
+
reply_to: mail.reply_to,
|
13
|
+
to: mail.to,
|
14
|
+
cc: mail.cc,
|
15
|
+
bcc: normalized_bcc,
|
16
|
+
subject: mail.subject,
|
17
|
+
text_body: text_part,
|
18
|
+
html_body: html_part
|
17
19
|
}
|
18
20
|
end
|
21
|
+
|
22
|
+
def text_part
|
23
|
+
return nil unless text?
|
24
|
+
return mail.body.decoded unless mail.text_part
|
25
|
+
|
26
|
+
mail.text_part.decoded
|
27
|
+
end
|
28
|
+
|
29
|
+
def html_part
|
30
|
+
return nil unless html?
|
31
|
+
return mail.body.decoded unless mail.html_part
|
32
|
+
|
33
|
+
mail.html_part.decoded
|
34
|
+
end
|
35
|
+
|
36
|
+
def mail
|
37
|
+
@mail ||= Mail.new(@data[:mail])
|
38
|
+
end
|
39
|
+
|
40
|
+
def normalized_bcc
|
41
|
+
Mail.new(to: @data[:bcc]).to
|
42
|
+
end
|
43
|
+
|
44
|
+
def text?
|
45
|
+
return true unless mail.has_content_type?
|
46
|
+
return true if mail.content_type.include?('text/plain')
|
47
|
+
return true if mail.multipart? && mail.text_part
|
48
|
+
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def html?
|
53
|
+
return true if mail.has_content_type? && mail.content_type.include?('text/html')
|
54
|
+
return true if mail.multipart? && mail.html_part
|
55
|
+
|
56
|
+
false
|
57
|
+
end
|
19
58
|
end
|
20
59
|
end
|
21
60
|
end
|
@@ -5,20 +5,20 @@ module Postmortem
|
|
5
5
|
# Base interface implementation for all Postmortem adapters.
|
6
6
|
class Base
|
7
7
|
def initialize(data)
|
8
|
-
@
|
8
|
+
@data = data
|
9
|
+
@adapted = adapted
|
9
10
|
end
|
10
11
|
|
11
|
-
%i[from to cc bcc subject html_body].each do |method_name|
|
12
|
+
%i[from reply_to to cc bcc subject text_body html_body].each do |method_name|
|
12
13
|
define_method method_name do
|
13
|
-
@
|
14
|
+
@adapted[method_name]
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
18
|
private
|
18
19
|
|
19
|
-
def adapted
|
20
|
-
raise NotImplementedError,
|
21
|
-
'Adapter must be a child class of Base which implements #adapted'
|
20
|
+
def adapted
|
21
|
+
raise NotImplementedError, 'Adapter child class must implement #adapted'
|
22
22
|
end
|
23
23
|
end
|
24
24
|
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,39 @@
|
|
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, :timestamp, :pony_skip_delivery
|
7
|
+
attr_accessor :log_path
|
8
|
+
|
9
|
+
def timestamp
|
10
|
+
defined?(@timestamp) ? @timestamp : true
|
11
|
+
end
|
12
|
+
|
13
|
+
def colorize
|
14
|
+
defined?(@colorize) ? @colorize : true
|
15
|
+
end
|
16
|
+
|
17
|
+
def preview_directory=(val)
|
18
|
+
@preview_directory = Pathname.new(val)
|
19
|
+
end
|
20
|
+
|
21
|
+
def layout=(val)
|
22
|
+
@layout = Pathname.new(val)
|
23
|
+
end
|
24
|
+
|
25
|
+
def layout
|
26
|
+
default = File.expand_path(File.join(__dir__, '..', '..', 'layout', 'default'))
|
27
|
+
path = Pathname.new(@layout || default)
|
28
|
+
path.extname.empty? ? path.sub_ext('.html.erb') : path
|
29
|
+
end
|
30
|
+
|
31
|
+
def preview_directory
|
32
|
+
@preview_directory ||= Pathname.new(File.join(Dir.tmpdir, 'postmortem'))
|
33
|
+
end
|
34
|
+
|
35
|
+
def pony_skip_delivery
|
36
|
+
defined?(@pony_skip_delivery) ? @pony_skip_delivery : true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/postmortem/delivery.rb
CHANGED
@@ -7,7 +7,7 @@ module Postmortem
|
|
7
7
|
|
8
8
|
def initialize(adapter)
|
9
9
|
@adapter = adapter
|
10
|
-
@path = Postmortem.
|
10
|
+
@path = Postmortem.config.preview_directory.join(filename)
|
11
11
|
end
|
12
12
|
|
13
13
|
def record
|
@@ -18,6 +18,8 @@ module Postmortem
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def filename
|
21
|
+
return "#{safe_subject}.html" unless Postmortem.config.timestamp
|
22
|
+
|
21
23
|
"#{timestamp}__#{safe_subject}.html"
|
22
24
|
end
|
23
25
|
|
@@ -26,7 +28,7 @@ module Postmortem
|
|
26
28
|
end
|
27
29
|
|
28
30
|
def safe_subject
|
29
|
-
return 'no-subject' if @adapter.subject.empty?
|
31
|
+
return 'no-subject' if @adapter.subject.nil? || @adapter.subject.empty?
|
30
32
|
|
31
33
|
@adapter.subject.tr(' ', '_').split('').select { |char| safe_chars.include?(char) }.join
|
32
34
|
end
|
data/lib/postmortem/layout.rb
CHANGED
@@ -7,9 +7,13 @@ module Postmortem
|
|
7
7
|
@adapter = adapter
|
8
8
|
end
|
9
9
|
|
10
|
+
def format_email_array(array)
|
11
|
+
array&.map { |email| %(<a href="mailto:#{email}">#{email}</a>) }&.join(', ')
|
12
|
+
end
|
13
|
+
|
10
14
|
def content
|
11
15
|
mail = @adapter
|
12
|
-
ERB.new(Postmortem.layout.read).result(binding)
|
16
|
+
ERB.new(Postmortem.config.layout.read).result(binding)
|
13
17
|
end
|
14
18
|
end
|
15
19
|
end
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Pony monkey-patch.
|
4
|
+
module Pony
|
5
|
+
class << self
|
6
|
+
alias _pony_mail mail
|
7
|
+
|
8
|
+
def mail(options)
|
9
|
+
result = _pony_mail(options) unless Postmortem.config.pony_skip_delivery
|
10
|
+
Postmortem.record_delivery(Postmortem::Adapters::Pony.new(options))
|
11
|
+
result
|
12
|
+
end
|
13
|
+
end
|
14
|
+
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.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Farrell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-16 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
|
@@ -119,21 +133,24 @@ files:
|
|
119
133
|
- ".rspec"
|
120
134
|
- ".rubocop.yml"
|
121
135
|
- Gemfile
|
122
|
-
- Gemfile.lock
|
123
136
|
- LICENSE.txt
|
124
137
|
- Makefile
|
125
138
|
- README.md
|
126
139
|
- Rakefile
|
127
140
|
- bin/console
|
128
141
|
- bin/setup
|
142
|
+
- doc/screenshot.png
|
129
143
|
- layout/default.html.erb
|
130
144
|
- lib/postmortem.rb
|
131
|
-
- lib/postmortem/action_mailer.rb
|
132
145
|
- lib/postmortem/adapters.rb
|
133
146
|
- lib/postmortem/adapters/action_mailer.rb
|
134
147
|
- lib/postmortem/adapters/base.rb
|
148
|
+
- lib/postmortem/adapters/pony.rb
|
149
|
+
- lib/postmortem/configuration.rb
|
135
150
|
- lib/postmortem/delivery.rb
|
136
151
|
- lib/postmortem/layout.rb
|
152
|
+
- lib/postmortem/plugins/action_mailer.rb
|
153
|
+
- lib/postmortem/plugins/pony.rb
|
137
154
|
- lib/postmortem/version.rb
|
138
155
|
- postmortem.gemspec
|
139
156
|
homepage: https://github.com/bobf/postmortem
|
data/Gemfile.lock
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
postmortem (0.1.0)
|
5
|
-
mail (~> 2.7)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
actionmailer (6.0.3.2)
|
11
|
-
actionpack (= 6.0.3.2)
|
12
|
-
actionview (= 6.0.3.2)
|
13
|
-
activejob (= 6.0.3.2)
|
14
|
-
mail (~> 2.5, >= 2.5.4)
|
15
|
-
rails-dom-testing (~> 2.0)
|
16
|
-
actionpack (6.0.3.2)
|
17
|
-
actionview (= 6.0.3.2)
|
18
|
-
activesupport (= 6.0.3.2)
|
19
|
-
rack (~> 2.0, >= 2.0.8)
|
20
|
-
rack-test (>= 0.6.3)
|
21
|
-
rails-dom-testing (~> 2.0)
|
22
|
-
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
23
|
-
actionview (6.0.3.2)
|
24
|
-
activesupport (= 6.0.3.2)
|
25
|
-
builder (~> 3.1)
|
26
|
-
erubi (~> 1.4)
|
27
|
-
rails-dom-testing (~> 2.0)
|
28
|
-
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
29
|
-
activejob (6.0.3.2)
|
30
|
-
activesupport (= 6.0.3.2)
|
31
|
-
globalid (>= 0.3.6)
|
32
|
-
activesupport (6.0.3.2)
|
33
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
34
|
-
i18n (>= 0.7, < 2)
|
35
|
-
minitest (~> 5.1)
|
36
|
-
tzinfo (~> 1.1)
|
37
|
-
zeitwerk (~> 2.2, >= 2.2.2)
|
38
|
-
ast (2.4.1)
|
39
|
-
builder (3.2.4)
|
40
|
-
concurrent-ruby (1.1.7)
|
41
|
-
crass (1.0.6)
|
42
|
-
devpack (0.2.1)
|
43
|
-
diff-lcs (1.4.4)
|
44
|
-
erubi (1.9.0)
|
45
|
-
globalid (0.4.2)
|
46
|
-
activesupport (>= 4.2.0)
|
47
|
-
i18n (1.8.5)
|
48
|
-
concurrent-ruby (~> 1.0)
|
49
|
-
loofah (2.6.0)
|
50
|
-
crass (~> 1.0.2)
|
51
|
-
nokogiri (>= 1.5.9)
|
52
|
-
mail (2.7.1)
|
53
|
-
mini_mime (>= 0.1.1)
|
54
|
-
mini_mime (1.0.2)
|
55
|
-
mini_portile2 (2.4.0)
|
56
|
-
minitest (5.14.1)
|
57
|
-
nokogiri (1.10.10)
|
58
|
-
mini_portile2 (~> 2.4.0)
|
59
|
-
paint (2.2.0)
|
60
|
-
parallel (1.19.2)
|
61
|
-
parser (2.7.1.4)
|
62
|
-
ast (~> 2.4.1)
|
63
|
-
rack (2.2.3)
|
64
|
-
rack-test (1.1.0)
|
65
|
-
rack (>= 1.0, < 3)
|
66
|
-
rails-dom-testing (2.0.3)
|
67
|
-
activesupport (>= 4.2.0)
|
68
|
-
nokogiri (>= 1.6)
|
69
|
-
rails-html-sanitizer (1.3.0)
|
70
|
-
loofah (~> 2.3)
|
71
|
-
rainbow (3.0.0)
|
72
|
-
regexp_parser (1.7.1)
|
73
|
-
rexml (3.2.4)
|
74
|
-
rspec (3.9.0)
|
75
|
-
rspec-core (~> 3.9.0)
|
76
|
-
rspec-expectations (~> 3.9.0)
|
77
|
-
rspec-mocks (~> 3.9.0)
|
78
|
-
rspec-core (3.9.2)
|
79
|
-
rspec-support (~> 3.9.3)
|
80
|
-
rspec-expectations (3.9.2)
|
81
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
82
|
-
rspec-support (~> 3.9.0)
|
83
|
-
rspec-its (1.3.0)
|
84
|
-
rspec-core (>= 3.0.0)
|
85
|
-
rspec-expectations (>= 3.0.0)
|
86
|
-
rspec-mocks (3.9.1)
|
87
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
88
|
-
rspec-support (~> 3.9.0)
|
89
|
-
rspec-support (3.9.3)
|
90
|
-
rubocop (0.88.0)
|
91
|
-
parallel (~> 1.10)
|
92
|
-
parser (>= 2.7.1.1)
|
93
|
-
rainbow (>= 2.2.2, < 4.0)
|
94
|
-
regexp_parser (>= 1.7)
|
95
|
-
rexml
|
96
|
-
rubocop-ast (>= 0.1.0, < 1.0)
|
97
|
-
ruby-progressbar (~> 1.7)
|
98
|
-
unicode-display_width (>= 1.4.0, < 2.0)
|
99
|
-
rubocop-ast (0.3.0)
|
100
|
-
parser (>= 2.7.1.4)
|
101
|
-
ruby-progressbar (1.10.1)
|
102
|
-
strong_versions (0.4.5)
|
103
|
-
i18n (>= 0.5)
|
104
|
-
paint (~> 2.0)
|
105
|
-
thread_safe (0.3.6)
|
106
|
-
timecop (0.9.1)
|
107
|
-
tzinfo (1.2.7)
|
108
|
-
thread_safe (~> 0.1)
|
109
|
-
unicode-display_width (1.7.0)
|
110
|
-
zeitwerk (2.4.0)
|
111
|
-
|
112
|
-
PLATFORMS
|
113
|
-
ruby
|
114
|
-
|
115
|
-
DEPENDENCIES
|
116
|
-
actionmailer (~> 6.0)
|
117
|
-
devpack (~> 0.2.0)
|
118
|
-
postmortem!
|
119
|
-
rspec (~> 3.9)
|
120
|
-
rspec-its (~> 1.3)
|
121
|
-
rubocop (~> 0.88.0)
|
122
|
-
strong_versions (~> 0.4.5)
|
123
|
-
timecop (~> 0.9.1)
|
124
|
-
|
125
|
-
BUNDLED WITH
|
126
|
-
2.1.4
|