htmlforge 0.0.1__py3-none-any.whl

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.
htmlforge/simple.py ADDED
@@ -0,0 +1,331 @@
1
+ """htmlforge simple — High-level API with built-in themes.
2
+
3
+ Designed for Python programmers who don't know HTML.
4
+ Everything looks great out of the box — no CSS required.
5
+
6
+ Usage::
7
+
8
+ from htmlforge import Doc
9
+
10
+ page = Doc("My Site")
11
+ page.heading("Hello World")
12
+ page.text("Some **bold** and *italic* text")
13
+ page.button("Click me", on_click="alert('hi')")
14
+ """
15
+
16
+ from __future__ import annotations
17
+ from typing import Union
18
+ import re
19
+
20
+ from htmlforge.core import (
21
+ Page, Element, Div, Hr, Text,
22
+ H1, H2, H3, P, Strong, Em, Link,
23
+ Ul, Ol, Li, Img,
24
+ Table, Thead, Tbody, Tr, Th, Td,
25
+ Input, Textarea, Select, Option, Button as _Button,
26
+ Details, Summary, Code, Pre, _to_kebab,
27
+ )
28
+
29
+
30
+ # ============================================================
31
+ # Default Theme — clean, modern, responsive
32
+ # ============================================================
33
+
34
+ _DEFAULT_CSS = """
35
+ *, *::before, *::after { box-sizing: border-box; }
36
+ body {
37
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
38
+ 'Helvetica Neue', Arial, sans-serif;
39
+ line-height: 1.7; max-width: 52rem;
40
+ margin: 0 auto; padding: 2rem 1.5rem;
41
+ color: #1d1d1f; background: #fff;
42
+ }
43
+ h1 { font-size: 2.2rem; font-weight: 700; margin: 2rem 0 .8rem; }
44
+ h2 { font-size: 1.6rem; font-weight: 600; margin: 1.8rem 0 .6rem; }
45
+ h3 { font-size: 1.25rem; font-weight: 600; margin: 1.4rem 0 .5rem; }
46
+ p { margin: 0 0 1rem; }
47
+ a { color: #0066cc; text-decoration: none; }
48
+ a:hover { text-decoration: underline; }
49
+ img { max-width: 100%; height: auto; border-radius: 8px; }
50
+ /* ---- button ---- */
51
+ .py-btn {
52
+ display: inline-block; padding: .55rem 1.4rem;
53
+ font-size: .95rem; font-weight: 500;
54
+ border: 2px solid #0066cc; border-radius: 8px;
55
+ background: #0066cc; color: #fff;
56
+ cursor: pointer; transition: all .2s;
57
+ margin: .25rem .25rem .25rem 0;
58
+ }
59
+ .py-btn:hover { background: #0052a3; border-color: #0052a3; }
60
+ /* ---- table ---- */
61
+ table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
62
+ th, td { padding: .6rem .8rem; text-align: left;
63
+ border-bottom: 1px solid #e5e7eb; }
64
+ th { background: #f8f9fa; font-weight: 600; }
65
+ tr:hover td { background: #f5f7fa; }
66
+ /* ---- card ---- */
67
+ .py-card {
68
+ border: 1px solid #e5e7eb; border-radius: 12px;
69
+ padding: 1.4rem; margin: 1rem 0;
70
+ transition: box-shadow .2s;
71
+ }
72
+ .py-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,.06); }
73
+ .py-card h3:first-child,
74
+ .py-card h2:first-child { margin-top: 0; }
75
+ /* ---- grid ---- */
76
+ .py-grid { display: flex; gap: 1rem; flex-wrap: wrap; }
77
+ .py-grid > * { flex: 1 1 16rem; min-width: 0; }
78
+ /* ---- input / textarea ---- */
79
+ .py-input, .py-textarea {
80
+ display: block; width: 100%;
81
+ padding: .5rem .75rem; font-size: .95rem;
82
+ border: 1px solid #d1d5db; border-radius: 8px;
83
+ margin: .4rem 0; outline: none;
84
+ transition: border-color .2s;
85
+ }
86
+ .py-input:focus, .py-textarea:focus { border-color: #0066cc; }
87
+ /* ---- select ---- */
88
+ .py-select {
89
+ display: block; width: 100%;
90
+ padding: .5rem .75rem; font-size: .95rem;
91
+ border: 1px solid #d1d5db; border-radius: 8px;
92
+ margin: .4rem 0;
93
+ }
94
+ /* ---- code ---- */
95
+ pre { background: #f4f4f5; padding: 1rem; border-radius: 8px;
96
+ overflow-x: auto; font-size: .9rem; }
97
+ code { font-family: 'Cascadia Code', 'Fira Code', Consolas, monospace; }
98
+ p code { background: #f4f4f5; padding: .15rem .3rem; border-radius: 4px; }
99
+ /* ---- list ---- */
100
+ ul, ol { padding-left: 1.5rem; margin: 0 0 1rem; }
101
+ li { margin: .2rem 0; }
102
+ /* ---- details ---- */
103
+ details { margin: .5rem 0; }
104
+ details > summary {
105
+ cursor: pointer; font-weight: 500;
106
+ padding: .5rem 0; user-select: none;
107
+ }
108
+ details > summary:hover { color: #0066cc; }
109
+ /* ---- nav ---- */
110
+ .py-nav {
111
+ display: flex; gap: 1.5rem; padding: .5rem 0;
112
+ margin-bottom: 1rem; border-bottom: 1px solid #e5e7eb;
113
+ }
114
+ /* ---- hr ---- */
115
+ hr { border: none; border-top: 1px solid #e5e7eb; margin: 2rem 0; }
116
+ """.strip()
117
+
118
+
119
+ # ============================================================
120
+ # Markdown inline helpers
121
+ # ============================================================
122
+
123
+ def _md(s: str) -> str:
124
+ """Convert inline Markdown to HTML."""
125
+ s = re.sub(r'`(.+?)`', r'<code>\1</code>', s)
126
+ s = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', s)
127
+ s = re.sub(r'\*(.+?)\*', r'<em>\1</em>', s)
128
+ s = re.sub(r'\[(.+?)\]\((.+?)\)', r'<a href="\2">\1</a>', s)
129
+ return s
130
+
131
+
132
+ # ============================================================
133
+ # Doc — the high-level page builder
134
+ # ============================================================
135
+
136
+ class Doc:
137
+ """Build a styled web page using only Python — no HTML needed.
138
+
139
+ Comparison::
140
+
141
+ # Low-level (core.py) # High-level (simple.py)
142
+ page = Page("Site") page = Doc("Site")
143
+ page.add_css("body{...}") # theme built-in
144
+ page.add(H1("Hi")) page.heading("Hi")
145
+ page.add(P("text")) page.text("text")
146
+ """
147
+
148
+ def __init__(self, title: str = "Untitled", *,
149
+ lang: str = "zh", theme: bool = True):
150
+ self._page = Page(title, lang=lang)
151
+ if theme:
152
+ self._page.add_css(_DEFAULT_CSS)
153
+
154
+ # ---- delegate to Page ----
155
+
156
+ def add_css(self, css: str) -> Doc:
157
+ """Add extra CSS rules."""
158
+ self._page.add_css(css)
159
+ return self
160
+
161
+ def add_script(self, code: str = "", src: str = "") -> Doc:
162
+ """Add JavaScript (inline code or external src)."""
163
+ self._page.add_script(code, src)
164
+ return self
165
+
166
+ def add_raw(self, html: str) -> Doc:
167
+ """Insert raw HTML string."""
168
+ self._page.add_raw(html)
169
+ return self
170
+
171
+ def add(self, *children: Union[Element, str]) -> Doc:
172
+ """Add raw Elements or text (auto-wraps strings in <p>)."""
173
+ for c in children:
174
+ if isinstance(c, str):
175
+ self._page.add(P(Text(_md(c))))
176
+ else:
177
+ self._page.add(c)
178
+ return self
179
+
180
+ # ---- text & headings ----
181
+
182
+ def heading(self, text: str, level: int = 1) -> Doc:
183
+ """Add a heading (level 1-6)."""
184
+ tags = {1: H1, 2: H2, 3: H3}
185
+ return self.add(tags.get(level, H3)(text))
186
+
187
+ def text(self, content: str) -> Doc:
188
+ """Add a paragraph. Supports **bold**, *italic*, `code`, [link](url)."""
189
+ self._page.add(P(Text(_md(content))))
190
+ return self
191
+
192
+ def link(self, text: str, url: str) -> Doc:
193
+ """Add a hyperlink."""
194
+ return self.add(Link(text, url))
195
+
196
+ def image(self, src: str, alt: str = "") -> Doc:
197
+ """Add an image (auto-responsive)."""
198
+ return self.add(Img(src, alt))
199
+
200
+ # ---- layout ----
201
+
202
+ def card(self, title: str = "", *content: Union[Element, str]) -> Doc:
203
+ """Add a card with optional title and content."""
204
+ return self.add(self.make_card(title, *content))
205
+
206
+ @staticmethod
207
+ def make_card(title: str = "", *content: Union[Element, str]) -> Div:
208
+ """Create a card element (use with ``row`` / ``grid``)."""
209
+ d = Div(class_="py-card")
210
+ if title:
211
+ d.add(H3(title))
212
+ for c in content:
213
+ if isinstance(c, str):
214
+ d.add(P(Text(_md(c))))
215
+ else:
216
+ d.add(c)
217
+ return d
218
+
219
+ def row(self, *children: Element) -> Doc:
220
+ """Arrange elements side-by-side in a flex row."""
221
+ return self.add(Div(class_="py-grid").add(*children))
222
+
223
+ def grid(self, *children: Element, cols: int = 0) -> Doc:
224
+ """Responsive grid layout. ``cols=0`` means auto-fit."""
225
+ d = Div(class_="py-grid")
226
+ if cols:
227
+ d.css(gridTemplateColumns=f"repeat({cols}, 1fr)")
228
+ d.add(*children)
229
+ return self.add(d)
230
+
231
+ # ---- interactive ----
232
+
233
+ def button(self, label: str, on_click: str = "", *,
234
+ color: str = "") -> Doc:
235
+ """Add a button. Set ``color`` for custom background."""
236
+ btn = _Button(class_="py-btn")
237
+ btn.add(label)
238
+ if color:
239
+ btn.css(background=color, borderColor=color)
240
+ if on_click:
241
+ btn.attr("onclick", on_click)
242
+ return self.add(btn)
243
+
244
+ def input(self, placeholder: str = "", *,
245
+ type: str = "text", name: str = "") -> Doc:
246
+ """Add a text input field."""
247
+ return self.add(Input(type_=type, name=name,
248
+ placeholder=placeholder, class_="py-input"))
249
+
250
+ def textarea(self, placeholder: str = "", *,
251
+ rows: str = "4", name: str = "") -> Doc:
252
+ """Add a multi-line text area."""
253
+ return self.add(Textarea(placeholder, name=name,
254
+ rows=rows, class_="py-textarea"))
255
+
256
+ def select(self, options: list[str], *,
257
+ name: str = "") -> Doc:
258
+ """Add a dropdown. ``options`` is a list of label strings."""
259
+ pairs = [(o, o) for o in options]
260
+ return self.add(Select(options=pairs, name=name, class_="py-select"))
261
+
262
+ # ---- data ----
263
+
264
+ def list(self, items: list[str], *, ordered: bool = False) -> Doc:
265
+ """Add a bullet list (or numbered if ``ordered=True``)."""
266
+ cls: type = Ol if ordered else Ul
267
+ el = cls()
268
+ for item in items:
269
+ el.add(Li(item))
270
+ return self.add(el)
271
+
272
+ def table(self, headers: list[str],
273
+ rows: list[list[str]]) -> Doc:
274
+ """Add a table."""
275
+ return self.add(Table(headers, rows))
276
+
277
+ def code(self, source: str, *, lang: str = "") -> Doc:
278
+ """Add a code block."""
279
+ c = Code(source)
280
+ if lang:
281
+ c.attr("class", f"language-{lang}")
282
+ return self.add(Pre().add(c))
283
+
284
+ # ---- extras ----
285
+
286
+ def details(self, summary: str, *content: Union[Element, str]) -> Doc:
287
+ """Add a collapsible section."""
288
+ det = Details().add(Summary(summary))
289
+ for c in content:
290
+ if isinstance(c, str):
291
+ det.add(P(Text(_md(c))))
292
+ else:
293
+ det.add(c)
294
+ return self.add(det)
295
+
296
+ def nav(self, *links: tuple[str, str]) -> Doc:
297
+ """Add a navigation bar. Each arg is ``(text, url)``."""
298
+ n = Div(class_="py-nav")
299
+ for label, url in links:
300
+ n.add(Link(label, url))
301
+ return self.add(n)
302
+
303
+ def divider(self) -> Doc:
304
+ """Add a horizontal rule."""
305
+ return self.add(Hr())
306
+
307
+ # ---- style (programmatic CSS) ----
308
+
309
+ def style(self, selector: str, **props: str) -> Doc:
310
+ """Add a CSS rule using Python kwargs.
311
+
312
+ ::
313
+
314
+ page.style("body", background="#f0f0f0", font_family="sans-serif")
315
+ page.style("h1", color="navy", font_size="2rem")
316
+ page.style(".card", border_radius="12px", padding="1rem")
317
+ page.style("button:hover", background="darkblue")
318
+ """
319
+ decls = "; ".join(
320
+ f"{_to_kebab(k)}: {v}" for k, v in props.items()
321
+ )
322
+ self._page.add_css(f"{selector} {{ {decls} }}")
323
+ return self
324
+
325
+ # ---- rendering ----
326
+
327
+ def render(self) -> str:
328
+ return self._page.render()
329
+
330
+ def __str__(self) -> str:
331
+ return self.render()
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.5
2
+ Name: htmlforge
3
+ Version: 0.0.1
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
+ ```
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install htmlforge
32
+ ```
33
+
34
+ Development install:
35
+
36
+ ```bash
37
+ pip install -e ".[dev]"
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ### 1. Write a Python page script `mysite.py`
43
+
44
+ ```python
45
+ from htmlforge import Doc
46
+
47
+ page = Doc("My Site")
48
+
49
+ # Generate styles with Python code (no CSS needed!)
50
+ page.style("body", background="#f5f5f5", font_family="sans-serif")
51
+ page.style(".py-card", border_radius="12px", box_shadow="0 2px 8px rgba(0,0,0,.1)")
52
+
53
+ # Build the page
54
+ page.heading("Welcome to My Site")
55
+ page.text("Built with **htmlforge**, supports *Markdown* syntax")
56
+
57
+ page.card("Features",
58
+ "40+ HTML components",
59
+ "Generate styles with Python code",
60
+ "Export HTML with one command",
61
+ )
62
+
63
+ page.table(
64
+ ["Feature", "Description"],
65
+ [
66
+ ["Components", "40+ HTML components"],
67
+ ["Built-in theme", "Works out of the box"],
68
+ ["CLI tools", "render / serve / watch"],
69
+ ],
70
+ )
71
+
72
+ page.button("Click me", on_click="alert('Hello!')", color="#4CAF50")
73
+ ```
74
+
75
+ ### 2. Run the command to export HTML
76
+
77
+ ```bash
78
+ # Basic render
79
+ htmlforge render mysite.py
80
+
81
+ # Specify output directory
82
+ htmlforge render mysite.py -o build
83
+
84
+ # Render and start a local server
85
+ htmlforge render mysite.py -s
86
+
87
+ # Watch for file changes and auto-rebuild
88
+ htmlforge render mysite.py -w
89
+
90
+ # Render a specific page (when file has multiple pages)
91
+ htmlforge render mysite.py MyPage
92
+ ```
93
+
94
+ ## CLI Commands
95
+
96
+ | Command | Description |
97
+ |---|---|
98
+ | `htmlforge render <file.py>` | Render all pages to `dist/` |
99
+ | `htmlforge render <file.py> -o <dir>` | Specify output directory |
100
+ | `htmlforge render <file.py> <page>` | Render only the specified page |
101
+ | `htmlforge render <file.py> -s` | Start HTTP server after render |
102
+ | `htmlforge render <file.py> -s -p 3000` | Specify server port |
103
+ | `htmlforge render <file.py> -w` | Watch for changes and auto-rebuild |
104
+
105
+ ## Doc API — Ultra Simple
106
+
107
+ `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.
108
+
109
+ ```python
110
+ from htmlforge import Doc
111
+
112
+ page = Doc("Title")
113
+
114
+ # Generate styles with Python kwargs (no CSS strings!)
115
+ page.style("body", background="linear-gradient(135deg, #667eea, #764ba2)", color="white")
116
+ page.style("h1", font_size="3rem", text_align="center")
117
+ page.style(".py-card", background="rgba(255,255,255,.15)", backdrop_filter="blur(10px)")
118
+
119
+ # Content (supports **Markdown** syntax)
120
+ page.heading("Hello World")
121
+ page.text("Supports **bold**, *italic*, `code`, [links](url)")
122
+
123
+ page.card("Card Title", "Card content, **supports Markdown**")
124
+ page.list(["Item A", "Item B", "Item C"])
125
+ page.table(["Col 1", "Col 2"], [["a", "b"], ["c", "d"]])
126
+ page.button("Button", on_click="alert('hi')", color="#e74c3c")
127
+ page.input("Type here...", name="name")
128
+ page.select(["Option A", "Option B", "Option C"])
129
+ page.code("print('hello')", lang="python")
130
+ page.details("Collapsible", "Hidden content...")
131
+ page.nav(("Home", "/"), ("About", "/about"))
132
+ page.divider()
133
+
134
+ # Layout
135
+ page.row(Doc.make_card("A"), Doc.make_card("B"), Doc.make_card("C"))
136
+ ```
137
+
138
+ ## Page API — Fine-grained Control
139
+
140
+ Use `Page` + the component system when you need more control:
141
+
142
+ ```python
143
+ from htmlforge import Page, H1, P, Div, Table, Button
144
+
145
+ page = Page("Title", lang="en")
146
+ page.add_css("body { font-family: sans-serif; }")
147
+ page.add(
148
+ H1("Welcome"),
149
+ P("Hello World"),
150
+ Div(class_="card").add(
151
+ H1("Features", level=2),
152
+ P("Content..."),
153
+ ),
154
+ )
155
+ ```
156
+
157
+ ### Element — Chainable API
158
+
159
+ ```python
160
+ div = Div(id="main", class_="wrapper")
161
+ div.add(H1("Title"), P("Content")) # Add child elements
162
+ div.css(color="red", font_size="16px") # Set inline styles (camelCase → kebab-case)
163
+ div.attr("data-id", "123") # Set attributes
164
+ html = div.render() # → HTML string
165
+ ```
166
+
167
+ ### Components
168
+
169
+ | Category | Components |
170
+ |---|---|
171
+ | **Structure** | `Div`, `Section`, `Header`, `Footer`, `Nav`, `Main`, `Article`, `Aside`, `Span`, `Container` |
172
+ | **Text** | `H1`-`H6`, `P`, `Strong`, `Em`, `Blockquote`, `Code`, `Pre`, `Small`, `Mark` |
173
+ | **Links/Media** | `Link`, `Img`, `Video`, `Audio`, `Iframe`, `Canvas` |
174
+ | **Lists** | `Ul`, `Ol`, `Li`, `List` |
175
+ | **Tables** | `Table`, `Thead`, `Tbody`, `Tr`, `Th`, `Td`, `Caption` |
176
+ | **Forms** | `Form`, `Input`, `Textarea`, `Select`, `Option`, `Checkbox`, `Radio`, `Label`, `Button` |
177
+ | **Other** | `Hr`, `Br`, `Text`, `Progress`, `Details`, `Summary` |
178
+
179
+ ## Example: Multi-page Site
180
+
181
+ ```python
182
+ # site.py
183
+ from htmlforge import Doc
184
+
185
+ home = Doc("Home")
186
+ home.heading("Home")
187
+ home.text("Welcome to the home page")
188
+
189
+ about = Doc("About")
190
+ about.heading("About Us")
191
+ about.text("This is the about page")
192
+
193
+ blog = Doc("Blog")
194
+ blog.heading("Blog")
195
+ blog.text("Latest posts...")
196
+ ```
197
+
198
+ ```bash
199
+ htmlforge render site.py
200
+ # → dist/home.html, dist/about.html, dist/blog.html
201
+ ```
202
+
203
+ ## License
204
+
205
+ [MPL-2.0](LICENSE)
@@ -0,0 +1,10 @@
1
+ htmlforge/__init__.py,sha256=om4GflxTfsgUBh4DWnbUhpASW5sXrlddrqPkzYZWFN0,725
2
+ htmlforge/__main__.py,sha256=I2IO0PQiXvzFeSkTADZYK3wjwCZocQqqZz-MGESrg14,104
3
+ htmlforge/cli.py,sha256=lrGhEaRqLaFeQybORduNSJOTmvrm2V777-GAbjoFZKI,6171
4
+ htmlforge/core.py,sha256=5i0Mxi3w0Pp6SvR8OYixP5PGF8x51DN8UCxFALc3Gl8,18731
5
+ htmlforge/simple.py,sha256=S3iXOWtneiUWJZUBaLqlrxtyTj84OpIMdQlVkyNvOt4,11487
6
+ htmlforge-0.0.1.dist-info/METADATA,sha256=Gy8a4RZEtmsQb85QT0mnMLmxJ5TS3NXwLTgPJRbP64A,5749
7
+ htmlforge-0.0.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
8
+ htmlforge-0.0.1.dist-info/entry_points.txt,sha256=4NFQHme4dPb8WjKirGaMRPa2jBE6X8WpZwZTwi7gqUQ,51
9
+ htmlforge-0.0.1.dist-info/licenses/LICENSE,sha256=H_SFL1HRjJcNiNCqq5FQPxzPKE9FL_A5qfYcPz9rdqg,36
10
+ htmlforge-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ htmlforge = htmlforge.cli:render
@@ -0,0 +1 @@
1
+ Mozilla Public License Version 2.0