Skip to content

Commit

Permalink
Customfonts (#2)
Browse files Browse the repository at this point in the history
* Code refactor.

* Major progress.

* Progress.

* Progress.

* Debugging.

* Fixed #1

* Dependency updates.

* Cleanup  before merge.
  • Loading branch information
geozeke authored Jan 18, 2025
1 parent 3256abe commit da941ca
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 110 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

<!--------------------------------------------------------------------->

## [0.2.0][0.2.0] - 2025-01-18

### Added

* Add option for user to select font family and size ([#1][issue1])

<!--------------------------------------------------------------------->

## [0.1.2][0.1.2] - 2025-01-15

_Beta Release._

[0.1.2]: https://github.com/geozeke/smvp/releases/tag/v0.1.2
[issue1]: https://github.com/geozeke/smvp/issues/1
[0.2.0]: https://github.com/geozeke/smvp/releases/tag/v0.2.0
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ contents of the file are put into into the body of the email._

## Use Case

There are probably many, but I wrote _smvp_ so my cron scripts can email
There are probably a few, but I wrote _smvp_ so my cron scripts can email
me status information and the contents of log files. Some of the files
contain ANSI escape sequences for terminal colors. The _smvp_ utility
converts those ANSI escape sequences into proper HTML tags, so the
emails I get are nicely formatted.

_smvp_ is not intended to be a bulk emailer for formatted messages. There
other (better) tools for that.

## Installation

Use your preferred python package installer for command line tools, for
Expand Down Expand Up @@ -69,16 +72,35 @@ port `587`. Check the SMTP settings for your email provider. This is the
default TLS port on Gmail, so if you're using your Gmail account to send
emails, you're good-to-go.

## Styling

_smvp_ offers custom font and font size options for your email. The
default font for formatted HTML email is `Courier New`, `12px`. Beyond
the default you can choose any font size from `2px` up to and including
`100px`, from among these font families:

```text
"Andale Mono", "Arial", "Brush Script MT", "Comic Sans MS",
"Courier New", "Garamond", "Georgia", "Helvetica", "Impact",
"Luminari", "Monaco", "Tahoma", "Times New Roman", "Trebuchet MS",
"Verdana", "fantasy", "monospace", "sans-serif", "serif"
```

_NOTE: Not every font will render properly on every device. When in
doubt, fonts like: "monospace", "sans-serif", "fantasy", and "serif" are
pretty safe. You may just have try a few options to land on the right
one for your use case._

## Usage

```text
usage: smvp [-h] recipient subject file
usage: smvp [-h] [-f FONT_FAMILY] [-s FONT_SIZE] [-v] recipient subject file
```

For example:

```text
smvp [email protected] "Hello, Friend" ~/logfile.txt
smvp [email protected] "Hello, Friend" ~/logfile.txt -f "Trebuchet MS"
```

For more details, run:
Expand Down
17 changes: 9 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
[project]
name = "smvp"
version = "0.1.2"
version = "0.2.0"
description = "Send a formatted email from the command line"
authors = [
{ name = "Peter Nardi", email = "[email protected]" }
]
license = {file = "LICENSE"}
readme = {file = "README.md", content-type = "text/markdown"}
keywords = [
"smvp",
"you",
"send",
"mail",
"sendmail",
"logs",
"scripting",
"ansi",
"cron",
"html",
"logs",
"mail",
"mime",
"scripting",
"send",
"sendmail",
"smvp",
]
classifiers = [
"Development Status :: 4 - Beta",
Expand Down
213 changes: 134 additions & 79 deletions src/smvp/engine.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,123 @@
import argparse
import os
import re
import smtplib
import ssl
import sys
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import List

from ansi2html import Ansi2HTMLConverter
from bs4 import BeautifulSoup

from smvp.utilities import file_is_html
from smvp.utilities import validate_environment
from smvp.utilities import print_docstring
from smvp.utilities import task_runner
from smvp.version import get_version


def font_size(size: str) -> str:
"""Validate size inputs.
Parameters
----------
size : str
User input for a font size option.
Returns
-------
str
The validated user input.
Raises
------
argparse.ArgumentTypeError
If the input is not a valid integer (for example is a float).
argparse.ArgumentTypeError
If the input is not between 2 and 100.
"""
for c in size:
if not c.isdigit():
msg = "Size must be a valid integer"
raise argparse.ArgumentTypeError(msg)

int_size = int(size)
min_size = 2
max_size = 100
if int_size >= min_size and int_size <= max_size:
return size
else:
msg = f"Font size must be between {min_size} and {max_size}"
raise argparse.ArgumentTypeError(msg)


# ======================================================================


def font_family(font: str) -> str:
"""Validate font_family inputs.
Parameters
----------
font : str
User input for a font family.
Returns
-------
str
The validated user input.
Raises
------
argparse.ArgumentTypeError
If the the selected font family is not recognized.
"""
valid_fonts = {
"ANDALE MONO": "Andale Mono",
"ARIAL": "Arial",
"BRUSH SCRIPT MT": "Brush Script MT",
"COMIC SANS MS": "Comic Sans MS",
"COURIER NEW": "Courier New",
"FANTASY": "fantasy",
"GARAMOND": "Garamond",
"GEORGIA": "Georgia",
"HELVETICA": "Helvetica",
"IMPACT": "Impact",
"LUMINARI": "Luminari",
"MONACO": "Monaco",
"MONOSPACE": "monospace",
"SANS-SERIF": "sans-serif",
"SERIF": "serif",
"TAHOMA": "Tahoma",
"TIMES NEW ROMAN": "Times New Roman",
"TREBUCHET MS": "Trebuchet MS",
"VERDANA": "Verdana",
}
user_input = " ".join([word.upper() for word in font.split()])
if user_input in valid_fonts:
return valid_fonts[user_input]
else:
print()
fonts = [f'"{token}"' for token in list(valid_fonts.values())]
fonts.sort()
msg = f"""
The font you entered ({font}) is not valid. The default font is
"Courier New". If you're changing the default font, please use
one of the options below. Check the spelling to make sure it's
correct.
"""
print_docstring(msg=msg)
print()
chunk_size = 5
start = 0
end = 4
chunks: List[str] = []
for font in fonts:
chunk = fonts[start:end]
if start > len(fonts) or len(chunk) == 0:
break
if len(chunk) > 1:
chunks.append(", ".join(fonts[start:end]))
else:
chunks.append(chunk[0])
start = end
end += chunk_size
print(",\n".join(chunks))
print()
raise argparse.ArgumentTypeError("invalid font family")


# ======================================================================


Expand Down Expand Up @@ -45,78 +149,13 @@ def email_type(address: str) -> str:
# ======================================================================


def task_runner(args: argparse.Namespace) -> None:
"""Package email contents and send message
Parameters
----------
args : argparse.Namespace
The collection of command line arguments.
"""
if not validate_environment():
sys.exit(1)

# Initialize
sender_email = os.environ["SMVP_USER"]
email_server = os.environ["SMVP_SERVER"]
email_token = os.environ["SMVP_TOKEN"]
receiver_email = args.recipient
email_subject = args.subject
email_port = 587

with args.file as f:
text_in = f.read()

# Create separate plantext and html versions of the input
if not file_is_html(text_in):
converter = Ansi2HTMLConverter(dark_bg=False)
html_text = converter.convert(text_in, full=True)
# Replace the dull-grey default
html_text = html_text.replace("color: #AAAAAA", "color: #FFFFFF")
else:
html_text = text_in
plain_text = BeautifulSoup(html_text, "lxml").get_text().strip()

# Package both parts into a MIME multipart message.
message = MIMEMultipart("alternative")
message["Subject"] = email_subject
message["From"] = sender_email
message["To"] = receiver_email
message.attach(MIMEText(plain_text, "plain"))
message.attach(MIMEText(html_text, "html"))

# Create a secure context for the TLS connection
context = ssl.create_default_context()

# Send the email
try:
server = smtplib.SMTP(email_server, email_port)
server.starttls(context=context)
server.login(sender_email, email_token)
server.sendmail(
from_addr=sender_email,
to_addrs=receiver_email,
msg=message.as_string(),
)
print("Message successfully sent.")
except Exception as e:
print(e)
finally:
server.quit()

return


# ======================================================================


def process_args() -> None:
msg = """
Send Mail Via Python (smvp). This tool will send an email from the
command line, with the body of the email taken from a specified
file. There are many use cases. For example, it's handy to use smvp
to have automated Linux scripts (i.e. cron jobs) email you status
updates and the contents of log files.
file. For example, it's handy to use smvp to have automated Linux
scripts (i.e. cron jobs) email you status updates and the contents
of log files.
"""
epi = f"Version: {get_version()}"
parser = argparse.ArgumentParser(description=msg, epilog=epi)
Expand All @@ -139,6 +178,21 @@ def process_args() -> None:
"""
parser.add_argument("file", type=argparse.FileType("r"), help=msg)

msg = """
Enter the desired font family (enclosed in quotes). Values here are
not case sensitive. See the README.md file for available options.
Default = \"Courier New\".
"""
parser.add_argument(
"-f", "--font_family", type=font_family, default="Courier New", help=msg
)

msg = """
Enter the desired font pixel size as an integer. Valid sizes are
between 2 and 100. Default = 12.
"""
parser.add_argument("-s", "--font_size", type=font_size, default=12, help=msg)

parser.add_argument(
"-v",
"--version",
Expand All @@ -148,3 +202,4 @@ def process_args() -> None:

args = parser.parse_args()
task_runner(args=args)
return
Loading

0 comments on commit da941ca

Please sign in to comment.