mdforge


Namemdforge JSON
Version 0.2.2 PyPI version JSON
download
home_pagehttps://github.com/mm21/mdforge
SummaryPythonic multi-flavor Markdown generator
upload_time2025-07-22 03:29:04
maintainerNone
docs_urlNone
authormm21
requires_python<4.0,>=3.12
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # MDForge
Pythonic multi-flavor Markdown generator

[![Python versions](https://img.shields.io/pypi/pyversions/mdforge.svg)](https://pypi.org/project/mdforge)
[![PyPI](https://img.shields.io/pypi/v/mdforge?color=%2334D058&label=pypi%20package)](https://pypi.org/project/mdforge)
[![Tests](./badges/tests.svg?dummy=8484744)]()
[![Coverage](./badges/cov.svg?dummy=8484744)]()
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

- [MDForge](#mdforge)
  - [Motivation](#motivation)
  - [Getting started](#getting-started)
  - [Working with documents](#working-with-documents)
  - [Pythonic Markdown elements](#pythonic-markdown-elements)
    - [Formatting](#formatting)
    - [Lists](#lists)
    - [Tables](#tables)
    - [Images](#images)
    - [Rendering standalone elements](#rendering-standalone-elements)
  - [Advanced features](#advanced-features)
    - [HTML attributes](#html-attributes)
    - [Pandoc extensions](#pandoc-extensions)

## Motivation

MDForge provides a Pythonic interface for generating Markdown documents programmatically. It allows you to construct a document tree and render it for various Markdown flavors, making it ideal for applications such as documentation generators and report builders. It emphasizes clean flavor-agnostic elements and type safety.

While MDForge supports the rich capabilities of [Pandoc's Markdown](https://pandoc.org/MANUAL.html#pandocs-markdown), it is designed to support generating documents with multiple flavors from the same document model. Other flavors currently planned are:

- [GitHub Flavored Markdown](https://github.github.com/gfm/)
- [MyST Markdown](https://mystmd.org/guide)

## Getting started

First, install using pip:

```bash
pip install mdforge
```

Create your first Markdown document:

```python
from mdforge import Document, Heading, Paragraph, Strong
from pathlib import Path

# create a document
doc = Document()

# add elements
doc += [
    Heading("My first MDForge document"),
    Paragraph("Hello, ", Strong("world"), "!"),
]

# render pandoc-flavored Markdown document to a file
doc.render_file("doc.md", flavor="pandoc")
```

This is rendered as:

```markdown
# My first MDForge document

Hello, **world**!

```

## Working with documents

The `Document` class is the fundamental interface to create a Markdown document. It implements a [Composite pattern](https://en.wikipedia.org/wiki/Composite_pattern) wherein elements are composed using the `+=` operator:

```python
from mdforge import BulletList, Document, Section

# create a document with frontmatter
doc = Document(frontmatter={"title": "My Document", "author": "Me"})

# create sections, automatically adding headings of the appropriate level
intro = Section("Introduction")
intro += "This is an introduction paragraph."

key_points = Section("Key Points")
key_points += BulletList(["Point 1", "Point 2", "Point 3"])

# add subsections for each point
key_points += [
    Section(
        "Point 1",
        elements=[
            "Elaboration on point 1:",
            BulletList(["Point 1-a", "Point 1-b"]),
        ],
    ),
    Section("Point 2", elements=["Elaboration on point 2."]),
    Section("Point 3", elements=["Elaboration on point 3."]),
]

# add sections to document
doc += [intro, key_points]

doc.render_file("doc.md", flavor="pandoc")
```

This is rendered as:

```markdown
---
title: My Document
author: Me
---

# Introduction

This is an introduction paragraph.

# Key Points

- Point 1
- Point 2
- Point 3
<!-- end of list -->

## Point 1

Elaboration on point 1:

- Point 1-a
- Point 1-b
<!-- end of list -->

## Point 2

Elaboration on point 2.

## Point 3

Elaboration on point 3.

```

Note the following observations:

- Sections can be nested in other sections, with optional headings
- Heading levels for sections are automatically set by their placement in the hierarchy
- Strings added to a document or section via `+=` are treated as raw text
- A comment is placed at the end of each list; this disambiguates the end of one list and the start of the next, in case there are no other elements in between

## Pythonic Markdown elements

MDForge provides a set of Markdown elements that can be composed to create rich documents. Not all common elements are implemented yet, e.g. code blocks. Nonetheless, you can insert raw text (which may be pre-formatted as Markdown) using `+=`.

We have already seen basic elements and container elements. The following examples illustrate the use of other common elements.

### Formatting

```python
from mdforge import (
    Document,
    Emph,
    Paragraph,
    Strikethrough,
    Strong,
    Underline,
)

doc = Document()

# basic formatting
doc += Strong("Bold text")
doc += Emph("Italicized text")
doc += Strikethrough("Strikethrough text")
doc += Underline("Underlined text")

# combined formatting
doc += Strong(Emph("Bold and italicized text"))

# mixed formatting in a paragraph, automatically putting spaces between elements
doc += Paragraph(
    "Normal text with",
    Strong("bold"),
    "and",
    Emph("italic"),
    "segments.",
    auto_space=True,
)

doc.render_file("doc.md", flavor="pandoc")
```

This is rendered as:

```markdown
**Bold text**

_Italicized text_

~~Strikethrough text~~

[Underlined text]{.underline}

**_Bold and italicized text_**

Normal text with **bold** and _italic_ segments.

```

Note the following observations:

- Here we pass `auto_space=True` in `Paragraph()`; this will automatically insert spaces between the inline elements passed to it
- As underline is not supported in CommonMark, pandoc implements its own [underline syntax](https://pandoc.org/MANUAL.html#underline)

### Lists

MDForge supports bullet lists, numbered lists, and definition lists:

```python
from mdforge import (
    BulletList,
    DefinitionItem,
    DefinitionList,
    Document,
    ListItem,
    NumberedList,
)

doc = Document()

# bullet list
doc += BulletList(["Item 1", "Item 2", "Item 3"])

# numbered list
doc += NumberedList(["First", "Second", "Third"])

# definition list
doc += DefinitionList(
    [
        DefinitionItem("Term A", "Definition A"),
        DefinitionItem("Term B", ["Definition B1", "Definition B2"]),
    ],
    compact=True,
)

# nested lists
doc += BulletList(
    [
        "Item 1",
        ListItem(
            "Item 2",
            [
                "Item 2-1",
                "Item 2-2",
            ],
        ),
    ]
)

# mixed nested lists
doc += BulletList(
    [
        "Item 1",
        ListItem(
            "Item 2",
            NumberedList(
                [
                    "Item 2-1",
                    "Item 2-2",
                ]
            ),
        ),
    ]
)

doc.render_file("doc.md", flavor="pandoc")
```

This is rendered as:

```markdown
- Item 1
- Item 2
- Item 3
<!-- end of list -->

1. First
1. Second
1. Third
<!-- end of list -->

Term A
:   Definition A

Term B
:   Definition B1
:   Definition B2

<!-- end of definition list -->

- Item 1
- Item 2
  - Item 2-1
  - Item 2-2
  <!-- end of list -->
<!-- end of list -->

- Item 1
- Item 2
  1. Item 2-1
  1. Item 2-2
  <!-- end of list -->
<!-- end of list -->

```

Note the following observations:

- Here we pass `compact=True` in `DefinitionList()`; this will generate [compact definition lists](https://pandoc.org/MANUAL.html#extension-compact_definition_lists) for pandoc flavor
- As mentioned above, a comment disambiguates the end of one list and the start of the next

### Tables

MDForge provides powerful table support with cell spanning, alignment, and formatting:

```python
from mdforge import BulletList, Cell, Document, Table

doc = Document()

# simple table
doc += Table(
    [
        ["Cell 1-1", "Cell 1-2"],
        ["Cell 2-1", "Cell 2-2"],
    ]
)

# table with alignment
doc += Table(
    [["Cell 1", "Cell 2", "Cell 3"]],
    align=["left", "center", "right"],
)

# table with header and footer (needs block=True)
doc += Table(
    [["Cell 1", "Cell 2"]],
    header=["Header 1", "Header 2"],
    footer=["Footer 1", "Footer 2"],
    block=True,
)

# table with row and column spanning (needs block=True)
doc += Table(
    [
        ["Cell 1-1", "Cell 1-2", "Cell 1-3"],
        ["Cell 2-1", "Cell 2-2", "Cell 2-3"],
    ],
    header=[
        [Cell("Column 1", rspan=2), Cell("Columns 2 & 3", cspan=2)],
        ["Column 2", "Column 3"],
    ],
    block=True,
)

# table with each cell wrapped in an explicit paragraph if it doesn't
# already contain block content (needs block=True)
doc += Table(
    [
        [
            BlockContainer(
                "This text is implicitly wrapped in a paragraph",
                BulletList(["Item 1", "Item 2"]),
            ),
            "Cell 2",
            "Cell 3",
        ]
    ],
    align=["left", "center", "right"],
    block=True,
    loose=True,
)

# table with character widths
doc += Table(
    [["Short text", "This is longer text that will be wrapped"]],
    widths=[15, 20],
)

# table with percentage widths
doc += Table(
    [["25% width", "75% width"]],
    widths_pct=[25, 75],
)

doc.render_file("doc.md", flavor="pandoc")
```

This is rendered as:

```markdown
<!-- table start -->

---------- ----------
Cell 1-1   Cell 1-2  

Cell 2-1   Cell 2-2  
---------------------

<!-- table end -->

<!-- table start: align=['left', 'center', 'right'] -->

-------- -------- --------
Cell 1    Cell 2    Cell 3

--------------------------

<!-- table end -->

<!-- table start: block=True -->

+----------+----------+
| Header 1 | Header 2 |
+==========+==========+
| Cell 1   | Cell 2   |
+==========+==========+
| Footer 1 | Footer 2 |
+==========+==========+

<!-- table end -->

<!-- table start: block=True -->

+----------+---------------------+
| Column 1 | Columns 2 & 3       |
|          +----------+----------+
|          | Column 2 | Column 3 |
+==========+==========+==========+
| Cell 1-1 | Cell 1-2 | Cell 1-3 |
+----------+----------+----------+
| Cell 2-1 | Cell 2-2 | Cell 2-3 |
+----------+----------+----------+

<!-- table end -->

<!-- table start: align=['left', 'center', 'right'], block=True, loose=True -->

+:-----------------------------------------------+:-------------:+--------------:+
| This text is implicitly wrapped in a paragraph | <p>Cell 2</p> | <p>Cell 3</p> |
|                                                |               |               |
| - Item 1                                       |               |               |
| - Item 2                                       |               |               |
| <!-- end of list -->                           |               |               |
+------------------------------------------------+---------------+---------------+

<!-- table end -->

<!-- table start: widths=[15, 20] -->

----------------- ----------------------
Short text        This is longer text   
                  that will be wrapped  

----------------------------------------

<!-- table end -->

<!-- table start: widths_pct=[25, 75] -->

----------- -----------------------------
25% width   75% width                    

-----------------------------------------

<!-- table end -->

```

Note the following observations:

- Merged cells and footers are only supported with `block=True`; this causes pandoc flavor to use a [grid table](https://pandoc.org/MANUAL.html#extension-grid_tables) instead of a [multiline table](https://pandoc.org/MANUAL.html#extension-multiline_tables)
- Pass `loose=True` to ensure all cells are wrapped in a paragraph, even if they don't get parsed as block content (requires `block=True`)
    - This ensures consistently-padded cells
- Use `BlockContainer` to wrap multiple block elements in a single element

### Images

MDForge supports both inline and block images:

```python
from mdforge import BlockImage, Document, InlineImage, Paragraph

doc = Document()

# inline image in a paragraph
doc += Paragraph(
    "Here is an inline image",
    InlineImage("./image.png", alt_text="Inline image"),
    "in a paragraph.",
    auto_space=True,
)

# block image with caption and alignment
doc += BlockImage("./image.png", caption="Block image", align="center")

doc.render_file("doc.md", flavor="pandoc")
```

This is rendered as:

```markdown
Here is an inline image ![Inline image](./image.png) in a paragraph.

![Block image](./image.png){fig-align="center"}

```

Note the following observations:

- Support for caption and alignment depends on flavor

### Rendering standalone elements

Any element class can be rendered by itself, even if not placed in a document. This is useful for generating document snippets which may be embedded in a document, e.g. via a templating engine.

```python
from mdforge import InlineContainer, Strong

element = InlineContainer("This is a ", Strong("test element"))

assert element.render(flavor="pandoc") == "This is a **test element**"
```

## Advanced features

### HTML attributes

For pandoc flavor, HTML attributes can be added to various elements:

```python
from mdforge import Attributes, Document, Heading, Ref, Span

doc = Document()

# heading with ID, classes, and custom attributes
my_heading = Heading(
    "Heading with attributes",
    attributes=Attributes(
        html_id="my-heading",
        css_classes=["class1", "class2"],
        attrs={"style": "color: blue;"},
    ),
)
doc += my_heading

# span with attributes
doc += Span(
    "Text with attributes",
    attributes=Attributes(html_id="my-span", css_classes="class1"),
)

# reference to heading by id
doc += Ref(my_heading, "See previous heading")

doc.render_file("doc.md", flavor="pandoc")
```

This is rendered as:

```markdown
# Heading with attributes {#my-heading .class1 .class2 style="color: blue;"}

[Text with attributes]{#my-span .class1}

[See previous heading](#my-heading)

```

### Pandoc extensions

As MDForge is designed with pandoc compatibility in mind, it automatically tracks required pandoc extensions:

```python
from mdforge import Attributes, Document, Heading, Strikethrough

doc = Document()

# requires "header_attributes"
doc += Heading("Heading 1", attributes=Attributes(html_id="heading-1"))

# requires "strikeout"
doc += Strikethrough("This text is struck through")

# get required pandoc extensions
extensions = doc.get_pandoc_extensions()
assert extensions == ["header_attributes", "strikeout"]
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/mm21/mdforge",
    "name": "mdforge",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.12",
    "maintainer_email": null,
    "keywords": null,
    "author": "mm21",
    "author_email": "mm21.dev@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/b0/86/9de17c9d7ddb522a96961cca4e071380d037fc59d2ceff91f36f6d9e69c9/mdforge-0.2.2.tar.gz",
    "platform": null,
    "description": "# MDForge\nPythonic multi-flavor Markdown generator\n\n[![Python versions](https://img.shields.io/pypi/pyversions/mdforge.svg)](https://pypi.org/project/mdforge)\n[![PyPI](https://img.shields.io/pypi/v/mdforge?color=%2334D058&label=pypi%20package)](https://pypi.org/project/mdforge)\n[![Tests](./badges/tests.svg?dummy=8484744)]()\n[![Coverage](./badges/cov.svg?dummy=8484744)]()\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n\n- [MDForge](#mdforge)\n  - [Motivation](#motivation)\n  - [Getting started](#getting-started)\n  - [Working with documents](#working-with-documents)\n  - [Pythonic Markdown elements](#pythonic-markdown-elements)\n    - [Formatting](#formatting)\n    - [Lists](#lists)\n    - [Tables](#tables)\n    - [Images](#images)\n    - [Rendering standalone elements](#rendering-standalone-elements)\n  - [Advanced features](#advanced-features)\n    - [HTML attributes](#html-attributes)\n    - [Pandoc extensions](#pandoc-extensions)\n\n## Motivation\n\nMDForge provides a Pythonic interface for generating Markdown documents programmatically. It allows you to construct a document tree and render it for various Markdown flavors, making it ideal for applications such as documentation generators and report builders. It emphasizes clean flavor-agnostic elements and type safety.\n\nWhile MDForge supports the rich capabilities of [Pandoc's Markdown](https://pandoc.org/MANUAL.html#pandocs-markdown), it is designed to support generating documents with multiple flavors from the same document model. Other flavors currently planned are:\n\n- [GitHub Flavored Markdown](https://github.github.com/gfm/)\n- [MyST Markdown](https://mystmd.org/guide)\n\n## Getting started\n\nFirst, install using pip:\n\n```bash\npip install mdforge\n```\n\nCreate your first Markdown document:\n\n```python\nfrom mdforge import Document, Heading, Paragraph, Strong\nfrom pathlib import Path\n\n# create a document\ndoc = Document()\n\n# add elements\ndoc += [\n    Heading(\"My first MDForge document\"),\n    Paragraph(\"Hello, \", Strong(\"world\"), \"!\"),\n]\n\n# render pandoc-flavored Markdown document to a file\ndoc.render_file(\"doc.md\", flavor=\"pandoc\")\n```\n\nThis is rendered as:\n\n```markdown\n# My first MDForge document\n\nHello, **world**!\n\n```\n\n## Working with documents\n\nThe `Document` class is the fundamental interface to create a Markdown document. It implements a [Composite pattern](https://en.wikipedia.org/wiki/Composite_pattern) wherein elements are composed using the `+=` operator:\n\n```python\nfrom mdforge import BulletList, Document, Section\n\n# create a document with frontmatter\ndoc = Document(frontmatter={\"title\": \"My Document\", \"author\": \"Me\"})\n\n# create sections, automatically adding headings of the appropriate level\nintro = Section(\"Introduction\")\nintro += \"This is an introduction paragraph.\"\n\nkey_points = Section(\"Key Points\")\nkey_points += BulletList([\"Point 1\", \"Point 2\", \"Point 3\"])\n\n# add subsections for each point\nkey_points += [\n    Section(\n        \"Point 1\",\n        elements=[\n            \"Elaboration on point 1:\",\n            BulletList([\"Point 1-a\", \"Point 1-b\"]),\n        ],\n    ),\n    Section(\"Point 2\", elements=[\"Elaboration on point 2.\"]),\n    Section(\"Point 3\", elements=[\"Elaboration on point 3.\"]),\n]\n\n# add sections to document\ndoc += [intro, key_points]\n\ndoc.render_file(\"doc.md\", flavor=\"pandoc\")\n```\n\nThis is rendered as:\n\n```markdown\n---\ntitle: My Document\nauthor: Me\n---\n\n# Introduction\n\nThis is an introduction paragraph.\n\n# Key Points\n\n- Point 1\n- Point 2\n- Point 3\n<!-- end of list -->\n\n## Point 1\n\nElaboration on point 1:\n\n- Point 1-a\n- Point 1-b\n<!-- end of list -->\n\n## Point 2\n\nElaboration on point 2.\n\n## Point 3\n\nElaboration on point 3.\n\n```\n\nNote the following observations:\n\n- Sections can be nested in other sections, with optional headings\n- Heading levels for sections are automatically set by their placement in the hierarchy\n- Strings added to a document or section via `+=` are treated as raw text\n- A comment is placed at the end of each list; this disambiguates the end of one list and the start of the next, in case there are no other elements in between\n\n## Pythonic Markdown elements\n\nMDForge provides a set of Markdown elements that can be composed to create rich documents. Not all common elements are implemented yet, e.g. code blocks. Nonetheless, you can insert raw text (which may be pre-formatted as Markdown) using `+=`.\n\nWe have already seen basic elements and container elements. The following examples illustrate the use of other common elements.\n\n### Formatting\n\n```python\nfrom mdforge import (\n    Document,\n    Emph,\n    Paragraph,\n    Strikethrough,\n    Strong,\n    Underline,\n)\n\ndoc = Document()\n\n# basic formatting\ndoc += Strong(\"Bold text\")\ndoc += Emph(\"Italicized text\")\ndoc += Strikethrough(\"Strikethrough text\")\ndoc += Underline(\"Underlined text\")\n\n# combined formatting\ndoc += Strong(Emph(\"Bold and italicized text\"))\n\n# mixed formatting in a paragraph, automatically putting spaces between elements\ndoc += Paragraph(\n    \"Normal text with\",\n    Strong(\"bold\"),\n    \"and\",\n    Emph(\"italic\"),\n    \"segments.\",\n    auto_space=True,\n)\n\ndoc.render_file(\"doc.md\", flavor=\"pandoc\")\n```\n\nThis is rendered as:\n\n```markdown\n**Bold text**\n\n_Italicized text_\n\n~~Strikethrough text~~\n\n[Underlined text]{.underline}\n\n**_Bold and italicized text_**\n\nNormal text with **bold** and _italic_ segments.\n\n```\n\nNote the following observations:\n\n- Here we pass `auto_space=True` in `Paragraph()`; this will automatically insert spaces between the inline elements passed to it\n- As underline is not supported in CommonMark, pandoc implements its own [underline syntax](https://pandoc.org/MANUAL.html#underline)\n\n### Lists\n\nMDForge supports bullet lists, numbered lists, and definition lists:\n\n```python\nfrom mdforge import (\n    BulletList,\n    DefinitionItem,\n    DefinitionList,\n    Document,\n    ListItem,\n    NumberedList,\n)\n\ndoc = Document()\n\n# bullet list\ndoc += BulletList([\"Item 1\", \"Item 2\", \"Item 3\"])\n\n# numbered list\ndoc += NumberedList([\"First\", \"Second\", \"Third\"])\n\n# definition list\ndoc += DefinitionList(\n    [\n        DefinitionItem(\"Term A\", \"Definition A\"),\n        DefinitionItem(\"Term B\", [\"Definition B1\", \"Definition B2\"]),\n    ],\n    compact=True,\n)\n\n# nested lists\ndoc += BulletList(\n    [\n        \"Item 1\",\n        ListItem(\n            \"Item 2\",\n            [\n                \"Item 2-1\",\n                \"Item 2-2\",\n            ],\n        ),\n    ]\n)\n\n# mixed nested lists\ndoc += BulletList(\n    [\n        \"Item 1\",\n        ListItem(\n            \"Item 2\",\n            NumberedList(\n                [\n                    \"Item 2-1\",\n                    \"Item 2-2\",\n                ]\n            ),\n        ),\n    ]\n)\n\ndoc.render_file(\"doc.md\", flavor=\"pandoc\")\n```\n\nThis is rendered as:\n\n```markdown\n- Item 1\n- Item 2\n- Item 3\n<!-- end of list -->\n\n1. First\n1. Second\n1. Third\n<!-- end of list -->\n\nTerm A\n:   Definition A\n\nTerm B\n:   Definition B1\n:   Definition B2\n\n<!-- end of definition list -->\n\n- Item 1\n- Item 2\n  - Item 2-1\n  - Item 2-2\n  <!-- end of list -->\n<!-- end of list -->\n\n- Item 1\n- Item 2\n  1. Item 2-1\n  1. Item 2-2\n  <!-- end of list -->\n<!-- end of list -->\n\n```\n\nNote the following observations:\n\n- Here we pass `compact=True` in `DefinitionList()`; this will generate [compact definition lists](https://pandoc.org/MANUAL.html#extension-compact_definition_lists) for pandoc flavor\n- As mentioned above, a comment disambiguates the end of one list and the start of the next\n\n### Tables\n\nMDForge provides powerful table support with cell spanning, alignment, and formatting:\n\n```python\nfrom mdforge import BulletList, Cell, Document, Table\n\ndoc = Document()\n\n# simple table\ndoc += Table(\n    [\n        [\"Cell 1-1\", \"Cell 1-2\"],\n        [\"Cell 2-1\", \"Cell 2-2\"],\n    ]\n)\n\n# table with alignment\ndoc += Table(\n    [[\"Cell 1\", \"Cell 2\", \"Cell 3\"]],\n    align=[\"left\", \"center\", \"right\"],\n)\n\n# table with header and footer (needs block=True)\ndoc += Table(\n    [[\"Cell 1\", \"Cell 2\"]],\n    header=[\"Header 1\", \"Header 2\"],\n    footer=[\"Footer 1\", \"Footer 2\"],\n    block=True,\n)\n\n# table with row and column spanning (needs block=True)\ndoc += Table(\n    [\n        [\"Cell 1-1\", \"Cell 1-2\", \"Cell 1-3\"],\n        [\"Cell 2-1\", \"Cell 2-2\", \"Cell 2-3\"],\n    ],\n    header=[\n        [Cell(\"Column 1\", rspan=2), Cell(\"Columns 2 & 3\", cspan=2)],\n        [\"Column 2\", \"Column 3\"],\n    ],\n    block=True,\n)\n\n# table with each cell wrapped in an explicit paragraph if it doesn't\n# already contain block content (needs block=True)\ndoc += Table(\n    [\n        [\n            BlockContainer(\n                \"This text is implicitly wrapped in a paragraph\",\n                BulletList([\"Item 1\", \"Item 2\"]),\n            ),\n            \"Cell 2\",\n            \"Cell 3\",\n        ]\n    ],\n    align=[\"left\", \"center\", \"right\"],\n    block=True,\n    loose=True,\n)\n\n# table with character widths\ndoc += Table(\n    [[\"Short text\", \"This is longer text that will be wrapped\"]],\n    widths=[15, 20],\n)\n\n# table with percentage widths\ndoc += Table(\n    [[\"25% width\", \"75% width\"]],\n    widths_pct=[25, 75],\n)\n\ndoc.render_file(\"doc.md\", flavor=\"pandoc\")\n```\n\nThis is rendered as:\n\n```markdown\n<!-- table start -->\n\n---------- ----------\nCell 1-1   Cell 1-2  \n\nCell 2-1   Cell 2-2  \n---------------------\n\n<!-- table end -->\n\n<!-- table start: align=['left', 'center', 'right'] -->\n\n-------- -------- --------\nCell 1    Cell 2    Cell 3\n\n--------------------------\n\n<!-- table end -->\n\n<!-- table start: block=True -->\n\n+----------+----------+\n| Header 1 | Header 2 |\n+==========+==========+\n| Cell 1   | Cell 2   |\n+==========+==========+\n| Footer 1 | Footer 2 |\n+==========+==========+\n\n<!-- table end -->\n\n<!-- table start: block=True -->\n\n+----------+---------------------+\n| Column 1 | Columns 2 & 3       |\n|          +----------+----------+\n|          | Column 2 | Column 3 |\n+==========+==========+==========+\n| Cell 1-1 | Cell 1-2 | Cell 1-3 |\n+----------+----------+----------+\n| Cell 2-1 | Cell 2-2 | Cell 2-3 |\n+----------+----------+----------+\n\n<!-- table end -->\n\n<!-- table start: align=['left', 'center', 'right'], block=True, loose=True -->\n\n+:-----------------------------------------------+:-------------:+--------------:+\n| This text is implicitly wrapped in a paragraph | <p>Cell 2</p> | <p>Cell 3</p> |\n|                                                |               |               |\n| - Item 1                                       |               |               |\n| - Item 2                                       |               |               |\n| <!-- end of list -->                           |               |               |\n+------------------------------------------------+---------------+---------------+\n\n<!-- table end -->\n\n<!-- table start: widths=[15, 20] -->\n\n----------------- ----------------------\nShort text        This is longer text   \n                  that will be wrapped  \n\n----------------------------------------\n\n<!-- table end -->\n\n<!-- table start: widths_pct=[25, 75] -->\n\n----------- -----------------------------\n25% width   75% width                    \n\n-----------------------------------------\n\n<!-- table end -->\n\n```\n\nNote the following observations:\n\n- Merged cells and footers are only supported with `block=True`; this causes pandoc flavor to use a [grid table](https://pandoc.org/MANUAL.html#extension-grid_tables) instead of a [multiline table](https://pandoc.org/MANUAL.html#extension-multiline_tables)\n- Pass `loose=True` to ensure all cells are wrapped in a paragraph, even if they don't get parsed as block content (requires `block=True`)\n    - This ensures consistently-padded cells\n- Use `BlockContainer` to wrap multiple block elements in a single element\n\n### Images\n\nMDForge supports both inline and block images:\n\n```python\nfrom mdforge import BlockImage, Document, InlineImage, Paragraph\n\ndoc = Document()\n\n# inline image in a paragraph\ndoc += Paragraph(\n    \"Here is an inline image\",\n    InlineImage(\"./image.png\", alt_text=\"Inline image\"),\n    \"in a paragraph.\",\n    auto_space=True,\n)\n\n# block image with caption and alignment\ndoc += BlockImage(\"./image.png\", caption=\"Block image\", align=\"center\")\n\ndoc.render_file(\"doc.md\", flavor=\"pandoc\")\n```\n\nThis is rendered as:\n\n```markdown\nHere is an inline image ![Inline image](./image.png) in a paragraph.\n\n![Block image](./image.png){fig-align=\"center\"}\n\n```\n\nNote the following observations:\n\n- Support for caption and alignment depends on flavor\n\n### Rendering standalone elements\n\nAny element class can be rendered by itself, even if not placed in a document. This is useful for generating document snippets which may be embedded in a document, e.g. via a templating engine.\n\n```python\nfrom mdforge import InlineContainer, Strong\n\nelement = InlineContainer(\"This is a \", Strong(\"test element\"))\n\nassert element.render(flavor=\"pandoc\") == \"This is a **test element**\"\n```\n\n## Advanced features\n\n### HTML attributes\n\nFor pandoc flavor, HTML attributes can be added to various elements:\n\n```python\nfrom mdforge import Attributes, Document, Heading, Ref, Span\n\ndoc = Document()\n\n# heading with ID, classes, and custom attributes\nmy_heading = Heading(\n    \"Heading with attributes\",\n    attributes=Attributes(\n        html_id=\"my-heading\",\n        css_classes=[\"class1\", \"class2\"],\n        attrs={\"style\": \"color: blue;\"},\n    ),\n)\ndoc += my_heading\n\n# span with attributes\ndoc += Span(\n    \"Text with attributes\",\n    attributes=Attributes(html_id=\"my-span\", css_classes=\"class1\"),\n)\n\n# reference to heading by id\ndoc += Ref(my_heading, \"See previous heading\")\n\ndoc.render_file(\"doc.md\", flavor=\"pandoc\")\n```\n\nThis is rendered as:\n\n```markdown\n# Heading with attributes {#my-heading .class1 .class2 style=\"color: blue;\"}\n\n[Text with attributes]{#my-span .class1}\n\n[See previous heading](#my-heading)\n\n```\n\n### Pandoc extensions\n\nAs MDForge is designed with pandoc compatibility in mind, it automatically tracks required pandoc extensions:\n\n```python\nfrom mdforge import Attributes, Document, Heading, Strikethrough\n\ndoc = Document()\n\n# requires \"header_attributes\"\ndoc += Heading(\"Heading 1\", attributes=Attributes(html_id=\"heading-1\"))\n\n# requires \"strikeout\"\ndoc += Strikethrough(\"This text is struck through\")\n\n# get required pandoc extensions\nextensions = doc.get_pandoc_extensions()\nassert extensions == [\"header_attributes\", \"strikeout\"]\n```\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Pythonic multi-flavor Markdown generator",
    "version": "0.2.2",
    "project_urls": {
        "Homepage": "https://github.com/mm21/mdforge"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d6559f73d27a7d92e3b0443343192bf8eddfafc34d3a672a3cfac8dbceea7ec9",
                "md5": "6498f1b02768e7e8ecef2df38a8e41e9",
                "sha256": "447bdddc5939d8706928710254a9d3b852191f5478c7c8942acafccfe2e0b9c2"
            },
            "downloads": -1,
            "filename": "mdforge-0.2.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "6498f1b02768e7e8ecef2df38a8e41e9",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.12",
            "size": 42263,
            "upload_time": "2025-07-22T03:29:03",
            "upload_time_iso_8601": "2025-07-22T03:29:03.516928Z",
            "url": "https://files.pythonhosted.org/packages/d6/55/9f73d27a7d92e3b0443343192bf8eddfafc34d3a672a3cfac8dbceea7ec9/mdforge-0.2.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b0869de17c9d7ddb522a96961cca4e071380d037fc59d2ceff91f36f6d9e69c9",
                "md5": "b42a5e5375b8fad4a4b1f77b8108bfae",
                "sha256": "1cd6c701662d379c458d42826c37852c3828e4ddaa558e2314f07f401799f131"
            },
            "downloads": -1,
            "filename": "mdforge-0.2.2.tar.gz",
            "has_sig": false,
            "md5_digest": "b42a5e5375b8fad4a4b1f77b8108bfae",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.12",
            "size": 33066,
            "upload_time": "2025-07-22T03:29:04",
            "upload_time_iso_8601": "2025-07-22T03:29:04.559482Z",
            "url": "https://files.pythonhosted.org/packages/b0/86/9de17c9d7ddb522a96961cca4e071380d037fc59d2ceff91f36f6d9e69c9/mdforge-0.2.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-22 03:29:04",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "mm21",
    "github_project": "mdforge",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "tox": true,
    "lcname": "mdforge"
}
        
Elapsed time: 3.55201s