Skip to main content

Plain text beautifier

Project description

// SPDX-FileCopyrightText: 2024 Philippe Proulx <eepp.ca>
// SPDX-License-Identifier: MIT

ifdef::env-github[]
:toc: macro
endif::env-github[]

ifndef::env-github[]
:toc: left
endif::env-github[]

:idprefix:
:idseparator: -

:py3: Python{nbsp}3

= Formol
Philippe Proulx
6 May 2024

[.normal]
image:https://img.shields.io/pypi/v/formol.svg?label=Latest%20version[link="https://pypi.python.org/pypi/formol"]

[.lead]
_**Formol**_ is a Python module which helps format a cheated version
of
https://0x3b.org/files/eepp-plain-text-format.html[eepp's plain text format]
to the real thing.

The main goal of this project is to help create editor/IDE extensions so
as to quickly convert an ordinary programming language block comment, a
Git commit message, or a plain text email message to some visually
appealing and readable text.

This module offers the simple `format()` function as well as the
`format_c_block_comment()` and `format_prefixed_block_comment()` helpers
to reformat C/{cpp} or prefixed block comments.

ifdef::env-github[]
toc::[]
endif::env-github[]

== Overview

The goal of Formol is to convert a <<cheats,cheated>> version of
https://0x3b.org/files/eepp-plain-text-format.html[eepp's plain text
format] to the actual specified format.

In today's difficult world, no one wants to find the `•`{nbsp}character,
insert the correct number of heading underline characters, or write and
align ordered list item numbers manually. On the other hand, you _do_
want all that stuff for your readers!

For example, given this C{nbsp}comment:

----
/*
* = québec poutine
*
* Poutine is a beloved dish originating from Québec, Canada. It's famous for its
* simple yet delicious combination of fries, cheese curds, and gravy.
*
* == History
*
* Poutine was first created in the late 1950s in rural Québec. Although several small towns claim to have invented it, its exact origins are still a topic
* of
* friendly dispute.
*
* As Jean Tremblay once said:
*
* >>>
* Poutine is not just a dish; it's a divine experience of taste that transcends the ordinary.
*
* Each bite harmonizes the humble potato with rich gravy and spirited cheese curds, embodying the warmth and resilience of Québec itself.
* >>>
*
* == Ingredients
*
* Fries:: Thick-cut fries that are crispy on the outside and soft on the inside.
* Cheese Curds:: Fresh, squeaky cheese curds are essential.
* Gravy:
* A light brown gravy, traditionally made from:
*
* * Chicken
* * Veal
*
* ***
*
* Some random code, why not:
*
* ```
* def _try_parse_pre(self):
* elem = self._try_parse_pre_backticks()
*
* if elem is not None:
* return elem
*
* return self._try_parse_pre_backticks()
* ```
* * Turkey stock
*
* == Preparation steps
*
* . Prepare the fries:
* . Wash and peel the potatoes.
*
* !!!
* CAUTION: Peeling potatoes can be risky if not done cautiously.
*
* Using sharp peelers or knives can lead to cuts, especially when tools slip on the slick surface of a potato.
*
* This common kitchen task can also strain the hands and wrists, potentially causing repetitive stress injuries.
*
* Furthermore, failing to wash potatoes before peeling can spread contaminants like:
* * Pesticides
* * Bacteria
*
* This poses additional health risks.
* !!!
* . Cut the potatoes into thick strips, approximately 1/2 inch wide.
* . Soak the potato strips in cold water for at least an hour to remove excess starch, then drain and pat dry.
* . Heat oil in a deep fryer or large pot to 300°F (150°C). Fry the potato strips in batches until they are soft but not browned, about 4-5 minutes per batch.
* . Increase the heat to 375°F (190°C) and refry the potatoes until golden and crispy, about 2-3 minutes.
* . Distribute the hot fries onto a plate or bowl.
* . Sprinkle fresh cheese curds over the hot fries.
* . Pour hot gravy over the fries and cheese curds to melt the cheese slightly.
*
* == Variations
*
* Classic poutine:: The original recipe with just fries, cheese curds, and gravy.
* Meat lovers poutine:: Includes additional toppings like pulled pork, bacon, or smoked meat.
* Veggie poutine:: Uses mushroom or vegetable-based gravy and might include other vegetable toppings.
*
* == Poutine in Québec culture
*
* Poutine is more than just a dish; it's a cultural icon
* in Québec. It embodies the joie de vivre of the Québécois people and is enjoyed
* in
* many settings, from
* fast food restaurants to fine dining establishments.
*
* == Conclusion
*
* Whether you enjoy it as a late-night snack or a hearty meal, poutine remains a testament to Québec's rich culinary traditions.
*
*
*/
----

If this whole string is named `str`, then the result of
`formol.format_c_block_comment(str)` (72{nbsp}columns by default) is:

----
/*
* QUÉBEC POUTINE
* ━━━━━━━━━━━━━━
* Poutine is a beloved dish originating from Québec, Canada. It's
* famous for its simple yet delicious combination of fries, cheese
* curds, and gravy.
*
* History
* ───────
* Poutine was first created in the late 1950s in rural Québec. Although
* several small towns claim to have invented it, its exact origins are
* still a topic of friendly dispute.
*
* As Jean Tremblay once said:
*
* > Poutine is not just a dish; it's a divine experience of taste that
* > transcends the ordinary.
* >
* > Each bite harmonizes the humble potato with rich gravy and spirited
* > cheese curds, embodying the warmth and resilience of Québec itself.
*
* Ingredients
* ───────────
* Fries:
* Thick-cut fries that are crispy on the outside and soft on
* the inside.
*
* Cheese Curds:
* Fresh, squeaky cheese curds are essential.
*
* Gravy:
* A light brown gravy, traditionally made from:
*
* • Chicken
*
* • Veal
*
* ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
*
* Some random code, why not:
*
* def _try_parse_pre(self):
* elem = self._try_parse_pre_backticks()
*
* if elem is not None:
* return elem
*
* return self._try_parse_pre_backticks()
*
* • Turkey stock
*
* Preparation steps
* ─────────────────
* 1. Prepare the fries:
*
* a) Wash and peel the potatoes.
*
* ┌────────────────────────────────────────────────────────────┐
* │ CAUTION: Peeling potatoes can be risky if not │
* │ done cautiously. │
* │ │
* │ Using sharp peelers or knives can lead to cuts, especially │
* │ when tools slip on the slick surface of a potato. │
* │ │
* │ This common kitchen task can also strain the hands and │
* │ wrists, potentially causing repetitive stress injuries. │
* │ │
* │ Furthermore, failing to wash potatoes before peeling can │
* │ spread contaminants like: │
* │ │
* │ • Pesticides │
* │ • Bacteria │
* │ │
* │ This poses additional health risks. │
* └────────────────────────────────────────────────────────────┘
*
* b) Cut the potatoes into thick strips, approximately 1/2
* inch wide.
*
* c) Soak the potato strips in cold water for at least an hour to
* remove excess starch, then drain and pat dry.
*
* d) Heat oil in a deep fryer or large pot to 300°F (150°C). Fry the
* potato strips in batches until they are soft but not browned,
* about 4-5 minutes per batch.
*
* e) Increase the heat to 375°F (190°C) and refry the potatoes until
* golden and crispy, about 2-3 minutes.
*
* 2. Distribute the hot fries onto a plate or bowl.
*
* 3. Sprinkle fresh cheese curds over the hot fries.
*
* 4. Pour hot gravy over the fries and cheese curds to melt the
* cheese slightly.
*
* Variations
* ──────────
* Classic poutine:
* The original recipe with just fries, cheese curds, and gravy.
*
* Meat lovers poutine:
* Includes additional toppings like pulled pork, bacon, or
* smoked meat.
*
* Veggie poutine:
* Uses mushroom or vegetable-based gravy and might include other
* vegetable toppings.
*
* Poutine in Québec culture
* ─────────────────────────
* Poutine is more than just a dish; it's a cultural icon in Québec. It
* embodies the joie de vivre of the Québécois people and is enjoyed in
* many settings, from fast food restaurants to fine
* dining establishments.
*
* Conclusion
* ──────────
* Whether you enjoy it as a late-night snack or a hearty meal, poutine
* remains a testament to Québec's rich culinary traditions.
*/
----

An important feature of Formol is that it can (most of the time) consume
its own output without changing it (idempotency). This makes it possible
to change parts of the formatted text, possibly cheating again, and then
reformat it again.

For example, starting with some previous output:

----
Preparation steps
─────────────────
1. Prepare the fries.

2. Sprinkle fresh cheese curds over the hot fries.

3. Pour hot gravy over the fries and cheese curds to melt the
cheese slightly.
----

You may change the heading and add a list item as such:

----
Arrangement
─────────────────
1. Prepare the fries.
. Distribute the hot fries onto a plate or bowl.

2. Sprinkle fresh cheese curds over the hot fries.

3. Pour hot gravy over the fries and cheese curds to melt the
cheese slightly.
----

Then the new result is:

----
Arrangement
───────────
1. Prepare the fries.

2. Distribute the hot fries onto a plate or bowl.

3. Sprinkle fresh cheese curds over the hot fries.

4. Pour hot gravy over the fries and cheese curds to melt the
cheese slightly.
----

== Install Formol

Formol only requires Python ≥ 3.8.

To install Formol:

----
$ python3 -m pip install --user formol
----

See
https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-to-the-user-site[Installing
to the User Site] to learn more about a user site installation.

[NOTE]
====
Formol is a single module file, `formol.py`, which you can copy as is to
your project to use it.

`formol.py` has _no external dependencies_.
====

== Cheats

Here are the Formol cheats:

=== First level heading

----
= salut la gang
----

becomes

----
SALUT LA GANG
━━━━━━━━━━━━━
----

[TIP]
====
You may modify a formatted first level heading and reformat: Formol
adjusts the case and the underline length.

For example:

----
SALUT LA GANG de malades
━━━━━━━━━━━━━
----
====

=== Second level heading

----
== Grease guns
----

becomes

----
Grease guns
───────────
----

[TIP]
====
You may modify a formatted second level heading and reformat: Formol
adjusts the underline length.

For example:

----
Grease
───────────
----
====

=== Paragraph

----
I'm baby tote bag kogi paleo kickstarter. Chillwave crucifix `hot chicken four dollar` toast biodiesel af. Etsy sriracha pickled bodega boys neutra
tattooed schlitz
jianbing neutral milk hotel gentrify health goth `DSA shoreditch`
slow-carb
mustache.
Bicycle rights distillery sus forage
mlkshk irony helvetica, listicle hoodie.
----

becomes

----
I'm baby tote bag kogi paleo kickstarter. Chillwave crucifix
`hot chicken four dollar` toast biodiesel af. Etsy sriracha pickled
bodega boys neutra tattooed schlitz jianbing neutral milk hotel gentrify
health goth `DSA shoreditch` slow-carb mustache. Bicycle rights
distillery sus forage mlkshk irony helvetica, listicle hoodie.
----

Note how there's no line break in the middle of a literal string
(between backticks).

=== Break

----
Incididunt officia magna.

***

Ut deserunt cupidatat exercitation.
----

becomes

----
Incididunt officia magna.

┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄

Ut deserunt cupidatat exercitation.
----

=== Unordered list

----
* Bacon ipsum dolor amet bacon shoulder bresaola meatloaf kielbasa. Spare ribs capicola pastrami, hamburger.
* Drumstick spare ribs doner filet mignon beef porchetta shankle chicken.

Alcatra ground round pork loin ham hock tenderloin chicken rump jowl. Sausage andouille ribeye turkey.

* Pastrami rump short.
* Prosciutto jowl alcatra.
* Leberkas tri-tip brisket.

* Sirloin swine turkey fatback prosciutto t-bone tongue short:

```
fn square(num int) int {
return num * num
}

fn main() {
println(square(3))
}
```
----

becomes

----
• Bacon ipsum dolor amet bacon shoulder bresaola meatloaf kielbasa.
Spare ribs capicola pastrami, hamburger.

• Drumstick spare ribs doner filet mignon beef porchetta
shankle chicken.

Alcatra ground round pork loin ham hock tenderloin chicken rump jowl.
Sausage andouille ribeye turkey.

‣ Pastrami rump short.
‣ Prosciutto jowl alcatra.
‣ Leberkas tri-tip brisket.

• Sirloin swine turkey fatback prosciutto t-bone tongue short:

fn square(num int) int {
return num * num
}

fn main() {
println(square(3))
}
----

[TIP]
====
You may modify a formatted unordered list: add more
`pass:[*]`{nbsp}characters if needed so that Formol converts them to
bullet points.

For example:

----
• Bacon ipsum dolor amet bacon shoulder bresaola
meatloaf kielbasa. Spare ribs capicola
pastrami, hamburger.

• Drumstick spare ribs doner filet mignon beef
porchetta shankle chicken.

Alcatra ground round pork loin ham hock tenderloin
chicken rump jowl. Sausage andouille ribeye turkey.

‣ Pastrami rump short.
‣ Prosciutto jowl alcatra.
* Tongue meatball frankfurter strip.
‣ Leberkas tri-tip brisket.
* Landjaeger doner ribeye, turkey shoulder
pancetta beef.
----
====

=== Ordered list

----
. Bacon ipsum dolor amet bacon shoulder bresaola meatloaf kielbasa. Spare ribs capicola pastrami, hamburger.
. Drumstick spare ribs doner filet mignon beef porchetta shankle chicken.

Alcatra ground round pork loin ham hock tenderloin chicken rump jowl. Sausage andouille ribeye turkey.

. Pastrami rump short.
. Prosciutto jowl alcatra.
. Leberkas tri-tip brisket.

. Sirloin swine turkey fatback prosciutto t-bone tongue short:

```
#define CUSTOM_DEFINE_gcboehm
#define CUSTOM_DEFINE_gcboehm_full
#define CUSTOM_DEFINE_gcboehm_opt
```
----

becomes

----
1. Bacon ipsum dolor amet bacon shoulder bresaola meatloaf kielbasa.
Spare ribs capicola pastrami, hamburger.

2. Drumstick spare ribs doner filet mignon beef porchetta
shankle chicken.

Alcatra ground round pork loin ham hock tenderloin chicken rump jowl.
Sausage andouille ribeye turkey.

a) Pastrami rump short.
b) Prosciutto jowl alcatra.
c) Leberkas tri-tip brisket.

3. Sirloin swine turkey fatback prosciutto t-bone tongue short:

#define CUSTOM_DEFINE_gcboehm
#define CUSTOM_DEFINE_gcboehm_full
#define CUSTOM_DEFINE_gcboehm_opt
----

[TIP]
====
You may modify a formatted unordered list: add more `.`{nbsp}characters
if needed so that Formol converts them to list item numbers.

For example:

----
1. Bacon ipsum dolor amet bacon shoulder bresaola
meatloaf kielbasa. Spare ribs capicola
pastrami, hamburger.

2. Drumstick spare ribs doner filet mignon beef
porchetta shankle chicken.

Alcatra ground round pork loin ham hock tenderloin
chicken rump jowl. Sausage andouille ribeye turkey.

a) Pastrami rump short.
b) Prosciutto jowl alcatra.
. Tongue meatball frankfurter strip.
d) Leberkas tri-tip brisket.

. Landjaeger doner ribeye, turkey shoulder
pancetta beef.
----
====

=== Definition list

----
Silken Tofu:: A soft, creamy form of tofu that blends smoothly into soups, desserts, and smoothies due to its high moisture content.
Firm Tofu:
A denser variety of tofu that holds its shape well, making it ideal for grilling, frying, or stir-frying.

Firm tofu is a popular type of tofu appreciated for its sturdier texture, which allows it to maintain its shape during cooking.
Tofu Press:
Tofu Mold:
Specialized utensils designed to aid in the making and processing of tofu, enhancing its texture and culinary versatility.
----

becomes

----
Silken Tofu:
A soft, creamy form of tofu that blends smoothly into soups,
desserts, and smoothies due to its high moisture content.

Firm Tofu:
A denser variety of tofu that holds its shape well, making it ideal
for grilling, frying, or stir-frying.

Firm tofu is a popular type of tofu appreciated for its sturdier
texture, which allows it to maintain its shape during cooking.

Tofu Press:
Tofu Mold:
Specialized utensils designed to aid in the making and processing of
tofu, enhancing its texture and culinary versatility.
----

=== Blockquote

----
>>>
Montana, known as the "Big Sky Country," is a state that offers vast and picturesque landscapes. Here are three key highlights:

* Glacier National Park
* Battle of Little Bighorn Site
* Fly Fishing
>>>
----

becomes

----
> Montana, known as the "Big Sky Country," is a state that offers vast
> and picturesque landscapes. Here are three key highlights:
>
> • Glacier National Park
> • Battle of Little Bighorn Site
> • Fly Fishing
----

[TIP]
====
You may modify a formatted blockquote: add more `>`{nbsp}characters as
needed.

For example:

----
> Montana, known as the "Big Sky Country," is a state that offers vast
> and picturesque landscapes. Here are three key highlights:
>
> Consequat ut cillum sunt nisi adipisicing nulla ut minim dolore aliqua dolore.
>
> • Glacier National Park
> • Battle of Little Bighorn Site
> * One more thing...
> • Fly Fishing
----
====

=== Admonition box

----
!!!
IMPORTANT: Be aware of the changing tides and strong currents that can swiftly turn
a peaceful day at the beach into a dangerous situation.

Before taking a dip, make sure to check the local tide schedules and swim in designated areas with lifeguards present.
!!!
----

becomes

----
┌────────────────────────────────────────────────────────────────────┐
│ IMPORTANT: Be aware of the changing tides and strong currents that │
│ can swiftly turn a peaceful day at the beach into a │
│ dangerous situation. │
│ │
│ Before taking a dip, make sure to check the local tide │
│ schedules and swim in designated areas with lifeguards present. │
└────────────────────────────────────────────────────────────────────┘
----

[TIP]
====
You may modify a formatted admonition box: modify the existing lines or
add new lines without dealing with box drawing characters.

The first content line must start with one of:

* `CAUTION:{nbsp}`
* `IMPORTANT:{nbsp}`
* `NOTE:{nbsp}`
* `TIP:{nbsp}`
* `WARNING:{nbsp}`

Each new content line must start with two spaces.

For example:

----
┌───────────────────────────────────────────────────────┐
│ IMPORTANT: Be aware of the changing tides and strong │
│ currents that can swiftly turn a wonderful day at the │
│ beach into a dangerous situation. │
│ │
│ Before taking a dip, make sure to:

* Check local weather and tide reports.
* Use proper safety gear.
* Never swim alone.

These precautions can help you enjoy water-related activities safely while respecting the power of nature's changing conditions.
└────────────────────────────────────────────────────────────────────┘
----
====

=== Preformatted text

Two ways:

Delimited:: {empty}
+
----
Here's some code for you:

```
if (idx < vec.size() - 1) {
vec[idx] = std::move(vec.back());
}
```

See?
----
+
becomes
+
----
Here's some code for you:

if (idx < vec.size() - 1) {
vec[idx] = std::move(vec.back());
}

See?
----

Indented:: {empty}
+
----
Here's some code for you:

if (idx < vec.size() - 1) {
vec[idx] = std::move(vec.back());
}

See?
----
+
stays
+
----
Here's some code for you:

if (idx < vec.size() - 1) {
vec[idx] = std::move(vec.back());
}

See?
----

== Limitations

I am convinced that you'll find yourself in situations where the output
of Formol isn't what you expect. If so, please create a corresponding
https://github.com/eepp/formol/issues/new[GitHub issue]. In the
meantime, just fix the output manually.

Formol ignores some input lines, keeping them as is, namely:

* Any line which starts with a link reference number, for example:
+
----
[1]: https://theluddite.org/
[2]: https://www.st-hubert.com/
----

* Except for an existing <<admonition-box,admonition box>>, any line which
starts with one of the following box drawing characters:
+
----
│ ┃ ┆ ┇ ┊ ┋ ┌ ┍ ┎ ┏ └ ┕ ┖ ┗ ├ ┝ ┞ ┟ ┠ ┡ ┢ ┣ ╎ ╏ ║ ╒ ╓ ╔ ╘ ╙ ╚ ╞ ╟ ╠ ╽ ╿
----
+
This makes it possible to add tables and general boxes at the paragraph
level (no special indentation).

== Tests

To run the tests:

```
$ poetry install
$ poetry run pytest
```

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

formol-0.10.0.tar.gz (25.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

formol-0.10.0-py3-none-any.whl (16.2 kB view details)

Uploaded Python 3

File details

Details for the file formol-0.10.0.tar.gz.

File metadata

  • Download URL: formol-0.10.0.tar.gz
  • Upload date:
  • Size: 25.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for formol-0.10.0.tar.gz
Algorithm Hash digest
SHA256 f9ef2134d2e17879fc5a1472405357befc228918bbc906394135c5d886892305
MD5 edbe572752efce28e04a720dbbcb066b
BLAKE2b-256 b10d800ead6c57a495528a5bd222b3600186ef6f5c5066d565a86bada3bb6f30

See more details on using hashes here.

File details

Details for the file formol-0.10.0-py3-none-any.whl.

File metadata

  • Download URL: formol-0.10.0-py3-none-any.whl
  • Upload date:
  • Size: 16.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for formol-0.10.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9db370873a3ef9ed0ea22dab59c65952ceb7b50e0f646da7c70c53bb6996c8d5
MD5 73497e3f07179c6e778abd1ae5378260
BLAKE2b-256 8c920205a6b135f7e67d33f5633a7494d885de4cc16b05d17ca9c6bf65fe8046

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page