Skip to content

Commit bf0861f

Browse files
committed
Adding code examples for md to email article
1 parent ba29218 commit bf0861f

File tree

4 files changed

+341
-0
lines changed

4 files changed

+341
-0
lines changed

code/md_to_email/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Markdown to HTML Email
2+
3+
This sample code accompanies the [article](https://pbpython.com/markdown-email.html) on Practical Business Python. Refer to the article for more background and details on the rationale
4+
for the project.
5+
6+
## Getting started
7+
8+
This is a standalone script that will take a simple markdown file as input,
9+
convert the markdown to HTML and insert it in the template file.
10+
11+
Sample input files are included in order to illustrate the concept.
12+
The two input files are sample_doc.md which contains the content and
13+
template.html which contain the HTML structure that should render
14+
as a responsive email.
15+
16+
### Prerequisites
17+
This script requires python >= 3.5 and the following dependencise:
18+
19+
* [python-markdown2](https://github.com/trentm/python-markdown2)
20+
* [jinja](https://jinja.palletsprojects.com/en/2.10.x/)
21+
* [premailer](https://github.com/peterbe/premailer)
22+
* [BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)
23+
24+
## Using the script
25+
The script is run from the command line.
26+
27+
To see the help:
28+
```
29+
python email_gen.py --help
30+
31+
usage: email_gen.py [-h] [-t T] [-o O] doc
32+
33+
Generate HTML email from markdown file
34+
35+
positional arguments:
36+
doc Markdown input document
37+
38+
optional arguments:
39+
-h, --help show this help message and exit
40+
-t T email HTML template
41+
-o O output filename. Default is inputfile_email.html
42+
43+
```
44+
45+
Using the sample template file on the provided file:
46+
47+
```
48+
python email_gen.py sample_doc.md
49+
```
50+
51+
Will generate a inlined HTML file titled sample_doc_email.html that can be copied
52+
into an HTML email.
53+
54+
## License
55+
This software is released under the BSD 3-Clause License.

code/md_to_email/email_gen.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
""" Generate responsive HTML emails from Markdown files used in a pelican blog.
2+
3+
Refer to https://pbpython.com/ for the details.
4+
5+
"""
6+
from markdown2 import Markdown
7+
from pathlib import Path
8+
from jinja2 import Environment, FileSystemLoader
9+
from premailer import transform
10+
from argparse import ArgumentParser
11+
from bs4 import BeautifulSoup
12+
13+
14+
def parse_args():
15+
"""Parse the command line input
16+
17+
Returns:
18+
args -- ArgumentParser object
19+
"""
20+
parser = ArgumentParser(
21+
description='Generate HTML email from markdown file')
22+
parser.add_argument('doc', action='store', help='Markdown input document')
23+
24+
parser.add_argument('-t',
25+
help='email HTML template',
26+
default='template.html')
27+
parser.add_argument(
28+
'-o', help='output filename. Default is inputfile_email.html')
29+
args = parser.parse_args()
30+
return args
31+
32+
33+
def create_HTML(config):
34+
"""Read in the source markdown file and convert it to a standalone
35+
HTML file suitable for emailing
36+
37+
Arguments:
38+
config -- ArgumentParser object that contains the input file
39+
"""
40+
# Define all the file locations
41+
in_doc = Path(config.doc)
42+
if config.o:
43+
out_file = Path(config.o)
44+
else:
45+
out_file = Path.cwd() / f'{in_doc.stem}_email.html'
46+
template_file = config.t
47+
48+
# Read in the entire file as a list
49+
# This can be problematic if the file is really large
50+
with open(in_doc) as f:
51+
all_content = f.readlines()
52+
53+
# Get the title line and clean it up
54+
title_line = all_content[0]
55+
title = f'My Newsletter - {title_line[7:].strip()}'
56+
57+
# Parse out the body from the meta data content at the top of the file
58+
body_content = all_content[6:]
59+
60+
# Create a markdown object and convert the list of file lines to HTML
61+
markdowner = Markdown()
62+
markdown_content = markdowner.convert(''.join(body_content))
63+
64+
# Set up jinja templates
65+
env = Environment(loader=FileSystemLoader('.'))
66+
template = env.get_template(template_file)
67+
68+
# Define the template variables and render
69+
template_vars = {'email_content': markdown_content, 'title': title}
70+
raw_html = template.render(template_vars)
71+
72+
# Generate the final output string
73+
# Inline all the CSS using premailer.transform
74+
# Use BeautifulSoup to make the formatting nicer
75+
soup = BeautifulSoup(transform(raw_html),
76+
'html.parser').prettify(formatter="html")
77+
78+
# The unsubscribe tag gets mangled. Clean it up.
79+
final_HTML = str(soup).replace('%7B%7BUnsubscribeURL%7D%7D',
80+
'{{UnsubscribeURL}}')
81+
out_file.write_text(final_HTML)
82+
83+
84+
if __name__ == '__main__':
85+
conf = parse_args()
86+
print('Creating output HTML')
87+
create_HTML(conf)
88+
print('Completed')

code/md_to_email/sample_doc.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Title: Newsletter Number X
2+
Date: 12-9-2019 10:04am
3+
Template: newsletter
4+
URL: newsletter/issue-X.html
5+
save_as: newsletter/issue-X.html
6+
7+
Welcome to the Xth edition of this newsletter.
8+
9+
## Around the site
10+
11+
* [Combining Multiple Excel Worksheets Into a Single Pandas Dataframe](https://pbpython.com/pandas-excel-tabs.html)
12+
covers a simple approach to parse multiple excel tabs into one DataFrame.
13+
14+
## Other news
15+
16+
* [Altair](https://altair-viz.github.io/index.html) just released a new version. If you haven't looked at it in a while,
17+
check out some of the [examples](https://altair-viz.github.io/gallery/index.html) for a snapshot of what you can do with it.
18+
19+
## Final Words
20+
21+
Thanks again for subscribing to the newsletter. Feel free to forward it on to others that may be interested.

code/md_to_email/template.html

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
2+
<html lang="en">
3+
4+
<head>
5+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
8+
9+
<title>{{title}}</title>
10+
11+
<style type="text/css">
12+
13+
/* CLIENT-SPECIFIC STYLES */
14+
body,
15+
table,
16+
td,
17+
a {
18+
-webkit-text-size-adjust: 100%;
19+
-ms-text-size-adjust: 100%;
20+
}
21+
22+
table,
23+
td {
24+
mso-table-lspace: 0pt;
25+
mso-table-rspace: 0pt;
26+
}
27+
28+
img {
29+
-ms-interpolation-mode: bicubic;
30+
}
31+
32+
/* RESET STYLES */
33+
img {
34+
border: 0;
35+
outline: none;
36+
text-decoration: none;
37+
}
38+
39+
table {
40+
border-collapse: collapse !important;
41+
}
42+
43+
body {
44+
margin: 0 !important;
45+
padding: 0 !important;
46+
width: 100% !important;
47+
font-family: 'Average Sans', sans-serif;
48+
font-size: 14px;
49+
}
50+
51+
pre {
52+
padding: 16px;
53+
font-size: 85%;
54+
line-height: 1.45;
55+
border-radius: 3px;
56+
}
57+
58+
code {
59+
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
60+
font-size: 85%;
61+
margin: 0;
62+
padding: 0.2em 0;
63+
border-radius: 3px;
64+
}
65+
66+
blockquote {
67+
padding: 0 1em;
68+
color: #6a737d;
69+
border-left: 0.25em solid #dfe2e5;
70+
}
71+
72+
73+
h1, h2, h3, h4 {
74+
font-family: sans-serif;
75+
font-weight: 500;
76+
margin: 0;
77+
}
78+
79+
h1 {
80+
font-size: 200%;
81+
82+
}
83+
84+
h1 a {
85+
text-decoration: none
86+
}
87+
88+
h2 {
89+
90+
font-size: 125%;
91+
}
92+
93+
h3, h4 {
94+
95+
font-size: 115%;
96+
}
97+
98+
99+
.lead {
100+
font-size: 16px;
101+
font-weight: 300;
102+
margin: 0;
103+
}
104+
105+
/* iOS BLUE LINKS */
106+
a[x-apple-data-detectors] {
107+
color: inherit !important;
108+
text-decoration: none !important;
109+
font-size: inherit !important;
110+
font-family: inherit !important;
111+
font-weight: inherit !important;
112+
line-height: inherit !important;
113+
}
114+
115+
li {
116+
font-size: 100%;
117+
}
118+
119+
/* ANDROID CENTER FIX */
120+
div[style*="margin: 16px 0;"] {
121+
margin: 0 !important;
122+
}
123+
</style>
124+
</head>
125+
126+
<body style="margin:0; padding:0; background-color:#F2F2F2;">
127+
<center>
128+
129+
<div style="background-color:#F2F2F2; max-width: 640px; margin: auto;">
130+
<!--[if mso]>
131+
<table role="presentation" width="640" cellspacing="0" cellpadding="0" border="0" align="center">
132+
<tr>
133+
<td>
134+
<![endif]-->
135+
136+
<table width="640" cellspacing="0" cellpadding="0" border="0" align="center" style="max-width:640px; width:100%;"
137+
bgcolor="#FFFFFF">
138+
<tr>
139+
<td align="center" valign="top" style="padding:10px;">
140+
141+
<table width="600" cellspacing="0" cellpadding="0" border="0" align="center"
142+
style="max-width:600px; width:100%;">
143+
<tr bgcolor="#eee">
144+
<td align="center" valign="top" style="padding:10px;">
145+
<h1> <a href=".">My Newsletter</a></h1>
146+
<p class="lead">Witty saying here</p>
147+
</td>
148+
</tr>
149+
150+
<tr>
151+
<td align="left" valign="top" style="padding:10px;">
152+
{{email_content}}
153+
</td>
154+
</tr>
155+
</table>
156+
157+
</td>
158+
</tr>
159+
</table>
160+
161+
<!--[if mso]>
162+
</td>
163+
</tr>
164+
</table>
165+
<![endif]-->
166+
</div>
167+
<p
168+
style="border-top: 1px solid #c6c6c6; color: #a9a9a9; margin-top: 50px; padding-top: 20px;font-size:13px; margin-bottom: 13px;">
169+
You received this email because you subscribed to our list.
170+
{% raw %}
171+
You can <a href="{{UnsubscribeURL}}" style="color:#a9a9a9;" target="_blank" >unsubscribe</a> at any time.</p>
172+
<p style="color: #a9a9a9;margin-bottom: 13px;font-size:13px;">{{SenderInfoLine}}</p>
173+
{% endraw %}
174+
</center>
175+
</body>
176+
177+
</html>

0 commit comments

Comments
 (0)