Simple HTML Generation https://minihtml.trendels.name/

start README.md

+169 -1
+164
README.md
··· 1 + # minihtml - Simple HTML Generation 2 + 3 + ![PyPI - Version](https://img.shields.io/pypi/v/minihtml) 4 + 5 + The minihtml library provides tools to generate HTML from Python. The goal of 6 + minihtml is to enable you to write HTML mixed with presentation logic without 7 + obscuring the structure of the resulting HTML document, not unlike the 8 + experience with a text-based templating system such as [Jinja][jinja]. 9 + 10 + jinja: https://jinja.palletsprojects.com/ 11 + 12 + ## Installation 13 + 14 + Install the `minihtml` package from PyPI: 15 + 16 + pip install minihtml 17 + 18 + or 19 + 20 + uv add minihtml 21 + 22 + ## Example 23 + 24 + A basic "hello, world" example: 25 + 26 + ~~~python 27 + >>> from minihtml.tags import html, head, title, body, div, p, a, img 28 + >>> with html(lang="en") as elem: 29 + ... with head: 30 + ... title("hello, world!") 31 + ... with body: 32 + ... with div["#content main"]: 33 + ... p("Welcome to ", a(href="https://example.com/")("my website")) 34 + ... img(src="hello.png", alt="hello") 35 + ... 36 + <ElementNonEmpty title> 37 + <ElementNonEmpty p> 38 + <ElementEmpty img> 39 + 40 + >>> print(elem) 41 + <html lang="en"> 42 + <head> 43 + <title>hello, world!</title> 44 + </head> 45 + <body> 46 + <div id="content" class="main"> 47 + <p>Welcome to <a href="https://example.com/">my website</a></p> 48 + <img src="hello.png" alt="hello"> 49 + </div> 50 + </body> 51 + </html> 52 + 53 + ~~~ 54 + 55 + ## Prototypes and Elements 56 + 57 + The objects you import from `minihtml.tags` are called *prototypes*. You can 58 + think of them as HTML element factories. Prototypes are immutable. There are 59 + several ways you can interact with a prototype to produce an actual *element*: 60 + 61 + Calling the prototype produces an element: 62 + 63 + ~~~python 64 + >>> print(div) 65 + <PrototypeNonEmpty div> 66 + >>> elem = div() 67 + >>> repr(elem) 68 + '<ElementNonEmpty div>' 69 + >>> print(elem) 70 + <div></div> 71 + 72 + ~~~ 73 + 74 + Positional arguments get converted to children of the new element, and keyword 75 + arguments to attributes: 76 + 77 + ~~~python 78 + >>> print(div("text")) 79 + <div>text</div> 80 + >>> print(div(style="background: green")) 81 + <div style="background: green"></div> 82 + 83 + ~~~ 84 + 85 + ## Element Content and Attributes 86 + 87 + Content can be text, other elements or a mix of the two: 88 + 89 + ~~~python 90 + >>> from minihtml.tags import em 91 + >>> print(div("this is ", em("emphasized text"), ".")) 92 + <div>this is <em>emphasized text</em>.</div> 93 + 94 + ~~~ 95 + 96 + You can chain calls and all other operations on the element to modify it 97 + further. Each operation modifies the element in-place (elements are mutable). 98 + 99 + ~~~python 100 + >>> print(a(href="http://github.com")("github")) 101 + <a href="http://github.com">github</a> 102 + 103 + ~~~ 104 + 105 + Indexing is a shortcut to set the `class` and `id` attributes: 106 + 107 + ~~~python 108 + >>> print(div["hello"]) 109 + <div class="hello"></div> 110 + >>> print(div["#main"]) 111 + <div id="main"></div> 112 + >>> print(div["#main foo bar"]) 113 + <div id="main" class="foo bar"></div> 114 + >>> print(div["foo"]["bar"]) 115 + <div class="foo bar"></div> 116 + 117 + ~~~ 118 + 119 + Finally, you can build up a document declaratively by using elements or 120 + prototypes as context managers. New elements created within the context will be 121 + added as children to the parent element: 122 + 123 + ~~~python 124 + >>> from minihtml.tags import ul, li 125 + >>> with div["main"] as elem: 126 + ... with ul: 127 + ... for color in ("red", "green", "blue"): 128 + ... li(color) 129 + ... 130 + <ElementNonEmpty li> 131 + <ElementNonEmpty li> 132 + <ElementNonEmpty li> 133 + 134 + >>> print(elem) 135 + <div class="main"> 136 + <ul> 137 + <li>red</li> 138 + <li>green</li> 139 + <li>blue</li> 140 + </ul> 141 + </div> 142 + 143 + ~~~ 144 + 145 + Elements that are passed to another element as a positional argument are exempt 146 + from this, so you can mix the two styles: 147 + 148 + ~~~python 149 + >>> with div as elem: 150 + ... p(em("this is emphasized")) 151 + ... 152 + <ElementNonEmpty p> 153 + 154 + # The `em` element does *not* also end up as a child of `div`. 155 + >>> print(elem) 156 + <div> 157 + <p><em>this is emphasized</em></p> 158 + </div> 159 + 160 + ~~~ 161 + 162 + <!-- TODO document attribute name mangling --> 163 + <!-- TODO document components --> 164 + <!-- TODO document @template -->
+5 -1
justfile
··· 1 1 # Generate, format, typecheck and test code 2 - all: codegen format typecheck test 2 + all: codegen format typecheck test doctest 3 3 4 4 # Run code generation 5 5 codegen: ··· 19 19 uv run coverage run -m pytest 20 20 uv run coverage report 21 21 uv run coverage html 22 + 23 + # Run doctests 24 + doctest: 25 + uv run python -m doctest -o ELLIPSIS README.md 22 26 23 27 # Run tests when code changes (requires "watchexec") 24 28 watch: