md2p
Markdown to Print (md2p)
md2p is a small command-line utility for rendering Markdown files to the terminal, using ANSI (or nroff) escape sequences for styling. It is intended as a lightweight helper to preview Markdown content without leaving the terminal.
Version
Current version: 1.0.1
Features
- Renders to stdout with ANSI colour/style escapes, or nroff overstrike sequences via
--nroff - Simple CLI usage with a Markdown file path, or via stdin
- Supported Markdown elements:
- ATX headers
#–######(each level styled distinctly) and setext headers (===/---) - Horizontal rules, blockquotes, fenced code blocks (
```/~~~) - Ordered and unordered lists, including nested indentation
- Tables, rendered as bordered ASCII grids
- Inline formatting: bold, italic, bold+italic, inline code, links, images
- ATX headers
- Non-printable bytes and hidden Unicode variation selectors (a common vector for invisible watermarks/payloads) are never passed through silently — each is replaced with a visible hex marker (see example below)
- Input is capped at 50 MB and raw ANSI escapes in the source file are stripped before rendering, so a malicious Markdown file can't inject terminal escape sequences
Installation
- Ensure you have Python 3.8+ installed.
- (Optional) Create and activate a virtual environment:
python3 -m venv .venv
source .venv/bin/activate
There are no external dependencies required by default.
From PyPI
pip install md2p
# or, without installing into an environment:
uvx md2p README.md
Linux (user install)
To install md2p for your user on Linux, use the included install.sh to copy the script to ~/.local/bin (or your XDG_BIN_HOME):
chmod +x install.sh
./install.sh
# or specify destination explicitly
./install.sh --dest "$HOME/.local/bin"
Uninstall with:
./install.sh --uninstall
Ensure your user bin directory is in PATH, for example add to your shell profile:
export PATH="$HOME/.local/bin:$PATH"
Usage
Run the script with a Markdown file path:
python3 md2p.py path/to/file.md
This prints the processed Markdown to stdout. Redirect output to a file if desired:
python3 md2p.py README.md > out.txt
You can also pipe Markdown in on stdin:
cat README.md | md2p
Options
-v,--version— print the version and exit.-n,--nroff— emit nroff overstrike sequences (X⌫Xfor bold,_⌫Xfor underline) instead of ANSI colour escapes. This is for viewers that interpret the classic nroff bold/underline convention but not ANSI SGR colours — most notably Midnight Commander's internal file viewer innroffmode. Colours, dim and italic are mapped onto the two available styles (bold / underline).
Midnight Commander integration
To preview Markdown with formatting inside mc's internal viewer (F3), add the
following to ~/.config/mc/mc.ext.ini:
[markdown]
Regex=\.(md|mkd|mdown|markdown)$
RegexIgnoreCase=true
View=%view{nroff} md2p --nroff %f
%view{nroff} tells the internal viewer to interpret the overstrike sequences
produced by md2p --nroff. For full ANSI colour instead, drop %view and pipe
through a colour-capable pager: View=md2p %f | less -R.
Example: Headers
Each ATX header level (# through ######) gets distinct styling — H1 is centred, bold, underlined; H2 gets an underline rule; H3–H6 step down through bold, dim, italic, and plain dim text.
Source Markdown:
# H1 Centered Title
## H2 Section With Rule
### H3 Subsection
#### H4 Minor Heading
##### H5 Detail
###### H6 Fine Print
Terminal output:
Example: Inline Formatting
Bold, italic, bold+italic, inline code, links, and images are all styled distinctly.
Source Markdown:
Plain text with **bold**, *italic*, and ***bold italic***.
Also `inline code`, a [link](https://example.com/docs), and an image .
Terminal output:
Example: Lists, Blockquotes & Code Blocks
Source Markdown:
- Top level item
- Nested item
1. First step
2. Second step
> A blockquote spanning
> the important caveat.
---
```
def hello():
print('hi')
```
Terminal output:
Example: Markdown Table
Tables are rendered as bordered ASCII grids with aligned columns.
Source Markdown:
| Name | Age | City |
|-------|-----|--------|
| Alice | 30 | London |
| Bob | 25 | Paris |
Terminal output:
Example: Non-printable Characters & Hidden Watermarks
Non-printable characters (anything that is not a printable Unicode character, newline, carriage return, tab, or space) are never passed through silently. Each such character is replaced inline with a red-background hex marker so it is immediately visible. Single-byte characters are shown as <HH>, and multi-byte UTF-8 sequences as <H0,H1,...>.
Source Markdown (paragraph contains a raw BEL U+0007 and a NULL U+0000):
This line has a bell \x07 and a null \x00 byte inside.
Terminal output:
The surrounding text is printed normally; only the offending bytes are highlighted, making it easy to spot encoding errors or accidental binary content in Markdown source files.
This also catches Unicode variation selectors (U+FE00–U+FE0F and U+E0100–U+E01EF). Python's str.isprintable() reports these as printable even though they render with zero visible width, and they are the block most commonly used today to smuggle hidden bytes — watermarks or payloads — onto an otherwise ordinary-looking character. md2p checks for them explicitly and flags each one with the same red-background hex marker, so text carrying a hidden variation-selector payload is not silently invisible in the rendered output.
Development & Tests
Run the existing tests with pytest:
pytest -q
Contributing
Contributions and bug reports are welcome. Open an issue or submit a pull request.
License
This project is licensed under the MIT License — see the LICENSE file for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file md2p-1.0.1.tar.gz.
File metadata
- Download URL: md2p-1.0.1.tar.gz
- Upload date:
- Size: 69.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4bb05deaed725d1d6b60655a3e10012b186214a71dcc023c88cdcda559961895
|
|
| MD5 |
9397fec81534a8d07f43ac24b5c9a34f
|
|
| BLAKE2b-256 |
c84f18f77c93af9f4fadb97a15b7cc49110abe0685a161aff811dc9f9c736240
|
Provenance
The following attestation bundles were made for md2p-1.0.1.tar.gz:
Publisher:
publish.yml on raulkivi/md2p
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
md2p-1.0.1.tar.gz -
Subject digest:
4bb05deaed725d1d6b60655a3e10012b186214a71dcc023c88cdcda559961895 - Sigstore transparency entry: 2480984437
- Sigstore integration time:
-
Permalink:
raulkivi/md2p@f60190bac12255c0eb28d8434139dadec458f29a -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/raulkivi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f60190bac12255c0eb28d8434139dadec458f29a -
Trigger Event:
push
-
Statement type:
File details
Details for the file md2p-1.0.1-py3-none-any.whl.
File metadata
- Download URL: md2p-1.0.1-py3-none-any.whl
- Upload date:
- Size: 10.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07e79459049b141cde280bb5075867a67559cc4f660124f3d8787e496236a2cc
|
|
| MD5 |
164b171846041782b150a7c491173cad
|
|
| BLAKE2b-256 |
a39137a0ff03ef6677b6d756c06b2d2117b1893fcb52bf635dfdd238337da036
|
Provenance
The following attestation bundles were made for md2p-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on raulkivi/md2p
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
md2p-1.0.1-py3-none-any.whl -
Subject digest:
07e79459049b141cde280bb5075867a67559cc4f660124f3d8787e496236a2cc - Sigstore transparency entry: 2480984464
- Sigstore integration time:
-
Permalink:
raulkivi/md2p@f60190bac12255c0eb28d8434139dadec458f29a -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/raulkivi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f60190bac12255c0eb28d8434139dadec458f29a -
Trigger Event:
push
-
Statement type: