Welcome to MarkFlow. This tool automatically reformats your Markdown to provide consistent looking Markdown files that look pretty similar to HTML that would be generated by them.
To use this tool, install it with pip then run markflow
:
pip install markflow
markflow SOMETHING.md
To install from source, assuming you already have poetry
installed, from the project
directory, run:
poetry install
poetry run markflow
Just want to see if there will be any changes? Use the --check
flag:
markflow --check $PATH_TO_MARKDOWN_FILE
For all features, we've got a help:
markflow --help
The tool ensures that the following rules are enforced for each different type of
Markdown section. For all sections, trailing spaces on each line are removed. It also
ensures that Markdown files end with a single newline and newlines are all '\n'
.
This tool uses the Markdown standard defined by CommonMark 0.29. It is expected to evolve with the standard and this section will be updated as support is added. If you notice any discrepancies, please open an issue.
Block quotes are fixed up with proper indentation markers for indented quotes, quote
indicators have any space between them removed, and unescaped >
that could be confused
with quote markers are escaped. e.g.:
>
> > Text >
> >
>
> > Ice Cream \> 0O0>
>
becomes:
>
>> Text \>
>>
>
>> Ice Cream \> 0O0>
>
Fenced codeblocks have any whitespace stripped from their markers and then printed out as usual.
``` markdown
# Markdown code
```
becomes
```markdown
# Markdown code
```
Indented code blocks simply have their trailing whitespace removed.
Footnotes will have their whitespace corrected and their titles wrapped. The tool will however respect what line URLs should appear on, even if they overflow. For example, the next two examples would be unchanged.
[really_really_really_long_link_that_could_go_on_a_new_line]: /but/doesnt/because/the/tool/understands/that/you/may/not/want/that
[short_link]:
/that/stays/on/separate/lines
'Even if title would fit'
Titles will be kept on whatever line you write them on, as long as they wouldn't be wrapped off the line.
[really_really_really_long_link_that_could_go_on_a_new_line]: /but/doesnt/because/the/tool/understands/that/you/may/not/want/that "But the title is moved to the next line and itself is wrapped because it is also really long."
becomes:
[really_really_really_long_link_that_could_go_on_a_new_line]: /but/doesnt/because/the/tool/understands/that/you/may/not/want/that
"But the title is moved to the next line and itself is wrapped because it is also really
long."
Heading lines begin and end with no whitespace. If you're using ATX headings (leading
#
s), but will correct missing or extra spaces between the octothorpe's and the
heading.
#Non-Standard Heading
becomes
# Non-Standard Heading
If you are using setext headings (i.e., underlined headings), they will automatically be fixed to ensure underlining matches the heading length. e.g.:
Heading 1
--
becomes
Heading 1
---------
If you have a heading that extends beyond an entire line, MarkFlow will wrap it for you.
This is a really long heading that I had to make up so that it would be at least 88 characters long
--
becomes
This is a really long heading that I had to make up so that it would be at least 88
characters long
-----------------------------------------------------------------------------------
Lists will be corrected to proper indentation. In addition, ordered lists will be properly numbered and bullet lists will be reformatted to use consistent bullets. Line lengths are also enforces. e.g.:
2. One
* Asterisk
- Dash
1. Two
5. Three
becomes
2. One
* Asterisk
* Dash
3. Two
4. Three
CommonMark doesn't allow lists to start with 0. That's not really a big deal for this tool, so we are OK with that. If this causes you issues, please let us know by opening an issue.
Paragraphs are reformatted to ensure they are the proper length. URLs and footnotes are properly split across lines. Inline code is placed all on a singular line. e.g. (assuming a line length of 1):
test `test =
1` [url](http://example.com)
becomes:
test
`test = 1`
[url](
http://example.com)
Separating lines (i.e., blank lines) contain only new lines, removing any horizontal whitespace.
Tables are reformatted to ensure proper width and headings are centered and all cells have at minimum one space between their contents and column separators. Alignment is supported too! e.g.:
|L|C|R|N|
|:--|:-:|--:|---|
|a|a|a|a|
|aa|aa|aa|aa|
|abcde|abcde|abcde|abcde|
becomes:
| L | C | R | N |
|:------|:-----:|------:|-------|
| a | a | a | a |
| aa | aa | aa | aa |
| abcde | abcde | abcde | abcde |
Thematic breaks are extended or reduced to match the length of the document. If line
length is set to infinity, it will instead use 3 of the separating character which must
be one of -
, _
, or *
.
-- - -
becomes:
----------------------------------------------------------------------------------------
The tool also provides a function to reformat Markdown strings yourself.
from markflow import reformat_markdown_text
markdown = " # Header 1"
nice_markdown = reformat_markdown_text(markdown, width=88)
To contribute to this project, check out our contributing guide.
If you run into an issue running a Markdown file, feel free to open an issue. If you can include the faulting file, that will make it so much easier to debug.
This script can help in anonymizing your file if you have any confidential information in it.
#!/usr/bin/env python3
""" Anonymize file XXXX.md and output it to XXXX.out.md """
import pathlib
import random
import string
FILE_NAME = "XXXX.md"
input_path = pathlib.Path(FILE_NAME)
output_path = pathlib.Path(".out.".join(FILE_NAME.rsplit(".", maxsplit=1)))
text = input_path.read_text()
output = ""
for char in text:
if char in string.ascii_lowercase:
char = random.choice(string.ascii_lowercase)
elif char in string.ascii_uppercase:
char = random.choice(string.ascii_uppercase)
output += char
output_path.write_text(output)
To read more about how the tool works, checkout the implementation outline.
This tool was inspired by a coworker not enjoying having to manually reformat Markdown files. He wanted a tool that would enforce it like black does for Python code. That is why the line length default is 88.
Escaping >
is especially important for the tool itself as otherwise updated block
quotes could be too deep. For instance, incorrect wrapping here could result in an extra
indented block of code.
> Please don't wrap after this period. >
> Because I don't want to be a double quote.
becomes:
> Please don't wrap after this period.
> > Because I don't want to be a
> double quote.
which would format to:
> Please don't wrap after this period.
> > Because I don't want to be a
> > double quote.
Of course, if the tool tried that, it would throw an exception since it double checks that if it were to be rerun the output would not change, at which point, hopefully, dear reader, you would open an issue. But I get it if you don't want to. I've been there.