Skip to main content

Lightweight HTML markup language — simplifies HTML authoring with shorthand syntax

Project description

LHTML — Lightweight HTML

Tests PyPI

LHTML is a markup language that simplifies HTML authoring with embedded CSS styling. It is designed to be HTML-first: raw HTML passes through untouched, and only a few shorthand symbols (::, *, =, **, __) trigger conversions.

LHTML is used to build static websites and presentation slides, typically combined with Jinja2 templates.

Installation

From PyPI:

pip install lhtml-markup

Or from source:

git clone https://github.com/drohmer/lhtml.git
cd lhtml
pip install .

For development:

pip install -e ".[dev]"

Dependencies (lark, pygments, pyyaml) are installed automatically.

Quick Start

Command Line

lhtml input.l.html                    # Convert to stdout
lhtml input.l.html -o output.html     # Convert to file
lhtml input.l.html -w                 # Wrap in full HTML document
python -m lhtml input.l.html          # Alternative invocation

Python API

import lhtml

html = lhtml.run('= Hello World\nSome **bold** text.\n')

html = lhtml.run(text, {
    'wrap-auto': True,
    'title': 'My Page',
    'css': ['style.css'],
    'js': ['script.js'],
})

Syntax Reference

Headings

= Main Title
== Subtitle
=== Level 3

Output:

<h1>Main Title</h1>
<h2>Subtitle</h2>
<h3>Level 3</h3>

With classes/IDs:

=(.highlight #intro) Styled Title
<h1 class="highlight" id="intro">Styled Title</h1>

Lists

* First item
* Second item
** Nested item A
** Nested item B
*** Deep nested
* Back to top level

Produces nested <ul><li> structures.

Inline Formatting

This is **bold** text.
This is __italic__ text.
This is `inline code` text.

Output:

This is <strong>bold</strong> text.
This is <em>italic</em> text.
This is <code class="code-inline">inline code</code> text.

Tag Elements (the :: system)

The core of LHTML. The general syntax is:

tagName::(.classes #id)[cssStyle]{htmlAttributes} content ::

All bracket groups are optional. If tagName is omitted, defaults to div.

Div / Span with Styles

div::[color:red; font-size:120%;]
This text is big and red.
::

span::(.highlight)[font-weight:bold;] inline content ::

Output:

<div style="color:red; font-size:120%;">
This text is big and red.
</div>

<span class="highlight" style="font-weight:bold;"> inline content </span>

Anonymous Div (no tag name)

::[padding:10px; background:#eee;]
Content in a styled div.
::

Output:

<div style="padding:10px; background:#eee;">
Content in a styled div.
</div>

Classes, IDs, and Inline Attributes

::(.classA .classB #myId)[margin:10px;]{data-role="main"}
Content
::

Output:

<div class="classA classB" id="myId" style="margin:10px;" data-role="main">
Content
</div>

Self-Closing (inline)

End the content with :: on the same line:

div::[color:blue;] short text ::

Output:

<div style="color:blue;"> short text </div>

Links

link::https://example.com[Click here]
link::page.html(.nav)[Back to home]

Output:

<a href="https://example.com">Click here</a>
<a class="nav" href="page.html">Back to home</a>

Images

img::photo.jpg[width:400px;]

Output:

<img style="width:400px;" src="photo.jpg" alt="photo.jpg">

Videos

video::assets/clip.mp4[width:600px;]
videoplay::assets/clip.mp4[width:600px;]

videoplay adds autoplay loop muted attributes. The parser automatically detects transcoded codec variants (-vp9.webm, -h265.mp4, -h264.mp4) and poster images (-poster.jpg).

Code Blocks

code::[python]
def hello():
    print("Hello, world!")
code::[-]

Syntax highlighting is powered by Pygments. Any language supported by Pygments can be used.

Spacer

::nl

Output:

<div style="height:1em;"></div>

Verbatim (raw passthrough)

Content inside verbatim blocks is preserved exactly as-is, with no LHTML processing:

verbatim::[]
This = is not a title
**not bold** __not italic__
div::[not a tag]
verbatim::[-]

Comments

Some text ::# This comment will be removed

File Inclusion

include::header.html
include::components/nav.html

Included files are recursively processed (up to 20 levels).

YAML Front Matter

---
title: "My Page"
css: ["style.css", "theme.css"]
js: "app.js"
wrap-auto: true
---

= Page content starts here

Supported metadata keys:

Key Type Description
title string Page title (used in HTML wrapper)
css string or list CSS files to include
js string or list JavaScript files to include
wrap-auto boolean Wrap output in full HTML document
directory_include list Directories to search for includes

Plugin System

Custom Tag Handlers

Register handlers for new :: tag types:

from lhtml.pipeline import tag_registry

def handle_alert(element, tag_to_close, current_directory):
    style = element.get('[]', '')
    text = element.get('text', '')
    return f'<div class="alert" style="{style}">{text}</div>', True

tag_registry.register('alert', handle_alert)

Then use in LHTML:

alert::[background:yellow; padding:10px;] Warning message ::

Custom Code Lexers

Register custom Pygments lexers for syntax highlighting:

from lhtml.pipeline import lexer_registry
from pygments.lexers import PythonLexer

lexer_registry.register('mypython', PythonLexer)

Custom Pipeline

Create an isolated pipeline with its own tag registry:

from lhtml.pipeline import ProcessingPipeline, TagRegistry

registry = TagRegistry()
registry.register('note', my_note_handler)

pipeline = ProcessingPipeline(registry=registry)
html = pipeline.run(text, {'wrap-auto': True})

Configuration Reference

All keys for the meta dict passed to lhtml.run():

{
    'wrap-auto': False,        # Wrap in HTML document
    'title': 'Webpage',        # Document title
    'css': [],                 # CSS files (string or list)
    'js': [],                  # JS files (string or list)
    'directory_include': [],   # Search paths for include::
    'current_directory': '',   # Base directory for video codec detection
}

Design Principles

  • HTML-first: Raw HTML is never modified. Only LHTML syntax triggers conversions.
  • Island grammar: LHTML syntax "islands" float in a sea of opaque content (HTML, Jinja2 templates, LaTeX, etc.) that passes through untouched.
  • Minimal: A few symbols (::, =, *, **, __, `) cover most needs. No complex configuration required.
  • Composable: LHTML works seamlessly with Jinja2 templates, making it suitable for static site generators.

Project Structure

src/lhtml/
  __init__.py          # Public API: run(), analyse_tag(), read_yaml()
  cli.py               # Command-line interface
  pipeline.py          # ProcessingPipeline, TagRegistry, LexerRegistry
  process.py           # Core transformation functions
  patterns.py          # Centralized regex patterns and utilities
  tag_parser.py        # Lark-based parser for :: bracket syntax
  tag_element.lark     # Lark grammar definition
  export_html.py       # HTML generation for tag elements
  listing.py           # List processing
  code.py              # Code syntax highlighting (Pygments)
  wrap_html.py         # HTML document wrapping
  ast_nodes.py         # AST node dataclasses
  errors.py            # Structured error types

License

MIT

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

lhtml_markup-2.1.0.tar.gz (24.5 kB view details)

Uploaded Source

Built Distribution

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

lhtml_markup-2.1.0-py3-none-any.whl (24.2 kB view details)

Uploaded Python 3

File details

Details for the file lhtml_markup-2.1.0.tar.gz.

File metadata

  • Download URL: lhtml_markup-2.1.0.tar.gz
  • Upload date:
  • Size: 24.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for lhtml_markup-2.1.0.tar.gz
Algorithm Hash digest
SHA256 ffc7e1c0bdc74f641e5ccfc53702d82b3f6d4e074fa46bc8bc1cfcc9b52b2e39
MD5 cee7cf700493692db2f5c5abe36afc97
BLAKE2b-256 ad50c3da9f5f224e5a544027ab74b3f3bc60ea57863fc044a8e97b1cb8c906b0

See more details on using hashes here.

File details

Details for the file lhtml_markup-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: lhtml_markup-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 24.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for lhtml_markup-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3fb4c8943d9d0c0344a4467faedd9c6722e6a1764394a76c23d292ccf74c872f
MD5 d2d229277caf0d1d4527af5e5da2e759
BLAKE2b-256 95940978c4193d06bd7879d70a64fbf52542f5976cf5207c6b8f6a7beef16746

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