Skip to content

Commit 5e479de

Browse files
committed
Merge remote-tracking branch 'origin/develop'
2 parents 0ca52cf + 7861bb3 commit 5e479de

20 files changed

+1007
-1115
lines changed

.github/workflows/main.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# GitHub Continuous Integration Configuration
2+
name: CI
3+
4+
# Define conditions for when to run this action
5+
on:
6+
pull_request: # Run on all pull requests
7+
push: # Run on all pushes to master
8+
branches:
9+
- main
10+
- develop
11+
12+
# Allows you to run this workflow manually from the Actions tab
13+
workflow_dispatch:
14+
15+
# A workflow run is made up of one or more jobs. Each job has an id, for
16+
# example, one of our jobs is "lint"
17+
jobs:
18+
test:
19+
name: Tests ${{ matrix.python-version }}
20+
runs-on: ${{ matrix.os }}
21+
strategy:
22+
# Define OS and Python versions to use. 3.x is the latest minor version.
23+
matrix:
24+
python-version: ["3.6", "3.7", "3.8", "3.9", "3.x"]
25+
os: [ubuntu-latest]
26+
27+
# Sequence of tasks for this job
28+
steps:
29+
# Check out latest code
30+
# Docs: https://github.com/actions/checkout
31+
- name: Checkout code
32+
uses: actions/checkout@v2
33+
34+
# Set up Python
35+
# Docs: https://github.com/actions/setup-python
36+
- name: Set up Python ${{ matrix.python-version }}
37+
uses: actions/setup-python@v2
38+
with:
39+
python-version: ${{ matrix.python-version }}
40+
41+
# Install dependencies
42+
# https://github.com/ymyzk/tox-gh-actions#workflow-configuration
43+
- name: Install dependencies
44+
run: |
45+
python -m pip install --upgrade pip
46+
pip install coverage tox tox-gh-actions
47+
48+
# Run tests
49+
# https://github.com/ymyzk/tox-gh-actions#workflow-configuration
50+
- name: Run tests
51+
run: tox
52+
- name: Combine coverage
53+
run: coverage xml
54+
55+
# Upload coverage report
56+
# https://github.com/codecov/codecov-action
57+
- name: Upload coverage report
58+
uses: codecov/codecov-action@v1
59+
with:
60+
fail_ci_if_error: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ build/
2727
/tmp/
2828
/.coverage*
2929
*,cover
30+
coverage.xml

.travis.yml

Lines changed: 0 additions & 31 deletions
This file was deleted.

CONTRIBUTING.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@ $ which mailmerge
1515
/Users/awdeorio/src/mailmerge/env/bin/mailmerge
1616
```
1717

18-
### Python2 development environment
19-
Mailmerge is tested to work in both Python 2 and Python 3. Set up a Python 2 virtual environment.
20-
```console
21-
$ virtualenv -p python2 env2
22-
$ source env2/bin/activate
23-
$ pip install -e .[dev,test]
24-
```
25-
2618
## Testing and code quality
2719
Run unit tests
2820
```console
@@ -42,8 +34,7 @@ $ pylint mailmerge tests setup.py
4234
$ check-manifest
4335
```
4436

45-
Test Python 2 and Python 3 compatibility. This will automatically create virtual environments and run all style and functional tests in each environment. Use `pyenv` to provide different versions of Python.
37+
Run linters and tests in a clean environment. This will automatically create a temporary virtual environment.
4638
```console
47-
$ eval "$(pyenv init -)"
4839
$ tox
4940
```

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Mailmerge
22
=========
33

4-
[![PyPI](https://img.shields.io/pypi/v/mailmerge.svg)](https://pypi.org/project/mailmerge/)
5-
[![Build Status](https://travis-ci.com/awdeorio/mailmerge.svg?branch=develop)](https://travis-ci.com/awdeorio/mailmerge)
4+
[![CI main](https://github.com/awdeorio/mailmerge/workflows/CI/badge.svg?branch=develop)](https://github.com/awdeorio/mailmerge/actions?query=branch%3Adevelop)
65
[![codecov](https://codecov.io/gh/awdeorio/mailmerge/branch/develop/graph/badge.svg)](https://codecov.io/gh/awdeorio/mailmerge)
6+
[![PyPI](https://img.shields.io/pypi/v/mailmerge.svg)](https://pypi.org/project/mailmerge/)
77

88
A simple, command line mail merge tool.
99

mailmerge/__main__.py

Lines changed: 34 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,15 @@
33
44
Andrew DeOrio <[email protected]>
55
"""
6-
from __future__ import print_function
76
import sys
87
import time
9-
import codecs
108
import textwrap
9+
from pathlib import Path
10+
import csv
1111
import click
1212
from .template_message import TemplateMessage
1313
from .sendmail_client import SendmailClient
1414
from . import exceptions
15-
from . import utils
16-
17-
# Python 2 pathlib support requires backport
18-
try:
19-
from pathlib2 import Path
20-
except ImportError:
21-
from pathlib import Path
22-
23-
# Python 2 UTF8 support requires csv backport
24-
try:
25-
from backports import csv
26-
except ImportError:
27-
import csv
28-
29-
# Python 2 UTF8 file redirection
30-
# http://www.macfreek.nl/memory/Encoding_of_Python_stdout
31-
if sys.stdout.encoding != 'UTF-8' and not hasattr(sys.stdout, "buffer"):
32-
sys.stdout = codecs.getwriter('utf-8')(sys.stdout, 'strict')
3315

3416

3517
@click.command(context_settings={"help_option_names": ['-h', '--help']})
@@ -128,37 +110,28 @@ def main(sample, dry_run, limit, no_limit, resume,
128110
break
129111
time.sleep(1)
130112
print_bright_white_on_cyan(
131-
">>> message {message_num}"
132-
.format(message_num=message_num),
113+
f">>> message {message_num}",
133114
output_format,
134115
)
135116
print_message(message, output_format)
136117
print_bright_white_on_cyan(
137-
">>> message {message_num} sent"
138-
.format(message_num=message_num),
118+
f">>> message {message_num} sent",
139119
output_format,
140120
)
141121
message_num += 1
142122

143123
except exceptions.MailmergeError as error:
144-
hint_text = '\nHint: "--resume {}"'.format(message_num)
145-
sys.exit(
146-
"Error on message {message_num}\n"
147-
"{error}"
148-
"{hint}"
149-
.format(
150-
message_num=message_num,
151-
error=error,
152-
hint=(hint_text if message_num > 1 else ""),
153-
)
154-
)
124+
hint_text = ""
125+
if message_num > 1:
126+
hint_text = f'\nHint: "--resume {message_num}"'
127+
sys.exit(f"Error on message {message_num}\n{error}{hint_text}")
155128

156129
# Hints for user
157130
if not no_limit:
131+
pluralizer = "" if limit == 1 else "s"
158132
print(
159-
">>> Limit was {limit} message{pluralizer}. "
133+
f">>> Limit was {limit} message{pluralizer}. "
160134
"To remove the limit, use the --no-limit option."
161-
.format(limit=limit, pluralizer=("" if limit == 1 else "s"))
162135
)
163136
if dry_run:
164137
print(
@@ -180,40 +153,40 @@ def check_input_files(template_path, database_path, config_path, sample):
180153
sys.exit(0)
181154

182155
if not template_path.exists():
183-
sys.exit(textwrap.dedent(u"""\
156+
sys.exit(textwrap.dedent(f"""\
184157
Error: can't find template "{template_path}".
185158
186159
Create a sample (--sample) or specify a file (--template).
187160
188161
See https://github.com/awdeorio/mailmerge for examples.\
189-
""".format(template_path=template_path)))
162+
"""))
190163

191164
if not database_path.exists():
192-
sys.exit(textwrap.dedent(u"""\
165+
sys.exit(textwrap.dedent(f"""\
193166
Error: can't find database "{database_path}".
194167
195168
Create a sample (--sample) or specify a file (--database).
196169
197170
See https://github.com/awdeorio/mailmerge for examples.\
198-
""".format(database_path=database_path)))
171+
"""))
199172

200173
if not config_path.exists():
201-
sys.exit(textwrap.dedent(u"""\
174+
sys.exit(textwrap.dedent(f"""\
202175
Error: can't find config "{config_path}".
203176
204177
Create a sample (--sample) or specify a file (--config).
205178
206179
See https://github.com/awdeorio/mailmerge for examples.\
207-
""".format(config_path=config_path)))
180+
"""))
208181

209182

210183
def create_sample_input_files(template_path, database_path, config_path):
211184
"""Create sample template, database and server config."""
212185
for path in [template_path, database_path, config_path]:
213186
if path.exists():
214-
sys.exit("Error: file exists: {}".format(path))
187+
sys.exit(f"Error: file exists: {path}")
215188
with template_path.open("w") as template_file:
216-
template_file.write(textwrap.dedent(u"""\
189+
template_file.write(textwrap.dedent("""\
217190
TO: {{email}}
218191
SUBJECT: Testing mailmerge
219192
FROM: My Self <[email protected]>
@@ -223,13 +196,13 @@ def create_sample_input_files(template_path, database_path, config_path):
223196
Your number is {{number}}.
224197
"""))
225198
with database_path.open("w") as database_file:
226-
database_file.write(textwrap.dedent(u"""\
199+
database_file.write(textwrap.dedent("""\
227200
email,name,number
228201
[email protected],"Myself",17
229202
230203
"""))
231204
with config_path.open("w") as config_file:
232-
config_file.write(textwrap.dedent(u"""\
205+
config_file.write(textwrap.dedent("""\
233206
# Mailmerge SMTP Server Config
234207
# https://github.com/awdeorio/mailmerge
235208
#
@@ -273,17 +246,13 @@ def create_sample_input_files(template_path, database_path, config_path):
273246
# port = 25
274247
# ratelimit = 0
275248
"""))
276-
print(textwrap.dedent(u"""\
249+
print(textwrap.dedent(f"""\
277250
Created sample template email "{template_path}"
278251
Created sample database "{database_path}"
279252
Created sample config file "{config_path}"
280253
281254
Edit these files, then run mailmerge again.\
282-
""".format(
283-
template_path=template_path,
284-
database_path=database_path,
285-
config_path=config_path,
286-
)))
255+
"""))
287256

288257

289258
def read_csv_database(database_path):
@@ -292,20 +261,25 @@ def read_csv_database(database_path):
292261
We'll use a class to modify the csv library's default dialect ('excel') to
293262
enable strict syntax checking. This will trigger errors for things like
294263
unclosed quotes.
264+
265+
We open the file with the utf-8-sig encoding, which skips a byte order mark
266+
(BOM), if any. Sometimes Excel will save CSV files with a BOM. See Issue
267+
#93 https://github.com/awdeorio/mailmerge/issues/93
268+
295269
"""
296270
class StrictExcel(csv.excel):
297271
# Our helper class is really simple
298272
# pylint: disable=too-few-public-methods, missing-class-docstring
299273
strict = True
300274

301-
with database_path.open(mode="r", encoding="utf-8") as database_file:
275+
with database_path.open(encoding="utf-8-sig") as database_file:
302276
reader = csv.DictReader(database_file, dialect=StrictExcel)
303277
try:
304278
for row in reader:
305279
yield row
306280
except csv.Error as err:
307281
raise exceptions.MailmergeError(
308-
"{}:{}: {}".format(database_path, reader.line_num, err)
282+
f"{database_path}:{reader.line_num}: {err}"
309283
)
310284

311285

@@ -343,11 +317,11 @@ def print_message(message, output_format):
343317
assert output_format in ["colorized", "text", "raw"]
344318

345319
if output_format == "raw":
346-
print(utils.flatten_message(message))
320+
print(message)
347321
return
348322

349323
for header, value in message.items():
350-
print(u"{header}: {value}".format(header=header, value=value))
324+
print(f"{header}: {value}")
351325
print()
352326
for part in message.walk():
353327
if part.get_content_maintype() == "multipart":
@@ -356,23 +330,20 @@ def print_message(message, output_format):
356330
if message.is_multipart():
357331
# Only print message part dividers for multipart messages
358332
print_cyan(
359-
">>> message part: {content_type}"
360-
.format(content_type=part.get_content_type()),
333+
f">>> message part: {part.get_content_type()}",
361334
output_format,
362335
)
363336
charset = str(part.get_charset())
364337
print(part.get_payload(decode=True).decode(charset))
365338
print()
366339
elif is_attachment(part):
367340
print_cyan(
368-
">>> message part: attachment {filename}"
369-
.format(filename=part.get_filename()),
341+
f">>> message part: attachment {part.get_filename()}",
370342
output_format,
371343
)
372344
else:
373345
print_cyan(
374-
">>> message part: {content_type}"
375-
.format(content_type=part.get_content_type()),
346+
f">>> message part: {part.get_content_type()}",
376347
output_format,
377348
)
378349

0 commit comments

Comments
 (0)