lhtml-markup 2.4.0__tar.gz → 2.5.0__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.
Files changed (28) hide show
  1. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/PKG-INFO +241 -15
  2. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/README.md +240 -14
  3. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/pyproject.toml +4 -1
  4. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/__init__.py +8 -4
  5. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/cli.py +15 -0
  6. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/errors.py +4 -0
  7. lhtml_markup-2.5.0/src/lhtml/macros.py +276 -0
  8. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/pipeline.py +21 -2
  9. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/process.py +4 -2
  10. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml_markup.egg-info/PKG-INFO +241 -15
  11. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml_markup.egg-info/SOURCES.txt +1 -0
  12. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/test/test_lhtml.py +241 -1
  13. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/LICENSE.md +0 -0
  14. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/setup.cfg +0 -0
  15. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/__main__.py +0 -0
  16. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/code.py +0 -0
  17. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/element_extract.py +0 -0
  18. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/export_html.py +0 -0
  19. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/insert_in_text.py +0 -0
  20. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/listing.py +0 -0
  21. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/patterns.py +0 -0
  22. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/tag_element.lark +0 -0
  23. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/tag_parser.py +0 -0
  24. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml/wrap_html.py +0 -0
  25. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml_markup.egg-info/dependency_links.txt +0 -0
  26. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml_markup.egg-info/entry_points.txt +0 -0
  27. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/src/lhtml_markup.egg-info/requires.txt +0 -0
  28. {lhtml_markup-2.4.0 → lhtml_markup-2.5.0}/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.0
3
+ Version: 2.5.0
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,45 @@ 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)
65
+ lhtml input.l.html -m macros.yaml # Declare macros, custom :: tags (--macros)
63
66
  lhtml a.l.html b.l.html # Several files: a.html, b.html next to sources
64
67
  lhtml a.l.html b.l.html -o build/ # Several files into a directory
65
68
  python -m lhtml input.l.html # Alternative invocation
69
+ lhtml --version # Show the version (also lhtml.__version__)
66
70
  ```
67
71
 
68
- Files are read and written as UTF-8 (a BOM is accepted). 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. No input file in the batch is overwritten (including through symbolic or hard links) (e.g. `lhtml page.html` without `-o`). Includes are looked up in the input file's directory first, then in the current directory.
72
+ Without `-o`, `page.l.html` is written to `page.html` next to its source.
73
+
74
+ - Files are read and written as UTF-8 (a BOM is accepted).
75
+ - 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.
76
+ - 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.
77
+ - Includes are looked up in the input file's directory first, then in the current directory.
78
+
79
+ With `-w`, the result is wrapped in a minimal HTML document using the `title`, `css` and `js` of the front matter:
80
+
81
+ ```html
82
+ <!DOCTYPE html>
83
+
84
+ <html lang="en">
85
+
86
+ <head>
87
+ <meta charset="utf-8">
88
+ <meta name="viewport" content="width=device-width, initial-scale=1">
89
+ <title>My Page</title>
90
+ <link rel="stylesheet" type="text/css" href="style.css">
91
+ <script src="app.js" defer></script>
92
+ </head>
93
+
94
+ <body>
95
+ <h1>Hello</h1>
96
+
97
+
98
+ </body>
99
+
100
+ </html>
101
+ ```
69
102
 
70
103
  ### Python API
71
104
 
@@ -80,6 +113,12 @@ html = lhtml.run(text, {
80
113
  'css': ['style.css'],
81
114
  'js': ['script.js'],
82
115
  })
116
+
117
+ # Macros (custom :: tags, see Macros below)
118
+ html = lhtml.run('box:: x ::\n', {'macros': {'box': {'class': 'box'}}})
119
+ macros = lhtml.load_macros('macros.yaml') # definitions from files or dicts
120
+ registry = lhtml.registry_with_macros(macros) # validates them (LHTMLMacroError)
121
+ info = lhtml.describe_macro('box', macros['box']) # what a definition renders, for docs
83
122
  ```
84
123
 
85
124
 
@@ -114,13 +153,33 @@ With classes/IDs:
114
153
  ```
115
154
  * First item
116
155
  * Second item
117
- ** Nested item A
118
- ** Nested item B
119
- *** Deep nested
156
+ ** Nested item
120
157
  * Back to top level
121
158
  ```
122
159
 
123
- Produces nested `<ul><li>` structures.
160
+ Output (a nested list is placed in its own `<li>`):
161
+ ```html
162
+ <ul>
163
+ <li>
164
+ First item
165
+ </li>
166
+ <li>
167
+ Second item
168
+ </li>
169
+ <li>
170
+ <ul>
171
+ <li>
172
+ Nested item
173
+ </li>
174
+ </ul>
175
+ </li>
176
+ <li>
177
+ Back to top level
178
+ </li>
179
+ </ul>
180
+ ```
181
+
182
+ Each `*` adds one level (`***` is level 3). A list ends at the first line that is not an item.
124
183
 
125
184
 
126
185
  ### Inline Formatting
@@ -155,7 +214,7 @@ Tag names start with a letter and may contain letters, digits, `_` and `-` (usef
155
214
 
156
215
  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
216
 
158
- A tag that is opened but never closed, or a `::` without matching opening tag, produces an `LHTMLWarning` quoting the beginning of the tag.
217
+ 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
218
 
160
219
  #### Div / Span with Styles
161
220
 
@@ -219,6 +278,25 @@ Output:
219
278
  <div style="color:blue;"> short text </div>
220
279
  ```
221
280
 
281
+ #### Explicit Closing Tags
282
+
283
+ 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 `::`):
284
+
285
+ ```
286
+ div::[color:red;]
287
+ Red **text**
288
+ ::div[-]
289
+ ```
290
+
291
+ Output:
292
+ ```html
293
+ <div style="color:red;">
294
+ Red <strong>text</strong>
295
+ </div>
296
+ ```
297
+
298
+ If the name does not match the last opened tag, a `LHTMLWarning` is emitted and the last opened tag is closed.
299
+
222
300
 
223
301
  ### Links
224
302
 
@@ -267,7 +345,22 @@ def hello():
267
345
  code::[-]
268
346
  ````
269
347
 
270
- `include::file` directives inside a code block insert the file as raw code. Syntax highlighting is powered by Pygments. Any language supported by Pygments can be used. An empty language (`code::[]`) renders plain text; an unknown language renders plain text with a warning.
348
+ Output (Pygments HTML inside `<div class="code">`):
349
+ ```html
350
+ <div class="code"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">():</span>
351
+ ...
352
+ </pre></div>
353
+ ```
354
+
355
+ 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)).
356
+
357
+ 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`):
358
+
359
+ ```bash
360
+ pygmentize -S default -f html -a .code > code.css
361
+ ```
362
+
363
+ `include::file` directives inside a code block insert the file as raw code.
271
364
 
272
365
 
273
366
  ### Spacer
@@ -328,6 +421,85 @@ include::components/nav.html
328
421
  Included files are recursively processed (up to 20 levels); their own YAML front matter is ignored. An included file looks for its own includes first in its own directory, then in `directory_include`. A circular include raises `LHTMLIncludeLoopError`.
329
422
 
330
423
 
424
+ ### Macros (custom tags from a configuration)
425
+
426
+ A macro is a named `::` tag declared in YAML (or a dict): a shortcut for an
427
+ HTML element with default classes, style and attributes. The styling stays
428
+ in CSS, so a deck or a site changes its look in one place.
429
+
430
+ ```yaml
431
+ # macros.yaml (the definitions may also be at the top level)
432
+ macros:
433
+ small: {class: small}
434
+ credit: {tag: span, class: credit}
435
+ aside: {class: aside}
436
+ box: {class: box}
437
+ gap: {class: gap, empty: true, variant: [s, m, l], default: m}
438
+ demo: {tag: iframe, class: demo, empty: true, url: src, attrs: 'frameborder="0"'}
439
+ ```
440
+
441
+ ```
442
+ aside::[top:400px;]
443
+ img::assets/figure.png[width:450px;]
444
+ ::
445
+
446
+ box::(.good) **Correct** ::
447
+ gap::l
448
+ credit:: Image: Wikimedia Commons ::
449
+ demo::assets/demo/index.html
450
+ ```
451
+
452
+ Output:
453
+ ```html
454
+ <div class="aside" style="top:400px;">
455
+ <img style="width:450px;" src="assets/figure.png" alt="assets/figure.png">
456
+ </div>
457
+
458
+ <div class="box good"> <strong>Correct</strong> </div>
459
+ <div class="gap gap-l"></div>
460
+ <span class="credit"> Image: Wikimedia Commons </span>
461
+ <iframe class="demo" src="assets/demo/index.html" frameborder="0"></iframe>
462
+ ```
463
+
464
+ | Field | Description |
465
+ |-------|-------------|
466
+ | `tag` | HTML element (default `div`) |
467
+ | `class` | Default classes (space separated) |
468
+ | `style` | Default inline style, placed before the style of the source |
469
+ | `attrs` | Default HTML attributes |
470
+ | `empty` | No content: the element is closed at once (`gap::`, `demo::url`) |
471
+ | `url` | The text after `::` is the value of this attribute (`src`, `data-src`, ...). Not needed for `tag: img`, `video`, `videoplay`, `link`: their text is already the URL |
472
+ | `variant` | The text after `::` selects a variant, added as the class `<first class>-<variant>`; a list restricts the allowed values (an unknown one gives a warning) |
473
+ | `default` | Variant used when none is given (one of the variants) |
474
+ | `doc` | Description, ignored by LHTML (for documentation tools; `lhtml.describe_macro(name, spec)` gives what a definition renders) |
475
+
476
+ The classes, style and attributes written in the source are added to the
477
+ defaults, then the element is rendered like any element: a macro whose `tag`
478
+ is an LHTML tag (`img`, `video`, `videoplay`, `link`, ...) uses that tag
479
+ (`photo: {tag: img, class: photo}` gives `photo::a.png` an `alt`, `videoplay`
480
+ keeps its codec variants, the `style` of a `link` macro is an attribute since
481
+ `[]` is the link text); a macro never renders through another macro. A macro is closed by `::` or by its name (`::box[-]`). Macros are
482
+ not active inside inline code (`` `box::` `` stays text), and a macro cannot
483
+ replace a built-in tag (`div`, `img`, ...). Unlike `img::`, the URL of a `url`
484
+ macro is not protected from inline formatting (avoid `__` in it).
485
+
486
+ A variant may be closed at once like any element: `gap::l::`.
487
+
488
+ Declare them with `-m` / `--macros` (repeatable, later files win), with the
489
+ `macros` key of the front matter (file names relative to the page, or
490
+ definitions), or with `lhtml.run(text, {'macros': ...})` (a dict, a file
491
+ name, or a list of them). The macros of the front matter are added to the
492
+ others (a macro of the same name is replaced, `name: null` removes it). A
493
+ file holds the definitions at its top level or under its only key `macros`
494
+ (`macros` is thus not a macro name). An HTML void element
495
+ (`tag: hr`, `br`, ...) is always empty. Invalid definitions (unknown field,
496
+ wrong type) raise `LHTMLMacroError`.
497
+
498
+ ```bash
499
+ lhtml -m macros.yaml slide.l.html
500
+ ```
501
+
502
+
331
503
  ### YAML Front Matter
332
504
 
333
505
  The front matter must be at the very beginning of the file (`---` separators elsewhere are kept as text). Input text is normalized first: a leading BOM is removed and CRLF line endings become LF.
@@ -353,6 +525,51 @@ Supported metadata keys:
353
525
  | `wrap-auto` | boolean | Wrap output in full HTML document |
354
526
  | `line-breaks` | boolean | Render source line breaks as `<br>` |
355
527
  | `directory_include` | list | Directories to search for includes |
528
+ | `macros` | string, list or mapping | Macros: YAML file(s) relative to the page, or definitions (see [Macros](#macros-custom-tags-from-a-configuration)) |
529
+
530
+
531
+ ## Using LHTML with Jinja2
532
+
533
+ 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`).
534
+
535
+ `blog.l.html`:
536
+ ```
537
+ = {{ page.title }}
538
+
539
+ {% for post in posts %}
540
+ div::(.post)
541
+ == link::{{ post.url }}[{{ post.title }}]
542
+ {{ post.summary }} **Read more**
543
+ ::
544
+ {% endfor %}
545
+ ```
546
+
547
+ ```python
548
+ import jinja2
549
+ import lhtml
550
+
551
+ with open('blog.l.html', encoding='utf-8') as f:
552
+ template = jinja2.Template(lhtml.run(f.read()))
553
+
554
+ html = template.render(page={'title': 'Blog'},
555
+ posts=[{'url': 'first.html', 'title': 'First post', 'summary': 'Hello.'}])
556
+ ```
557
+
558
+ `lhtml.run()` produces the Jinja2 template:
559
+ ```html
560
+ <h1>{{ page.title }}</h1>
561
+
562
+
563
+ {% for post in posts %}
564
+ <div class="post">
565
+ <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
566
+
567
+ {{ post.summary }} <strong>Read more</strong>
568
+ </div>
569
+ {% endfor %}
570
+ ```
571
+
572
+ Jinja2 expressions can also be used in URLs (`img::{{ base }}/photo.jpg`) and in tag groups (`div::[color:{{ color }};]`).
356
573
 
357
574
 
358
575
  ## Plugin System
@@ -421,8 +638,9 @@ All keys for the `meta` dict passed to `lhtml.run()`:
421
638
  'title': 'Webpage', # Document title
422
639
  'css': [], # CSS files (string or list)
423
640
  'js': [], # JS files (string or list)
424
- 'directory_include': [], # Search paths for include::
641
+ 'directory_include': [cwd], # Search paths for include:: (default: current directory)
425
642
  'current_directory': '', # Base directory for video codec detection
643
+ 'macros': None, # Macros: dict, YAML file name, or list of them
426
644
  }
427
645
  ```
428
646
 
@@ -430,8 +648,8 @@ All keys for the `meta` dict passed to `lhtml.run()`:
430
648
  ## Design Principles
431
649
 
432
650
  - **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
- - Styles, classes/IDs and HTML attributes in LHTML tag groups are preserved without inline formatting. Link labels still support formatting.
434
- - Jinja2 expressions (`{{ ... }}`), statements (`{% ... %}`) and comments (`{# ... #}`) pass through unchanged.
651
+ - **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.
652
+ - **Template-friendly**: Jinja2 expressions (`{{ ... }}`), statements (`{% ... %}`) and comments (`{# ... #}`) pass through unchanged (see [Using LHTML with Jinja2](#using-lhtml-with-jinja2)).
435
653
  - **Island grammar**: LHTML syntax "islands" float in a sea of opaque content (HTML, Jinja2 templates, LaTeX, etc.) that passes through untouched.
436
654
  - **Minimal**: A few symbols (`::`, `=`, `*`, `**`, `__`, `` ` ``) cover most needs. No complex configuration required.
437
655
  - **Composable**: LHTML works seamlessly with Jinja2 templates, making it suitable for static site generators.
@@ -441,21 +659,29 @@ All keys for the `meta` dict passed to `lhtml.run()`:
441
659
 
442
660
  ```
443
661
  src/lhtml/
444
- __init__.py # Public API: run(), analyse_tag(), read_yaml()
662
+ __init__.py # Public API: run(), analyse_tag(), read_yaml(), macro functions
663
+ __main__.py # python -m lhtml
445
664
  cli.py # Command-line interface
446
665
  pipeline.py # ProcessingPipeline, TagRegistry, LexerRegistry
666
+ macros.py # Macros: custom :: tags declared in YAML
447
667
  process.py # Core transformation functions
448
668
  patterns.py # Centralized regex patterns and utilities
449
669
  tag_parser.py # Lark-based parser for :: bracket syntax
450
670
  tag_element.lark # Lark grammar definition
671
+ element_extract.py # extract_bracket_elements() (entry point of the tag parser)
451
672
  export_html.py # HTML generation for tag elements
452
673
  listing.py # List processing
453
674
  code.py # Code syntax highlighting (Pygments)
454
675
  wrap_html.py # HTML document wrapping
455
676
  errors.py # Structured error types
677
+ insert_in_text.py # Store/restore helpers kept for backward compatibility
678
+ test/ # pytest suite and .l.html / -out.html reference pairs
679
+ examples/ # Example sources (see examples/README.md)
456
680
  ```
457
681
 
682
+ Run the tests with `pytest` (after `pip install -e ".[dev]"`).
683
+
458
684
 
459
685
  ## License
460
686
 
461
- MIT
687
+ MIT, see [LICENSE.md](LICENSE.md).