lhtml-markup 2.3.0__tar.gz → 2.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/PKG-INFO +155 -16
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/README.md +153 -14
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/pyproject.toml +5 -2
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/__init__.py +3 -1
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/cli.py +8 -1
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/export_html.py +12 -3
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/patterns.py +23 -7
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/pipeline.py +4 -7
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/process.py +99 -36
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/PKG-INFO +155 -16
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/requires.txt +0 -1
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/test/test_lhtml.py +234 -1
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/LICENSE.md +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/setup.cfg +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/__main__.py +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/code.py +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/element_extract.py +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/errors.py +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/insert_in_text.py +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/listing.py +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/tag_element.lark +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/tag_parser.py +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml/wrap_html.py +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/SOURCES.txt +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/dependency_links.txt +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/entry_points.txt +0 -0
- {lhtml_markup-2.3.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/top_level.txt +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lhtml-markup
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.1
|
|
4
4
|
Summary: Lightweight HTML markup language — simplifies HTML authoring with shorthand syntax
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/drohmer/lhtml
|
|
7
7
|
Project-URL: Repository, https://github.com/drohmer/lhtml
|
|
8
8
|
Project-URL: Issues, https://github.com/drohmer/lhtml/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/drohmer/lhtml/blob/main/CHANGELOG.md
|
|
9
10
|
Requires-Python: >=3.10
|
|
10
11
|
Description-Content-Type: text/markdown
|
|
11
12
|
License-File: LICENSE.md
|
|
@@ -14,18 +15,19 @@ Requires-Dist: pygments>=2.15
|
|
|
14
15
|
Requires-Dist: pyyaml>=6.0
|
|
15
16
|
Provides-Extra: dev
|
|
16
17
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
17
|
-
Requires-Dist: ansicolors; extra == "dev"
|
|
18
18
|
Dynamic: license-file
|
|
19
19
|
|
|
20
20
|
# LHTML — Lightweight HTML
|
|
21
21
|
|
|
22
|
-
[](https://github.com/drohmer/lhtml/actions/workflows/tests.yml)
|
|
23
23
|
[](https://pypi.org/project/lhtml-markup/)
|
|
24
24
|
|
|
25
25
|
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.
|
|
26
26
|
|
|
27
27
|
LHTML is used to build static websites and presentation slides, typically combined with Jinja2 templates.
|
|
28
28
|
|
|
29
|
+
See the [examples](examples/) and the [changelog](CHANGELOG.md).
|
|
30
|
+
|
|
29
31
|
## Installation
|
|
30
32
|
|
|
31
33
|
From PyPI:
|
|
@@ -58,14 +60,44 @@ Dependencies (`lark`, `pygments`, `pyyaml`) are installed automatically.
|
|
|
58
60
|
```bash
|
|
59
61
|
lhtml input.l.html # Convert to stdout
|
|
60
62
|
lhtml input.l.html -o output.html # Convert to file
|
|
61
|
-
lhtml input.l.html -w # Wrap in full HTML document
|
|
62
|
-
lhtml input.l.html -b # Render source line breaks as <br>
|
|
63
|
+
lhtml input.l.html -w # Wrap in full HTML document (--wrapAuto)
|
|
64
|
+
lhtml input.l.html -b # Render source line breaks as <br> (--line-breaks)
|
|
63
65
|
lhtml a.l.html b.l.html # Several files: a.html, b.html next to sources
|
|
64
66
|
lhtml a.l.html b.l.html -o build/ # Several files into a directory
|
|
65
67
|
python -m lhtml input.l.html # Alternative invocation
|
|
68
|
+
lhtml --version # Show the version (also lhtml.__version__)
|
|
66
69
|
```
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
Without `-o`, `page.l.html` is written to `page.html` next to its source.
|
|
72
|
+
|
|
73
|
+
- Files are read and written as UTF-8 (a BOM is accepted).
|
|
74
|
+
- Errors and warnings are reported on stderr with the file name. The remaining files are still processed, and the exit code is non-zero if any file failed.
|
|
75
|
+
- An input file is never overwritten: `lhtml page.html` (output `page.html`) is an error, as is an output that is another input of the batch, or a symbolic or hard link to one.
|
|
76
|
+
- Includes are looked up in the input file's directory first, then in the current directory.
|
|
77
|
+
|
|
78
|
+
With `-w`, the result is wrapped in a minimal HTML document using the `title`, `css` and `js` of the front matter:
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<!DOCTYPE html>
|
|
82
|
+
|
|
83
|
+
<html lang="en">
|
|
84
|
+
|
|
85
|
+
<head>
|
|
86
|
+
<meta charset="utf-8">
|
|
87
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
88
|
+
<title>My Page</title>
|
|
89
|
+
<link rel="stylesheet" type="text/css" href="style.css">
|
|
90
|
+
<script src="app.js" defer></script>
|
|
91
|
+
</head>
|
|
92
|
+
|
|
93
|
+
<body>
|
|
94
|
+
<h1>Hello</h1>
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
</body>
|
|
98
|
+
|
|
99
|
+
</html>
|
|
100
|
+
```
|
|
69
101
|
|
|
70
102
|
### Python API
|
|
71
103
|
|
|
@@ -114,13 +146,33 @@ With classes/IDs:
|
|
|
114
146
|
```
|
|
115
147
|
* First item
|
|
116
148
|
* Second item
|
|
117
|
-
** Nested item
|
|
118
|
-
** Nested item B
|
|
119
|
-
*** Deep nested
|
|
149
|
+
** Nested item
|
|
120
150
|
* Back to top level
|
|
121
151
|
```
|
|
122
152
|
|
|
123
|
-
|
|
153
|
+
Output (a nested list is placed in its own `<li>`):
|
|
154
|
+
```html
|
|
155
|
+
<ul>
|
|
156
|
+
<li>
|
|
157
|
+
First item
|
|
158
|
+
</li>
|
|
159
|
+
<li>
|
|
160
|
+
Second item
|
|
161
|
+
</li>
|
|
162
|
+
<li>
|
|
163
|
+
<ul>
|
|
164
|
+
<li>
|
|
165
|
+
Nested item
|
|
166
|
+
</li>
|
|
167
|
+
</ul>
|
|
168
|
+
</li>
|
|
169
|
+
<li>
|
|
170
|
+
Back to top level
|
|
171
|
+
</li>
|
|
172
|
+
</ul>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Each `*` adds one level (`***` is level 3). A list ends at the first line that is not an item.
|
|
124
176
|
|
|
125
177
|
|
|
126
178
|
### Inline Formatting
|
|
@@ -155,7 +207,7 @@ Tag names start with a letter and may contain letters, digits, `_` and `-` (usef
|
|
|
155
207
|
|
|
156
208
|
A closing `::` may be directly followed by punctuation or HTML (`**span::[c] x ::**`, `important ::,`). In a run of colons, `::` markers are the pairs ending the run: `::::[...]` is a closing `::` followed by `::[...]`, and `x:::nl` is `x:` followed by `::nl`. A `::` between two HTML tags (as in pre-highlighted code `<span>::</span>`) is left untouched.
|
|
157
209
|
|
|
158
|
-
A tag that is opened but never closed, or a `::` without matching opening tag, produces an `LHTMLWarning` quoting the beginning of the tag.
|
|
210
|
+
A tag that is opened but never closed, or a `::` without matching opening tag, produces an `LHTMLWarning` quoting the beginning of the tag. An unmatched `::` is kept as text in the output.
|
|
159
211
|
|
|
160
212
|
#### Div / Span with Styles
|
|
161
213
|
|
|
@@ -219,10 +271,29 @@ Output:
|
|
|
219
271
|
<div style="color:blue;"> short text </div>
|
|
220
272
|
```
|
|
221
273
|
|
|
274
|
+
#### Explicit Closing Tags
|
|
275
|
+
|
|
276
|
+
A bare `::` closes the last opened tag. To make long or nested blocks easier to read, name the tag you close with `::name[-]` (`::[-]` closes the last one, like `::`):
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
div::[color:red;]
|
|
280
|
+
Red **text**
|
|
281
|
+
::div[-]
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Output:
|
|
285
|
+
```html
|
|
286
|
+
<div style="color:red;">
|
|
287
|
+
Red <strong>text</strong>
|
|
288
|
+
</div>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
If the name does not match the last opened tag, a `LHTMLWarning` is emitted and the last opened tag is closed.
|
|
292
|
+
|
|
222
293
|
|
|
223
294
|
### Links
|
|
224
295
|
|
|
225
|
-
The URL of `link::`, `img::`, `video::` and `videoplay::` is never modified (`__`, `**`, `$` are kept). Parentheses that are part of the URL are kept (`Mercury_(planet)`, `fig(1).png`), while a group starting with `.` or `#` is a class/id group.
|
|
296
|
+
The URL of `link::`, `img::`, `video::` and `videoplay::` is never modified (`__`, `**`, `$` are kept). Parentheses that are part of the URL are kept (`Mercury_(planet)`, `fig(1).png`), while a group starting with `.` or `#` is a class/id group. The URL may contain Jinja expressions (`img::{{ base }}/photo.jpg`, `link::{{ url_for('page') }}[Home]`), which are kept unchanged.
|
|
226
297
|
|
|
227
298
|
```
|
|
228
299
|
link::https://example.com[Click here]
|
|
@@ -267,7 +338,22 @@ def hello():
|
|
|
267
338
|
code::[-]
|
|
268
339
|
````
|
|
269
340
|
|
|
270
|
-
|
|
341
|
+
Output (Pygments HTML inside `<div class="code">`):
|
|
342
|
+
```html
|
|
343
|
+
<div class="code"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
|
|
344
|
+
...
|
|
345
|
+
</pre></div>
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Syntax highlighting is powered by Pygments: any language supported by Pygments can be used, case-insensitively. An empty language (`code::[]`) renders plain text; an unknown language renders plain text with a warning. The built-in `c++` language is C++ with the types of the [CGP library](https://github.com/drohmer/cgp) highlighted; other languages can be added (see [Custom Code Lexers](#custom-code-lexers)).
|
|
349
|
+
|
|
350
|
+
The colors come from a Pygments stylesheet, which you must include in your page. Generate one for the `.code` class (any [Pygments style](https://pygments.org/styles/) can replace `default`):
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
pygmentize -S default -f html -a .code > code.css
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`include::file` directives inside a code block insert the file as raw code.
|
|
271
357
|
|
|
272
358
|
|
|
273
359
|
### Spacer
|
|
@@ -355,6 +441,50 @@ Supported metadata keys:
|
|
|
355
441
|
| `directory_include` | list | Directories to search for includes |
|
|
356
442
|
|
|
357
443
|
|
|
444
|
+
## Using LHTML with Jinja2
|
|
445
|
+
|
|
446
|
+
LHTML leaves Jinja2 untouched, so a template can be written in LHTML: convert it to HTML first, then render it with Jinja2 (`pip install jinja2`).
|
|
447
|
+
|
|
448
|
+
`blog.l.html`:
|
|
449
|
+
```
|
|
450
|
+
= {{ page.title }}
|
|
451
|
+
|
|
452
|
+
{% for post in posts %}
|
|
453
|
+
div::(.post)
|
|
454
|
+
== link::{{ post.url }}[{{ post.title }}]
|
|
455
|
+
{{ post.summary }} **Read more**
|
|
456
|
+
::
|
|
457
|
+
{% endfor %}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
import jinja2
|
|
462
|
+
import lhtml
|
|
463
|
+
|
|
464
|
+
with open('blog.l.html', encoding='utf-8') as f:
|
|
465
|
+
template = jinja2.Template(lhtml.run(f.read()))
|
|
466
|
+
|
|
467
|
+
html = template.render(page={'title': 'Blog'},
|
|
468
|
+
posts=[{'url': 'first.html', 'title': 'First post', 'summary': 'Hello.'}])
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
`lhtml.run()` produces the Jinja2 template:
|
|
472
|
+
```html
|
|
473
|
+
<h1>{{ page.title }}</h1>
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
{% for post in posts %}
|
|
477
|
+
<div class="post">
|
|
478
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
479
|
+
|
|
480
|
+
{{ post.summary }} <strong>Read more</strong>
|
|
481
|
+
</div>
|
|
482
|
+
{% endfor %}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Jinja2 expressions can also be used in URLs (`img::{{ base }}/photo.jpg`) and in tag groups (`div::[color:{{ color }};]`).
|
|
486
|
+
|
|
487
|
+
|
|
358
488
|
## Plugin System
|
|
359
489
|
|
|
360
490
|
### Custom Tag Handlers
|
|
@@ -421,7 +551,7 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
421
551
|
'title': 'Webpage', # Document title
|
|
422
552
|
'css': [], # CSS files (string or list)
|
|
423
553
|
'js': [], # JS files (string or list)
|
|
424
|
-
'directory_include': [],
|
|
554
|
+
'directory_include': [cwd], # Search paths for include:: (default: current directory)
|
|
425
555
|
'current_directory': '', # Base directory for video codec detection
|
|
426
556
|
}
|
|
427
557
|
```
|
|
@@ -429,7 +559,9 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
429
559
|
|
|
430
560
|
## Design Principles
|
|
431
561
|
|
|
432
|
-
- **HTML-first**: Raw HTML is never modified. Only LHTML syntax triggers conversions. In particular, the following are never transformed (not even by `include::` or `::#`): HTML tags and their attributes (URLs containing `__`, quoted values containing `>`, ...), `<script>` and `<style>` blocks (CSS `::before`, ...), HTML comments, and math (`$...$`, `$$...$$`, `\(...\)`, `\[...\]`, so that `$x**2$` reaches MathJax/KaTeX intact). Text between HTML tags is still processed.
|
|
562
|
+
- **HTML-first**: Raw HTML is never modified. Only LHTML syntax triggers conversions. In particular, the following are never transformed (not even by `include::` or `::#`): HTML tags and their attributes, including tags spanning multiple lines (URLs containing `__`, quoted values containing `>`, ...), `<script>` and `<style>` blocks (CSS `::before`, ...), HTML comments, and math (`$...$`, `$$...$$`, `\(...\)`, `\[...\]`, so that `$x**2$` reaches MathJax/KaTeX intact). Text between HTML tags is still processed.
|
|
563
|
+
- **Attributes stay literal**: styles, classes/IDs and HTML attributes in LHTML tag groups and headings are preserved without inline formatting (`div::(.my__class__)` keeps `my__class__`). Link labels still support formatting.
|
|
564
|
+
- **Template-friendly**: Jinja2 expressions (`{{ ... }}`), statements (`{% ... %}`) and comments (`{# ... #}`) pass through unchanged (see [Using LHTML with Jinja2](#using-lhtml-with-jinja2)).
|
|
433
565
|
- **Island grammar**: LHTML syntax "islands" float in a sea of opaque content (HTML, Jinja2 templates, LaTeX, etc.) that passes through untouched.
|
|
434
566
|
- **Minimal**: A few symbols (`::`, `=`, `*`, `**`, `__`, `` ` ``) cover most needs. No complex configuration required.
|
|
435
567
|
- **Composable**: LHTML works seamlessly with Jinja2 templates, making it suitable for static site generators.
|
|
@@ -440,20 +572,27 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
440
572
|
```
|
|
441
573
|
src/lhtml/
|
|
442
574
|
__init__.py # Public API: run(), analyse_tag(), read_yaml()
|
|
575
|
+
__main__.py # python -m lhtml
|
|
443
576
|
cli.py # Command-line interface
|
|
444
577
|
pipeline.py # ProcessingPipeline, TagRegistry, LexerRegistry
|
|
445
578
|
process.py # Core transformation functions
|
|
446
579
|
patterns.py # Centralized regex patterns and utilities
|
|
447
580
|
tag_parser.py # Lark-based parser for :: bracket syntax
|
|
448
581
|
tag_element.lark # Lark grammar definition
|
|
582
|
+
element_extract.py # extract_bracket_elements() (entry point of the tag parser)
|
|
449
583
|
export_html.py # HTML generation for tag elements
|
|
450
584
|
listing.py # List processing
|
|
451
585
|
code.py # Code syntax highlighting (Pygments)
|
|
452
586
|
wrap_html.py # HTML document wrapping
|
|
453
587
|
errors.py # Structured error types
|
|
588
|
+
insert_in_text.py # Store/restore helpers kept for backward compatibility
|
|
589
|
+
test/ # pytest suite and .l.html / -out.html reference pairs
|
|
590
|
+
examples/ # Example sources (see examples/README.md)
|
|
454
591
|
```
|
|
455
592
|
|
|
593
|
+
Run the tests with `pytest` (after `pip install -e ".[dev]"`).
|
|
594
|
+
|
|
456
595
|
|
|
457
596
|
## License
|
|
458
597
|
|
|
459
|
-
MIT
|
|
598
|
+
MIT, see [LICENSE.md](LICENSE.md).
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# LHTML — Lightweight HTML
|
|
2
2
|
|
|
3
|
-
[](https://github.com/drohmer/lhtml/actions/workflows/tests.yml)
|
|
4
4
|
[](https://pypi.org/project/lhtml-markup/)
|
|
5
5
|
|
|
6
6
|
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.
|
|
7
7
|
|
|
8
8
|
LHTML is used to build static websites and presentation slides, typically combined with Jinja2 templates.
|
|
9
9
|
|
|
10
|
+
See the [examples](examples/) and the [changelog](CHANGELOG.md).
|
|
11
|
+
|
|
10
12
|
## Installation
|
|
11
13
|
|
|
12
14
|
From PyPI:
|
|
@@ -39,14 +41,44 @@ Dependencies (`lark`, `pygments`, `pyyaml`) are installed automatically.
|
|
|
39
41
|
```bash
|
|
40
42
|
lhtml input.l.html # Convert to stdout
|
|
41
43
|
lhtml input.l.html -o output.html # Convert to file
|
|
42
|
-
lhtml input.l.html -w # Wrap in full HTML document
|
|
43
|
-
lhtml input.l.html -b # Render source line breaks as <br>
|
|
44
|
+
lhtml input.l.html -w # Wrap in full HTML document (--wrapAuto)
|
|
45
|
+
lhtml input.l.html -b # Render source line breaks as <br> (--line-breaks)
|
|
44
46
|
lhtml a.l.html b.l.html # Several files: a.html, b.html next to sources
|
|
45
47
|
lhtml a.l.html b.l.html -o build/ # Several files into a directory
|
|
46
48
|
python -m lhtml input.l.html # Alternative invocation
|
|
49
|
+
lhtml --version # Show the version (also lhtml.__version__)
|
|
47
50
|
```
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
Without `-o`, `page.l.html` is written to `page.html` next to its source.
|
|
53
|
+
|
|
54
|
+
- Files are read and written as UTF-8 (a BOM is accepted).
|
|
55
|
+
- Errors and warnings are reported on stderr with the file name. The remaining files are still processed, and the exit code is non-zero if any file failed.
|
|
56
|
+
- An input file is never overwritten: `lhtml page.html` (output `page.html`) is an error, as is an output that is another input of the batch, or a symbolic or hard link to one.
|
|
57
|
+
- Includes are looked up in the input file's directory first, then in the current directory.
|
|
58
|
+
|
|
59
|
+
With `-w`, the result is wrapped in a minimal HTML document using the `title`, `css` and `js` of the front matter:
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<!DOCTYPE html>
|
|
63
|
+
|
|
64
|
+
<html lang="en">
|
|
65
|
+
|
|
66
|
+
<head>
|
|
67
|
+
<meta charset="utf-8">
|
|
68
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
69
|
+
<title>My Page</title>
|
|
70
|
+
<link rel="stylesheet" type="text/css" href="style.css">
|
|
71
|
+
<script src="app.js" defer></script>
|
|
72
|
+
</head>
|
|
73
|
+
|
|
74
|
+
<body>
|
|
75
|
+
<h1>Hello</h1>
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
</body>
|
|
79
|
+
|
|
80
|
+
</html>
|
|
81
|
+
```
|
|
50
82
|
|
|
51
83
|
### Python API
|
|
52
84
|
|
|
@@ -95,13 +127,33 @@ With classes/IDs:
|
|
|
95
127
|
```
|
|
96
128
|
* First item
|
|
97
129
|
* Second item
|
|
98
|
-
** Nested item
|
|
99
|
-
** Nested item B
|
|
100
|
-
*** Deep nested
|
|
130
|
+
** Nested item
|
|
101
131
|
* Back to top level
|
|
102
132
|
```
|
|
103
133
|
|
|
104
|
-
|
|
134
|
+
Output (a nested list is placed in its own `<li>`):
|
|
135
|
+
```html
|
|
136
|
+
<ul>
|
|
137
|
+
<li>
|
|
138
|
+
First item
|
|
139
|
+
</li>
|
|
140
|
+
<li>
|
|
141
|
+
Second item
|
|
142
|
+
</li>
|
|
143
|
+
<li>
|
|
144
|
+
<ul>
|
|
145
|
+
<li>
|
|
146
|
+
Nested item
|
|
147
|
+
</li>
|
|
148
|
+
</ul>
|
|
149
|
+
</li>
|
|
150
|
+
<li>
|
|
151
|
+
Back to top level
|
|
152
|
+
</li>
|
|
153
|
+
</ul>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Each `*` adds one level (`***` is level 3). A list ends at the first line that is not an item.
|
|
105
157
|
|
|
106
158
|
|
|
107
159
|
### Inline Formatting
|
|
@@ -136,7 +188,7 @@ Tag names start with a letter and may contain letters, digits, `_` and `-` (usef
|
|
|
136
188
|
|
|
137
189
|
A closing `::` may be directly followed by punctuation or HTML (`**span::[c] x ::**`, `important ::,`). In a run of colons, `::` markers are the pairs ending the run: `::::[...]` is a closing `::` followed by `::[...]`, and `x:::nl` is `x:` followed by `::nl`. A `::` between two HTML tags (as in pre-highlighted code `<span>::</span>`) is left untouched.
|
|
138
190
|
|
|
139
|
-
A tag that is opened but never closed, or a `::` without matching opening tag, produces an `LHTMLWarning` quoting the beginning of the tag.
|
|
191
|
+
A tag that is opened but never closed, or a `::` without matching opening tag, produces an `LHTMLWarning` quoting the beginning of the tag. An unmatched `::` is kept as text in the output.
|
|
140
192
|
|
|
141
193
|
#### Div / Span with Styles
|
|
142
194
|
|
|
@@ -200,10 +252,29 @@ Output:
|
|
|
200
252
|
<div style="color:blue;"> short text </div>
|
|
201
253
|
```
|
|
202
254
|
|
|
255
|
+
#### Explicit Closing Tags
|
|
256
|
+
|
|
257
|
+
A bare `::` closes the last opened tag. To make long or nested blocks easier to read, name the tag you close with `::name[-]` (`::[-]` closes the last one, like `::`):
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
div::[color:red;]
|
|
261
|
+
Red **text**
|
|
262
|
+
::div[-]
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Output:
|
|
266
|
+
```html
|
|
267
|
+
<div style="color:red;">
|
|
268
|
+
Red <strong>text</strong>
|
|
269
|
+
</div>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
If the name does not match the last opened tag, a `LHTMLWarning` is emitted and the last opened tag is closed.
|
|
273
|
+
|
|
203
274
|
|
|
204
275
|
### Links
|
|
205
276
|
|
|
206
|
-
The URL of `link::`, `img::`, `video::` and `videoplay::` is never modified (`__`, `**`, `$` are kept). Parentheses that are part of the URL are kept (`Mercury_(planet)`, `fig(1).png`), while a group starting with `.` or `#` is a class/id group.
|
|
277
|
+
The URL of `link::`, `img::`, `video::` and `videoplay::` is never modified (`__`, `**`, `$` are kept). Parentheses that are part of the URL are kept (`Mercury_(planet)`, `fig(1).png`), while a group starting with `.` or `#` is a class/id group. The URL may contain Jinja expressions (`img::{{ base }}/photo.jpg`, `link::{{ url_for('page') }}[Home]`), which are kept unchanged.
|
|
207
278
|
|
|
208
279
|
```
|
|
209
280
|
link::https://example.com[Click here]
|
|
@@ -248,7 +319,22 @@ def hello():
|
|
|
248
319
|
code::[-]
|
|
249
320
|
````
|
|
250
321
|
|
|
251
|
-
|
|
322
|
+
Output (Pygments HTML inside `<div class="code">`):
|
|
323
|
+
```html
|
|
324
|
+
<div class="code"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
|
|
325
|
+
...
|
|
326
|
+
</pre></div>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Syntax highlighting is powered by Pygments: any language supported by Pygments can be used, case-insensitively. An empty language (`code::[]`) renders plain text; an unknown language renders plain text with a warning. The built-in `c++` language is C++ with the types of the [CGP library](https://github.com/drohmer/cgp) highlighted; other languages can be added (see [Custom Code Lexers](#custom-code-lexers)).
|
|
330
|
+
|
|
331
|
+
The colors come from a Pygments stylesheet, which you must include in your page. Generate one for the `.code` class (any [Pygments style](https://pygments.org/styles/) can replace `default`):
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
pygmentize -S default -f html -a .code > code.css
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
`include::file` directives inside a code block insert the file as raw code.
|
|
252
338
|
|
|
253
339
|
|
|
254
340
|
### Spacer
|
|
@@ -336,6 +422,50 @@ Supported metadata keys:
|
|
|
336
422
|
| `directory_include` | list | Directories to search for includes |
|
|
337
423
|
|
|
338
424
|
|
|
425
|
+
## Using LHTML with Jinja2
|
|
426
|
+
|
|
427
|
+
LHTML leaves Jinja2 untouched, so a template can be written in LHTML: convert it to HTML first, then render it with Jinja2 (`pip install jinja2`).
|
|
428
|
+
|
|
429
|
+
`blog.l.html`:
|
|
430
|
+
```
|
|
431
|
+
= {{ page.title }}
|
|
432
|
+
|
|
433
|
+
{% for post in posts %}
|
|
434
|
+
div::(.post)
|
|
435
|
+
== link::{{ post.url }}[{{ post.title }}]
|
|
436
|
+
{{ post.summary }} **Read more**
|
|
437
|
+
::
|
|
438
|
+
{% endfor %}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
import jinja2
|
|
443
|
+
import lhtml
|
|
444
|
+
|
|
445
|
+
with open('blog.l.html', encoding='utf-8') as f:
|
|
446
|
+
template = jinja2.Template(lhtml.run(f.read()))
|
|
447
|
+
|
|
448
|
+
html = template.render(page={'title': 'Blog'},
|
|
449
|
+
posts=[{'url': 'first.html', 'title': 'First post', 'summary': 'Hello.'}])
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
`lhtml.run()` produces the Jinja2 template:
|
|
453
|
+
```html
|
|
454
|
+
<h1>{{ page.title }}</h1>
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
{% for post in posts %}
|
|
458
|
+
<div class="post">
|
|
459
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
460
|
+
|
|
461
|
+
{{ post.summary }} <strong>Read more</strong>
|
|
462
|
+
</div>
|
|
463
|
+
{% endfor %}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Jinja2 expressions can also be used in URLs (`img::{{ base }}/photo.jpg`) and in tag groups (`div::[color:{{ color }};]`).
|
|
467
|
+
|
|
468
|
+
|
|
339
469
|
## Plugin System
|
|
340
470
|
|
|
341
471
|
### Custom Tag Handlers
|
|
@@ -402,7 +532,7 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
402
532
|
'title': 'Webpage', # Document title
|
|
403
533
|
'css': [], # CSS files (string or list)
|
|
404
534
|
'js': [], # JS files (string or list)
|
|
405
|
-
'directory_include': [],
|
|
535
|
+
'directory_include': [cwd], # Search paths for include:: (default: current directory)
|
|
406
536
|
'current_directory': '', # Base directory for video codec detection
|
|
407
537
|
}
|
|
408
538
|
```
|
|
@@ -410,7 +540,9 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
410
540
|
|
|
411
541
|
## Design Principles
|
|
412
542
|
|
|
413
|
-
- **HTML-first**: Raw HTML is never modified. Only LHTML syntax triggers conversions. In particular, the following are never transformed (not even by `include::` or `::#`): HTML tags and their attributes (URLs containing `__`, quoted values containing `>`, ...), `<script>` and `<style>` blocks (CSS `::before`, ...), HTML comments, and math (`$...$`, `$$...$$`, `\(...\)`, `\[...\]`, so that `$x**2$` reaches MathJax/KaTeX intact). Text between HTML tags is still processed.
|
|
543
|
+
- **HTML-first**: Raw HTML is never modified. Only LHTML syntax triggers conversions. In particular, the following are never transformed (not even by `include::` or `::#`): HTML tags and their attributes, including tags spanning multiple lines (URLs containing `__`, quoted values containing `>`, ...), `<script>` and `<style>` blocks (CSS `::before`, ...), HTML comments, and math (`$...$`, `$$...$$`, `\(...\)`, `\[...\]`, so that `$x**2$` reaches MathJax/KaTeX intact). Text between HTML tags is still processed.
|
|
544
|
+
- **Attributes stay literal**: styles, classes/IDs and HTML attributes in LHTML tag groups and headings are preserved without inline formatting (`div::(.my__class__)` keeps `my__class__`). Link labels still support formatting.
|
|
545
|
+
- **Template-friendly**: Jinja2 expressions (`{{ ... }}`), statements (`{% ... %}`) and comments (`{# ... #}`) pass through unchanged (see [Using LHTML with Jinja2](#using-lhtml-with-jinja2)).
|
|
414
546
|
- **Island grammar**: LHTML syntax "islands" float in a sea of opaque content (HTML, Jinja2 templates, LaTeX, etc.) that passes through untouched.
|
|
415
547
|
- **Minimal**: A few symbols (`::`, `=`, `*`, `**`, `__`, `` ` ``) cover most needs. No complex configuration required.
|
|
416
548
|
- **Composable**: LHTML works seamlessly with Jinja2 templates, making it suitable for static site generators.
|
|
@@ -421,20 +553,27 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
421
553
|
```
|
|
422
554
|
src/lhtml/
|
|
423
555
|
__init__.py # Public API: run(), analyse_tag(), read_yaml()
|
|
556
|
+
__main__.py # python -m lhtml
|
|
424
557
|
cli.py # Command-line interface
|
|
425
558
|
pipeline.py # ProcessingPipeline, TagRegistry, LexerRegistry
|
|
426
559
|
process.py # Core transformation functions
|
|
427
560
|
patterns.py # Centralized regex patterns and utilities
|
|
428
561
|
tag_parser.py # Lark-based parser for :: bracket syntax
|
|
429
562
|
tag_element.lark # Lark grammar definition
|
|
563
|
+
element_extract.py # extract_bracket_elements() (entry point of the tag parser)
|
|
430
564
|
export_html.py # HTML generation for tag elements
|
|
431
565
|
listing.py # List processing
|
|
432
566
|
code.py # Code syntax highlighting (Pygments)
|
|
433
567
|
wrap_html.py # HTML document wrapping
|
|
434
568
|
errors.py # Structured error types
|
|
569
|
+
insert_in_text.py # Store/restore helpers kept for backward compatibility
|
|
570
|
+
test/ # pytest suite and .l.html / -out.html reference pairs
|
|
571
|
+
examples/ # Example sources (see examples/README.md)
|
|
435
572
|
```
|
|
436
573
|
|
|
574
|
+
Run the tests with `pytest` (after `pip install -e ".[dev]"`).
|
|
575
|
+
|
|
437
576
|
|
|
438
577
|
## License
|
|
439
578
|
|
|
440
|
-
MIT
|
|
579
|
+
MIT, see [LICENSE.md](LICENSE.md).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lhtml-markup"
|
|
7
|
-
|
|
7
|
+
dynamic = ["version"]
|
|
8
8
|
description = "Lightweight HTML markup language — simplifies HTML authoring with shorthand syntax"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -19,16 +19,19 @@ dependencies = [
|
|
|
19
19
|
Homepage = "https://github.com/drohmer/lhtml"
|
|
20
20
|
Repository = "https://github.com/drohmer/lhtml"
|
|
21
21
|
Issues = "https://github.com/drohmer/lhtml/issues"
|
|
22
|
+
Changelog = "https://github.com/drohmer/lhtml/blob/main/CHANGELOG.md"
|
|
22
23
|
|
|
23
24
|
[project.optional-dependencies]
|
|
24
25
|
dev = [
|
|
25
26
|
"pytest>=7.0",
|
|
26
|
-
"ansicolors",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.scripts]
|
|
30
30
|
lhtml = "lhtml:main"
|
|
31
31
|
|
|
32
|
+
[tool.setuptools.dynamic]
|
|
33
|
+
version = {attr = "lhtml.__version__"}
|
|
34
|
+
|
|
32
35
|
[tool.setuptools.packages.find]
|
|
33
36
|
where = ["src"]
|
|
34
37
|
|
|
@@ -9,6 +9,8 @@ Usage:
|
|
|
9
9
|
html = lhtml.run(text, {'wrap-auto': True, 'title': 'My Page'})
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
__version__ = '2.4.1'
|
|
13
|
+
|
|
12
14
|
from .element_extract import extract_bracket_elements
|
|
13
15
|
from .insert_in_text import insert_element_from_index, remove_element_to_index
|
|
14
16
|
from .wrap_html import wrap_auto
|
|
@@ -70,7 +72,7 @@ def main():
|
|
|
70
72
|
|
|
71
73
|
__all__ = [
|
|
72
74
|
# Core API
|
|
73
|
-
'run', 'analyse_tag', 'read_yaml', 'main',
|
|
75
|
+
'__version__', 'run', 'analyse_tag', 'read_yaml', 'main',
|
|
74
76
|
# Processing functions
|
|
75
77
|
'extract_bracket_elements',
|
|
76
78
|
'process_yaml', 'process_verbatim_to_index', 'process_verbatim_back_from_index',
|
|
@@ -6,6 +6,7 @@ Usage:
|
|
|
6
6
|
lhtml [-w] a.l.html b.l.html # Multiple files → .html next to sources
|
|
7
7
|
lhtml [-w] a.l.html b.l.html -o build/ # Multiple files → output directory
|
|
8
8
|
lhtml -b file.l.html # Source line breaks rendered as <br>
|
|
9
|
+
lhtml --version
|
|
9
10
|
python -m lhtml [same options]
|
|
10
11
|
"""
|
|
11
12
|
|
|
@@ -14,6 +15,7 @@ import sys
|
|
|
14
15
|
import argparse
|
|
15
16
|
import warnings
|
|
16
17
|
|
|
18
|
+
from . import __version__
|
|
17
19
|
from .errors import LHTMLError
|
|
18
20
|
from .pipeline import ProcessingPipeline
|
|
19
21
|
from .process import read_source
|
|
@@ -84,6 +86,8 @@ def main():
|
|
|
84
86
|
action='store_true')
|
|
85
87
|
parser.add_argument('-o', '--output',
|
|
86
88
|
help='Output file (single input) or directory (multiple inputs)')
|
|
89
|
+
parser.add_argument('-V', '--version', action='version',
|
|
90
|
+
version=f'lhtml {__version__}')
|
|
87
91
|
args = parser.parse_args()
|
|
88
92
|
|
|
89
93
|
meta = {'directory_include': [os.getcwd() + '/']}
|
|
@@ -118,7 +122,10 @@ def main():
|
|
|
118
122
|
continue
|
|
119
123
|
|
|
120
124
|
out_path = _output_path_for(f_in, args.output)
|
|
121
|
-
if os.path.
|
|
125
|
+
if any(os.path.realpath(out_path) == os.path.realpath(source)
|
|
126
|
+
or (os.path.exists(out_path) and os.path.exists(source)
|
|
127
|
+
and os.path.samefile(out_path, source))
|
|
128
|
+
for source in args.inputFiles):
|
|
122
129
|
raise ValueError(f'output file would overwrite the input file [{out_path}]')
|
|
123
130
|
out_dir = os.path.dirname(out_path)
|
|
124
131
|
if out_dir:
|
|
@@ -9,6 +9,8 @@ import html
|
|
|
9
9
|
import os
|
|
10
10
|
import re
|
|
11
11
|
|
|
12
|
+
from .patterns import JINJA_RE
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
# ---------------------------------------------------------------------------
|
|
14
16
|
# Attribute helpers
|
|
@@ -24,8 +26,15 @@ def _build_attrs(elements):
|
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
def _attr(value):
|
|
27
|
-
"""Escape the double quotes of a value placed inside a double-quoted HTML
|
|
28
|
-
|
|
29
|
+
"""Escape the double quotes of a value placed inside a double-quoted HTML
|
|
30
|
+
attribute (Jinja zones are kept as is, they are rendered before HTML)."""
|
|
31
|
+
parts, prev = [], 0
|
|
32
|
+
for m in JINJA_RE.finditer(value):
|
|
33
|
+
parts.append(value[prev:m.start()].replace('"', '"'))
|
|
34
|
+
parts.append(m.group(0))
|
|
35
|
+
prev = m.end()
|
|
36
|
+
parts.append(value[prev:].replace('"', '"'))
|
|
37
|
+
return ''.join(parts)
|
|
29
38
|
|
|
30
39
|
|
|
31
40
|
def export_html_element_style(text):
|
|
@@ -115,7 +124,7 @@ def export_html_video(elements, default_inline='', current_directory=''):
|
|
|
115
124
|
parts = ['<video']
|
|
116
125
|
parts.append(export_html_element_inline(elements['{}']))
|
|
117
126
|
if default_inline:
|
|
118
|
-
parts.append(f' {default_inline}
|
|
127
|
+
parts.append(f' {default_inline}')
|
|
119
128
|
parts.append(export_html_element_class_and_id(elements['()']))
|
|
120
129
|
parts.append(export_html_element_style(elements['[]']))
|
|
121
130
|
if os.path.isfile(os.path.join(current_directory, poster_candidate)):
|