without-html 0.0.0__tar.gz → 0.0.6__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.
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: without-html
3
+ Version: 0.0.6
4
+ Summary: HTML as immutable Python values: build a node tree, render it to a string.
5
+ Author: Josh Karpel
6
+ Author-email: Josh Karpel <josh.karpel@gmail.com>
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Classifier: Typing :: Typed
16
+ Requires-Dist: markupsafe>=3
17
+ Requires-Python: >=3.14
18
+ Description-Content-Type: text/markdown
19
+
20
+ # without-html
21
+
22
+ HTML as immutable Python values. Build a node tree with plain function calls,
23
+ render it to a string with a pure function. No template language, no loader
24
+ search path, no ambient application object, and no dependency on anything else in
25
+ the workspace: this is a value library that happens to be useful next to
26
+ [`without-web`](../without_web).
27
+
28
+ ```python
29
+ from without_asgi import Response, html_content
30
+ from without_html import render, table, tbody, td, th, thead, tr
31
+
32
+
33
+ def runs_table(runs): # a component is a function that returns a node
34
+ return table(
35
+ cls=["table", "table-striped"],
36
+ children=[
37
+ thead(children=tr(children=[th(children="run"), th(children="status")])),
38
+ tbody(children=(run_row(run) for run in runs)),
39
+ ],
40
+ )
41
+
42
+
43
+ def run_row(run):
44
+ return tr(attrs={"id": f"run-{run.id}"}, children=[td(children=run.id), td(children=run.status)])
45
+
46
+
47
+ Response.from_content(200, html_content(render(runs_table(runs))))
48
+ ```
49
+
50
+ Escaping is a type rather than a setting: text is escaped on the way in, and
51
+ `Markup` (MarkupSafe's own, kept under its own name, so anything Jinja or tdom
52
+ produced is already one) is the only thing that renders verbatim. The element
53
+ constructors carry HTML's own constraints in their signatures, so a void element
54
+ with children and an unescaped `<script>` body are type errors rather than runtime
55
+ surprises.
56
+
57
+ `render` returns the whole string; `render_chunks` walks the same tree and yields
58
+ the same bytes a chunk at a time, so a large page starts reaching a client while
59
+ the rest of it is still being built.
60
+
61
+ The tree is the interface, not the string: a component can be rendered whole for a
62
+ page or on its own for a fragment, and syntax sugar can be layered on later without
63
+ changing anything written against it.
64
+
65
+ See the
66
+ [`without-html` guide](https://without.help/without-html/)
67
+ (with the [API reference](https://without.help/without-html/reference/))
68
+ for nodes and escaping, the attribute rules, custom elements, and what this
69
+ deliberately does not do.
@@ -0,0 +1,50 @@
1
+ # without-html
2
+
3
+ HTML as immutable Python values. Build a node tree with plain function calls,
4
+ render it to a string with a pure function. No template language, no loader
5
+ search path, no ambient application object, and no dependency on anything else in
6
+ the workspace: this is a value library that happens to be useful next to
7
+ [`without-web`](../without_web).
8
+
9
+ ```python
10
+ from without_asgi import Response, html_content
11
+ from without_html import render, table, tbody, td, th, thead, tr
12
+
13
+
14
+ def runs_table(runs): # a component is a function that returns a node
15
+ return table(
16
+ cls=["table", "table-striped"],
17
+ children=[
18
+ thead(children=tr(children=[th(children="run"), th(children="status")])),
19
+ tbody(children=(run_row(run) for run in runs)),
20
+ ],
21
+ )
22
+
23
+
24
+ def run_row(run):
25
+ return tr(attrs={"id": f"run-{run.id}"}, children=[td(children=run.id), td(children=run.status)])
26
+
27
+
28
+ Response.from_content(200, html_content(render(runs_table(runs))))
29
+ ```
30
+
31
+ Escaping is a type rather than a setting: text is escaped on the way in, and
32
+ `Markup` (MarkupSafe's own, kept under its own name, so anything Jinja or tdom
33
+ produced is already one) is the only thing that renders verbatim. The element
34
+ constructors carry HTML's own constraints in their signatures, so a void element
35
+ with children and an unescaped `<script>` body are type errors rather than runtime
36
+ surprises.
37
+
38
+ `render` returns the whole string; `render_chunks` walks the same tree and yields
39
+ the same bytes a chunk at a time, so a large page starts reaching a client while
40
+ the rest of it is still being built.
41
+
42
+ The tree is the interface, not the string: a component can be rendered whole for a
43
+ page or on its own for a fragment, and syntax sugar can be layered on later without
44
+ changing anything written against it.
45
+
46
+ See the
47
+ [`without-html` guide](https://without.help/without-html/)
48
+ (with the [API reference](https://without.help/without-html/reference/))
49
+ for nodes and escaping, the attribute rules, custom elements, and what this
50
+ deliberately does not do.
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.12.3,<0.13"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "without-html"
7
+ version = "0.0.6"
8
+ description = "HTML as immutable Python values: build a node tree, render it to a string."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.14"
12
+ classifiers = [
13
+ "Development Status :: 2 - Pre-Alpha",
14
+ "Intended Audience :: Developers",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Programming Language :: Python :: 3.14",
19
+ "Topic :: Software Development :: Libraries",
20
+ "Typing :: Typed",
21
+ ]
22
+ dependencies = ["markupsafe>=3"]
23
+
24
+ [[project.authors]]
25
+ name = "Josh Karpel"
26
+ email = "josh.karpel@gmail.com"
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.12.3,<0.13"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "without-html"
7
+ version = "0.0.6"
8
+ description = "HTML as immutable Python values: build a node tree, render it to a string."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ authors = [
12
+ { name = "Josh Karpel", email = "josh.karpel@gmail.com" },
13
+ ]
14
+ requires-python = ">=3.14"
15
+ classifiers = [
16
+ "Development Status :: 2 - Pre-Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Programming Language :: Python :: 3.14",
22
+ "Topic :: Software Development :: Libraries",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = [
26
+ "markupsafe>=3",
27
+ ]
@@ -0,0 +1,296 @@
1
+ from markupsafe import Markup
2
+ from markupsafe import escape
3
+
4
+ from without_html.markup import DOCTYPE
5
+ from without_html.markup import SupportsHtml
6
+ from without_html.nodes import AnyElement
7
+ from without_html.nodes import Attributes
8
+ from without_html.nodes import AttributeValue
9
+ from without_html.nodes import Child
10
+ from without_html.nodes import ClassNames
11
+ from without_html.nodes import Element
12
+ from without_html.nodes import ElementConstructor
13
+ from without_html.nodes import Node
14
+ from without_html.nodes import RawTextElementConstructor
15
+ from without_html.nodes import VoidElement
16
+ from without_html.nodes import VoidElementConstructor
17
+ from without_html.nodes import element
18
+ from without_html.nodes import element_type
19
+ from without_html.nodes import void_element_type
20
+ from without_html.render import render
21
+ from without_html.render import render_chunks
22
+
23
+ # The element constructors are re-exported here so that `from without_html import div`
24
+ # works; the block is generated, and isort is held off it so that the generator and the
25
+ # formatter do not disagree about where it starts.
26
+ # isort: off
27
+ # [[[cog import cog; from tags import emit_imports; cog.outl(emit_imports()) ]]]
28
+ from without_html.elements import a
29
+ from without_html.elements import abbr
30
+ from without_html.elements import address
31
+ from without_html.elements import area
32
+ from without_html.elements import article
33
+ from without_html.elements import aside
34
+ from without_html.elements import audio
35
+ from without_html.elements import b
36
+ from without_html.elements import base
37
+ from without_html.elements import bdi
38
+ from without_html.elements import bdo
39
+ from without_html.elements import blockquote
40
+ from without_html.elements import body
41
+ from without_html.elements import br
42
+ from without_html.elements import button
43
+ from without_html.elements import canvas
44
+ from without_html.elements import caption
45
+ from without_html.elements import cite
46
+ from without_html.elements import code
47
+ from without_html.elements import col
48
+ from without_html.elements import colgroup
49
+ from without_html.elements import data
50
+ from without_html.elements import datalist
51
+ from without_html.elements import dd
52
+ from without_html.elements import del_
53
+ from without_html.elements import details
54
+ from without_html.elements import dfn
55
+ from without_html.elements import dialog
56
+ from without_html.elements import div
57
+ from without_html.elements import dl
58
+ from without_html.elements import dt
59
+ from without_html.elements import em
60
+ from without_html.elements import embed
61
+ from without_html.elements import fieldset
62
+ from without_html.elements import figcaption
63
+ from without_html.elements import figure
64
+ from without_html.elements import footer
65
+ from without_html.elements import form
66
+ from without_html.elements import h1
67
+ from without_html.elements import h2
68
+ from without_html.elements import h3
69
+ from without_html.elements import h4
70
+ from without_html.elements import h5
71
+ from without_html.elements import h6
72
+ from without_html.elements import head
73
+ from without_html.elements import header
74
+ from without_html.elements import hgroup
75
+ from without_html.elements import hr
76
+ from without_html.elements import html
77
+ from without_html.elements import i
78
+ from without_html.elements import iframe
79
+ from without_html.elements import img
80
+ from without_html.elements import input_
81
+ from without_html.elements import ins
82
+ from without_html.elements import kbd
83
+ from without_html.elements import label
84
+ from without_html.elements import legend
85
+ from without_html.elements import li
86
+ from without_html.elements import link
87
+ from without_html.elements import main
88
+ from without_html.elements import map_
89
+ from without_html.elements import mark
90
+ from without_html.elements import menu
91
+ from without_html.elements import meta
92
+ from without_html.elements import meter
93
+ from without_html.elements import nav
94
+ from without_html.elements import noembed
95
+ from without_html.elements import noframes
96
+ from without_html.elements import noscript
97
+ from without_html.elements import object_
98
+ from without_html.elements import ol
99
+ from without_html.elements import optgroup
100
+ from without_html.elements import option
101
+ from without_html.elements import output
102
+ from without_html.elements import p
103
+ from without_html.elements import picture
104
+ from without_html.elements import pre
105
+ from without_html.elements import progress
106
+ from without_html.elements import q
107
+ from without_html.elements import rp
108
+ from without_html.elements import rt
109
+ from without_html.elements import ruby
110
+ from without_html.elements import s
111
+ from without_html.elements import samp
112
+ from without_html.elements import script
113
+ from without_html.elements import search
114
+ from without_html.elements import section
115
+ from without_html.elements import select
116
+ from without_html.elements import slot
117
+ from without_html.elements import small
118
+ from without_html.elements import source
119
+ from without_html.elements import span
120
+ from without_html.elements import strong
121
+ from without_html.elements import style
122
+ from without_html.elements import sub
123
+ from without_html.elements import summary
124
+ from without_html.elements import sup
125
+ from without_html.elements import svg
126
+ from without_html.elements import table
127
+ from without_html.elements import tbody
128
+ from without_html.elements import td
129
+ from without_html.elements import template
130
+ from without_html.elements import textarea
131
+ from without_html.elements import tfoot
132
+ from without_html.elements import th
133
+ from without_html.elements import thead
134
+ from without_html.elements import time
135
+ from without_html.elements import title
136
+ from without_html.elements import tr
137
+ from without_html.elements import track
138
+ from without_html.elements import u
139
+ from without_html.elements import ul
140
+ from without_html.elements import var
141
+ from without_html.elements import video
142
+ from without_html.elements import wbr
143
+ from without_html.elements import xmp
144
+ # [[[end]]]
145
+ # isort: on
146
+
147
+ __all__ = [
148
+ # [[[cog
149
+ # import cog
150
+ # from tags import emit_names
151
+ # cog.outl(emit_names([
152
+ # "DOCTYPE", "AttributeValue", "Attributes", "Child", "ClassNames", "Element",
153
+ # "Node", "Markup", "SupportsHtml", "AnyElement", "VoidElement",
154
+ # "ElementConstructor", "RawTextElementConstructor", "VoidElementConstructor",
155
+ # "element", "element_type", "void_element_type",
156
+ # "escape", "render", "render_chunks",
157
+ # ]))
158
+ # ]]]
159
+ "DOCTYPE",
160
+ "AnyElement",
161
+ "AttributeValue",
162
+ "Attributes",
163
+ "Child",
164
+ "ClassNames",
165
+ "Element",
166
+ "ElementConstructor",
167
+ "Markup",
168
+ "Node",
169
+ "RawTextElementConstructor",
170
+ "SupportsHtml",
171
+ "VoidElement",
172
+ "VoidElementConstructor",
173
+ "a",
174
+ "abbr",
175
+ "address",
176
+ "area",
177
+ "article",
178
+ "aside",
179
+ "audio",
180
+ "b",
181
+ "base",
182
+ "bdi",
183
+ "bdo",
184
+ "blockquote",
185
+ "body",
186
+ "br",
187
+ "button",
188
+ "canvas",
189
+ "caption",
190
+ "cite",
191
+ "code",
192
+ "col",
193
+ "colgroup",
194
+ "data",
195
+ "datalist",
196
+ "dd",
197
+ "del_",
198
+ "details",
199
+ "dfn",
200
+ "dialog",
201
+ "div",
202
+ "dl",
203
+ "dt",
204
+ "element",
205
+ "element_type",
206
+ "em",
207
+ "embed",
208
+ "escape",
209
+ "fieldset",
210
+ "figcaption",
211
+ "figure",
212
+ "footer",
213
+ "form",
214
+ "h1",
215
+ "h2",
216
+ "h3",
217
+ "h4",
218
+ "h5",
219
+ "h6",
220
+ "head",
221
+ "header",
222
+ "hgroup",
223
+ "hr",
224
+ "html",
225
+ "i",
226
+ "iframe",
227
+ "img",
228
+ "input_",
229
+ "ins",
230
+ "kbd",
231
+ "label",
232
+ "legend",
233
+ "li",
234
+ "link",
235
+ "main",
236
+ "map_",
237
+ "mark",
238
+ "menu",
239
+ "meta",
240
+ "meter",
241
+ "nav",
242
+ "noembed",
243
+ "noframes",
244
+ "noscript",
245
+ "object_",
246
+ "ol",
247
+ "optgroup",
248
+ "option",
249
+ "output",
250
+ "p",
251
+ "picture",
252
+ "pre",
253
+ "progress",
254
+ "q",
255
+ "render",
256
+ "render_chunks",
257
+ "rp",
258
+ "rt",
259
+ "ruby",
260
+ "s",
261
+ "samp",
262
+ "script",
263
+ "search",
264
+ "section",
265
+ "select",
266
+ "slot",
267
+ "small",
268
+ "source",
269
+ "span",
270
+ "strong",
271
+ "style",
272
+ "sub",
273
+ "summary",
274
+ "sup",
275
+ "svg",
276
+ "table",
277
+ "tbody",
278
+ "td",
279
+ "template",
280
+ "textarea",
281
+ "tfoot",
282
+ "th",
283
+ "thead",
284
+ "time",
285
+ "title",
286
+ "tr",
287
+ "track",
288
+ "u",
289
+ "ul",
290
+ "var",
291
+ "video",
292
+ "void_element_type",
293
+ "wbr",
294
+ "xmp",
295
+ # [[[end]]]
296
+ ]