domonic-libs 0.0.1__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 (118) hide show
  1. domonic_libs-0.0.1/LICENSE +21 -0
  2. domonic_libs-0.0.1/PKG-INFO +308 -0
  3. domonic_libs-0.0.1/README.md +269 -0
  4. domonic_libs-0.0.1/THIRD_PARTY_LICENSES.md +278 -0
  5. domonic_libs-0.0.1/pyproject.toml +65 -0
  6. domonic_libs-0.0.1/setup.cfg +4 -0
  7. domonic_libs-0.0.1/src/domonic_libs/__init__.py +43 -0
  8. domonic_libs-0.0.1/src/domonic_libs/__main__.py +8 -0
  9. domonic_libs-0.0.1/src/domonic_libs/app/__init__.py +29 -0
  10. domonic_libs-0.0.1/src/domonic_libs/app/bridge.py +55 -0
  11. domonic_libs-0.0.1/src/domonic_libs/app/client.py +494 -0
  12. domonic_libs-0.0.1/src/domonic_libs/app/core.py +320 -0
  13. domonic_libs-0.0.1/src/domonic_libs/app/desktop.py +165 -0
  14. domonic_libs-0.0.1/src/domonic_libs/app/events.py +130 -0
  15. domonic_libs-0.0.1/src/domonic_libs/app/file_drop.py +50 -0
  16. domonic_libs-0.0.1/src/domonic_libs/app/menus.py +77 -0
  17. domonic_libs-0.0.1/src/domonic_libs/app/storage.py +48 -0
  18. domonic_libs-0.0.1/src/domonic_libs/app/web.py +144 -0
  19. domonic_libs-0.0.1/src/domonic_libs/cli.py +464 -0
  20. domonic_libs-0.0.1/src/domonic_libs/dagre/__init__.py +29 -0
  21. domonic_libs-0.0.1/src/domonic_libs/dagre/acyclic.py +55 -0
  22. domonic_libs-0.0.1/src/domonic_libs/dagre/add_border_segments.py +36 -0
  23. domonic_libs-0.0.1/src/domonic_libs/dagre/coordinate_system.py +61 -0
  24. domonic_libs-0.0.1/src/domonic_libs/dagre/graphlib.py +496 -0
  25. domonic_libs-0.0.1/src/domonic_libs/dagre/greedy_fas.py +113 -0
  26. domonic_libs-0.0.1/src/domonic_libs/dagre/layout.py +348 -0
  27. domonic_libs-0.0.1/src/domonic_libs/dagre/list.py +44 -0
  28. domonic_libs-0.0.1/src/domonic_libs/dagre/nesting_graph.py +84 -0
  29. domonic_libs-0.0.1/src/domonic_libs/dagre/normalize.py +72 -0
  30. domonic_libs-0.0.1/src/domonic_libs/dagre/order.py +439 -0
  31. domonic_libs-0.0.1/src/domonic_libs/dagre/parent_dummy_chains.py +86 -0
  32. domonic_libs-0.0.1/src/domonic_libs/dagre/position.py +360 -0
  33. domonic_libs-0.0.1/src/domonic_libs/dagre/rank.py +251 -0
  34. domonic_libs-0.0.1/src/domonic_libs/dagre/util.py +190 -0
  35. domonic_libs-0.0.1/src/domonic_libs/dompurify.py +1542 -0
  36. domonic_libs-0.0.1/src/domonic_libs/marked/__init__.py +106 -0
  37. domonic_libs-0.0.1/src/domonic_libs/marked/_unicode.py +137 -0
  38. domonic_libs-0.0.1/src/domonic_libs/marked/defaults.py +27 -0
  39. domonic_libs-0.0.1/src/domonic_libs/marked/helpers.py +138 -0
  40. domonic_libs-0.0.1/src/domonic_libs/marked/lexer.py +354 -0
  41. domonic_libs-0.0.1/src/domonic_libs/marked/parser.py +99 -0
  42. domonic_libs-0.0.1/src/domonic_libs/marked/renderer.py +171 -0
  43. domonic_libs-0.0.1/src/domonic_libs/marked/rules.py +644 -0
  44. domonic_libs-0.0.1/src/domonic_libs/marked/text_renderer.py +38 -0
  45. domonic_libs-0.0.1/src/domonic_libs/marked/tokenizer.py +843 -0
  46. domonic_libs-0.0.1/src/domonic_libs/mermaid/__init__.py +77 -0
  47. domonic_libs-0.0.1/src/domonic_libs/mermaid/common.py +57 -0
  48. domonic_libs-0.0.1/src/domonic_libs/mermaid/config.py +77 -0
  49. domonic_libs-0.0.1/src/domonic_libs/mermaid/flowchart/__init__.py +10 -0
  50. domonic_libs-0.0.1/src/domonic_libs/mermaid/flowchart/db.py +70 -0
  51. domonic_libs-0.0.1/src/domonic_libs/mermaid/flowchart/parser.py +227 -0
  52. domonic_libs-0.0.1/src/domonic_libs/mermaid/flowchart/renderer.py +200 -0
  53. domonic_libs-0.0.1/src/domonic_libs/mermaid/pie/__init__.py +10 -0
  54. domonic_libs-0.0.1/src/domonic_libs/mermaid/pie/db.py +52 -0
  55. domonic_libs-0.0.1/src/domonic_libs/mermaid/pie/parser.py +83 -0
  56. domonic_libs-0.0.1/src/domonic_libs/mermaid/pie/renderer.py +131 -0
  57. domonic_libs-0.0.1/src/domonic_libs/mermaid/sequence/__init__.py +13 -0
  58. domonic_libs-0.0.1/src/domonic_libs/mermaid/sequence/db.py +456 -0
  59. domonic_libs-0.0.1/src/domonic_libs/mermaid/sequence/parser.py +282 -0
  60. domonic_libs-0.0.1/src/domonic_libs/mermaid/sequence/renderer.py +569 -0
  61. domonic_libs-0.0.1/src/domonic_libs/mermaid/text_metrics.py +101 -0
  62. domonic_libs-0.0.1/src/domonic_libs/mermaid/timeline/__init__.py +10 -0
  63. domonic_libs-0.0.1/src/domonic_libs/mermaid/timeline/db.py +71 -0
  64. domonic_libs-0.0.1/src/domonic_libs/mermaid/timeline/parser.py +74 -0
  65. domonic_libs-0.0.1/src/domonic_libs/mermaid/timeline/renderer.py +186 -0
  66. domonic_libs-0.0.1/src/domonic_libs/preact/__init__.py +66 -0
  67. domonic_libs-0.0.1/src/domonic_libs/preact/clone_element.py +45 -0
  68. domonic_libs-0.0.1/src/domonic_libs/preact/component.py +217 -0
  69. domonic_libs-0.0.1/src/domonic_libs/preact/constants.py +57 -0
  70. domonic_libs-0.0.1/src/domonic_libs/preact/create_context.py +75 -0
  71. domonic_libs-0.0.1/src/domonic_libs/preact/create_element.py +132 -0
  72. domonic_libs-0.0.1/src/domonic_libs/preact/diff/catch_error.py +42 -0
  73. domonic_libs-0.0.1/src/domonic_libs/preact/diff/children.py +273 -0
  74. domonic_libs-0.0.1/src/domonic_libs/preact/diff/index.py +558 -0
  75. domonic_libs-0.0.1/src/domonic_libs/preact/diff/props.py +178 -0
  76. domonic_libs-0.0.1/src/domonic_libs/preact/hooks.py +439 -0
  77. domonic_libs-0.0.1/src/domonic_libs/preact/options.py +43 -0
  78. domonic_libs-0.0.1/src/domonic_libs/preact/render.py +90 -0
  79. domonic_libs-0.0.1/src/domonic_libs/preact/util.py +35 -0
  80. domonic_libs-0.0.1/src/domonic_libs/py.typed +1 -0
  81. domonic_libs-0.0.1/src/domonic_libs/qs/__init__.py +6 -0
  82. domonic_libs-0.0.1/src/domonic_libs/qs/formats.py +17 -0
  83. domonic_libs-0.0.1/src/domonic_libs/qs/index.py +8 -0
  84. domonic_libs-0.0.1/src/domonic_libs/qs/parse.py +387 -0
  85. domonic_libs-0.0.1/src/domonic_libs/qs/stringify.py +447 -0
  86. domonic_libs-0.0.1/src/domonic_libs/qs/utils.py +372 -0
  87. domonic_libs-0.0.1/src/domonic_libs/readability.py +1586 -0
  88. domonic_libs-0.0.1/src/domonic_libs/turndown/__init__.py +7 -0
  89. domonic_libs-0.0.1/src/domonic_libs/turndown/_turndown.py +166 -0
  90. domonic_libs-0.0.1/src/domonic_libs/turndown/collapse_whitespace.py +87 -0
  91. domonic_libs-0.0.1/src/domonic_libs/turndown/commonmark_rules.py +270 -0
  92. domonic_libs-0.0.1/src/domonic_libs/turndown/gfm.py +161 -0
  93. domonic_libs-0.0.1/src/domonic_libs/turndown/html_parser.py +53 -0
  94. domonic_libs-0.0.1/src/domonic_libs/turndown/node.py +95 -0
  95. domonic_libs-0.0.1/src/domonic_libs/turndown/root_node.py +44 -0
  96. domonic_libs-0.0.1/src/domonic_libs/turndown/rules.py +72 -0
  97. domonic_libs-0.0.1/src/domonic_libs/turndown/utilities.py +119 -0
  98. domonic_libs-0.0.1/src/domonic_libs/validator/__init__.py +1699 -0
  99. domonic_libs-0.0.1/src/domonic_libs/validator/_data.py +230 -0
  100. domonic_libs-0.0.1/src/domonic_libs.egg-info/PKG-INFO +308 -0
  101. domonic_libs-0.0.1/src/domonic_libs.egg-info/SOURCES.txt +116 -0
  102. domonic_libs-0.0.1/src/domonic_libs.egg-info/dependency_links.txt +1 -0
  103. domonic_libs-0.0.1/src/domonic_libs.egg-info/entry_points.txt +3 -0
  104. domonic_libs-0.0.1/src/domonic_libs.egg-info/requires.txt +11 -0
  105. domonic_libs-0.0.1/src/domonic_libs.egg-info/top_level.txt +1 -0
  106. domonic_libs-0.0.1/tests/test_app.py +331 -0
  107. domonic_libs-0.0.1/tests/test_cli.py +119 -0
  108. domonic_libs-0.0.1/tests/test_dagre.py +132 -0
  109. domonic_libs-0.0.1/tests/test_dompurify.py +171 -0
  110. domonic_libs-0.0.1/tests/test_events.py +74 -0
  111. domonic_libs-0.0.1/tests/test_marked.py +213 -0
  112. domonic_libs-0.0.1/tests/test_menus.py +79 -0
  113. domonic_libs-0.0.1/tests/test_mermaid.py +607 -0
  114. domonic_libs-0.0.1/tests/test_preact.py +631 -0
  115. domonic_libs-0.0.1/tests/test_qs.py +195 -0
  116. domonic_libs-0.0.1/tests/test_readability.py +902 -0
  117. domonic_libs-0.0.1/tests/test_turndown.py +191 -0
  118. domonic_libs-0.0.1/tests/test_validator.py +119 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 byteface
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: domonic-libs
3
+ Version: 0.0.1
4
+ Summary: DOM-focused Python ports and an optional pywebview app wrapper built on domonic
5
+ Author-email: byteface <byteface@googlemail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/byteface/domonic-libs
8
+ Project-URL: Source, https://github.com/byteface/domonic-libs
9
+ Project-URL: Tracker, https://github.com/byteface/domonic-libs/issues
10
+ Project-URL: Examples, https://github.com/byteface/domonic-libs/tree/v0.0.1/examples
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: MacOS X
13
+ Classifier: Environment :: Win32 (MS Windows)
14
+ Classifier: Environment :: X11 Applications
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
24
+ Classifier: Topic :: Software Development :: User Interfaces
25
+ Classifier: Topic :: Text Processing :: Markup :: HTML
26
+ Requires-Python: >=3.10
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ License-File: THIRD_PARTY_LICENSES.md
30
+ Requires-Dist: domonic>=1.5.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=8.0; extra == "dev"
33
+ Requires-Dist: pywebview>=5.0; extra == "dev"
34
+ Provides-Extra: app
35
+ Requires-Dist: pywebview>=5.0; extra == "app"
36
+ Provides-Extra: examples
37
+ Requires-Dist: pywebview>=5.0; extra == "examples"
38
+ Dynamic: license-file
39
+
40
+ # domonic-libs
41
+
42
+ [![Tests](https://github.com/byteface/domonic-libs/actions/workflows/tests.yml/badge.svg)](https://github.com/byteface/domonic-libs/actions/workflows/tests.yml)
43
+ <!-- [![PyPI version](https://img.shields.io/pypi/v/domonic-libs.svg)](https://pypi.org/project/domonic-libs/) -->
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
45
+
46
+ DOM-shaped Python ports and an optional pywebview app wrapper built on [domonic](https://github.com/byteface/domonic).
47
+
48
+
49
+ ## Experimental libraries
50
+
51
+ Additional ports and experimental libraries not included with the standard domonic installation.
52
+
53
+ This package is deliberately split:
54
+
55
+ - libs for compatibility testing domonic.
56
+ - an `app` wrapper for building desktop UIs from domonic trees.
57
+
58
+ but they will be here so you can use them in an app if you want by importing off the tags here.
59
+
60
+ DOM behaviours that trip up the ports are logged in [docs/domonic-wrinkles.md](docs/domonic-wrinkles.md) to feed back upstream.
61
+
62
+ ## Install
63
+
64
+ To get all the libraries like Mermaid, preact, turndown, marked, validator, qs, dompurify, readability etc...
65
+
66
+ ```bash
67
+ pip install domonic-libs
68
+ ```
69
+
70
+ The pywebview app wrapper is an optional extra:
71
+
72
+ ```bash
73
+ pip install "domonic-libs[app]"
74
+ ```
75
+
76
+ Hack on the repo and run examples:
77
+
78
+ ```bash
79
+ python3 -m venv .venv
80
+ ./.venv/bin/pip install -r requirements.txt # editable install + dev deps
81
+ ./.venv/bin/python examples/dompurify_demo.py
82
+ make test
83
+ ```
84
+
85
+ The app wrapper API is in [docs/app.md](docs/app.md); the `dlx` CLI in [docs/cli.md](docs/cli.md). Ported modules keep their upstream licenses -- see [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md).
86
+
87
+ ## Command line -- `dlx`
88
+
89
+ Installing the package puts a `dlx` command on your `PATH` (also aliased `domonic-libs`, or run it as `python -m domonic_libs`). The cleanest way to get just the CLI, isolated, is [pipx](https://pipx.pypa.io):
90
+
91
+ ```bash
92
+ pipx install domonic-libs
93
+
94
+ # ...or run it once without installing
95
+ pipx run --spec domonic-libs dlx --help
96
+ ```
97
+
98
+ Every text command reads a file argument, or **stdin** when the argument is `-` or omitted, and writes to `--output` / `-o` or **stdout** -- so they pipe.
99
+
100
+ ```bash
101
+ # Mermaid -> SVG (flowchart / sequence / pie / timeline, auto-detected)
102
+ dlx mermaid architecture.mmd -o architecture.svg
103
+ dlx mermaid architecture.mmd --open # render + open in the browser
104
+ dlx mermaid architecture.mmd --stats # counts on stderr
105
+ cat flow.mmd | dlx mermaid --html > flow.html
106
+
107
+ # Markdown <-> HTML
108
+ echo "# Hi *there*" | dlx md
109
+ dlx html2md article.html --gfm > article.md
110
+
111
+ # Pull the readable article out of a page
112
+ dlx read https://example.com/some/post --md > post.md
113
+ dlx read page.html --json # just the metadata
114
+
115
+ # Sanitise hostile HTML (DOMPurify)
116
+ dlx sanitize comment.html --profile html --report
117
+
118
+ # One-off validator.js checks (exit 0 = true, 1 = false)
119
+ dlx validate isEmail ada@example.com
120
+ dlx validate isIBAN DE89370400440532013000
121
+ dlx validate --list
122
+
123
+ # Query strings
124
+ dlx qs parse "user[name]=ada&tags[]=a&tags[]=b"
125
+ dlx qs stringify '{"a": 1, "b": {"c": 2}}'
126
+
127
+ # Lay out and draw an arbitrary graph with the dagre port
128
+ printf 'build -> test\ntest -> deploy\nbuild -> lint\nlint -> deploy\n' \
129
+ | dlx dagre --rankdir LR -o pipeline.svg
130
+ ```
131
+
132
+ `dlx <command> -h` lists each command's options; full reference in [docs/cli.md](docs/cli.md).
133
+
134
+ ## Library Ports
135
+
136
+ The domonic repo has tests but also small complex ports like javascript and libraries like dquery and d3 were done to make sure the DOM was behaving as it should.
137
+
138
+ The more things I can port faithfully from js, the more expectations on the DOM I can correct. And out of that will also pop useful tools.
139
+
140
+ ### DOMPurify
141
+
142
+ ```python
143
+ from domonic_libs.dompurify import sanitize
144
+
145
+ clean = sanitize('<p onclick="x"><a href="javascript:bad()">bad</a>Hello</p>')
146
+ print(clean)
147
+ ```
148
+
149
+ A port of [DOMPurify](https://github.com/cure53/DOMPurify) -- config options, hooks, namespaces, `RETURN_DOM` / `WHOLE_DOCUMENT`, and DOM-clobbering protection. It runs DOMPurify's own `test/fixtures/expect.mjs` corpus (`tests/fixtures/dompurify/expect.json`, 223 real-world XSS payloads) at **212/223 exact-match** with default config; the remaining eleven are fidelity gaps (safe output, not byte-identical) from domonic's HTML5 parser or DOMPurify's deepest namespace-confusion checks, listed in `tests/test_dompurify.py::KNOWN_GAPS`.
150
+
151
+ ### QS
152
+
153
+ ```python
154
+ from domonic_libs.qs import parse, stringify
155
+
156
+ state = parse("filters[status][]=open&filters[status][]=draft")
157
+ print(state)
158
+ print(stringify(state, {"arrayFormat": "brackets"}))
159
+ ```
160
+
161
+ `qs` is useful when browser-style URL state needs to round-trip through nested Python dictionaries and arrays.
162
+
163
+ ### validator
164
+
165
+ ```python
166
+ from domonic_libs import validator
167
+
168
+ validator.isEmail("ada@example.com") # True
169
+ validator.isIBAN("DE89370400440532013000") # True (mod-97 checksum)
170
+ validator.isCreditCard("4111111111111111") # True (Luhn)
171
+ validator.normalizeEmail("Foo.Bar+x@googlemail.com") # 'foobar@gmail.com'
172
+ ```
173
+
174
+ A file-by-file port of [validator.js](https://github.com/validatorjs/validator.js) v13 -- ~90 string validators and sanitizers with the upstream camelCase names. Options are passed as a dict or as keyword arguments. It runs the `valid` / `invalid` case tables from validator.js's own test suite (`tests/fixtures/validator/cases.json`, 858 cases). Locale-table-heavy validators (`isMobilePhone`, `isPostalCode`, `isTaxID`, ...) and the full `normalizeEmail` provider lists are not ported yet.
175
+
176
+ ### Readability And Turndown
177
+
178
+ ```python
179
+ from domonic_libs.readability import Readability
180
+ from domonic_libs.turndown import turndown, TurndownService
181
+
182
+ article = Readability(html).parse()
183
+ markdown = turndown(article["content"])
184
+
185
+ # TurndownService mirrors turndown.js: options, .use(plugin), .addRule,
186
+ # .keep, .remove. Python option names are snake_case (heading_style="atx").
187
+ service = TurndownService(heading_style="atx", code_block_style="fenced")
188
+
189
+ from domonic_libs.turndown.gfm import gfm
190
+ service.use(gfm) # tables, strikethrough, task lists (turndown-plugin-gfm port)
191
+ ```
192
+
193
+ `turndown` is a file-for-file port of [turndown.js](https://github.com/mixmark-io/turndown) and runs its upstream fixture suite (see `tests/test_turndown.py`). Together with Readability it is useful for content extraction, reader views, local archives, and LLM-friendly page summaries.
194
+
195
+ ### marked
196
+
197
+ ```python
198
+ from domonic_libs.marked import marked
199
+
200
+ marked("# Title\n\nSome **bold** text and a [link](https://x.io).")
201
+ ```
202
+
203
+ A file-for-file port of [marked](https://github.com/markedjs/marked) v18 (Markdown to HTML) -- the inverse of turndown. It passes marked's own CommonMark 0.31.2 and GFM 0.29 conformance suites in full and 98% of marked's `new/` regression specs (`tests/test_marked.py`), compared with the same html-differ semantics marked uses. Options mirror marked's (`gfm`, `breaks`, `pedantic`, `silent`, custom `renderer`); hooks, async, and third-party extensions are not ported. marked's ``\p{P}``/``\p{S}`` regex classes are baked from `unicodedata`, keeping the port dependency-free.
204
+
205
+ ### preact
206
+
207
+ ```python
208
+ from domonic.dom import document
209
+ from domonic_libs.preact import h, render, Component
210
+ from domonic_libs.preact.hooks import useState, useEffect
211
+
212
+ def Greeting(props):
213
+ return h("h1", None, "Hello ", props["name"])
214
+
215
+ root = document.createElement("div")
216
+ render(h(Greeting, {"name": "world"}), root)
217
+ str(root) # '<div><h1>Hello world</h1></div>'
218
+ ```
219
+
220
+ A file-for-file port of [Preact](https://github.com/preactjs/preact) 10.29.8 -- `create_element`/`h`, the vnode-diffing reconciler (keyed children with the skew algorithm, Fragments, refs, `dangerouslySetInnerHTML`), the `Component` class with the full lifecycle, `createContext`, and `preact/hooks` (as `domonic_libs.preact.hooks`) -- running against domonic's server-side DOM instead of a browser. There is no JSX, so `h` is the authoring API and lifecycle names keep their upstream camelCase. Rendering is synchronous (Preact's microtask batching is replaced by a flush at the end of each `render` or event handler); Suspense and `preact/compat` are not ported. `tests/test_preact.py` ports a cross-section of Preact's browser suite (render, components, keys, fragments, refs, context, hooks) to `unittest`.
221
+
222
+ ### mermaid
223
+
224
+ ```python
225
+ from domonic_libs.mermaid import render
226
+
227
+ svg = render("""flowchart TD
228
+ A[Parse text] --> B{Diagram type?}
229
+ B -->|sequence / pie| C[Direct renderer]
230
+ B -->|flowchart| D[dagre layout]
231
+ C --> E[Emit SVG]
232
+ D --> E
233
+ E --> F((domonic tree))""")
234
+ # -> '<svg class="mermaid flowchart" viewBox="0 0 ..." ...>...</svg>'
235
+ ```
236
+
237
+ A port of [Mermaid](https://github.com/mermaid-js/mermaid) (mermaid@11.9.0) that turns diagram text into a domonic SVG tree -- no `mermaid.js` bundle, no headless browser, no Graphviz binary. `render(text)` auto-detects the type; the CLI is `dlx mermaid`.
238
+
239
+ | diagram | status |
240
+ | --- | --- |
241
+ | **sequence** | grammar + `SequenceDB` + renderer: participants (box and actor glyph), lifelines, the full arrow set, self-message curves, notes, activation bars, and the `loop` / `alt` / `opt` / `par` / `critical` / `break` / `rect` box machinery (nested boxes grow to enclose their contents). Gaps: `box` groups, autonumber badges, KaTeX, wrapping |
242
+ | **pie** | slices, percentages, legend, `showData` -- over domonic's `d3.shape` (`pie`/`arc`) and `d3.scale` |
243
+ | **timeline** | sections, periods, events, the activity line, wrapped node text |
244
+ | **flowchart** / `graph` | node shapes, the arrow zoo (labels, lengths, `--x` / `--o`), `&` groups, chains, `subgraph`; laid out by the bundled **dagre** port |
245
+
246
+ The sequence / pie / timeline parsers are checked against mermaid's own spec assertions (`tests/test_mermaid.py`); the flowchart parser is a pragmatic scanner for the common `flow.jison` subset (mermaid's is ~630 lines of stateful lexer), tested against hand-written cases. Text is measured through domonic's real `getBBox()` -- a gap this port surfaced, [since implemented in domonic 1.5.0](docs/domonic-wrinkles.md#resolved-in-domonic-150). `examples/mermaid_demo.py` is a live workbench.
247
+
248
+ ### dagre
249
+
250
+ ```python
251
+ from domonic_libs.dagre import Graph, layout
252
+
253
+ g = Graph({"compound": True})
254
+ g.setGraph({"rankdir": "LR", "nodesep": 40, "ranksep": 60})
255
+ g.setDefaultEdgeLabel(lambda *a: {})
256
+ for v in "abc":
257
+ g.setNode(v, {"width": 80, "height": 30})
258
+ g.setEdge("a", "b", {}); g.setEdge("a", "c", {})
259
+ layout(g)
260
+ g.node("b") # {'x': ..., 'y': ..., 'rank': ..., ...}
261
+ g.edge("a", "b")["points"]
262
+ ```
263
+
264
+ A faithful file-for-file port of [dagre](https://github.com/dagrejs/dagre) and the slice of [graphlib](https://github.com/dagrejs/graphlib) it needs -- directed-graph hierarchical layout, no C or JS dependency. The whole pipeline is ported: greedy-FAS acyclic → Sander nesting graph → network-simplex rank → barycenter + Barth bilayer crossing minimisation → Brandes-Köpf x-coordinates → edge routing. Used by the Mermaid flowchart renderer, and usable on its own for any layered-graph drawing (`dlx dagre` takes an edge list). Per-cluster `rankdir` recursion is the one omission; `tests/test_dagre.py` does structural/invariant checks.
265
+
266
+ ## App Wrapper
267
+
268
+ Install `.[app]` or `.[examples]` before using this part.
269
+
270
+ ```python
271
+ from domonic.events import Event
272
+ from domonic.html import button, h1, main
273
+
274
+ from domonic_libs import App, on
275
+
276
+
277
+ app = App("Hello")
278
+
279
+
280
+ def clicked(event):
281
+ print(event.type)
282
+
283
+
284
+ @app.route("/")
285
+ def index():
286
+ return main(h1("Hello"), on(button("Click me"), Event.CLICK, clicked))
287
+
288
+
289
+ app.run()
290
+ ```
291
+
292
+ `App` is a thin bridge for domonic trees: routes return domonic HTML, callbacks receive domonic events, and examples can use OS file dialogs, menus, drag/drop, timers, storage, and transparent windows.
293
+
294
+ The render loop lives in a host-agnostic `BaseApp`. `App` is an alias for `DesktopApp`, which hosts it in a pywebview window. `BrowserApp` serves the same application to an ordinary browser over a small stdlib HTTP server -- the same route/handler code, no changes:
295
+
296
+ ```python
297
+ from domonic_libs.app import BrowserApp
298
+
299
+ app = BrowserApp("Hello")
300
+
301
+ @app.route("/")
302
+ def index():
303
+ return main(h1("Hello"), on(button("Click me"), Event.CLICK, clicked))
304
+
305
+ app.run(port=8000) # serves http://127.0.0.1:8000/
306
+ ```
307
+
308
+ Only the transport differs (pywebview's `js_api` vs `fetch`) and the native host (menus, dialogs, native drag/drop paths are desktop-only). Because the browser has no server-to-client push, `evaluate_js` / `refresh` on `BrowserApp` queue a command that rides back on the next event or timer response.
@@ -0,0 +1,269 @@
1
+ # domonic-libs
2
+
3
+ [![Tests](https://github.com/byteface/domonic-libs/actions/workflows/tests.yml/badge.svg)](https://github.com/byteface/domonic-libs/actions/workflows/tests.yml)
4
+ <!-- [![PyPI version](https://img.shields.io/pypi/v/domonic-libs.svg)](https://pypi.org/project/domonic-libs/) -->
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ DOM-shaped Python ports and an optional pywebview app wrapper built on [domonic](https://github.com/byteface/domonic).
8
+
9
+
10
+ ## Experimental libraries
11
+
12
+ Additional ports and experimental libraries not included with the standard domonic installation.
13
+
14
+ This package is deliberately split:
15
+
16
+ - libs for compatibility testing domonic.
17
+ - an `app` wrapper for building desktop UIs from domonic trees.
18
+
19
+ but they will be here so you can use them in an app if you want by importing off the tags here.
20
+
21
+ DOM behaviours that trip up the ports are logged in [docs/domonic-wrinkles.md](docs/domonic-wrinkles.md) to feed back upstream.
22
+
23
+ ## Install
24
+
25
+ To get all the libraries like Mermaid, preact, turndown, marked, validator, qs, dompurify, readability etc...
26
+
27
+ ```bash
28
+ pip install domonic-libs
29
+ ```
30
+
31
+ The pywebview app wrapper is an optional extra:
32
+
33
+ ```bash
34
+ pip install "domonic-libs[app]"
35
+ ```
36
+
37
+ Hack on the repo and run examples:
38
+
39
+ ```bash
40
+ python3 -m venv .venv
41
+ ./.venv/bin/pip install -r requirements.txt # editable install + dev deps
42
+ ./.venv/bin/python examples/dompurify_demo.py
43
+ make test
44
+ ```
45
+
46
+ The app wrapper API is in [docs/app.md](docs/app.md); the `dlx` CLI in [docs/cli.md](docs/cli.md). Ported modules keep their upstream licenses -- see [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md).
47
+
48
+ ## Command line -- `dlx`
49
+
50
+ Installing the package puts a `dlx` command on your `PATH` (also aliased `domonic-libs`, or run it as `python -m domonic_libs`). The cleanest way to get just the CLI, isolated, is [pipx](https://pipx.pypa.io):
51
+
52
+ ```bash
53
+ pipx install domonic-libs
54
+
55
+ # ...or run it once without installing
56
+ pipx run --spec domonic-libs dlx --help
57
+ ```
58
+
59
+ Every text command reads a file argument, or **stdin** when the argument is `-` or omitted, and writes to `--output` / `-o` or **stdout** -- so they pipe.
60
+
61
+ ```bash
62
+ # Mermaid -> SVG (flowchart / sequence / pie / timeline, auto-detected)
63
+ dlx mermaid architecture.mmd -o architecture.svg
64
+ dlx mermaid architecture.mmd --open # render + open in the browser
65
+ dlx mermaid architecture.mmd --stats # counts on stderr
66
+ cat flow.mmd | dlx mermaid --html > flow.html
67
+
68
+ # Markdown <-> HTML
69
+ echo "# Hi *there*" | dlx md
70
+ dlx html2md article.html --gfm > article.md
71
+
72
+ # Pull the readable article out of a page
73
+ dlx read https://example.com/some/post --md > post.md
74
+ dlx read page.html --json # just the metadata
75
+
76
+ # Sanitise hostile HTML (DOMPurify)
77
+ dlx sanitize comment.html --profile html --report
78
+
79
+ # One-off validator.js checks (exit 0 = true, 1 = false)
80
+ dlx validate isEmail ada@example.com
81
+ dlx validate isIBAN DE89370400440532013000
82
+ dlx validate --list
83
+
84
+ # Query strings
85
+ dlx qs parse "user[name]=ada&tags[]=a&tags[]=b"
86
+ dlx qs stringify '{"a": 1, "b": {"c": 2}}'
87
+
88
+ # Lay out and draw an arbitrary graph with the dagre port
89
+ printf 'build -> test\ntest -> deploy\nbuild -> lint\nlint -> deploy\n' \
90
+ | dlx dagre --rankdir LR -o pipeline.svg
91
+ ```
92
+
93
+ `dlx <command> -h` lists each command's options; full reference in [docs/cli.md](docs/cli.md).
94
+
95
+ ## Library Ports
96
+
97
+ The domonic repo has tests but also small complex ports like javascript and libraries like dquery and d3 were done to make sure the DOM was behaving as it should.
98
+
99
+ The more things I can port faithfully from js, the more expectations on the DOM I can correct. And out of that will also pop useful tools.
100
+
101
+ ### DOMPurify
102
+
103
+ ```python
104
+ from domonic_libs.dompurify import sanitize
105
+
106
+ clean = sanitize('<p onclick="x"><a href="javascript:bad()">bad</a>Hello</p>')
107
+ print(clean)
108
+ ```
109
+
110
+ A port of [DOMPurify](https://github.com/cure53/DOMPurify) -- config options, hooks, namespaces, `RETURN_DOM` / `WHOLE_DOCUMENT`, and DOM-clobbering protection. It runs DOMPurify's own `test/fixtures/expect.mjs` corpus (`tests/fixtures/dompurify/expect.json`, 223 real-world XSS payloads) at **212/223 exact-match** with default config; the remaining eleven are fidelity gaps (safe output, not byte-identical) from domonic's HTML5 parser or DOMPurify's deepest namespace-confusion checks, listed in `tests/test_dompurify.py::KNOWN_GAPS`.
111
+
112
+ ### QS
113
+
114
+ ```python
115
+ from domonic_libs.qs import parse, stringify
116
+
117
+ state = parse("filters[status][]=open&filters[status][]=draft")
118
+ print(state)
119
+ print(stringify(state, {"arrayFormat": "brackets"}))
120
+ ```
121
+
122
+ `qs` is useful when browser-style URL state needs to round-trip through nested Python dictionaries and arrays.
123
+
124
+ ### validator
125
+
126
+ ```python
127
+ from domonic_libs import validator
128
+
129
+ validator.isEmail("ada@example.com") # True
130
+ validator.isIBAN("DE89370400440532013000") # True (mod-97 checksum)
131
+ validator.isCreditCard("4111111111111111") # True (Luhn)
132
+ validator.normalizeEmail("Foo.Bar+x@googlemail.com") # 'foobar@gmail.com'
133
+ ```
134
+
135
+ A file-by-file port of [validator.js](https://github.com/validatorjs/validator.js) v13 -- ~90 string validators and sanitizers with the upstream camelCase names. Options are passed as a dict or as keyword arguments. It runs the `valid` / `invalid` case tables from validator.js's own test suite (`tests/fixtures/validator/cases.json`, 858 cases). Locale-table-heavy validators (`isMobilePhone`, `isPostalCode`, `isTaxID`, ...) and the full `normalizeEmail` provider lists are not ported yet.
136
+
137
+ ### Readability And Turndown
138
+
139
+ ```python
140
+ from domonic_libs.readability import Readability
141
+ from domonic_libs.turndown import turndown, TurndownService
142
+
143
+ article = Readability(html).parse()
144
+ markdown = turndown(article["content"])
145
+
146
+ # TurndownService mirrors turndown.js: options, .use(plugin), .addRule,
147
+ # .keep, .remove. Python option names are snake_case (heading_style="atx").
148
+ service = TurndownService(heading_style="atx", code_block_style="fenced")
149
+
150
+ from domonic_libs.turndown.gfm import gfm
151
+ service.use(gfm) # tables, strikethrough, task lists (turndown-plugin-gfm port)
152
+ ```
153
+
154
+ `turndown` is a file-for-file port of [turndown.js](https://github.com/mixmark-io/turndown) and runs its upstream fixture suite (see `tests/test_turndown.py`). Together with Readability it is useful for content extraction, reader views, local archives, and LLM-friendly page summaries.
155
+
156
+ ### marked
157
+
158
+ ```python
159
+ from domonic_libs.marked import marked
160
+
161
+ marked("# Title\n\nSome **bold** text and a [link](https://x.io).")
162
+ ```
163
+
164
+ A file-for-file port of [marked](https://github.com/markedjs/marked) v18 (Markdown to HTML) -- the inverse of turndown. It passes marked's own CommonMark 0.31.2 and GFM 0.29 conformance suites in full and 98% of marked's `new/` regression specs (`tests/test_marked.py`), compared with the same html-differ semantics marked uses. Options mirror marked's (`gfm`, `breaks`, `pedantic`, `silent`, custom `renderer`); hooks, async, and third-party extensions are not ported. marked's ``\p{P}``/``\p{S}`` regex classes are baked from `unicodedata`, keeping the port dependency-free.
165
+
166
+ ### preact
167
+
168
+ ```python
169
+ from domonic.dom import document
170
+ from domonic_libs.preact import h, render, Component
171
+ from domonic_libs.preact.hooks import useState, useEffect
172
+
173
+ def Greeting(props):
174
+ return h("h1", None, "Hello ", props["name"])
175
+
176
+ root = document.createElement("div")
177
+ render(h(Greeting, {"name": "world"}), root)
178
+ str(root) # '<div><h1>Hello world</h1></div>'
179
+ ```
180
+
181
+ A file-for-file port of [Preact](https://github.com/preactjs/preact) 10.29.8 -- `create_element`/`h`, the vnode-diffing reconciler (keyed children with the skew algorithm, Fragments, refs, `dangerouslySetInnerHTML`), the `Component` class with the full lifecycle, `createContext`, and `preact/hooks` (as `domonic_libs.preact.hooks`) -- running against domonic's server-side DOM instead of a browser. There is no JSX, so `h` is the authoring API and lifecycle names keep their upstream camelCase. Rendering is synchronous (Preact's microtask batching is replaced by a flush at the end of each `render` or event handler); Suspense and `preact/compat` are not ported. `tests/test_preact.py` ports a cross-section of Preact's browser suite (render, components, keys, fragments, refs, context, hooks) to `unittest`.
182
+
183
+ ### mermaid
184
+
185
+ ```python
186
+ from domonic_libs.mermaid import render
187
+
188
+ svg = render("""flowchart TD
189
+ A[Parse text] --> B{Diagram type?}
190
+ B -->|sequence / pie| C[Direct renderer]
191
+ B -->|flowchart| D[dagre layout]
192
+ C --> E[Emit SVG]
193
+ D --> E
194
+ E --> F((domonic tree))""")
195
+ # -> '<svg class="mermaid flowchart" viewBox="0 0 ..." ...>...</svg>'
196
+ ```
197
+
198
+ A port of [Mermaid](https://github.com/mermaid-js/mermaid) (mermaid@11.9.0) that turns diagram text into a domonic SVG tree -- no `mermaid.js` bundle, no headless browser, no Graphviz binary. `render(text)` auto-detects the type; the CLI is `dlx mermaid`.
199
+
200
+ | diagram | status |
201
+ | --- | --- |
202
+ | **sequence** | grammar + `SequenceDB` + renderer: participants (box and actor glyph), lifelines, the full arrow set, self-message curves, notes, activation bars, and the `loop` / `alt` / `opt` / `par` / `critical` / `break` / `rect` box machinery (nested boxes grow to enclose their contents). Gaps: `box` groups, autonumber badges, KaTeX, wrapping |
203
+ | **pie** | slices, percentages, legend, `showData` -- over domonic's `d3.shape` (`pie`/`arc`) and `d3.scale` |
204
+ | **timeline** | sections, periods, events, the activity line, wrapped node text |
205
+ | **flowchart** / `graph` | node shapes, the arrow zoo (labels, lengths, `--x` / `--o`), `&` groups, chains, `subgraph`; laid out by the bundled **dagre** port |
206
+
207
+ The sequence / pie / timeline parsers are checked against mermaid's own spec assertions (`tests/test_mermaid.py`); the flowchart parser is a pragmatic scanner for the common `flow.jison` subset (mermaid's is ~630 lines of stateful lexer), tested against hand-written cases. Text is measured through domonic's real `getBBox()` -- a gap this port surfaced, [since implemented in domonic 1.5.0](docs/domonic-wrinkles.md#resolved-in-domonic-150). `examples/mermaid_demo.py` is a live workbench.
208
+
209
+ ### dagre
210
+
211
+ ```python
212
+ from domonic_libs.dagre import Graph, layout
213
+
214
+ g = Graph({"compound": True})
215
+ g.setGraph({"rankdir": "LR", "nodesep": 40, "ranksep": 60})
216
+ g.setDefaultEdgeLabel(lambda *a: {})
217
+ for v in "abc":
218
+ g.setNode(v, {"width": 80, "height": 30})
219
+ g.setEdge("a", "b", {}); g.setEdge("a", "c", {})
220
+ layout(g)
221
+ g.node("b") # {'x': ..., 'y': ..., 'rank': ..., ...}
222
+ g.edge("a", "b")["points"]
223
+ ```
224
+
225
+ A faithful file-for-file port of [dagre](https://github.com/dagrejs/dagre) and the slice of [graphlib](https://github.com/dagrejs/graphlib) it needs -- directed-graph hierarchical layout, no C or JS dependency. The whole pipeline is ported: greedy-FAS acyclic → Sander nesting graph → network-simplex rank → barycenter + Barth bilayer crossing minimisation → Brandes-Köpf x-coordinates → edge routing. Used by the Mermaid flowchart renderer, and usable on its own for any layered-graph drawing (`dlx dagre` takes an edge list). Per-cluster `rankdir` recursion is the one omission; `tests/test_dagre.py` does structural/invariant checks.
226
+
227
+ ## App Wrapper
228
+
229
+ Install `.[app]` or `.[examples]` before using this part.
230
+
231
+ ```python
232
+ from domonic.events import Event
233
+ from domonic.html import button, h1, main
234
+
235
+ from domonic_libs import App, on
236
+
237
+
238
+ app = App("Hello")
239
+
240
+
241
+ def clicked(event):
242
+ print(event.type)
243
+
244
+
245
+ @app.route("/")
246
+ def index():
247
+ return main(h1("Hello"), on(button("Click me"), Event.CLICK, clicked))
248
+
249
+
250
+ app.run()
251
+ ```
252
+
253
+ `App` is a thin bridge for domonic trees: routes return domonic HTML, callbacks receive domonic events, and examples can use OS file dialogs, menus, drag/drop, timers, storage, and transparent windows.
254
+
255
+ The render loop lives in a host-agnostic `BaseApp`. `App` is an alias for `DesktopApp`, which hosts it in a pywebview window. `BrowserApp` serves the same application to an ordinary browser over a small stdlib HTTP server -- the same route/handler code, no changes:
256
+
257
+ ```python
258
+ from domonic_libs.app import BrowserApp
259
+
260
+ app = BrowserApp("Hello")
261
+
262
+ @app.route("/")
263
+ def index():
264
+ return main(h1("Hello"), on(button("Click me"), Event.CLICK, clicked))
265
+
266
+ app.run(port=8000) # serves http://127.0.0.1:8000/
267
+ ```
268
+
269
+ Only the transport differs (pywebview's `js_api` vs `fetch`) and the native host (menus, dialogs, native drag/drop paths are desktop-only). Because the browser has no server-to-client push, `evaluate_js` / `refresh` on `BrowserApp` queue a command that rides back on the next event or timer response.