mailfmt 0.1.0__tar.gz

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.
@@ -0,0 +1,177 @@
1
+ typings/
2
+
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ share/python-wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+ MANIFEST
30
+
31
+ # PyInstaller
32
+ # Usually these files are written by a python script from a template
33
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Installer logs
38
+ pip-log.txt
39
+ pip-delete-this-directory.txt
40
+
41
+ # Unit test / coverage reports
42
+ htmlcov/
43
+ .tox/
44
+ .nox/
45
+ .coverage
46
+ .coverage.*
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+ *.cover
51
+ *.py,cover
52
+ .hypothesis/
53
+ .pytest_cache/
54
+ cover/
55
+
56
+ # Translations
57
+ *.mo
58
+ *.pot
59
+
60
+ # Django stuff:
61
+ *.log
62
+ local_settings.py
63
+ db.sqlite3
64
+ db.sqlite3-journal
65
+
66
+ # Flask stuff:
67
+ instance/
68
+ .webassets-cache
69
+
70
+ # Scrapy stuff:
71
+ .scrapy
72
+
73
+ # Sphinx documentation
74
+ docs/_build/
75
+
76
+ # PyBuilder
77
+ .pybuilder/
78
+ target/
79
+
80
+ # Jupyter Notebook
81
+ .ipynb_checkpoints
82
+
83
+ # IPython
84
+ profile_default/
85
+ ipython_config.py
86
+
87
+ # pyenv
88
+ # For a library or package, you might want to ignore these files since the code is
89
+ # intended to run in multiple environments; otherwise, check them in:
90
+ # .python-version
91
+
92
+ # pipenv
93
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
+ # install all needed dependencies.
97
+ #Pipfile.lock
98
+
99
+ # UV
100
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
101
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
102
+ # commonly ignored for libraries.
103
+ #uv.lock
104
+
105
+ # poetry
106
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
107
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
108
+ # commonly ignored for libraries.
109
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
110
+ #poetry.lock
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ #pdm.lock
115
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
116
+ # in version control.
117
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
118
+ .pdm.toml
119
+ .pdm-python
120
+ .pdm-build/
121
+
122
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
123
+ __pypackages__/
124
+
125
+ # Celery stuff
126
+ celerybeat-schedule
127
+ celerybeat.pid
128
+
129
+ # SageMath parsed files
130
+ *.sage.py
131
+
132
+ # Environments
133
+ .env
134
+ .venv
135
+ env/
136
+ venv/
137
+ ENV/
138
+ env.bak/
139
+ venv.bak/
140
+
141
+ # Spyder project settings
142
+ .spyderproject
143
+ .spyproject
144
+
145
+ # Rope project settings
146
+ .ropeproject
147
+
148
+ # mkdocs documentation
149
+ /site
150
+
151
+ # mypy
152
+ .mypy_cache/
153
+ .dmypy.json
154
+ dmypy.json
155
+
156
+ # Pyre type checker
157
+ .pyre/
158
+
159
+ # pytype static type analyzer
160
+ .pytype/
161
+
162
+ # Cython debug symbols
163
+ cython_debug/
164
+
165
+ # PyCharm
166
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
167
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
168
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
169
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
170
+ #.idea/
171
+
172
+ # Ruff stuff:
173
+ .ruff_cache/
174
+
175
+ # PyPI configuration file
176
+ .pypirc
177
+
@@ -0,0 +1 @@
1
+ 3.11
mailfmt-0.1.0/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2025 Daniel Fichtinger <daniel@ficd.ca>
2
+
3
+ Permission to use, copy, modify, and distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
mailfmt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: mailfmt
3
+ Version: 0.1.0
4
+ Summary: Plain text email formatter
5
+ Author-email: Daniel Fichtinger <daniel@ficd.ca>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+
10
+ <h1>Mail Format</h1>
11
+
12
+ `mailfmt` is a simple plain text email formatter. It's designed to ensure
13
+ consistent paragraph spacing while preserving markdown syntax, email headers,
14
+ sign-offs, and signature blocks.
15
+
16
+ By default, this script accepts its input on `stdin` and prints to `stdout`.
17
+ This makes it well suited for use as a formatter with a text editor like Kakoune
18
+ or Helix. It has no dependencies besides the standard Python interpreter, and
19
+ was written and tested against Python 3.13.3.
20
+
21
+ <!--toc:start-->
22
+
23
+ - [Features](#features)
24
+ - [Usage](#usage)
25
+ - [Output Example](#output-example)
26
+ - [Markdown Safety](#markdown-safety)
27
+ - [Aerc Integration](#aerc-integration)
28
+ - [Contributing](#contributing)
29
+
30
+ <!--toc:end-->
31
+
32
+ ## Features
33
+
34
+ - Wraps emails at specified columns.
35
+ - Automatically reflows paragraphs.
36
+ - Squashes consecutive paragraph breaks.
37
+ - Preserves:
38
+ - Any long word not broken by spaces (e.g. URLs, email addresses).
39
+ - Quoted lines.
40
+ - Indented lines.
41
+ - Lists.
42
+ - Markdown-style code blocks.
43
+ - Usenet-style signature block at EOF.
44
+ - Sign-offs.
45
+ - If specified, output can be made safe for passing to a Markdown renderer.
46
+ - Use case: piping the output to `pandoc` to write a `text/html` message. See
47
+ [Markdown Safety](#markdown-safety).
48
+
49
+ ## Usage
50
+
51
+ ```
52
+ usage: mailfmt [-h] [-w WIDTH] [-b] [--no-replace-whitespace] [--no-reflow]
53
+ [--no-signoff] [--no-signature] [--no-squash] [-m] [-i INPUT]
54
+ [-o OUTPUT]
55
+
56
+ Formatter for plain text email.
57
+ "--no-*" options are NOT passed by default.
58
+
59
+ options:
60
+ -h, --help show this help message and exit
61
+ -w, --width WIDTH Text width for wrapping. (default: 74)
62
+ -b, --break-long-words
63
+ Break long words while wrapping. (default: False)
64
+ --no-replace-whitespace
65
+ Don't normalize whitespace when wrapping.
66
+ --no-reflow Don't reflow lines.
67
+ --no-signoff Don't preserve signoff line breaks.
68
+ --no-signature Don't preserve signature block.
69
+ --no-squash Don't squash consecutive paragraph breaks.
70
+ -m, --markdown-safe Output format safe for Markdown rendering.
71
+ -i, --input INPUT Input file. (default: STDIN)
72
+ -o, --output OUTPUT Output file. (default: STDOUT)
73
+
74
+ Author : Daniel Fichtinger
75
+ License: ISC
76
+ Contact: daniel@ficd.ca
77
+ ```
78
+
79
+ ## Output Example
80
+
81
+ Before:
82
+
83
+ ```
84
+ Hey,
85
+
86
+ This is a really long paragraph with lots of words in it. However, my text editor uses soft-wrapping, so it ends up looking horrible when viewed without wrapping! Additionally,
87
+ if I manually add some line breaks, things start to look _super_ janky!
88
+
89
+ I can't just pipe this to `fmt` because it may break my beautiful
90
+ markdown
91
+ syntax. Markdown formatters are also problematic because they mess up
92
+ my signoff and signature blocks! What should I do?
93
+
94
+ Best wishes,
95
+ Daniel
96
+
97
+ --
98
+ Daniel
99
+ sr.ht/~ficd
100
+ daniel@ficd.ca
101
+ ```
102
+
103
+ After:
104
+
105
+ ```
106
+ Hey,
107
+
108
+ This is a really long paragraph with lots of words in it. However, my text
109
+ editor uses soft-wrapping, so it ends up looking horrible when viewed
110
+ without wrapping! Additionally, if I manually add some line breaks, things
111
+ start to look _super_ janky!
112
+
113
+ I can't just pipe this to `fmt` because it may break my beautiful markdown
114
+ syntax. Markdown formatters are also problematic because they mess up my
115
+ signoff and signature blocks! What should I do?
116
+
117
+ Best wishes,
118
+ Daniel
119
+
120
+ --
121
+ Daniel
122
+ sr.ht/~ficd
123
+ daniel@ficd.ca
124
+ ```
125
+
126
+ ## Markdown Safety
127
+
128
+ In some cases, you may want to generate an HTML email. Ideally, you'd want the
129
+ HTML to be generated directly from the plain text message, and for _both_
130
+ versions to be legible and have the same semantics.
131
+
132
+ Although `mailfmt` was written with Markdown markup in mind, its intended output
133
+ is still the `text/plain` format. If you pass its output directly to a Markdown
134
+ renderer, line breaks in sign-offs and the signature block won't be preserved.
135
+
136
+ If you invoke `mailfmt --markdown-safe`, then `\` characters will be appended to
137
+ mark line breaks that would otherwise be squashed, making the output suitable
138
+ for conversion into HTML. Here's an example of one such pipeline:
139
+
140
+ ```bash
141
+ cat message.txt | mailfmt --markdown-safe | pandoc -f markdown -t html
142
+ --standalone > message.html
143
+ ```
144
+
145
+ Here's the earlier example message with markdown safe output:
146
+
147
+ ```
148
+ Hey,
149
+
150
+ This is a really long paragraph with lots of words in it. However, my text
151
+ editor uses soft-wrapping, so it ends up looking horrible when viewed
152
+ without wrapping! Additionally, if I manually add some line breaks, things
153
+ start to look _super_ janky!
154
+
155
+ I can't just pipe this to `fmt` because it may break my beautiful markdown
156
+ syntax. Markdown formatters are also problematic because they mess up my
157
+ signoff and signature blocks! What should I do?
158
+
159
+ Best wishes, \
160
+ Daniel \
161
+
162
+ -- \
163
+ Daniel \
164
+ sr.ht/~ficd \
165
+ daniel@ficd.ca \
166
+ ```
167
+
168
+ ## Aerc Integration
169
+
170
+ For integration with `aerc`, consider adding the following to your `aerc.conf`:
171
+
172
+ ```ini
173
+ [multipart-converters]
174
+ text/html=mailfmt --markdown-safe | pandoc -f markdown -t html --standalone
175
+ ```
176
+
177
+ When you're done writing your email, you can call the `:multipart text/html`
178
+ command to generate a `multipart/alternative` message which includes _both_ your
179
+ original `text/plain` _and_ the newly generated `text/html` content.
180
+
181
+ ## Contributing
182
+
183
+ Please send patches, requests, and concerns to my
184
+ [public inbox](https://lists.sr.ht/~ficd/public-inbox).
@@ -0,0 +1,175 @@
1
+ <h1>Mail Format</h1>
2
+
3
+ `mailfmt` is a simple plain text email formatter. It's designed to ensure
4
+ consistent paragraph spacing while preserving markdown syntax, email headers,
5
+ sign-offs, and signature blocks.
6
+
7
+ By default, this script accepts its input on `stdin` and prints to `stdout`.
8
+ This makes it well suited for use as a formatter with a text editor like Kakoune
9
+ or Helix. It has no dependencies besides the standard Python interpreter, and
10
+ was written and tested against Python 3.13.3.
11
+
12
+ <!--toc:start-->
13
+
14
+ - [Features](#features)
15
+ - [Usage](#usage)
16
+ - [Output Example](#output-example)
17
+ - [Markdown Safety](#markdown-safety)
18
+ - [Aerc Integration](#aerc-integration)
19
+ - [Contributing](#contributing)
20
+
21
+ <!--toc:end-->
22
+
23
+ ## Features
24
+
25
+ - Wraps emails at specified columns.
26
+ - Automatically reflows paragraphs.
27
+ - Squashes consecutive paragraph breaks.
28
+ - Preserves:
29
+ - Any long word not broken by spaces (e.g. URLs, email addresses).
30
+ - Quoted lines.
31
+ - Indented lines.
32
+ - Lists.
33
+ - Markdown-style code blocks.
34
+ - Usenet-style signature block at EOF.
35
+ - Sign-offs.
36
+ - If specified, output can be made safe for passing to a Markdown renderer.
37
+ - Use case: piping the output to `pandoc` to write a `text/html` message. See
38
+ [Markdown Safety](#markdown-safety).
39
+
40
+ ## Usage
41
+
42
+ ```
43
+ usage: mailfmt [-h] [-w WIDTH] [-b] [--no-replace-whitespace] [--no-reflow]
44
+ [--no-signoff] [--no-signature] [--no-squash] [-m] [-i INPUT]
45
+ [-o OUTPUT]
46
+
47
+ Formatter for plain text email.
48
+ "--no-*" options are NOT passed by default.
49
+
50
+ options:
51
+ -h, --help show this help message and exit
52
+ -w, --width WIDTH Text width for wrapping. (default: 74)
53
+ -b, --break-long-words
54
+ Break long words while wrapping. (default: False)
55
+ --no-replace-whitespace
56
+ Don't normalize whitespace when wrapping.
57
+ --no-reflow Don't reflow lines.
58
+ --no-signoff Don't preserve signoff line breaks.
59
+ --no-signature Don't preserve signature block.
60
+ --no-squash Don't squash consecutive paragraph breaks.
61
+ -m, --markdown-safe Output format safe for Markdown rendering.
62
+ -i, --input INPUT Input file. (default: STDIN)
63
+ -o, --output OUTPUT Output file. (default: STDOUT)
64
+
65
+ Author : Daniel Fichtinger
66
+ License: ISC
67
+ Contact: daniel@ficd.ca
68
+ ```
69
+
70
+ ## Output Example
71
+
72
+ Before:
73
+
74
+ ```
75
+ Hey,
76
+
77
+ This is a really long paragraph with lots of words in it. However, my text editor uses soft-wrapping, so it ends up looking horrible when viewed without wrapping! Additionally,
78
+ if I manually add some line breaks, things start to look _super_ janky!
79
+
80
+ I can't just pipe this to `fmt` because it may break my beautiful
81
+ markdown
82
+ syntax. Markdown formatters are also problematic because they mess up
83
+ my signoff and signature blocks! What should I do?
84
+
85
+ Best wishes,
86
+ Daniel
87
+
88
+ --
89
+ Daniel
90
+ sr.ht/~ficd
91
+ daniel@ficd.ca
92
+ ```
93
+
94
+ After:
95
+
96
+ ```
97
+ Hey,
98
+
99
+ This is a really long paragraph with lots of words in it. However, my text
100
+ editor uses soft-wrapping, so it ends up looking horrible when viewed
101
+ without wrapping! Additionally, if I manually add some line breaks, things
102
+ start to look _super_ janky!
103
+
104
+ I can't just pipe this to `fmt` because it may break my beautiful markdown
105
+ syntax. Markdown formatters are also problematic because they mess up my
106
+ signoff and signature blocks! What should I do?
107
+
108
+ Best wishes,
109
+ Daniel
110
+
111
+ --
112
+ Daniel
113
+ sr.ht/~ficd
114
+ daniel@ficd.ca
115
+ ```
116
+
117
+ ## Markdown Safety
118
+
119
+ In some cases, you may want to generate an HTML email. Ideally, you'd want the
120
+ HTML to be generated directly from the plain text message, and for _both_
121
+ versions to be legible and have the same semantics.
122
+
123
+ Although `mailfmt` was written with Markdown markup in mind, its intended output
124
+ is still the `text/plain` format. If you pass its output directly to a Markdown
125
+ renderer, line breaks in sign-offs and the signature block won't be preserved.
126
+
127
+ If you invoke `mailfmt --markdown-safe`, then `\` characters will be appended to
128
+ mark line breaks that would otherwise be squashed, making the output suitable
129
+ for conversion into HTML. Here's an example of one such pipeline:
130
+
131
+ ```bash
132
+ cat message.txt | mailfmt --markdown-safe | pandoc -f markdown -t html
133
+ --standalone > message.html
134
+ ```
135
+
136
+ Here's the earlier example message with markdown safe output:
137
+
138
+ ```
139
+ Hey,
140
+
141
+ This is a really long paragraph with lots of words in it. However, my text
142
+ editor uses soft-wrapping, so it ends up looking horrible when viewed
143
+ without wrapping! Additionally, if I manually add some line breaks, things
144
+ start to look _super_ janky!
145
+
146
+ I can't just pipe this to `fmt` because it may break my beautiful markdown
147
+ syntax. Markdown formatters are also problematic because they mess up my
148
+ signoff and signature blocks! What should I do?
149
+
150
+ Best wishes, \
151
+ Daniel \
152
+
153
+ -- \
154
+ Daniel \
155
+ sr.ht/~ficd \
156
+ daniel@ficd.ca \
157
+ ```
158
+
159
+ ## Aerc Integration
160
+
161
+ For integration with `aerc`, consider adding the following to your `aerc.conf`:
162
+
163
+ ```ini
164
+ [multipart-converters]
165
+ text/html=mailfmt --markdown-safe | pandoc -f markdown -t html --standalone
166
+ ```
167
+
168
+ When you're done writing your email, you can call the `:multipart text/html`
169
+ command to generate a `multipart/alternative` message which includes _both_ your
170
+ original `text/plain` _and_ the newly generated `text/html` content.
171
+
172
+ ## Contributing
173
+
174
+ Please send patches, requests, and concerns to my
175
+ [public inbox](https://lists.sr.ht/~ficd/public-inbox).
@@ -0,0 +1,230 @@
1
+ #!/bin/env python
2
+
3
+ # Simple text-wrapping script for email.
4
+ # Preserves code blocks, quotes, and signature.
5
+ # Automatically joins and re-wraps paragraphs to
6
+ # ensure even spacing & avoid ugly wrapping.
7
+ # Preserves signoffs.
8
+ # Signoff heuristic:
9
+ # 1-5 words ending with a comma, followed by
10
+ # 1-5 words that each start with capital letters.
11
+ # Author: Daniel Fichtinger
12
+ # License: ISC
13
+
14
+ import textwrap
15
+ import sys
16
+ import re
17
+ import argparse
18
+
19
+
20
+ def main() -> None:
21
+ paragraph: list[str] = []
22
+ skipping = False
23
+ squash = True
24
+ prev_is_parbreak = False
25
+ out_stream = sys.stdout
26
+ reflow = True
27
+ width = 74
28
+ break_long_words = False
29
+ replace_whitespace = True
30
+ markdown_safe = False
31
+
32
+ in_signoff = False
33
+ in_signature = False
34
+
35
+ def pprint(string: str):
36
+ if markdown_safe and (in_signoff or in_signature) and string:
37
+ string += " \\"
38
+ if not squash:
39
+ print(string, file=out_stream)
40
+ else:
41
+ parbreak = not string
42
+ nonlocal prev_is_parbreak
43
+ if skipping or not (parbreak and prev_is_parbreak):
44
+ print(string, file=out_stream)
45
+ prev_is_parbreak = parbreak
46
+
47
+ def wrap(text: str):
48
+ return textwrap.wrap(
49
+ text,
50
+ width=width,
51
+ break_long_words=break_long_words,
52
+ replace_whitespace=replace_whitespace,
53
+ )
54
+
55
+ def flush_paragraph():
56
+ if paragraph:
57
+ if reflow:
58
+ joined = " ".join(paragraph)
59
+ wrapped = wrap(joined)
60
+ pprint("\n".join(wrapped))
61
+ else:
62
+ for line in paragraph:
63
+ for wrapped_line in wrap(line):
64
+ pprint(wrapped_line)
65
+ paragraph.clear()
66
+
67
+ signoff_cache: str = ""
68
+
69
+ def check_signoff(line: str) -> bool:
70
+ if not line:
71
+ return False
72
+ words = line.split()
73
+ n = len(words)
74
+ # first potential signoff line
75
+ if not signoff_cache and 1 <= n <= 5 and line[-1] == ",":
76
+ return True
77
+ # second potential line
78
+ elif signoff_cache and 1 <= n <= 5 and line[-1].isalpha():
79
+ for w in words:
80
+ if not w[0].isupper():
81
+ return False
82
+ return True
83
+ else:
84
+ return False
85
+
86
+ parser = argparse.ArgumentParser(
87
+ description='Formatter for plain text email.\n"--no-*" options are NOT passed by default.',
88
+ epilog="""
89
+ Author : Daniel Fichtinger
90
+ License: ISC
91
+ Contact: daniel@ficd.ca
92
+ """,
93
+ formatter_class=argparse.RawDescriptionHelpFormatter,
94
+ )
95
+ parser.add_argument(
96
+ "-w",
97
+ "--width",
98
+ required=False,
99
+ help="Text width for wrapping. (default: %(default)s)",
100
+ default=width,
101
+ type=int,
102
+ )
103
+ parser.add_argument(
104
+ "-b",
105
+ "--break-long-words",
106
+ required=False,
107
+ help="Break long words while wrapping. (default: %(default)s)",
108
+ action="store_true",
109
+ )
110
+ parser.add_argument(
111
+ "--no-replace-whitespace",
112
+ required=False,
113
+ help="Don't normalize whitespace when wrapping.",
114
+ action="store_false",
115
+ )
116
+ parser.add_argument(
117
+ "--no-reflow",
118
+ required=False,
119
+ help="Don't reflow lines.",
120
+ action="store_false",
121
+ )
122
+ parser.add_argument(
123
+ "--no-signoff",
124
+ required=False,
125
+ help="Don't preserve signoff line breaks.",
126
+ action="store_false",
127
+ )
128
+ parser.add_argument(
129
+ "--no-signature",
130
+ required=False,
131
+ help="Don't preserve signature block.",
132
+ action="store_false",
133
+ )
134
+ parser.add_argument(
135
+ "--no-squash",
136
+ required=False,
137
+ help="Don't squash consecutive paragraph breaks.",
138
+ action="store_false",
139
+ )
140
+ parser.add_argument(
141
+ "-m",
142
+ "--markdown-safe",
143
+ required=False,
144
+ help="Output format safe for Markdown rendering.",
145
+ action="store_true",
146
+ )
147
+ parser.add_argument(
148
+ "-i",
149
+ "--input",
150
+ required=False,
151
+ type=str,
152
+ default="STDIN",
153
+ help="Input file. (default: %(default)s)",
154
+ )
155
+ parser.add_argument(
156
+ "-o",
157
+ "--output",
158
+ required=False,
159
+ type=str,
160
+ default="STDOUT",
161
+ help="Output file. (default: %(default)s)",
162
+ )
163
+ args = parser.parse_args()
164
+ width = args.width
165
+ should_check_signoff = args.no_signoff
166
+ should_check_signature = args.no_signature
167
+ reflow = args.no_reflow
168
+ squash = args.no_squash
169
+ replace_whitespace = args.no_replace_whitespace
170
+ break_long_words = args.break_long_words
171
+ markdown_safe = args.markdown_safe
172
+
173
+ if args.input == "STDIN":
174
+ reader = sys.stdin
175
+ else:
176
+ with open(args.input, "r") as in_stream:
177
+ reader = in_stream
178
+ if args.output != "STDOUT":
179
+ out_stream = open(args.output, "w")
180
+
181
+ for line in reader:
182
+ line = line.rstrip()
183
+ if should_check_signoff:
184
+ is_signoff = check_signoff(line)
185
+ if is_signoff:
186
+ in_signoff = True
187
+ if not signoff_cache:
188
+ signoff_cache = line
189
+ else:
190
+ pprint(signoff_cache)
191
+ pprint(line)
192
+ in_signoff = False
193
+ signoff_cache = ""
194
+ continue
195
+ elif not is_signoff and signoff_cache:
196
+ paragraph.append(signoff_cache)
197
+ signoff_cache = ""
198
+ in_signoff = False
199
+ if line.startswith("```"):
200
+ flush_paragraph()
201
+ skipping = not skipping
202
+ pprint(line)
203
+ elif should_check_signature and line == "--":
204
+ flush_paragraph()
205
+ skipping = True
206
+ in_signature = True
207
+ pprint("-- ")
208
+ elif not line or re.match(
209
+ r"^(\s+|-\s+|\+\s+|\*\s+|>\s*|#\s+|From:|To:|Cc:|Bcc:|Subject:|Reply-To:|In-Reply-To:|References:|Date:|Message-Id:|User-Agent:)",
210
+ line,
211
+ ):
212
+ flush_paragraph()
213
+ pprint(line)
214
+ elif skipping:
215
+ pprint(line)
216
+ else:
217
+ paragraph.append(line)
218
+ else:
219
+ if signoff_cache:
220
+ paragraph.append(signoff_cache)
221
+ signoff_cache = ""
222
+ flush_paragraph()
223
+ if out_stream is not None:
224
+ out_stream.close()
225
+
226
+
227
+ if __name__ == "__main__":
228
+ main()
229
+
230
+ # pyright: basic
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "mailfmt"
3
+ version = "0.1.0"
4
+ description = "Plain text email formatter"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Daniel Fichtinger", email = "daniel@ficd.ca" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ dependencies = []
11
+
12
+ [project.scripts]
13
+ mailfmt = "mailfmt:main"
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
mailfmt-0.1.0/uv.lock ADDED
@@ -0,0 +1,8 @@
1
+ version = 1
2
+ revision = 2
3
+ requires-python = ">=3.11"
4
+
5
+ [[package]]
6
+ name = "mailfmt"
7
+ version = "0.1.0"
8
+ source = { editable = "." }