flowmark 0.5.0__tar.gz → 0.5.2__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.
- {flowmark-0.5.0 → flowmark-0.5.2}/.copier-answers.yml +1 -1
- {flowmark-0.5.0 → flowmark-0.5.2}/.cursor/rules/general.mdc +11 -11
- {flowmark-0.5.0 → flowmark-0.5.2}/.cursor/rules/python.mdc +23 -8
- {flowmark-0.5.0 → flowmark-0.5.2}/.github/workflows/ci.yml +1 -1
- {flowmark-0.5.0 → flowmark-0.5.2}/.github/workflows/publish.yml +1 -1
- {flowmark-0.5.0 → flowmark-0.5.2}/Makefile +8 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/PKG-INFO +35 -22
- {flowmark-0.5.0 → flowmark-0.5.2}/README.md +33 -20
- {flowmark-0.5.0 → flowmark-0.5.2}/pyproject.toml +1 -1
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/cli.py +17 -2
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/doc_transforms.py +2 -0
- flowmark-0.5.2/src/flowmark/ellipses.py +58 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/markdown_filling.py +4 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/reformat_api.py +11 -2
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/smartquotes.py +4 -2
- flowmark-0.5.2/tests/test_ellipses.py +97 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_ref_docs.py +3 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/testdocs/testdoc.expected.auto.md +27 -5
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/testdocs/testdoc.expected.cleaned.md +23 -1
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/testdocs/testdoc.expected.plain.md +22 -1
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/testdocs/testdoc.expected.semantic.md +23 -1
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/testdocs/testdoc.orig.md +24 -1
- {flowmark-0.5.0 → flowmark-0.5.2}/uv.lock +48 -47
- {flowmark-0.5.0 → flowmark-0.5.2}/.cursor/rules/flowmark.mdc +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/.gitignore +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/.vscode/settings.json +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/LICENSE +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/development.md +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/devtools/lint.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/installation.md +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/poetry.lock +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/publishing.md +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/__init__.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/custom_marko.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/doc_cleanups.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/frontmatter.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/line_wrappers.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/py.typed +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/sentence_split_regex.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/text_filling.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/src/flowmark/text_wrapping.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_cleanups.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_filling.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_frontmatter.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_list_spacing.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_sentences.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_smartquotes.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_width_options.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/test_wrapping.py +0 -0
- {flowmark-0.5.0 → flowmark-0.5.2}/tests/testdocs/.gitignore +0 -0
|
@@ -7,7 +7,7 @@ alwaysApply: true
|
|
|
7
7
|
|
|
8
8
|
**Your fundamental responsibility:** Remember you are a senior engineer and have a
|
|
9
9
|
serious responsibility to be clear, factual, think step by step and be systematic,
|
|
10
|
-
express expert opinion, and make use of the user
|
|
10
|
+
express expert opinion, and make use of the user’s attention wisely.
|
|
11
11
|
|
|
12
12
|
**Rules must be followed:** It is your responsibility to carefully read these rules as
|
|
13
13
|
well as Python or other language-specific rules included here.
|
|
@@ -22,19 +22,19 @@ Therefore:
|
|
|
22
22
|
confirmation.
|
|
23
23
|
|
|
24
24
|
- If you can think of a much better approach that the user requests, be sure to mention
|
|
25
|
-
it. It
|
|
25
|
+
it. It’s your responsibility to suggest approaches that lead to better, simpler
|
|
26
26
|
solutions.
|
|
27
27
|
|
|
28
|
-
- Give thoughtful opinions on better/worse approaches, but NEVER say
|
|
29
|
-
or
|
|
28
|
+
- Give thoughtful opinions on better/worse approaches, but NEVER say “great idea!”
|
|
29
|
+
or “good job” or other compliments, encouragement, or non-essential banter.
|
|
30
30
|
Your job is to give expert opinions and to solve problems, not to motivate the user.
|
|
31
31
|
|
|
32
32
|
- Avoid gratuitous enthusiasm or generalizations.
|
|
33
|
-
Use thoughtful comparisons like saying which code is
|
|
33
|
+
Use thoughtful comparisons like saying which code is “cleaner” but don’t congratulate
|
|
34
34
|
yourself. Avoid subjective descriptions.
|
|
35
|
-
For example, don
|
|
35
|
+
For example, don’t say “I’ve meticulously improved the code and it is in great shape!”
|
|
36
36
|
That is useless generalization.
|
|
37
|
-
Instead, specifically say what you
|
|
37
|
+
Instead, specifically say what you’ve done, e.g., "I’ve added types, including
|
|
38
38
|
generics, to all the methods in `Foo` and fixed all linter errors."
|
|
39
39
|
|
|
40
40
|
# General Coding Guidelines
|
|
@@ -49,17 +49,17 @@ Therefore:
|
|
|
49
49
|
- DO NOT repeat in comments what is obvious from the names of functions or variables or
|
|
50
50
|
types.
|
|
51
51
|
|
|
52
|
-
- DO NOT include comments that reflect what you did, such as
|
|
52
|
+
- DO NOT include comments that reflect what you did, such as “Added this function” as
|
|
53
53
|
this is meaningless to anyone reading the code later.
|
|
54
54
|
(Instead, describe in your message to the user any other contextual information.)
|
|
55
55
|
|
|
56
|
-
- DO NOT use fancy or needlessly decorated headings like
|
|
56
|
+
- DO NOT use fancy or needlessly decorated headings like “===== MIGRATION TOOLS =====”
|
|
57
57
|
in comments
|
|
58
58
|
|
|
59
59
|
- DO NOT number steps in comments.
|
|
60
60
|
These are hard to maintain if the code changes.
|
|
61
|
-
NEVER DO THIS:
|
|
62
|
-
This is fine:
|
|
61
|
+
NEVER DO THIS: “// Step 3: Fetch the data from the cache”\
|
|
62
|
+
This is fine: “// Now fetch the data from the cache”
|
|
63
63
|
|
|
64
64
|
- DO NOT use emojis or special unicode characters like ① or • or – or — in comments.
|
|
65
65
|
|
|
@@ -87,7 +87,7 @@ Always use full type annotations, generics, and other modern practices.
|
|
|
87
87
|
- Use pathlib `Path` instead of strings.
|
|
88
88
|
Use `Path(filename).read_text()` instead of two-line `with open(...)` blocks.
|
|
89
89
|
|
|
90
|
-
- Use strif
|
|
90
|
+
- Use strif’s `atomic_output_file` context manager when writing files to ensure output
|
|
91
91
|
files are written atomically.
|
|
92
92
|
|
|
93
93
|
## Use Modern Python Practices
|
|
@@ -112,7 +112,7 @@ Always use full type annotations, generics, and other modern practices.
|
|
|
112
112
|
|
|
113
113
|
- You can run such individual tests with `uv run pytest -s src/.../path/to/test`
|
|
114
114
|
|
|
115
|
-
- Don
|
|
115
|
+
- Don’t add docs to assertions unless it’s not obvious what they’re checking - the
|
|
116
116
|
assertion appears in the stack trace.
|
|
117
117
|
Do NOT write `assert x == 5, "x should be 5"`. Do NOT write `assert x == 5 # Check if
|
|
118
118
|
x is 5`. That is redundant.
|
|
@@ -122,7 +122,7 @@ Always use full type annotations, generics, and other modern practices.
|
|
|
122
122
|
assertions that confirm the value of a constant setting.
|
|
123
123
|
|
|
124
124
|
- NEVER write `assert False`. If a test reaches an unexpected branch and must fail
|
|
125
|
-
explicitly, `raise AssertionError("
|
|
125
|
+
explicitly, `raise AssertionError("Some explanation")` instead.
|
|
126
126
|
This is best typical best practice in Python since assertions can be removed with
|
|
127
127
|
optimization.
|
|
128
128
|
|
|
@@ -132,6 +132,21 @@ Always use full type annotations, generics, and other modern practices.
|
|
|
132
132
|
This is also preferable because then simple tests have no explicit pytest dependencies
|
|
133
133
|
and can be placed in code anywhere.
|
|
134
134
|
|
|
135
|
+
- DO NOT write trivial tests that test something we know already works, like
|
|
136
|
+
instantiating a Pydantic object.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
class Link(BaseModel):
|
|
140
|
+
url: str
|
|
141
|
+
title: str = None
|
|
142
|
+
|
|
143
|
+
# DO NOT write tests like this. They are trivial and only create clutter!
|
|
144
|
+
def test_link_model():
|
|
145
|
+
link = Link(url="https://example.com", title="Example")
|
|
146
|
+
assert link.url == "https://example.com"
|
|
147
|
+
assert link.title == "Example"
|
|
148
|
+
```
|
|
149
|
+
|
|
135
150
|
## Types and Type Annotations
|
|
136
151
|
|
|
137
152
|
- Use modern union syntax: `str | None` instead of `Optional[str]`, `dict[str]` instead
|
|
@@ -142,7 +157,7 @@ Always use full type annotations, generics, and other modern practices.
|
|
|
142
157
|
- Use modern enums like `StrEnum` if appropriate.
|
|
143
158
|
|
|
144
159
|
- One exception to common practice on enums: If an enum has many values that are
|
|
145
|
-
strings, and they have a literal value as a string (like in a JSON protocol), it
|
|
160
|
+
strings, and they have a literal value as a string (like in a JSON protocol), it’s
|
|
146
161
|
fine to use lower_snake_case for enum values to match the actual value.
|
|
147
162
|
This is more readable than LONG_ALL_CAPS_VALUES, and you can simply set the value to
|
|
148
163
|
be the same as the name for each.
|
|
@@ -228,7 +243,7 @@ Always use full type annotations, generics, and other modern practices.
|
|
|
228
243
|
- For classes with many methods, use a concise docstring on the class that explains all
|
|
229
244
|
the common information, and avoid repeating the same information on every method.
|
|
230
245
|
|
|
231
|
-
- Docstrings should provide context or as concisely as possible explain
|
|
246
|
+
- Docstrings should provide context or as concisely as possible explain “why”, not
|
|
232
247
|
obvious details evident from the class names, function names, parameter names, and
|
|
233
248
|
type annotations.
|
|
234
249
|
|
|
@@ -240,10 +255,10 @@ Always use full type annotations, generics, and other modern practices.
|
|
|
240
255
|
name, variable name, or types.
|
|
241
256
|
That is silly and obvious and makes the code longer for no reason.
|
|
242
257
|
|
|
243
|
-
- Do NOT list args and return values if they
|
|
258
|
+
- Do NOT list args and return values if they’re obvious.
|
|
244
259
|
In the above examples, you do not need and `Arguments:` or `Returns:` section, since
|
|
245
|
-
|
|
246
|
-
do list these if there are many arguments and their meaning isn
|
|
260
|
+
sections as it is obvious from context.
|
|
261
|
+
do list these if there are many arguments and their meaning isn’t clear.
|
|
247
262
|
If it returns a less obvious type like a tuple, do explain in the pydoc.
|
|
248
263
|
|
|
249
264
|
- Exported/public variables, functions, or methods SHOULD have concise docstrings.
|
|
@@ -47,3 +47,11 @@ clean:
|
|
|
47
47
|
|
|
48
48
|
format-docs:
|
|
49
49
|
uv run flowmark --auto README.md
|
|
50
|
+
|
|
51
|
+
# Reset the expected reference docs to the actual ones currently produced.
|
|
52
|
+
reset-ref-docs:
|
|
53
|
+
cp tests/testdocs/testdoc.actual.auto.md tests/testdocs/testdoc.expected.auto.md
|
|
54
|
+
cp tests/testdocs/testdoc.actual.cleaned.md tests/testdocs/testdoc.expected.cleaned.md
|
|
55
|
+
cp tests/testdocs/testdoc.actual.plain.md tests/testdocs/testdoc.expected.plain.md
|
|
56
|
+
cp tests/testdocs/testdoc.actual.semantic.md tests/testdocs/testdoc.expected.semantic.md
|
|
57
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowmark
|
|
3
|
-
Version: 0.5.
|
|
4
|
-
Summary: Better auto-formatting
|
|
3
|
+
Version: 0.5.2
|
|
4
|
+
Summary: Better auto-formatting for Markdown and plaintext
|
|
5
5
|
Project-URL: Repository, https://github.com/jlevy/flowmark
|
|
6
6
|
Author-email: Joshua Levy <joshua@cal.berkeley.edu>
|
|
7
7
|
License-Expression: MIT
|
|
@@ -141,16 +141,16 @@ and sometimes controversial.
|
|
|
141
141
|
However, now we are using versioned Markdown more than ever, it’s a good time to revisit
|
|
142
142
|
this idea, as it can **make diffs in git much more readable**. The change may seem
|
|
143
143
|
subtle but avoids having paragraphs reflow for very small edits, which does a lot to
|
|
144
|
-
**minimize merge conflicts**.
|
|
144
|
+
**minimize merge conflicts**.
|
|
145
145
|
|
|
146
|
-
This is my own refinement of [traditional semantic line
|
|
146
|
+
This is my own refinement of [traditional semantic line
|
|
147
|
+
breaks](https://github.com/sembr/specification).
|
|
147
148
|
Instead of just allowing you to break lines as you wish, it auto-applies fixed
|
|
148
149
|
conventions about likely sentence boundaries in a conservative and reasonable way.
|
|
149
150
|
It uses simple and fast **regex-based sentence splitting**. While not perfect, this
|
|
150
151
|
works well for these purposes (and is much faster and simpler than a proper sentence
|
|
151
152
|
parser like SpaCy). It should work fine for English and many other Latin/Cyrillic
|
|
152
|
-
languages, but hasn’t been tested on CJK.
|
|
153
|
-
You can see some
|
|
153
|
+
languages, but hasn’t been tested on CJK. You can see some
|
|
154
154
|
[old discussion](https://github.com/shurcooL/markdownfmt/issues/17) of this idea with
|
|
155
155
|
the markdownfmt author.
|
|
156
156
|
|
|
@@ -160,7 +160,9 @@ edit/commit documents.
|
|
|
160
160
|
|
|
161
161
|
This feature is enabled with the `--semantic` flag or the `--auto` convenience flag.
|
|
162
162
|
|
|
163
|
-
##
|
|
163
|
+
## Typographic Cleanups
|
|
164
|
+
|
|
165
|
+
### Smart Quote Support
|
|
164
166
|
|
|
165
167
|
Flowmark offers optional **automatic smart quotes** to convert \"non-oriented quotes\"
|
|
166
168
|
to “oriented quotes” and apostrophes intelligently.
|
|
@@ -175,6 +177,14 @@ quote marks around regular text.
|
|
|
175
177
|
|
|
176
178
|
This feature is enabled with the `--smartquotes` flag or the `--auto` convenience flag.
|
|
177
179
|
|
|
180
|
+
### Ellipsis Support
|
|
181
|
+
|
|
182
|
+
There is a similar feature for converting `...` to an ellipsis character `…` when it
|
|
183
|
+
appears to be appropriate (i.e., not in code blocks and when adjacent to words or
|
|
184
|
+
punctuation).
|
|
185
|
+
|
|
186
|
+
This feature is enabled with the `--ellipses` flag or the `--auto` convenience flag.
|
|
187
|
+
|
|
178
188
|
## Frontmatter Support
|
|
179
189
|
|
|
180
190
|
Because **YAML frontmatter** is common on Markdown files, any YAML frontmatter (content
|
|
@@ -191,11 +201,11 @@ YAML is not normalized.
|
|
|
191
201
|
Flowmark can be used as a library or as a CLI.
|
|
192
202
|
|
|
193
203
|
```
|
|
194
|
-
usage: flowmark [-h] [-o OUTPUT] [-w WIDTH] [-p] [-s] [-c] [--smartquotes] [-i]
|
|
204
|
+
usage: flowmark [-h] [-o OUTPUT] [-w WIDTH] [-p] [-s] [-c] [--smartquotes] [--ellipses] [-i]
|
|
195
205
|
[--nobackup] [--auto] [--version]
|
|
196
206
|
[file]
|
|
197
207
|
|
|
198
|
-
Flowmark: Better auto-formatting
|
|
208
|
+
Flowmark: Better auto-formatting for Markdown and plaintext
|
|
199
209
|
|
|
200
210
|
positional arguments:
|
|
201
211
|
file Input file (use '-' for stdin)
|
|
@@ -203,21 +213,20 @@ positional arguments:
|
|
|
203
213
|
options:
|
|
204
214
|
-h, --help show this help message and exit
|
|
205
215
|
-o, --output OUTPUT Output file (use '-' for stdout)
|
|
206
|
-
-w, --width WIDTH Line width to wrap to, or 0 to disable line wrapping
|
|
207
|
-
(default: 88)
|
|
216
|
+
-w, --width WIDTH Line width to wrap to, or 0 to disable line wrapping (default: 88)
|
|
208
217
|
-p, --plaintext Process as plaintext (no Markdown parsing)
|
|
209
|
-
-s, --semantic Enable semantic (sentence-based) line breaks (only applies to
|
|
210
|
-
|
|
211
|
-
-c, --cleanups Enable (safe) cleanups for common issues like accidentally
|
|
212
|
-
|
|
213
|
-
--smartquotes Convert straight quotes to typographic (curly) quotes and
|
|
214
|
-
|
|
218
|
+
-s, --semantic Enable semantic (sentence-based) line breaks (only applies to Markdown
|
|
219
|
+
mode)
|
|
220
|
+
-c, --cleanups Enable (safe) cleanups for common issues like accidentally boldfaced
|
|
221
|
+
section headers (only applies to Markdown mode)
|
|
222
|
+
--smartquotes Convert straight quotes to typographic (curly) quotes and apostrophes
|
|
223
|
+
(only applies to Markdown mode)
|
|
224
|
+
--ellipses Convert three dots (...) to ellipsis character (…) with normalized
|
|
225
|
+
spacing (only applies to Markdown mode)
|
|
215
226
|
-i, --inplace Edit the file in place (ignores --output)
|
|
216
|
-
--nobackup Do not make a backup of the original file when using
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
--smartquotes`, as a convenience for fully auto-formatting
|
|
220
|
-
files
|
|
227
|
+
--nobackup Do not make a backup of the original file when using --inplace
|
|
228
|
+
--auto Same as `--inplace --nobackup --semantic --cleanups --smartquotes
|
|
229
|
+
--ellipses`, as a convenience for fully auto-formatting files
|
|
221
230
|
--version Show version information and exit
|
|
222
231
|
|
|
223
232
|
Flowmark provides enhanced text wrapping capabilities with special handling for
|
|
@@ -237,6 +246,10 @@ Command-line usage examples:
|
|
|
237
246
|
# Format a Markdown file to stdout
|
|
238
247
|
flowmark README.md
|
|
239
248
|
|
|
249
|
+
# Format a Markdown file in-place without backups and all auto-formatting
|
|
250
|
+
# options enabled
|
|
251
|
+
flowmark --auto README.md
|
|
252
|
+
|
|
240
253
|
# Format a Markdown file and save to a new file
|
|
241
254
|
flowmark README.md -o README_formatted.md
|
|
242
255
|
|
|
@@ -117,16 +117,16 @@ and sometimes controversial.
|
|
|
117
117
|
However, now we are using versioned Markdown more than ever, it’s a good time to revisit
|
|
118
118
|
this idea, as it can **make diffs in git much more readable**. The change may seem
|
|
119
119
|
subtle but avoids having paragraphs reflow for very small edits, which does a lot to
|
|
120
|
-
**minimize merge conflicts**.
|
|
120
|
+
**minimize merge conflicts**.
|
|
121
121
|
|
|
122
|
-
This is my own refinement of [traditional semantic line
|
|
122
|
+
This is my own refinement of [traditional semantic line
|
|
123
|
+
breaks](https://github.com/sembr/specification).
|
|
123
124
|
Instead of just allowing you to break lines as you wish, it auto-applies fixed
|
|
124
125
|
conventions about likely sentence boundaries in a conservative and reasonable way.
|
|
125
126
|
It uses simple and fast **regex-based sentence splitting**. While not perfect, this
|
|
126
127
|
works well for these purposes (and is much faster and simpler than a proper sentence
|
|
127
128
|
parser like SpaCy). It should work fine for English and many other Latin/Cyrillic
|
|
128
|
-
languages, but hasn’t been tested on CJK.
|
|
129
|
-
You can see some
|
|
129
|
+
languages, but hasn’t been tested on CJK. You can see some
|
|
130
130
|
[old discussion](https://github.com/shurcooL/markdownfmt/issues/17) of this idea with
|
|
131
131
|
the markdownfmt author.
|
|
132
132
|
|
|
@@ -136,7 +136,9 @@ edit/commit documents.
|
|
|
136
136
|
|
|
137
137
|
This feature is enabled with the `--semantic` flag or the `--auto` convenience flag.
|
|
138
138
|
|
|
139
|
-
##
|
|
139
|
+
## Typographic Cleanups
|
|
140
|
+
|
|
141
|
+
### Smart Quote Support
|
|
140
142
|
|
|
141
143
|
Flowmark offers optional **automatic smart quotes** to convert \"non-oriented quotes\"
|
|
142
144
|
to “oriented quotes” and apostrophes intelligently.
|
|
@@ -151,6 +153,14 @@ quote marks around regular text.
|
|
|
151
153
|
|
|
152
154
|
This feature is enabled with the `--smartquotes` flag or the `--auto` convenience flag.
|
|
153
155
|
|
|
156
|
+
### Ellipsis Support
|
|
157
|
+
|
|
158
|
+
There is a similar feature for converting `...` to an ellipsis character `…` when it
|
|
159
|
+
appears to be appropriate (i.e., not in code blocks and when adjacent to words or
|
|
160
|
+
punctuation).
|
|
161
|
+
|
|
162
|
+
This feature is enabled with the `--ellipses` flag or the `--auto` convenience flag.
|
|
163
|
+
|
|
154
164
|
## Frontmatter Support
|
|
155
165
|
|
|
156
166
|
Because **YAML frontmatter** is common on Markdown files, any YAML frontmatter (content
|
|
@@ -167,11 +177,11 @@ YAML is not normalized.
|
|
|
167
177
|
Flowmark can be used as a library or as a CLI.
|
|
168
178
|
|
|
169
179
|
```
|
|
170
|
-
usage: flowmark [-h] [-o OUTPUT] [-w WIDTH] [-p] [-s] [-c] [--smartquotes] [-i]
|
|
180
|
+
usage: flowmark [-h] [-o OUTPUT] [-w WIDTH] [-p] [-s] [-c] [--smartquotes] [--ellipses] [-i]
|
|
171
181
|
[--nobackup] [--auto] [--version]
|
|
172
182
|
[file]
|
|
173
183
|
|
|
174
|
-
Flowmark: Better auto-formatting
|
|
184
|
+
Flowmark: Better auto-formatting for Markdown and plaintext
|
|
175
185
|
|
|
176
186
|
positional arguments:
|
|
177
187
|
file Input file (use '-' for stdin)
|
|
@@ -179,21 +189,20 @@ positional arguments:
|
|
|
179
189
|
options:
|
|
180
190
|
-h, --help show this help message and exit
|
|
181
191
|
-o, --output OUTPUT Output file (use '-' for stdout)
|
|
182
|
-
-w, --width WIDTH Line width to wrap to, or 0 to disable line wrapping
|
|
183
|
-
(default: 88)
|
|
192
|
+
-w, --width WIDTH Line width to wrap to, or 0 to disable line wrapping (default: 88)
|
|
184
193
|
-p, --plaintext Process as plaintext (no Markdown parsing)
|
|
185
|
-
-s, --semantic Enable semantic (sentence-based) line breaks (only applies to
|
|
186
|
-
|
|
187
|
-
-c, --cleanups Enable (safe) cleanups for common issues like accidentally
|
|
188
|
-
|
|
189
|
-
--smartquotes Convert straight quotes to typographic (curly) quotes and
|
|
190
|
-
|
|
194
|
+
-s, --semantic Enable semantic (sentence-based) line breaks (only applies to Markdown
|
|
195
|
+
mode)
|
|
196
|
+
-c, --cleanups Enable (safe) cleanups for common issues like accidentally boldfaced
|
|
197
|
+
section headers (only applies to Markdown mode)
|
|
198
|
+
--smartquotes Convert straight quotes to typographic (curly) quotes and apostrophes
|
|
199
|
+
(only applies to Markdown mode)
|
|
200
|
+
--ellipses Convert three dots (...) to ellipsis character (…) with normalized
|
|
201
|
+
spacing (only applies to Markdown mode)
|
|
191
202
|
-i, --inplace Edit the file in place (ignores --output)
|
|
192
|
-
--nobackup Do not make a backup of the original file when using
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
--smartquotes`, as a convenience for fully auto-formatting
|
|
196
|
-
files
|
|
203
|
+
--nobackup Do not make a backup of the original file when using --inplace
|
|
204
|
+
--auto Same as `--inplace --nobackup --semantic --cleanups --smartquotes
|
|
205
|
+
--ellipses`, as a convenience for fully auto-formatting files
|
|
197
206
|
--version Show version information and exit
|
|
198
207
|
|
|
199
208
|
Flowmark provides enhanced text wrapping capabilities with special handling for
|
|
@@ -213,6 +222,10 @@ Command-line usage examples:
|
|
|
213
222
|
# Format a Markdown file to stdout
|
|
214
223
|
flowmark README.md
|
|
215
224
|
|
|
225
|
+
# Format a Markdown file in-place without backups and all auto-formatting
|
|
226
|
+
# options enabled
|
|
227
|
+
flowmark --auto README.md
|
|
228
|
+
|
|
216
229
|
# Format a Markdown file and save to a new file
|
|
217
230
|
flowmark README.md -o README_formatted.md
|
|
218
231
|
|
|
@@ -7,7 +7,7 @@ Repository = "https://github.com/jlevy/flowmark"
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "flowmark"
|
|
10
|
-
description = "Better auto-formatting
|
|
10
|
+
description = "Better auto-formatting for Markdown and plaintext"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name="Joshua Levy", email="joshua@cal.berkeley.edu" },
|
|
13
13
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
Flowmark: Better auto-formatting
|
|
3
|
+
Flowmark: Better auto-formatting for Markdown and plaintext
|
|
4
4
|
|
|
5
5
|
Flowmark provides enhanced text wrapping capabilities with special handling for
|
|
6
6
|
Markdown content. It can:
|
|
@@ -19,6 +19,10 @@ Command-line usage examples:
|
|
|
19
19
|
# Format a Markdown file to stdout
|
|
20
20
|
flowmark README.md
|
|
21
21
|
|
|
22
|
+
# Format a Markdown file in-place without backups and all auto-formatting
|
|
23
|
+
# options enabled
|
|
24
|
+
flowmark --auto README.md
|
|
25
|
+
|
|
22
26
|
# Format a Markdown file and save to a new file
|
|
23
27
|
flowmark README.md -o README_formatted.md
|
|
24
28
|
|
|
@@ -55,6 +59,7 @@ class Options:
|
|
|
55
59
|
semantic: bool
|
|
56
60
|
cleanups: bool
|
|
57
61
|
smartquotes: bool
|
|
62
|
+
ellipses: bool
|
|
58
63
|
inplace: bool
|
|
59
64
|
nobackup: bool
|
|
60
65
|
version: bool
|
|
@@ -119,6 +124,13 @@ def _parse_args(args: list[str] | None = None) -> Options:
|
|
|
119
124
|
help="Convert straight quotes to typographic (curly) quotes and apostrophes "
|
|
120
125
|
"(only applies to Markdown mode)",
|
|
121
126
|
)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--ellipses",
|
|
129
|
+
action="store_true",
|
|
130
|
+
default=False,
|
|
131
|
+
help="Convert three dots (...) to ellipsis character (…) with normalized spacing "
|
|
132
|
+
"(only applies to Markdown mode)",
|
|
133
|
+
)
|
|
122
134
|
parser.add_argument(
|
|
123
135
|
"-i", "--inplace", action="store_true", help="Edit the file in place (ignores --output)"
|
|
124
136
|
)
|
|
@@ -130,7 +142,7 @@ def _parse_args(args: list[str] | None = None) -> Options:
|
|
|
130
142
|
parser.add_argument(
|
|
131
143
|
"--auto",
|
|
132
144
|
action="store_true",
|
|
133
|
-
help="Same as `--inplace --nobackup --semantic --cleanups --smartquotes`, as a convenience for "
|
|
145
|
+
help="Same as `--inplace --nobackup --semantic --cleanups --smartquotes --ellipses`, as a convenience for "
|
|
134
146
|
"fully auto-formatting files",
|
|
135
147
|
)
|
|
136
148
|
parser.add_argument(
|
|
@@ -146,6 +158,7 @@ def _parse_args(args: list[str] | None = None) -> Options:
|
|
|
146
158
|
opts.semantic = True
|
|
147
159
|
opts.cleanups = True
|
|
148
160
|
opts.smartquotes = True
|
|
161
|
+
opts.ellipses = True
|
|
149
162
|
|
|
150
163
|
return Options(
|
|
151
164
|
file=opts.file,
|
|
@@ -155,6 +168,7 @@ def _parse_args(args: list[str] | None = None) -> Options:
|
|
|
155
168
|
semantic=opts.semantic,
|
|
156
169
|
cleanups=opts.cleanups,
|
|
157
170
|
smartquotes=opts.smartquotes,
|
|
171
|
+
ellipses=opts.ellipses,
|
|
158
172
|
inplace=opts.inplace,
|
|
159
173
|
nobackup=opts.nobackup,
|
|
160
174
|
version=opts.version,
|
|
@@ -193,6 +207,7 @@ def main(args: list[str] | None = None) -> int:
|
|
|
193
207
|
semantic=options.semantic,
|
|
194
208
|
cleanups=options.cleanups,
|
|
195
209
|
smartquotes=options.smartquotes,
|
|
210
|
+
ellipses=options.ellipses,
|
|
196
211
|
make_parents=True,
|
|
197
212
|
)
|
|
198
213
|
except ValueError as e:
|
|
@@ -5,6 +5,7 @@ from collections.abc import Callable
|
|
|
5
5
|
from marko import block, inline
|
|
6
6
|
from marko.block import Document
|
|
7
7
|
from marko.element import Element
|
|
8
|
+
from marko.ext import footnote
|
|
8
9
|
|
|
9
10
|
ContainerElement = (
|
|
10
11
|
block.Document,
|
|
@@ -16,6 +17,7 @@ ContainerElement = (
|
|
|
16
17
|
inline.Emphasis,
|
|
17
18
|
inline.StrongEmphasis,
|
|
18
19
|
inline.Link,
|
|
20
|
+
footnote.FootnoteDef, # Footnote definitions contain paragraphs and other elements
|
|
19
21
|
)
|
|
20
22
|
|
|
21
23
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from re import Pattern
|
|
3
|
+
|
|
4
|
+
ELLIPSIS_PATTERN: Pattern[str] = re.compile(
|
|
5
|
+
r"(^|[\w\"\'“‘])(\s*)(\.\.\.)([.,:;?!)\-—\"\'”’]?)(\s*)",
|
|
6
|
+
re.MULTILINE,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def ellipses(text: str) -> str:
|
|
11
|
+
r"""
|
|
12
|
+
Replace three consecutive dots with a proper ellipsis character (…).
|
|
13
|
+
|
|
14
|
+
Rules:
|
|
15
|
+
- `...` must be preceded by start of line OR a word character (with optional space)
|
|
16
|
+
- `...` must be followed by word character (with optional space) OR punctuation OR end of line
|
|
17
|
+
- If immediately before the `...` is a word character (no whitespace), a space is inserted before it.
|
|
18
|
+
- If immediately after the `...` is a word character (no whitespace), a space is inserted after it.
|
|
19
|
+
- If the punctuation [\"\'“‘] immediately precedes the ellipsis, there is no space between the
|
|
20
|
+
punctuation and the ellipsis.
|
|
21
|
+
- If punctuation [.,:;?!)\-—] follows the ellipsis, there is no space between the ellipsis and
|
|
22
|
+
the punctuation.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def replace_match(match: re.Match[str]) -> str:
|
|
26
|
+
prefix = match.group(1)
|
|
27
|
+
spaces_before = match.group(2)
|
|
28
|
+
punct = match.group(4)
|
|
29
|
+
spaces_after = match.group(5)
|
|
30
|
+
|
|
31
|
+
# Get what follows the match
|
|
32
|
+
end_pos = match.end()
|
|
33
|
+
remaining = text[end_pos:] if end_pos < len(text) else ""
|
|
34
|
+
next_char = remaining[0] if remaining else ""
|
|
35
|
+
|
|
36
|
+
# Check boundary - must be followed by word or end of line
|
|
37
|
+
if remaining and not re.match(r"\w|$", next_char):
|
|
38
|
+
return match.group(0)
|
|
39
|
+
|
|
40
|
+
result = prefix
|
|
41
|
+
|
|
42
|
+
# Add space before ellipsis if word char with no existing space
|
|
43
|
+
if prefix and re.match(r"\w", prefix) and not spaces_before:
|
|
44
|
+
result += " "
|
|
45
|
+
else:
|
|
46
|
+
result += spaces_before
|
|
47
|
+
|
|
48
|
+
result += "…" + punct
|
|
49
|
+
|
|
50
|
+
# Add space after ellipsis if word char follows with no space and no punct
|
|
51
|
+
if next_char and re.match(r"\w", next_char) and not spaces_after and not punct:
|
|
52
|
+
result += " "
|
|
53
|
+
else:
|
|
54
|
+
result += spaces_after
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
return ELLIPSIS_PATTERN.sub(replace_match, text)
|
|
@@ -18,6 +18,7 @@ from textwrap import dedent
|
|
|
18
18
|
from flowmark.custom_marko import custom_marko
|
|
19
19
|
from flowmark.doc_cleanups import doc_cleanups
|
|
20
20
|
from flowmark.doc_transforms import rewrite_text_content
|
|
21
|
+
from flowmark.ellipses import ellipses as apply_ellipses
|
|
21
22
|
from flowmark.frontmatter import split_frontmatter
|
|
22
23
|
from flowmark.line_wrappers import LineWrapper, line_wrap_by_sentence, line_wrap_to_width
|
|
23
24
|
from flowmark.sentence_split_regex import split_sentences_regex
|
|
@@ -82,6 +83,7 @@ def fill_markdown(
|
|
|
82
83
|
semantic: bool = False,
|
|
83
84
|
cleanups: bool = False,
|
|
84
85
|
smartquotes: bool = False,
|
|
86
|
+
ellipses: bool = False,
|
|
85
87
|
line_wrapper: LineWrapper | None = None,
|
|
86
88
|
) -> str:
|
|
87
89
|
"""
|
|
@@ -130,6 +132,8 @@ def fill_markdown(
|
|
|
130
132
|
doc_cleanups(document)
|
|
131
133
|
if smartquotes:
|
|
132
134
|
rewrite_text_content(document, smart_quotes, coalesce_lines=True)
|
|
135
|
+
if ellipses:
|
|
136
|
+
rewrite_text_content(document, apply_ellipses, coalesce_lines=True)
|
|
133
137
|
result = marko.render(document)
|
|
134
138
|
|
|
135
139
|
# Reattach frontmatter if it was present
|
|
@@ -15,6 +15,7 @@ def reformat_text(
|
|
|
15
15
|
semantic: bool = True,
|
|
16
16
|
cleanups: bool = True,
|
|
17
17
|
smartquotes: bool = False,
|
|
18
|
+
ellipses: bool = False,
|
|
18
19
|
) -> str:
|
|
19
20
|
"""
|
|
20
21
|
Reformat text or markdown and wrap lines. Simply a convenient wrapper
|
|
@@ -31,7 +32,12 @@ def reformat_text(
|
|
|
31
32
|
else:
|
|
32
33
|
# Markdown mode
|
|
33
34
|
result = fill_markdown(
|
|
34
|
-
text,
|
|
35
|
+
text,
|
|
36
|
+
width=width,
|
|
37
|
+
semantic=semantic,
|
|
38
|
+
cleanups=cleanups,
|
|
39
|
+
smartquotes=smartquotes,
|
|
40
|
+
ellipses=ellipses,
|
|
35
41
|
)
|
|
36
42
|
|
|
37
43
|
return result
|
|
@@ -47,6 +53,7 @@ def reformat_file(
|
|
|
47
53
|
semantic: bool = False,
|
|
48
54
|
cleanups: bool = True,
|
|
49
55
|
smartquotes: bool = False,
|
|
56
|
+
ellipses: bool = False,
|
|
50
57
|
make_parents: bool = True,
|
|
51
58
|
) -> None:
|
|
52
59
|
"""
|
|
@@ -66,6 +73,8 @@ def reformat_file(
|
|
|
66
73
|
headers (only applies to Markdown mode).
|
|
67
74
|
smartquotes: Convert straight quotes to typographic (curly) quotes and apostrophes
|
|
68
75
|
(only applies to Markdown mode).
|
|
76
|
+
ellipses: Convert three dots (...) to ellipsis character (…) with normalized spacing
|
|
77
|
+
(only applies to Markdown mode).
|
|
69
78
|
make_parents: Whether to make parent directories if they don't exist.
|
|
70
79
|
"""
|
|
71
80
|
read_stdin = path == "-"
|
|
@@ -79,7 +88,7 @@ def reformat_file(
|
|
|
79
88
|
else:
|
|
80
89
|
text = Path(path).read_text()
|
|
81
90
|
|
|
82
|
-
result = reformat_text(text, width, plaintext, semantic, cleanups, smartquotes)
|
|
91
|
+
result = reformat_text(text, width, plaintext, semantic, cleanups, smartquotes, ellipses)
|
|
83
92
|
|
|
84
93
|
if inplace:
|
|
85
94
|
backup_suffix = ".orig" if not nobackup else ""
|
|
@@ -5,9 +5,11 @@ from re import Match, Pattern
|
|
|
5
5
|
PARAGRAPH_BREAK_PATTERN: Pattern[str] = re.compile(r"\n\s*\n")
|
|
6
6
|
|
|
7
7
|
# Pattern excludes content that contains the same type of quote characters
|
|
8
|
-
# Double quotes exclude double quotes, single quotes exclude single quotes
|
|
8
|
+
# Double quotes exclude double quotes, single quotes exclude single quotes.
|
|
9
|
+
# Also as a special case allows quotes to start after an em dash (but not other punctuation
|
|
10
|
+
# as this is more likely to be code).
|
|
9
11
|
QUOTE_PATTERN: Pattern[str] = re.compile(
|
|
10
|
-
r'(^|\s)(?:"([^"\u201c\u201d]*)"|\'([^\'\u2018\u2019]*)\')(\s|$|\.|,|;|:|\?|!|—|\))',
|
|
12
|
+
r'(^|\s|—)(?:"([^"\u201c\u201d]*)"|\'([^\'\u2018\u2019]*)\')(\s|$|\.|,|;|:|\?|!|—|\))',
|
|
11
13
|
re.MULTILINE,
|
|
12
14
|
)
|
|
13
15
|
|