htmlforge 0.0.1__tar.gz → 0.0.3__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,427 @@
1
+ Metadata-Version: 2.5
2
+ Name: htmlforge
3
+ Version: 0.0.3
4
+ Summary: Build web pages with Python, export to HTML with a single command
5
+ Project-URL: Homepage, https://github.com/htmlforge/htmlforge
6
+ Author: htmlforge contributors
7
+ License-Expression: MPL-2.0
8
+ License-File: LICENSE
9
+ Keywords: builder,html,page,static-site,web
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Classifier: Topic :: Text Processing :: Markup :: HTML
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+
19
+ # htmlforge
20
+
21
+ Build web pages with Python, export to HTML with a single command — like manim generates videos.
22
+
23
+ ```
24
+ manim scene.py MyScene → mp4
25
+ htmlforge page.py MyPage → html
26
+ htmlforge script app.py → js
27
+ ```
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install htmlforge
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### 1. Write a Python page script `mysite.py`
38
+
39
+ ```python
40
+ from htmlforge import Doc
41
+
42
+ page = Doc("My Site")
43
+
44
+ # Generate styles with Python kwargs (no CSS strings!)
45
+ page.style("body", background="#f5f5f5", font_family="sans-serif")
46
+ page.style(".py-card", border_radius="12px", box_shadow="0 2px 8px rgba(0,0,0,.1)")
47
+
48
+ # Build the page
49
+ page.heading("Welcome to My Site")
50
+ page.text("Built with **htmlforge**, supports *Markdown* syntax")
51
+
52
+ page.card("Features",
53
+ "40+ HTML components",
54
+ "Generate styles with Python code",
55
+ "Export HTML with one command",
56
+ )
57
+
58
+ page.table(
59
+ ["Feature", "Description"],
60
+ [
61
+ ["Components", "40+ HTML components"],
62
+ ["Built-in theme", "Works out of the box"],
63
+ ["CLI tools", "render / serve / watch / script"],
64
+ ],
65
+ )
66
+
67
+ page.button("Click me", on_click="alert('Hello!')", color="#4CAF50")
68
+ ```
69
+
70
+ ### 2. Run the command to export HTML
71
+
72
+ ```bash
73
+ # Basic render
74
+ htmlforge render mysite.py
75
+
76
+ # Specify output directory
77
+ htmlforge render mysite.py -o build
78
+
79
+ # Render and start a local server
80
+ htmlforge render mysite.py -s
81
+
82
+ # Watch for file changes and auto-rebuild
83
+ htmlforge render mysite.py -w
84
+
85
+ # Render a specific page (when file has multiple pages)
86
+ htmlforge render mysite.py MyPage
87
+ ```
88
+
89
+ ## CLI Commands
90
+
91
+ | Command | Description |
92
+ |---|---|
93
+ | `htmlforge render <file.py>` | Render all pages to `dist/` |
94
+ | `htmlforge render <file.py> -o <dir>` | Specify output directory |
95
+ | `htmlforge render <file.py> <page>` | Render only the specified page |
96
+ | `htmlforge render <file.py> -s` | Start HTTP server after render |
97
+ | `htmlforge render <file.py> -s -p 3000` | Specify server port |
98
+ | `htmlforge render <file.py> -w` | Watch for changes and auto-rebuild |
99
+ | `htmlforge render <file.py> -s -w` | Serve + watch, browser auto-reloads on change |
100
+ | `htmlforge script <file.py>` | Convert Python file to JavaScript |
101
+ | `htmlforge script <file.py> -o app.js` | Specify output JS filename |
102
+ | `htmlforge script <file.py> --helpers` | Include DOM helper functions |
103
+
104
+ ## Doc API — Ultra Simple
105
+
106
+ `Doc` is a high-level API designed for Python programmers who don't know HTML. It comes with a built-in theme — no CSS required to build great-looking pages.
107
+
108
+ ```python
109
+ from htmlforge import Doc
110
+
111
+ page = Doc("Title")
112
+
113
+ # Generate styles with Python kwargs (no CSS strings!)
114
+ page.style("body", background="linear-gradient(135deg, #667eea, #764ba2)", color="white")
115
+ page.style("h1", font_size="3rem", text_align="center")
116
+ page.style(".py-card", background="rgba(255,255,255,.15)", backdrop_filter="blur(10px)")
117
+
118
+ # Content (supports **Markdown** syntax)
119
+ page.heading("Hello World")
120
+ page.text("Supports **bold**, *italic*, `code`, [links](url)")
121
+
122
+ page.card("Card Title", "Card content, **supports Markdown**")
123
+ page.list(["Item A", "Item B", "Item C"])
124
+ page.table(["Col 1", "Col 2"], [["a", "b"], ["c", "d"]])
125
+ page.button("Button", on_click="alert('hi')", color="#e74c3c")
126
+ page.input("Type here...", name="name")
127
+ page.select(["Option A", "Option B", "Option C"])
128
+ page.code("print('hello')", lang="python")
129
+ page.details("Collapsible", "Hidden content...")
130
+ page.nav(("Home", "/"), ("About", "/about"))
131
+ page.divider()
132
+
133
+ # Row / grid
134
+ page.row(Doc.make_card("A"), Doc.make_card("B"), Doc.make_card("C"))
135
+
136
+ # Head / SEO
137
+ page.set_meta(description="My site", author="Me", keywords="python, web")
138
+ page.meta("My Site", property="og:title")
139
+ page.favicon("favicon.ico")
140
+ ```
141
+
142
+ ## Block-level Markdown
143
+
144
+ `page.markdown()` renders full Markdown — not just inline — including headings,
145
+ lists, fenced code, tables, blockquotes and rules:
146
+
147
+ ```python
148
+ page.markdown('''
149
+ # Release Notes
150
+
151
+ - **Fast** rendering
152
+ - Zero dependencies
153
+
154
+ | Command | Purpose |
155
+ |---------|---------|
156
+ | render | Build HTML |
157
+
158
+ > Tip: pull in a whole README with `markdown_file`.
159
+ ''')
160
+
161
+ # Or load straight from a file
162
+ page.markdown_file("README.md")
163
+ ```
164
+
165
+ ## Static Assets
166
+
167
+ `page.asset()` registers a local file (image / css / js). It returns the URL to
168
+ reference, and `htmlforge render` copies the file into `dist/assets/` so the
169
+ exported site is fully self-contained:
170
+
171
+ ```python
172
+ logo = page.asset("logo.png") # → "assets/logo.png"
173
+ page.image(logo, "Logo")
174
+
175
+ page.add_stylesheet(page.asset("theme.css"))
176
+ page.js_file(page.asset("app.js"))
177
+ ```
178
+
179
+ After `htmlforge render site.py`, `dist/` holds both `index.html` and
180
+ `assets/logo.png`.
181
+
182
+ ## Layouts — Share Header / Footer
183
+
184
+ A `Layout` is a reusable page skeleton: every page built from it shares the
185
+ same head tags, header and footer.
186
+
187
+ ```python
188
+ from htmlforge import Doc, Layout, Div, H1, P
189
+
190
+ site = Layout(
191
+ head=lambda doc: doc.set_meta(author="Me"),
192
+ header=lambda doc: Div(class_="top").add(H1("My Site")),
193
+ footer=lambda doc: P(f"© 2026 — {doc.title}"),
194
+ )
195
+
196
+ home = site.page("Home") # → a Doc with the shared chrome
197
+ home.heading("Welcome")
198
+
199
+ about = site.page("About")
200
+ about.text("About us...")
201
+ ```
202
+
203
+ ```bash
204
+ htmlforge render site.py
205
+ # → dist/home.html, dist/about.html (both wrapped in the shared header/footer)
206
+ ```
207
+
208
+ Each slot accepts an `Element`, a list of elements, or a callable receiving the
209
+ page — so you can highlight the current nav item, inject the title, and more.
210
+
211
+ ## JavaScript & DOM Interaction
212
+
213
+ ### Bind events to elements
214
+
215
+ ```python
216
+ from htmlforge import Button
217
+
218
+ btn = Button("Click me")
219
+
220
+ # Bind raw JavaScript
221
+ btn.on("click", "alert('hello')")
222
+ btn.on("mouseover", "this.style.color='red'")
223
+
224
+ # Bind Python code (auto-converted to JS)
225
+ btn.on_py("click", "print('clicked!')")
226
+ # → onclick="console.log('clicked!');"
227
+ ```
228
+
229
+ ### Add JavaScript to the page
230
+
231
+ ```python
232
+ # Raw JavaScript
233
+ page.js("document.title = 'Dynamic'")
234
+ page.js("function greet() { alert('hello'); }")
235
+
236
+ # External JS file
237
+ page.js_file("app.js")
238
+
239
+ # Python code → JavaScript (auto-converted)
240
+ page.py_script('''
241
+ def greet(name):
242
+ print(f"Hello {name}")
243
+ greet("World")
244
+ ''')
245
+
246
+ # Run on page load (DOMContentLoaded)
247
+ page.on_ready("console.log('Page loaded!')")
248
+ page.on_ready_py('print("Ready!")')
249
+ ```
250
+
251
+ ### DOM manipulation helpers
252
+
253
+ ```python
254
+ # Get element properties (returns JS expressions)
255
+ val = page.get_attr("#input", "value") # → document.querySelector('#input').getAttribute('value')
256
+ text = page.get_text("#title") # → document.querySelector('#title').textContent
257
+ html = page.get_html("#content") # → document.querySelector('#content').innerHTML
258
+ value = page.get_value("#myInput") # → document.querySelector('#myInput').value
259
+
260
+ # Set element properties (generates JS scripts)
261
+ page.set_attr("#el", "class", "active")
262
+ page.set_text("#title", "New Title")
263
+ page.set_html("#content", "<b>bold</b>")
264
+ page.set_value("#input", "hello")
265
+
266
+ # Visibility
267
+ page.show("#modal")
268
+ page.hide("#loading")
269
+ page.toggle("#sidebar")
270
+
271
+ # CSS class manipulation
272
+ page.add_class("#el", "active")
273
+ page.remove_class("#el", "hidden")
274
+ page.toggle_class("#menu", "open")
275
+ ```
276
+
277
+ > **Security:** every selector and value passed to these helpers is
278
+ > automatically escaped, so it is safe to feed them dynamic / user data.
279
+
280
+ ## Python → JavaScript Transpiler
281
+
282
+ Use `htmlforge script` to convert Python files to JavaScript:
283
+
284
+ ```bash
285
+ htmlforge script app.py # → app.js
286
+ htmlforge script app.py -o out.js # → out.js
287
+ htmlforge script app.py --helpers # include DOM helpers ($, $$, getAttr, setAttr, ...)
288
+ ```
289
+
290
+ Or use it programmatically:
291
+
292
+ ```python
293
+ from htmlforge import py_to_js, convert_file
294
+
295
+ # Convert Python source string to JS
296
+ js = py_to_js("""
297
+ def greet(name):
298
+ print(f"Hello {name}")
299
+
300
+ for i in range(10):
301
+ greet("World")
302
+ """)
303
+
304
+ # Convert a file
305
+ convert_file("app.py", "app.js")
306
+ ```
307
+
308
+ ### Supported Python → JS conversions
309
+
310
+ | Python | JavaScript |
311
+ |---|---|
312
+ | `print(...)` | `console.log(...)` |
313
+ | `input(...)` | `prompt(...)` |
314
+ | `def f(x):` | `function f(x) {` |
315
+ | `class Foo(Bar):` | `class Foo extends Bar {` |
316
+ | `self.x` | `this.x` |
317
+ | `if/elif/else:` | `if/else if/else {` |
318
+ | `for i in range(n):` | `for (let i = 0; i < n; i++) {` |
319
+ | `for x in items:` | `for (let x of items) {` |
320
+ | `while cond:` | `while (cond) {` |
321
+ | `try/except/finally:` | `try/catch/finally {` |
322
+ | `True / False / None` | `true / false / null` |
323
+ | `and / or / not` | `&& / \|\| / !` |
324
+ | `a // b` | `Math.floor(a / b)` |
325
+ | `a ** b` | `Math.pow(a, b)` |
326
+ | `len(x)` | `x.length` |
327
+ | `f"Hello {name}"` | `` `Hello ${name}` `` |
328
+ | `lambda x: x + 1` | `(x) => x + 1` |
329
+ | `x in items` | `items.includes(x)` |
330
+ | `sorted(items)` | `[...items].sort()` |
331
+
332
+ ## Page API — Fine-grained Control
333
+
334
+ Use `Page` + the component system when you need more control:
335
+
336
+ ```python
337
+ from htmlforge import Page, H1, P, Div, Table, Button
338
+
339
+ page = Page("Title", lang="en")
340
+ page.add_css("body { font-family: sans-serif; }")
341
+ page.add(
342
+ H1("Welcome"),
343
+ P("Hello World"),
344
+ Div(class_="card").add(
345
+ H1("Features", level=2),
346
+ P("Content..."),
347
+ ),
348
+ )
349
+ ```
350
+
351
+ ### Element — Chainable API
352
+
353
+ ```python
354
+ div = Div(id="main", class_="wrapper")
355
+ div.add(H1("Title"), P("Content")) # Add child elements
356
+ div.css(color="red", font_size="16px") # Set inline styles (camelCase → kebab-case)
357
+ div.attr("data-id", "123") # Set attributes
358
+ div.on("click", "alert('clicked')") # Bind JS event
359
+ div.on_py("click", "print('clicked')") # Bind Python event (auto-converted)
360
+ html = div.render() # → HTML string
361
+ ```
362
+
363
+ ### Components
364
+
365
+ | Category | Components |
366
+ |---|---|
367
+ | **Structure** | `Div`, `Section`, `Header`, `Footer`, `Nav`, `Main`, `Article`, `Aside`, `Span`, `Container` |
368
+ | **Text** | `H1`-`H6`, `P`, `Strong`, `Em`, `Blockquote`, `Code`, `Pre`, `Small`, `Mark` |
369
+ | **Links/Media** | `Link`, `Img`, `Video`, `Audio`, `Iframe`, `Canvas` |
370
+ | **Lists** | `Ul`, `Ol`, `Li`, `List` |
371
+ | **Tables** | `Table`, `Thead`, `Tbody`, `Tr`, `Th`, `Td`, `Caption` |
372
+ | **Forms** | `Form`, `Input`, `Textarea`, `Select`, `Option`, `Checkbox`, `Radio`, `Label`, `Button` |
373
+ | **Other** | `Hr`, `Br`, `Text`, `Progress`, `Details`, `Summary` |
374
+
375
+ ## Example: Multi-page Site
376
+
377
+ ```python
378
+ # site.py
379
+ from htmlforge import Doc
380
+
381
+ home = Doc("Home")
382
+ home.heading("Home")
383
+ home.text("Welcome to the home page")
384
+
385
+ about = Doc("About")
386
+ about.heading("About Us")
387
+ about.text("This is the about page")
388
+
389
+ blog = Doc("Blog")
390
+ blog.heading("Blog")
391
+ blog.text("Latest posts...")
392
+ ```
393
+
394
+ ```bash
395
+ htmlforge render site.py
396
+ # → dist/home.html, dist/about.html, dist/blog.html
397
+ ```
398
+
399
+ ## Changelog
400
+
401
+ ### 0.0.3
402
+
403
+ **Security**
404
+
405
+ - `_esc` now also escapes single quotes; Markdown links reject
406
+ `javascript:` / `vbscript:` / `data:text/html` URLs.
407
+ - All DOM helpers (`set_text`, `set_attr`, `set_value`, `add_class`, ...)
408
+ escape their selectors and values, preventing JS injection and syntax
409
+ breakage from dynamic data.
410
+
411
+ **New**
412
+
413
+ - **SEO / meta:** `page.meta()`, `page.set_meta()`, `page.favicon()`.
414
+ - **Block-level Markdown:** `page.markdown()` / `page.markdown_file()` —
415
+ headings, lists, fenced code, tables, blockquotes and rules.
416
+ - **Static assets:** `page.asset()` copies local files into `dist/assets/`
417
+ at build time, so the exported site is self-contained.
418
+ - **Layouts:** reusable `Layout` shares head / header / footer across pages.
419
+ - **Live reload:** `htmlforge render <file> -s -w` auto-refreshes the browser.
420
+
421
+ **Fixed**
422
+
423
+ - `Video` builds its `<source>` child correctly (no more fragile `__new__`).
424
+
425
+ ## License
426
+
427
+ [MPL-2.0](LICENSE)