Lightweight HTML markup language — simplifies HTML authoring with shorthand syntax
Project description
LHTML — Lightweight HTML
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
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ffc7e1c0bdc74f641e5ccfc53702d82b3f6d4e074fa46bc8bc1cfcc9b52b2e39
|
|
| MD5 |
cee7cf700493692db2f5c5abe36afc97
|
|
| BLAKE2b-256 |
ad50c3da9f5f224e5a544027ab74b3f3bc60ea57863fc044a8e97b1cb8c906b0
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3fb4c8943d9d0c0344a4467faedd9c6722e6a1764394a76c23d292ccf74c872f
|
|
| MD5 |
d2d229277caf0d1d4527af5e5da2e759
|
|
| BLAKE2b-256 |
95940978c4193d06bd7879d70a64fbf52542f5976cf5207c6b8f6a7beef16746
|