Skip to main content

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

pip install .

Or in development mode:

pip install -e .

Dependencies: lark, pygments, pyyaml (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.0.0.tar.gz (23.6 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.0.0-py3-none-any.whl (23.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: lhtml_markup-2.0.0.tar.gz
  • Upload date:
  • Size: 23.6 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.0.0.tar.gz
Algorithm Hash digest
SHA256 5142b8d7806a261a58fae33d36d8438e7060a2afc7534d6a0db5953f49057aa2
MD5 ff9d0bfbdf43c5c424626c5c1e128e9c
BLAKE2b-256 dc9b459ea66fbf6931b996c396d146145d33c083a85b781ce49edf483f96eef1

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lhtml_markup-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 23.5 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.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a9c8a6b9bd8df197bfd4454a393e1a843ba9a0c217c1803cb991562f6e7f1053
MD5 be28e1665feda61c50e2fa95b6f91c79
BLAKE2b-256 571fde43dd2bf59ae99a322e1d391afa42146cb10663966dcf5e7c470b9f250d

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