lhtml-markup 2.4.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.4.0 → lhtml_markup-2.4.1}/PKG-INFO +151 -14
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/README.md +150 -13
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/pyproject.toml +4 -1
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/__init__.py +3 -1
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/cli.py +4 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/process.py +2 -1
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/PKG-INFO +151 -14
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/test/test_lhtml.py +22 -1
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/LICENSE.md +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/setup.cfg +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/__main__.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/code.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/element_extract.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/errors.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/export_html.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/insert_in_text.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/listing.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/patterns.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/pipeline.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/tag_element.lark +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/tag_parser.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml/wrap_html.py +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/SOURCES.txt +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/dependency_links.txt +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/entry_points.txt +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/requires.txt +0 -0
- {lhtml_markup-2.4.0 → lhtml_markup-2.4.1}/src/lhtml_markup.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lhtml-markup
|
|
3
|
-
Version: 2.4.
|
|
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
|
|
@@ -26,6 +26,8 @@ LHTML is a markup language that simplifies HTML authoring with embedded CSS styl
|
|
|
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,6 +271,25 @@ 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
|
|
|
@@ -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
|
```
|
|
@@ -430,8 +560,8 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
430
560
|
## Design Principles
|
|
431
561
|
|
|
432
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.
|
|
433
|
-
-
|
|
434
|
-
- Jinja2 expressions (`{{ ... }}`), statements (`{% ... %}`) and comments (`{# ... #}`) pass through unchanged.
|
|
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)).
|
|
435
565
|
- **Island grammar**: LHTML syntax "islands" float in a sea of opaque content (HTML, Jinja2 templates, LaTeX, etc.) that passes through untouched.
|
|
436
566
|
- **Minimal**: A few symbols (`::`, `=`, `*`, `**`, `__`, `` ` ``) cover most needs. No complex configuration required.
|
|
437
567
|
- **Composable**: LHTML works seamlessly with Jinja2 templates, making it suitable for static site generators.
|
|
@@ -442,20 +572,27 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
442
572
|
```
|
|
443
573
|
src/lhtml/
|
|
444
574
|
__init__.py # Public API: run(), analyse_tag(), read_yaml()
|
|
575
|
+
__main__.py # python -m lhtml
|
|
445
576
|
cli.py # Command-line interface
|
|
446
577
|
pipeline.py # ProcessingPipeline, TagRegistry, LexerRegistry
|
|
447
578
|
process.py # Core transformation functions
|
|
448
579
|
patterns.py # Centralized regex patterns and utilities
|
|
449
580
|
tag_parser.py # Lark-based parser for :: bracket syntax
|
|
450
581
|
tag_element.lark # Lark grammar definition
|
|
582
|
+
element_extract.py # extract_bracket_elements() (entry point of the tag parser)
|
|
451
583
|
export_html.py # HTML generation for tag elements
|
|
452
584
|
listing.py # List processing
|
|
453
585
|
code.py # Code syntax highlighting (Pygments)
|
|
454
586
|
wrap_html.py # HTML document wrapping
|
|
455
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)
|
|
456
591
|
```
|
|
457
592
|
|
|
593
|
+
Run the tests with `pytest` (after `pip install -e ".[dev]"`).
|
|
594
|
+
|
|
458
595
|
|
|
459
596
|
## License
|
|
460
597
|
|
|
461
|
-
MIT
|
|
598
|
+
MIT, see [LICENSE.md](LICENSE.md).
|
|
@@ -7,6 +7,8 @@ LHTML is a markup language that simplifies HTML authoring with embedded CSS styl
|
|
|
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,6 +252,25 @@ 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
|
|
|
@@ -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
|
```
|
|
@@ -411,8 +541,8 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
411
541
|
## Design Principles
|
|
412
542
|
|
|
413
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.
|
|
414
|
-
-
|
|
415
|
-
- Jinja2 expressions (`{{ ... }}`), statements (`{% ... %}`) and comments (`{# ... #}`) pass through unchanged.
|
|
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)).
|
|
416
546
|
- **Island grammar**: LHTML syntax "islands" float in a sea of opaque content (HTML, Jinja2 templates, LaTeX, etc.) that passes through untouched.
|
|
417
547
|
- **Minimal**: A few symbols (`::`, `=`, `*`, `**`, `__`, `` ` ``) cover most needs. No complex configuration required.
|
|
418
548
|
- **Composable**: LHTML works seamlessly with Jinja2 templates, making it suitable for static site generators.
|
|
@@ -423,20 +553,27 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
423
553
|
```
|
|
424
554
|
src/lhtml/
|
|
425
555
|
__init__.py # Public API: run(), analyse_tag(), read_yaml()
|
|
556
|
+
__main__.py # python -m lhtml
|
|
426
557
|
cli.py # Command-line interface
|
|
427
558
|
pipeline.py # ProcessingPipeline, TagRegistry, LexerRegistry
|
|
428
559
|
process.py # Core transformation functions
|
|
429
560
|
patterns.py # Centralized regex patterns and utilities
|
|
430
561
|
tag_parser.py # Lark-based parser for :: bracket syntax
|
|
431
562
|
tag_element.lark # Lark grammar definition
|
|
563
|
+
element_extract.py # extract_bracket_elements() (entry point of the tag parser)
|
|
432
564
|
export_html.py # HTML generation for tag elements
|
|
433
565
|
listing.py # List processing
|
|
434
566
|
code.py # Code syntax highlighting (Pygments)
|
|
435
567
|
wrap_html.py # HTML document wrapping
|
|
436
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)
|
|
437
572
|
```
|
|
438
573
|
|
|
574
|
+
Run the tests with `pytest` (after `pip install -e ".[dev]"`).
|
|
575
|
+
|
|
439
576
|
|
|
440
577
|
## License
|
|
441
578
|
|
|
442
|
-
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"}
|
|
@@ -29,6 +29,9 @@ dev = [
|
|
|
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() + '/']}
|
|
@@ -396,9 +396,10 @@ def process_code(text):
|
|
|
396
396
|
# ---------------------------------------------------------------------------
|
|
397
397
|
|
|
398
398
|
def _warn_unmatched_closing(element):
|
|
399
|
+
"""Warn about a closing :: without opening tag; the source is kept."""
|
|
399
400
|
warnings.warn(str(LHTMLTagStackError(element.get('context', ''))),
|
|
400
401
|
LHTMLWarning, stacklevel=4)
|
|
401
|
-
return '
|
|
402
|
+
return '', False
|
|
402
403
|
|
|
403
404
|
|
|
404
405
|
def _dispatch_tag(element, tag_to_close, current_directory, registry=None):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lhtml-markup
|
|
3
|
-
Version: 2.4.
|
|
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
|
|
@@ -26,6 +26,8 @@ LHTML is a markup language that simplifies HTML authoring with embedded CSS styl
|
|
|
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,6 +271,25 @@ 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
|
|
|
@@ -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
|
```
|
|
@@ -430,8 +560,8 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
430
560
|
## Design Principles
|
|
431
561
|
|
|
432
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.
|
|
433
|
-
-
|
|
434
|
-
- Jinja2 expressions (`{{ ... }}`), statements (`{% ... %}`) and comments (`{# ... #}`) pass through unchanged.
|
|
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)).
|
|
435
565
|
- **Island grammar**: LHTML syntax "islands" float in a sea of opaque content (HTML, Jinja2 templates, LaTeX, etc.) that passes through untouched.
|
|
436
566
|
- **Minimal**: A few symbols (`::`, `=`, `*`, `**`, `__`, `` ` ``) cover most needs. No complex configuration required.
|
|
437
567
|
- **Composable**: LHTML works seamlessly with Jinja2 templates, making it suitable for static site generators.
|
|
@@ -442,20 +572,27 @@ All keys for the `meta` dict passed to `lhtml.run()`:
|
|
|
442
572
|
```
|
|
443
573
|
src/lhtml/
|
|
444
574
|
__init__.py # Public API: run(), analyse_tag(), read_yaml()
|
|
575
|
+
__main__.py # python -m lhtml
|
|
445
576
|
cli.py # Command-line interface
|
|
446
577
|
pipeline.py # ProcessingPipeline, TagRegistry, LexerRegistry
|
|
447
578
|
process.py # Core transformation functions
|
|
448
579
|
patterns.py # Centralized regex patterns and utilities
|
|
449
580
|
tag_parser.py # Lark-based parser for :: bracket syntax
|
|
450
581
|
tag_element.lark # Lark grammar definition
|
|
582
|
+
element_extract.py # extract_bracket_elements() (entry point of the tag parser)
|
|
451
583
|
export_html.py # HTML generation for tag elements
|
|
452
584
|
listing.py # List processing
|
|
453
585
|
code.py # Code syntax highlighting (Pygments)
|
|
454
586
|
wrap_html.py # HTML document wrapping
|
|
455
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)
|
|
456
591
|
```
|
|
457
592
|
|
|
593
|
+
Run the tests with `pytest` (after `pip install -e ".[dev]"`).
|
|
594
|
+
|
|
458
595
|
|
|
459
596
|
## License
|
|
460
597
|
|
|
461
|
-
MIT
|
|
598
|
+
MIT, see [LICENSE.md](LICENSE.md).
|
|
@@ -597,7 +597,17 @@ class TestUnclosedTags:
|
|
|
597
597
|
|
|
598
598
|
def test_extra_closing_warns_with_context(self):
|
|
599
599
|
r, w = _run_with_warnings('hello\n::\n')
|
|
600
|
-
assert '
|
|
600
|
+
assert r == 'hello\n::\n'
|
|
601
|
+
assert any('no matching opening tag' in x for x in w)
|
|
602
|
+
|
|
603
|
+
@pytest.mark.parametrize('source, expected', [
|
|
604
|
+
('a :: b', 'a :: b'),
|
|
605
|
+
('a ::div[-] b', 'a ::div[-] b'),
|
|
606
|
+
('span::[c] x :: y ::', '<span style="c"> x </span> y ::'),
|
|
607
|
+
])
|
|
608
|
+
def test_unmatched_closing_is_kept_as_text(self, source, expected):
|
|
609
|
+
r, w = _run_with_warnings(source)
|
|
610
|
+
assert r == expected
|
|
601
611
|
assert any('no matching opening tag' in x for x in w)
|
|
602
612
|
|
|
603
613
|
|
|
@@ -1330,3 +1340,14 @@ class TestJinjaInUrlsAndHeadings:
|
|
|
1330
1340
|
])
|
|
1331
1341
|
def test_video_opening_tag_spacing(source, expected):
|
|
1332
1342
|
assert lhtml.run(source).startswith(expected + '\n')
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
@pytest.mark.parametrize('flag', ['--version', '-V'])
|
|
1346
|
+
def test_cli_version(flag, monkeypatch, capsys):
|
|
1347
|
+
code, out, _ = TestCli._run_cli(None, monkeypatch, capsys, flag)
|
|
1348
|
+
assert code == 0
|
|
1349
|
+
assert out == f'lhtml {lhtml.__version__}\n'
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
def test_version_format():
|
|
1353
|
+
assert re.fullmatch(r'\d+\.\d+\.\d+', lhtml.__version__)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|