The Element API

For users who want a little more control over how their markdown is formatted, SnakeMD provides a low-level API constructed of elements.

Element Interface

Broadly speaking, anything that can be rendered as markdown is known as an element. Below is the element interface.

class snakemd.Element

Bases: ABC

A generic element interface which provides a framework for all types of elements in the collection. In short, elements must be able to be converted to their markdown representation using the built-in str constructor. They must also be able to be converted into development strings using the repr() function.

abstract __repr__() str

The developer’s string method to help make sense of objects. For the purposes of this repo, the __repr__ method should create strings that can be used to recreate the element, much like the built-in feature of dataclasses (a feature which may be adopted in future versions of snakemd). Ultimately, this method must be implemented by all inheriting classes.

Returns:

an unambiguous representation of the element

abstract __str__() str

The default string method to be implemented by all inheriting classes.

Returns:

a markdown ready representation of the element

For consistency, element mutators all return self to allow for method chaining. This is sometimes referred to as the fluent interface pattern, and it’s particularly useful for applying a series of changes to a single element. This design choice most obviously shines in both snakemd.Paragraph, which allows different aspects of the text to be replaced over a series of chained methods, and snakemd.Inline, which allows inline elements to be styled over a series of chained methods.

For practical purposes, elements cannot be constructed directly. Instead, they are broken down into two main categories: block and inline.

Block Elements

SnakeMD block elements borrow from the idea of block-level elements from HTML. And because Markdown documents are constructed from a series of blocks, users of SnakeMD can seemlessly append their own custom blocks using the snakemd.Document.add_block() method. To make use of this method, blocks must be imported and constructed manually, like the following snakemd.Heading example:

>>> from snakemd import Heading, new_doc
>>> doc = new_doc()
>>> heading = doc.add_block(Heading("Hello, World!", 2))

The remainder of this section introduces the Block interface as well as all of the Blocks currently available for use.

Block Interface

All markdown blocks inherit from the Block interface.

class snakemd.Block

Bases: Element

A block element in Markdown. A block is defined as a standalone element starting on a newline. Examples of blocks include paragraphs (i.e., <p>), headings (e.g., <h1>, <h2>, etc.), tables (i.e., <table>), and lists (e.g., <ol>, <ul>, etc.).

Code

class snakemd.Code(code: str | Code, lang: str = 'generic')

Bases: Block

A code block is a standalone block of syntax-highlighted code. Code blocks can have generic highlighting or highlighting based on their language.

Parameters:
  • code (str | Code) –

    the sourcecode to format as a Markdown code block

    • set to a string to render a preformatted code block (i.e., whitespace is respected)

    • set to a Code object to render a nested code block

  • lang (str) – the programming language for the code block; defaults to ‘generic’

__repr__() str

Renders self as an unambiguous string for development. In this case, it displays in the style of a dataclass, where instance variables are listed with their values.

>>> code = Code('x = 87')
>>> repr(code)
"Code(code='x = 87', lang='generic')"
Returns:

the Code object as a development string

__str__() str

Renders the code block as a markdown string. Markdown code blocks are returned with the fenced code block format using backticks:

```python
x = 5
y = 2 + x
```

Code blocks can be nested and will be rendered with increasing numbers of backticks.

Returns:

the code block as a markdown string

Heading

class snakemd.Heading(text: str | Inline | Iterable[Inline | str], level: int)

Bases: Block

A heading is a text block which serves as the title for a new section of a document. Headings come in six main sizes which correspond to the six headings sizes in HTML (e.g., <h1>).

Raises:

ValueError – when level < 1 or level > 6

Parameters:
  • text (str | Inline | Iterable[Inline | str]) –

    the heading text

    • set to a string to render raw heading text

    • set to an Inline object to render a styled heading (e.g., bold, link, code, etc.)

    • set to a “list” of the prior options to render a header with more granular control over the individual inline elements

  • level (int) – the heading level between 1 and 6

__repr__() str

Renders self as an unambiguous string for development. In this case, it displays in the style of a dataclass, where instance variables are listed with their values.

Note that Headings can accept a variety of string-like inputs. However, the underlying representation forces all possible inputs to be a list of Inline objects. As a result, the repr representation will often be significantly more complex than expected.

>>> heading = Heading("", 1)
>>> repr(heading)
"Heading(text=[Inline(text='',...)], level=1)"
Returns:

the Code object as a development string

__str__() str

Renders the heading as a markdown string. Markdown headings are returned using the # syntax where the number of # symbols corresponds to the heading level:

# This is an H1
## This is an H2
### This is an H3
Returns:

the heading as a markdown string

demote() Heading

Demotes a heading down a level. Fails silently if the heading is already at the lowest level (i.e., <h6>).

>>> heading = Heading("This is an H2 heading", 1).demote()
>>> str(heading)
'## This is an H2 heading'
Returns:

self

get_level() int

Retrieves the level of the heading.

>>> heading = Heading("This is the heading text", 1)
>>> heading.get_level()
1

New in version 2.2: Included to avoid protected member access scenarios.

Returns:

the heading level

get_text() str

Returns the heading text free of any styling. Useful when the heading is composed of various Inline elements, and the raw text is needed without styling or linking.

>>> heading = Heading("This is the heading text", 1)
>>> heading.get_text()
'This is the heading text'
Returns:

the heading as a string

promote() Heading

Promotes a heading up a level. Fails silently if the heading is already at the highest level (i.e., <h1>).

>>> heading = Heading("This is an H2 heading", 3).promote()
>>> str(heading)
'## This is an H2 heading'
Returns:

self

HorizontalRule

class snakemd.HorizontalRule

Bases: Block

A horizontal rule is a line separating different sections of a document. Horizontal rules only come in one form, so there are no settings to adjust.

__repr__() str

Renders self as an unambiguous string for development. In this case, it displays in the style of a dataclass, where instance variables are listed with their values.

>>> horizontal_rule = HorizontalRule()
>>> repr(horizontal_rule)
'HorizontalRule()'
Returns:

the HorizontalRule object as a development string

__str__() str

Renders the horizontal rule as a markdown string. Markdown horizontal rules come in a variety of flavors, but the format used in this repo is the triple asterisk (i.e., ***) to avoid clashes with list formatting.

Returns:

the horizontal rule as a markdown string

MDList

class snakemd.MDList(items: Iterable[str | Inline | Block], ordered: bool = False, checked: None | bool | Iterable[bool] = None)

Bases: Block

A markdown list is a standalone list that comes in three varieties: ordered, unordered, and checked.

Raises:

ValueError – when the checked argument is an Iterable[bool] that does not match the number of top-level elements in the list

Parameters:
  • items (Iterable[str | Inline | Block]) – a “list” of objects to be rendered as a list

  • ordered (bool) –

    the ordered state of the list

    • defaults to False which renders an unordered list (i.e., -)

    • set to True to render an ordered list (i.e., 1.)

  • checked (None | bool | Iterable[bool]) –

    the checked state of the list

    • defaults to None which excludes checkboxes from being rendered

    • set to False to render a series of unchecked boxes (i.e., - [ ])

    • set to True to render a series of checked boxes (i.e., - [x])

    • set to Iterable[bool] to render the checked status of the top-level list elements directly

__repr__() str

Renders self as an unambiguous string for development. In this case, it displays in the style of a dataclass, where instance variables are listed with their values.

>>> mdlist = MDList(["Plus", "Ultra"])
>>> repr(mdlist)
"MDList(items=[Paragraph(...), Paragraph(...)],...)"
Returns:

the MDList object as a development string

__str__() str

Renders the markdown list as a markdown string. Markdown lists come in a variety of flavors and are customized according to the settings provided. For example, if the the ordered flag is set, an ordered list will be rendered in markdown. Unordered lists and checklists both use the hyphen syntax for markdown (i.e., -) to avoid clashes with horizontal rules:

- This is an unordered list item
- So, is this

Ordered lists use numbers for each list item:

1. This is an ordered list item
2. So, is this
Returns:

the list as a markdown string

Paragraph

class snakemd.Paragraph(content: str | Iterable[Inline | str])

Bases: Block

A paragraph is a standalone block of text.

Parameters:

content (str | Iterable[str | Inline]) –

the text to be rendered as a paragraph where whitespace is not respected (see snakemd.Raw for whitespace sensitive applications)

  • set to a string to render a single line of unformatted text

  • set to a “list” of text objects to render a paragraph with more granular control over the individual text objects (e.g., linking, styling, etc.)

__repr__() str

Renders self as an unambiguous string for development. In this case, it displays in the style of a dataclass, where instance variables are listed with their values.

Like Heading, the actual format of the development string may be more complex than expected. Specifically, all of the contents are automatically converted to a list of Inline objects.

>>> paragraph = Paragraph("Howdy!")
>>> repr(paragraph)
"Paragraph(content=[Inline(text='Howdy!',...)])"
Returns:

the Paragraph object as a development string

__str__() str

Renders the paragraph as a markdown string. Markdown paragraphs are returned as a singular line of text with all of the underlying elements rendered as expected:

This is an example of a **paragraph** with _formatting_
Returns:

the paragraph as a markdown string

add(text: str | Inline) Paragraph

Adds a text object to the paragraph.

>>> paragraph = Paragraph("Hello! ").add("I come in peace")
>>> str(paragraph)
'Hello! I come in peace'
Parameters:

text (str | Inline) – a custom Inline element

Returns:

self

A convenience method which inserts links in the paragraph for all matching instances of a target string. This method is modeled after str.replace(), so a count can be provided to limit the number of insertions. This method will not replace links of text that have already been linked. See snakemd.Paragraph.replace_link() for that behavior.

>>> paragraph = Paragraph("Go here for docs")
>>> paragraph.insert_link("here", "https://snakemd.io")
Paragraph(content=[...])
>>> str(paragraph)
'Go [here](https://snakemd.io) for docs'
Parameters:
  • target (str) – the string to link

  • link (str) – the url or path

  • count (int) – the number of links to insert; defaults to -1 (all)

Returns:

self

replace(target: str, replacement: str, count: int = -1) Paragraph

A convenience method which replaces a target string with a string of the users choice. Like insert_link(), this method is modeled after str.replace() of the standard library. As a result, a count can be provided to limit the number of strings replaced in the paragraph.

>>> paragraph = Paragraph("I come in piece").replace("piece", "peace")
>>> str(paragraph)
'I come in peace'
Parameters:
  • target (str) – the target string to replace

  • replacement (str) – the string to insert in place of the target

  • count (int) – the number of targets to replace; defaults to -1 (all)

Returns:

self

A convenience method which replaces matching URLs in the paragraph with a new url. Like insert_link() and replace(), this method is also modeled after str.replace(), so a count can be provided to limit the number of links replaced in the paragraph. This method is useful if you want to replace existing URLs but don’t necessarily care what the anchor text is.

>>> old = "https://therenegadecoder.com"
>>> new = "https://snakemd.io"
>>> paragraph = Paragraph("Go here for docs")
>>> paragraph.insert_link("here", old).replace_link(old, new)
Paragraph(content=[...])
>>> str(paragraph)
'Go [here](https://snakemd.io) for docs'
Parameters:
  • target_link (str) – the link to replace

  • replacement_link (str) – the link to swap in

  • count (int) – the number of links to replace; defaults to -1 (all)

Returns:

self

Quote

class snakemd.Quote(content: str | Iterable[str | Inline | Block])

Bases: Block

A quote is a standalone block of emphasized text. Quotes can be nested and can contain other blocks.

Parameters:

content (str | Iterable[str | Inline | Block]) –

the text to be formatted as a Markdown quote

  • set to a string to render a whitespace respected quote (similar to snakemd.Code)

  • set to a “list” of text objects to render a document-like quote (i.e., all items will be separated by newlines)

__repr__() str

The developer’s string method to help make sense of objects. For the purposes of this repo, the __repr__ method should create strings that can be used to recreate the element, much like the built-in feature of dataclasses (a feature which may be adopted in future versions of snakemd). Ultimately, this method must be implemented by all inheriting classes.

Returns:

an unambiguous representation of the element

__str__() str

Renders the quote as a markdown string. Markdown quotes vary in syntax, but the general approach in this repo is to apply the quote symbol (i.e., >) to the front of each line in the quote:

> this
> is
> a quote

Quotes can also be nested. To make this possible, nested quotes are padded by empty quote lines:

> Outer quote
>
> > Inner quote
>
> Outer quote

It’s unclear what is the correct way to handle nested quotes, but this format seems to be the most friendly for GitHub markdown. Future work may involve including the option to removing the padding.

Returns:

the quote formatted as a markdown string

Raw

class snakemd.Raw(text: str)

Bases: Block

Raw blocks allow a user to insert text into a Markdown document without any processing. Use this block to insert raw Markdown or other types of text (e.g., Jekyll frontmatter) into a document.

Parameters:

text (str) – the raw text to append to a Document

__repr__() str

The developer’s string method to help make sense of objects. For the purposes of this repo, the __repr__ method should create strings that can be used to recreate the element, much like the built-in feature of dataclasses (a feature which may be adopted in future versions of snakemd). Ultimately, this method must be implemented by all inheriting classes.

Returns:

an unambiguous representation of the element

__str__() str

Renders the raw block as a markdown string. Raw markdown is unprocessed and passes directly through to the document.

Returns:

the raw block as a markdown string

Table

class snakemd.Table(header: Iterable[str | Inline | Paragraph], body: Iterable[Iterable[str | Inline | Paragraph]] = None, align: None | Iterable[Align] = None, indent: int = 0)

Bases: Block

A table is a standalone block of rows and columns. Data is rendered according to underlying Inline items.

Raises:

ValueError

  • when rows of table are of varying lengths

  • when lengths of header and rows of table do not match

Parameters:
  • header (Iterable[str | Inline | Paragraph]) – the header row of labels

  • body (Iterable[Iterable[str | Inline | Paragraph]]) – the collection of rows of data; defaults to an empty list

  • align (None | Iterable[Align]) – the column alignment; defaults to None

  • indent (int) – indent size for the whole table; defaults to 0

class Align(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

Bases: Enum

Align is an enum only used by the Table class to specify the alignment of various columns in the table.

CENTER = 3
LEFT = 1
RIGHT = 2
__repr__() str

The developer’s string method to help make sense of objects. For the purposes of this repo, the __repr__ method should create strings that can be used to recreate the element, much like the built-in feature of dataclasses (a feature which may be adopted in future versions of snakemd). Ultimately, this method must be implemented by all inheriting classes.

Returns:

an unambiguous representation of the element

__str__() str

Renders the table as a markdown string. Table markdown follows the standard pipe syntax:

| Header 1 | Header 2 |
| -------- | -------- |
| Item 1A  | Item 2A  |
| Item 1B  | Item 2B  |

Alignment code adds colons in the appropriate locations. Final tables are rendered according to the widest items in each column for readability.

Returns:

a table as a markdown string

add_row(row: Iterable[str | Inline | Paragraph]) Table

A convenience method which adds a row to the end of table. Use this method to build a table row-by-row rather than constructing the table rows upfront.

>>> table = Table(
... ["Rank", "Player"],
... [["1st", "Crosby"], ["2nd", "McDavid"]]
... )
>>> table.add_row(["3rd", "Matthews"])
Table(header=[...], body=[...], align=None, indent=0)
>>> print(table)
| Rank | Player   |
| ---- | -------- |
| 1st  | Crosby   |
| 2nd  | McDavid  |
| 3rd  | Matthews |
Raises:

ValueError – when row is not the same width as the table header

Parameters:

row (Iterable[str | Inline | Paragraph]) – a row of data

Returns:

self

Inline Elements

One of the benefits of creating Block elements over using the Document methods is the control users now have over the underlying structure and style. Instead of being bound to the string inputs, users can provide Inline elements directly. For example, there is often a need to link Headings. This is not exactly possible through the Document methods as even the returned Heading object has no support for linking. However, with Inline elements, we can create a custom Heading to our standards:

>>> from snakemd import Heading, Inline, new_doc
>>> doc = new_doc()
>>> heading = doc.add_block(Heading(Inline("Hello, World!", "https://snakemd.io"), 2))

The remainder of this section introduces the Inline class.

Inline

class snakemd.Inline(text: str, image: None | str = None, link: None | str = None, bold: bool = False, italics: bool = False, strikethrough: bool = False, code: bool = False)

Bases: Element

The basic unit of text in markdown. All components which contain text are built using this class instead of strings directly. That way, those elements capture all styling information.

Inline element parameters are in order of precedence. In other words, image markdown is applied to the text first while code markdown is applied last. Due to this design, some forms of inline text are not possible. For example, inline elements can be used to show inline markdown as an inline code element (e.g., ![here](https://example.com)). However, inline elements cannot be used to style inline code (e.g., **`code`**). If styled code is necessary, it’s possible to render the inline element as a string and pass the result to another inline element.

Parameters:
  • text (str) – the inline text to render

  • image (None | str) –

    the source (either url or path) associated with an image

    • defaults to None

    • set to a string representing a URL or path to render an image (i.e., ![text](image))

  • link (None | str) –

    the link (either url or path) associated with the inline element

    • defaults to None

    • set to a string representing a URL or path to render a link (i.e., [text](link))

  • bold (bool) –

    the bold state of the inline text

    • defaults to False

    • set to True to render bold text (i.e., **text**)

  • italics (bool) –

    the italics state of the inline element

    • defaults to False

    • set to True to render text in italics (i.e., _text_)

  • strikethrough (bool) –

    the strikethrough state of the inline text

    • defaults to False

    • set to True to render text with a strikethrough (i.e., ~~text~~)

  • code (bool) –

    the code state of the inline text

    • defaults to False

    • set to True to render text as code (i.e., `text`)

__repr__() str

Renders self as an unambiguous string for development. In this case, it displays in the style of a dataclass, where instance variables are listed with their values.

Returns:

the Inline object as a development string

__str__() str

Renders self as a markdown ready string. In this case, inline can represent many different types of data from stylized text to code, links, and images.

>>> inline = Inline("This is formatted text", bold=True, italics=True)
>>> str(inline)
'_**This is formatted text**_'
Returns:

the Inline object as a markdown string

bold() Inline

Adds bold styling to self.

>>> inline = Inline("This is bold text").bold()
>>> print(inline)
**This is bold text**
Returns:

self

code() Inline

Adds code style to self.

>>> inline = Inline("x = 5").code()
>>> print(inline)
`x = 5`
Returns:

self

Retrieves the link attribute of the Inline element.

>>> inline = Inline("Here", link="https://snakemd.io")
>>> inline.get_link()
'https://snakemd.io'

New in version 2.2: Included to avoid protected member access scenarios.

Returns:

the link of the Inline element

get_text() str

Retrieves the text attribute of the Inline element.

>>> inline = Inline("This is text")
>>> inline.get_text()
'This is text'

New in version 2.2: Included to avoid protected member access scenarios.

Returns:

the text of the Inline element

Checks if the Inline object represents a link.

>>> inline = Inline("This is not a link")
>>> inline.is_link()
False
Returns:

True if the object has a link; False otherwise

is_text() bool

Checks if this Inline element is a text-only element. If not, it must be an image, a link, or a code snippet.

>>> inline = Inline("This is text")
>>> inline.is_text()
True
Returns:

True if this is a text-only element; False otherwise

italicize() Inline

Adds italics styling to self.

>>> inline = Inline("This is italicized text").italicize()
>>> print(inline)
_This is italicized text_
Returns:

self

Adds link to self.

>>> inline = Inline("here").link("https://snakemd.io")
>>> print(inline)
[here](https://snakemd.io)
Parameters:

link (str) – the URL or path to apply to this Inline element

Returns:

self

reset() Inline

Removes all settings from self (e.g., bold, code, italics, url, etc.). All that will remain is the text itself.

>>> inline = Inline(
... "This is normal text",
... link="https://snakemd.io",
... bold=True
... )
>>> inline.reset()
Inline(text='This is normal text',...)
>>> print(inline)
This is normal text
Returns:

self

strikethrough() Inline

Adds strikethrough styling to self.

>>> inline = Inline("This is striked text").strikethrough()
>>> print(inline)
~~This is striked text~~
Returns:

self

unbold() Inline

Removes bold styling from self.

>>> inline = Inline("This is normal text", bold=True).unbold()
>>> print(inline)
This is normal text
Returns:

self

uncode() Inline

Removes code styling from self.

>>> inline = Inline("This is normal text", code=True).uncode()
>>> print(inline)
This is normal text
Returns:

self

unitalicize() Inline

Removes italics styling from self.

>>> inline = Inline("This is normal text", italics=True).unitalicize()
>>> print(inline)
This is normal text
Returns:

self

Removes link from self.

>>> inline = Inline("This is normal text", link="https://snakemd.io")
>>> inline.unlink()
Inline(text='This is normal text',... link=None,...)
>>> print(inline)
This is normal text
Returns:

self

unstrikethrough() Inline

Remove strikethrough styling from self.

>>> inline = Inline("This is normal text", strikethrough=True)
>>> inline.unstrikethrough()
Inline(text='This is normal text',... strikethrough=False,...)
>>> print(inline)
This is normal text
Returns:

self