zenfmt 0.1.0__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.
- zenfmt-0.1.0/.gitignore +15 -0
- zenfmt-0.1.0/LICENSE +21 -0
- zenfmt-0.1.0/PKG-INFO +119 -0
- zenfmt-0.1.0/README.md +88 -0
- zenfmt-0.1.0/engine/bindings/python/abi.zig +217 -0
- zenfmt-0.1.0/engine/bindings/python/abi_test.zig +335 -0
- zenfmt-0.1.0/engine/bindings/python/capabilities.zig +167 -0
- zenfmt-0.1.0/engine/bindings/python/fuzz_test.zig +86 -0
- zenfmt-0.1.0/engine/bindings/python/oom_test.zig +66 -0
- zenfmt-0.1.0/engine/bindings/python/request.zig +219 -0
- zenfmt-0.1.0/engine/bindings/python/result.zig +255 -0
- zenfmt-0.1.0/engine/build.zig +856 -0
- zenfmt-0.1.0/engine/build.zig.zon +23 -0
- zenfmt-0.1.0/engine/core/src/adjacent_manifest.zig +72 -0
- zenfmt-0.1.0/engine/core/src/artifact_output.zig +137 -0
- zenfmt-0.1.0/engine/core/src/ast.zig +991 -0
- zenfmt-0.1.0/engine/core/src/ast_side_validation.zig +158 -0
- zenfmt-0.1.0/engine/core/src/ast_test.zig +95 -0
- zenfmt-0.1.0/engine/core/src/builder.zig +997 -0
- zenfmt-0.1.0/engine/core/src/builder_facets.zig +190 -0
- zenfmt-0.1.0/engine/core/src/builder_test.zig +149 -0
- zenfmt-0.1.0/engine/core/src/bundle_validation.zig +72 -0
- zenfmt-0.1.0/engine/core/src/detect.zig +246 -0
- zenfmt-0.1.0/engine/core/src/engine_reports.zig +824 -0
- zenfmt-0.1.0/engine/core/src/facets.zig +258 -0
- zenfmt-0.1.0/engine/core/src/filters.zig +245 -0
- zenfmt-0.1.0/engine/core/src/format_suggestion.zig +46 -0
- zenfmt-0.1.0/engine/core/src/json.zig +591 -0
- zenfmt-0.1.0/engine/core/src/limits.zig +203 -0
- zenfmt-0.1.0/engine/core/src/lowering.zig +998 -0
- zenfmt-0.1.0/engine/core/src/lowering_cost.zig +93 -0
- zenfmt-0.1.0/engine/core/src/lowering_decisions.zig +60 -0
- zenfmt-0.1.0/engine/core/src/lowering_reachability.zig +77 -0
- zenfmt-0.1.0/engine/core/src/manifest.zig +737 -0
- zenfmt-0.1.0/engine/core/src/metadata.zig +693 -0
- zenfmt-0.1.0/engine/core/src/options.zig +54 -0
- zenfmt-0.1.0/engine/core/src/payload.zig +543 -0
- zenfmt-0.1.0/engine/core/src/pipeline.zig +641 -0
- zenfmt-0.1.0/engine/core/src/plugin.zig +330 -0
- zenfmt-0.1.0/engine/core/src/preservation.zig +121 -0
- zenfmt-0.1.0/engine/core/src/publication.zig +168 -0
- zenfmt-0.1.0/engine/core/src/report.zig +841 -0
- zenfmt-0.1.0/engine/core/src/resource_output.zig +138 -0
- zenfmt-0.1.0/engine/core/src/resources.zig +45 -0
- zenfmt-0.1.0/engine/core/src/root.zig +1066 -0
- zenfmt-0.1.0/engine/core/src/root_test.zig +20 -0
- zenfmt-0.1.0/engine/core/src/schema.zig +242 -0
- zenfmt-0.1.0/engine/core/src/stream_output.zig +99 -0
- zenfmt-0.1.0/engine/core/src/transform.zig +519 -0
- zenfmt-0.1.0/engine/formats/asciidoc/src/reader.zig +563 -0
- zenfmt-0.1.0/engine/formats/asciidoc/src/root.zig +7 -0
- zenfmt-0.1.0/engine/formats/csv/src/reader.zig +305 -0
- zenfmt-0.1.0/engine/formats/csv/src/root.zig +7 -0
- zenfmt-0.1.0/engine/formats/doc/src/reader.zig +765 -0
- zenfmt-0.1.0/engine/formats/doc/src/root.zig +8 -0
- zenfmt-0.1.0/engine/formats/doc/src/styles.zig +277 -0
- zenfmt-0.1.0/engine/formats/docx/src/facets.zig +167 -0
- zenfmt-0.1.0/engine/formats/docx/src/numbering.zig +134 -0
- zenfmt-0.1.0/engine/formats/docx/src/package.zig +126 -0
- zenfmt-0.1.0/engine/formats/docx/src/reader.zig +993 -0
- zenfmt-0.1.0/engine/formats/docx/src/reader_test.zig +133 -0
- zenfmt-0.1.0/engine/formats/docx/src/reports.zig +287 -0
- zenfmt-0.1.0/engine/formats/docx/src/root.zig +15 -0
- zenfmt-0.1.0/engine/formats/docx/src/styles.zig +157 -0
- zenfmt-0.1.0/engine/formats/docx/src/tables.zig +191 -0
- zenfmt-0.1.0/engine/formats/docx/src/util.zig +76 -0
- zenfmt-0.1.0/engine/formats/epub/src/reader.zig +439 -0
- zenfmt-0.1.0/engine/formats/epub/src/reader_test.zig +183 -0
- zenfmt-0.1.0/engine/formats/epub/src/root.zig +12 -0
- zenfmt-0.1.0/engine/formats/html/src/entities.zig +2285 -0
- zenfmt-0.1.0/engine/formats/html/src/reader.zig +954 -0
- zenfmt-0.1.0/engine/formats/html/src/reader_test.zig +219 -0
- zenfmt-0.1.0/engine/formats/html/src/root.zig +10 -0
- zenfmt-0.1.0/engine/formats/markdown/src/capabilities.zig +270 -0
- zenfmt-0.1.0/engine/formats/markdown/src/inlines.zig +905 -0
- zenfmt-0.1.0/engine/formats/markdown/src/lines.zig +281 -0
- zenfmt-0.1.0/engine/formats/markdown/src/reader.zig +799 -0
- zenfmt-0.1.0/engine/formats/markdown/src/root.zig +17 -0
- zenfmt-0.1.0/engine/formats/markdown/src/writer.zig +992 -0
- zenfmt-0.1.0/engine/formats/markdown/src/writer_inline.zig +141 -0
- zenfmt-0.1.0/engine/formats/markdown/src/writer_reports.zig +213 -0
- zenfmt-0.1.0/engine/formats/markdown/src/writer_support.zig +130 -0
- zenfmt-0.1.0/engine/formats/markdown/src/writer_test.zig +224 -0
- zenfmt-0.1.0/engine/formats/odp/src/reader.zig +826 -0
- zenfmt-0.1.0/engine/formats/odp/src/reader_test.zig +300 -0
- zenfmt-0.1.0/engine/formats/odp/src/root.zig +11 -0
- zenfmt-0.1.0/engine/formats/ods/src/reader.zig +721 -0
- zenfmt-0.1.0/engine/formats/ods/src/root.zig +10 -0
- zenfmt-0.1.0/engine/formats/odt/src/reader.zig +957 -0
- zenfmt-0.1.0/engine/formats/odt/src/reader_test.zig +267 -0
- zenfmt-0.1.0/engine/formats/odt/src/reports.zig +130 -0
- zenfmt-0.1.0/engine/formats/odt/src/root.zig +9 -0
- zenfmt-0.1.0/engine/formats/pdf/src/content.zig +716 -0
- zenfmt-0.1.0/engine/formats/pdf/src/fonts.zig +521 -0
- zenfmt-0.1.0/engine/formats/pdf/src/glyphs.zig +246 -0
- zenfmt-0.1.0/engine/formats/pdf/src/graphics.zig +287 -0
- zenfmt-0.1.0/engine/formats/pdf/src/images.zig +216 -0
- zenfmt-0.1.0/engine/formats/pdf/src/layout.zig +482 -0
- zenfmt-0.1.0/engine/formats/pdf/src/objects.zig +525 -0
- zenfmt-0.1.0/engine/formats/pdf/src/reader.zig +289 -0
- zenfmt-0.1.0/engine/formats/pdf/src/reader_test.zig +398 -0
- zenfmt-0.1.0/engine/formats/pdf/src/reports.zig +217 -0
- zenfmt-0.1.0/engine/formats/pdf/src/root.zig +26 -0
- zenfmt-0.1.0/engine/formats/pdf/src/streams.zig +311 -0
- zenfmt-0.1.0/engine/formats/pdf/src/testpdf.zig +100 -0
- zenfmt-0.1.0/engine/formats/pdf/src/xref.zig +489 -0
- zenfmt-0.1.0/engine/formats/ppt/src/reader.zig +422 -0
- zenfmt-0.1.0/engine/formats/ppt/src/root.zig +7 -0
- zenfmt-0.1.0/engine/formats/pptx/src/reader.zig +952 -0
- zenfmt-0.1.0/engine/formats/pptx/src/reader_test.zig +304 -0
- zenfmt-0.1.0/engine/formats/pptx/src/reports.zig +96 -0
- zenfmt-0.1.0/engine/formats/pptx/src/root.zig +9 -0
- zenfmt-0.1.0/engine/formats/rst/src/reader.zig +534 -0
- zenfmt-0.1.0/engine/formats/rst/src/root.zig +7 -0
- zenfmt-0.1.0/engine/formats/rtf/src/reader.zig +724 -0
- zenfmt-0.1.0/engine/formats/rtf/src/reader_test.zig +275 -0
- zenfmt-0.1.0/engine/formats/rtf/src/reports.zig +114 -0
- zenfmt-0.1.0/engine/formats/rtf/src/root.zig +10 -0
- zenfmt-0.1.0/engine/formats/rtf/src/structure.zig +244 -0
- zenfmt-0.1.0/engine/formats/text/src/reader.zig +119 -0
- zenfmt-0.1.0/engine/formats/text/src/root.zig +7 -0
- zenfmt-0.1.0/engine/formats/xls/src/reader.zig +990 -0
- zenfmt-0.1.0/engine/formats/xls/src/root.zig +7 -0
- zenfmt-0.1.0/engine/formats/xlsb/src/reader.zig +924 -0
- zenfmt-0.1.0/engine/formats/xlsb/src/root.zig +7 -0
- zenfmt-0.1.0/engine/formats/xlsx/src/reader.zig +706 -0
- zenfmt-0.1.0/engine/formats/xlsx/src/root.zig +7 -0
- zenfmt-0.1.0/engine/src/cli.zig +532 -0
- zenfmt-0.1.0/engine/src/default_bundle.zig +59 -0
- zenfmt-0.1.0/engine/src/root.zig +51 -0
- zenfmt-0.1.0/engine/support/cfb/src/root.zig +594 -0
- zenfmt-0.1.0/engine/support/ooxml/src/root.zig +131 -0
- zenfmt-0.1.0/engine/support/ooxml/src/zip.zig +480 -0
- zenfmt-0.1.0/engine/support/xml/src/root.zig +491 -0
- zenfmt-0.1.0/hatch_build.py +210 -0
- zenfmt-0.1.0/pyproject.toml +160 -0
- zenfmt-0.1.0/src/zenfmt/__init__.py +72 -0
- zenfmt-0.1.0/src/zenfmt/_capabilities.py +102 -0
- zenfmt-0.1.0/src/zenfmt/_convert.py +374 -0
- zenfmt-0.1.0/src/zenfmt/_diagnostics.py +422 -0
- zenfmt-0.1.0/src/zenfmt/_errors.py +147 -0
- zenfmt-0.1.0/src/zenfmt/_ffi.py +139 -0
- zenfmt-0.1.0/src/zenfmt/_limits.py +174 -0
- zenfmt-0.1.0/src/zenfmt/_loader.py +378 -0
- zenfmt-0.1.0/src/zenfmt/_marshal.py +372 -0
- zenfmt-0.1.0/src/zenfmt/_models.py +402 -0
- zenfmt-0.1.0/src/zenfmt/_native/README.md +14 -0
- zenfmt-0.1.0/src/zenfmt/py.typed +0 -0
- zenfmt-0.1.0/tests/conftest.py +25 -0
- zenfmt-0.1.0/tests/integration/conftest.py +100 -0
- zenfmt-0.1.0/tests/integration/fixtures/doc.adoc +3 -0
- zenfmt-0.1.0/tests/integration/fixtures/doc.rst +4 -0
- zenfmt-0.1.0/tests/integration/fixtures/doc.rtf +1 -0
- zenfmt-0.1.0/tests/integration/fixtures/make_fixtures.py +287 -0
- zenfmt-0.1.0/tests/integration/fixtures/min.docx +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/min.epub +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/min.odp +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/min.ods +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/min.odt +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/min.pdf +32 -0
- zenfmt-0.1.0/tests/integration/fixtures/min.pptx +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/min.xlsx +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/note.md +3 -0
- zenfmt-0.1.0/tests/integration/fixtures/note.txt +3 -0
- zenfmt-0.1.0/tests/integration/fixtures/page.html +1 -0
- zenfmt-0.1.0/tests/integration/fixtures/table.csv +3 -0
- zenfmt-0.1.0/tests/integration/fixtures/tiny.doc +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/tiny.ppt +0 -0
- zenfmt-0.1.0/tests/integration/fixtures/tiny.xls +0 -0
- zenfmt-0.1.0/tests/integration/test_matrix.py +35 -0
- zenfmt-0.1.0/tests/integration/test_native.py +58 -0
- zenfmt-0.1.0/tests/integration/test_parity.py +48 -0
- zenfmt-0.1.0/tests/integration/test_paths.py +104 -0
- zenfmt-0.1.0/tests/integration/test_threads.py +43 -0
- zenfmt-0.1.0/tests/release/conftest.py +34 -0
- zenfmt-0.1.0/tests/release/smoke.py +92 -0
- zenfmt-0.1.0/tests/release/test_sdist_standalone.py +83 -0
- zenfmt-0.1.0/tests/release/test_wheel_contents.py +77 -0
- zenfmt-0.1.0/tests/release/test_wheel_install.py +72 -0
- zenfmt-0.1.0/tests/release_support.py +47 -0
- zenfmt-0.1.0/tests/support.py +240 -0
- zenfmt-0.1.0/tests/unit/test_convert.py +329 -0
- zenfmt-0.1.0/tests/unit/test_errors.py +255 -0
- zenfmt-0.1.0/tests/unit/test_exports.py +71 -0
- zenfmt-0.1.0/tests/unit/test_limits.py +74 -0
- zenfmt-0.1.0/tests/unit/test_loader.py +160 -0
- zenfmt-0.1.0/tests/unit/test_models.py +220 -0
- zenfmt-0.1.0/tests/unit/test_sources.py +182 -0
- zenfmt-0.1.0/uv.lock +223 -0
zenfmt-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
.zig-cache/
|
|
3
|
+
zig-out/
|
|
4
|
+
docs/build/
|
|
5
|
+
benchmarks/corpus/
|
|
6
|
+
benchmarks/.anydoc/
|
|
7
|
+
benchmarks/.venv-wheel/
|
|
8
|
+
.venv/
|
|
9
|
+
dist/
|
|
10
|
+
__pycache__/
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
python/src/zenfmt/_native/*.so
|
|
14
|
+
python/src/zenfmt/_native/*.dylib
|
|
15
|
+
python/src/zenfmt/_native/*.dll
|
zenfmt-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vikrant Rathore and Ronak Rathore
|
|
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.
|
zenfmt-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zenfmt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Universal document conversion: typed Python API over the zenfmt native engine
|
|
5
|
+
Project-URL: Homepage, https://github.com/insanai/zenfmt
|
|
6
|
+
Project-URL: Documentation, https://insanai.github.io/zenfmt/
|
|
7
|
+
Project-URL: Repository, https://github.com/insanai/zenfmt
|
|
8
|
+
Project-URL: Issues, https://github.com/insanai/zenfmt/issues
|
|
9
|
+
Author: Vikrant Rathore
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: conversion,document,docx,epub,markdown,office,pdf
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Programming Language :: Zig
|
|
26
|
+
Classifier: Topic :: Office/Business :: Office Suites
|
|
27
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# zenfmt
|
|
33
|
+
|
|
34
|
+
Universal document conversion as a typed, dependency-free Python library.
|
|
35
|
+
zenfmt reads 19 formats—DOCX, DOC, ODT, RTF, XLSX, XLS, XLSB, ODS, CSV, PPTX,
|
|
36
|
+
PPT, ODP, EPUB, PDF, HTML, Markdown, AsciiDoc, reStructuredText, and plain
|
|
37
|
+
text—and writes clean Markdown through a native engine written in Zig. The
|
|
38
|
+
wheel bundles the engine: no runtime dependencies, subprocesses, or network
|
|
39
|
+
downloads are required, and the GIL is released during conversion.
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pip install zenfmt
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import zenfmt
|
|
47
|
+
|
|
48
|
+
# A str is always a path; the result is the whole in-memory ensemble:
|
|
49
|
+
# artifact bytes, embedded resources, the canonical manifest, and reports.
|
|
50
|
+
conversion = zenfmt.convert("report.docx")
|
|
51
|
+
print(conversion.text)
|
|
52
|
+
for report in conversion.reports:
|
|
53
|
+
print(report.code, report.problem)
|
|
54
|
+
|
|
55
|
+
# Bytes are explicit; the source format is named or sniffed.
|
|
56
|
+
conversion = zenfmt.convert(uploaded_bytes, name="upload.docx", to="markdown")
|
|
57
|
+
|
|
58
|
+
# An output path selects transactional publication: the artifact, its media,
|
|
59
|
+
# and a manifest land together or not at all. Graded strictness refuses
|
|
60
|
+
# priced fidelity loss before anything is written.
|
|
61
|
+
conversion = zenfmt.convert(
|
|
62
|
+
"report.docx", output="build/report.md", strict="structure"
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Failures raise a compact exception tree (`ConversionError`,
|
|
67
|
+
`LimitExceededError`, `UnknownFormatError`, …) whose messages answer the same
|
|
68
|
+
four questions as the engine's CLI diagnostics — what happened, where, what it
|
|
69
|
+
costs, and what you can do. Reusable policy lives in immutable
|
|
70
|
+
`zenfmt.Converter` values; there is no global configuration or environment
|
|
71
|
+
lookup. `zenfmt.formats()` enumerates the compiled-in format capabilities.
|
|
72
|
+
|
|
73
|
+
Every library error includes structured facts and an actionable hint. Handle
|
|
74
|
+
stable codes in application logic and render the message for people:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
try:
|
|
78
|
+
zenfmt.convert(uploaded_bytes, name="upload.docx")
|
|
79
|
+
except zenfmt.ConversionError as error:
|
|
80
|
+
log.info("conversion failed", extra={"code": error.code})
|
|
81
|
+
print(error.hint)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Security and authority
|
|
85
|
+
|
|
86
|
+
A string is always a path. Passing a path explicitly authorizes zenfmt to read
|
|
87
|
+
that file and, when present, its adjacent digest-bound `.zenfmt.json` manifest.
|
|
88
|
+
Passing bytes or a binary reader grants no filesystem authority: `name` is
|
|
89
|
+
display-only, is never opened, and no adjacent file is inspected. External
|
|
90
|
+
resource references are reported in `conversion.resources` but are never
|
|
91
|
+
fetched. The runtime does not read configuration from the environment, follow
|
|
92
|
+
includes, resolve external entities, discover plugins, or use the network.
|
|
93
|
+
|
|
94
|
+
Conversion is bounded by `zenfmt.Limits`; the defaults cap input, decoded
|
|
95
|
+
text, archive expansion, document structure, reports, embedded resources, and
|
|
96
|
+
artifact output. Services handling large or untrusted documents should lower
|
|
97
|
+
those limits and publish path output inside a per-job directory with suitable
|
|
98
|
+
permissions and quotas. Path publication is transactional and refuses to
|
|
99
|
+
replace an existing artifact ensemble unless `overwrite=True`.
|
|
100
|
+
|
|
101
|
+
The native engine runs inside the Python process with that process's
|
|
102
|
+
authority. A wheel is not a sandbox. Workloads that need a stronger isolation
|
|
103
|
+
boundary should perform conversion in a restricted worker process or
|
|
104
|
+
container. Independent calls are thread-safe; create worker processes before
|
|
105
|
+
starting conversion threads, or use the multiprocessing `spawn` start method.
|
|
106
|
+
|
|
107
|
+
Wheels are published for Linux (manylinux and musl, x86_64 and aarch64),
|
|
108
|
+
macOS 12+ (x86_64 and arm64), and Windows x86_64, for CPython 3.10–3.14.
|
|
109
|
+
Building from the source distribution requires only a
|
|
110
|
+
[Zig](https://ziglang.org/) toolchain — the sdist carries the complete engine
|
|
111
|
+
source under `engine/`.
|
|
112
|
+
|
|
113
|
+
CLI archives and Python wheels are attached to each
|
|
114
|
+
[GitHub release](https://github.com/insanai/zenfmt/releases). This package is
|
|
115
|
+
the Python surface of the [zenfmt monorepo](https://github.com/insanai/zenfmt);
|
|
116
|
+
the engine, design records (ZDS 0014 covers this library), and development
|
|
117
|
+
workflow live there.
|
|
118
|
+
|
|
119
|
+
zenfmt is authored by Vikrant Rathore with assistance from Ronak Rathore.
|
zenfmt-0.1.0/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# zenfmt
|
|
2
|
+
|
|
3
|
+
Universal document conversion as a typed, dependency-free Python library.
|
|
4
|
+
zenfmt reads 19 formats—DOCX, DOC, ODT, RTF, XLSX, XLS, XLSB, ODS, CSV, PPTX,
|
|
5
|
+
PPT, ODP, EPUB, PDF, HTML, Markdown, AsciiDoc, reStructuredText, and plain
|
|
6
|
+
text—and writes clean Markdown through a native engine written in Zig. The
|
|
7
|
+
wheel bundles the engine: no runtime dependencies, subprocesses, or network
|
|
8
|
+
downloads are required, and the GIL is released during conversion.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
pip install zenfmt
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import zenfmt
|
|
16
|
+
|
|
17
|
+
# A str is always a path; the result is the whole in-memory ensemble:
|
|
18
|
+
# artifact bytes, embedded resources, the canonical manifest, and reports.
|
|
19
|
+
conversion = zenfmt.convert("report.docx")
|
|
20
|
+
print(conversion.text)
|
|
21
|
+
for report in conversion.reports:
|
|
22
|
+
print(report.code, report.problem)
|
|
23
|
+
|
|
24
|
+
# Bytes are explicit; the source format is named or sniffed.
|
|
25
|
+
conversion = zenfmt.convert(uploaded_bytes, name="upload.docx", to="markdown")
|
|
26
|
+
|
|
27
|
+
# An output path selects transactional publication: the artifact, its media,
|
|
28
|
+
# and a manifest land together or not at all. Graded strictness refuses
|
|
29
|
+
# priced fidelity loss before anything is written.
|
|
30
|
+
conversion = zenfmt.convert(
|
|
31
|
+
"report.docx", output="build/report.md", strict="structure"
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Failures raise a compact exception tree (`ConversionError`,
|
|
36
|
+
`LimitExceededError`, `UnknownFormatError`, …) whose messages answer the same
|
|
37
|
+
four questions as the engine's CLI diagnostics — what happened, where, what it
|
|
38
|
+
costs, and what you can do. Reusable policy lives in immutable
|
|
39
|
+
`zenfmt.Converter` values; there is no global configuration or environment
|
|
40
|
+
lookup. `zenfmt.formats()` enumerates the compiled-in format capabilities.
|
|
41
|
+
|
|
42
|
+
Every library error includes structured facts and an actionable hint. Handle
|
|
43
|
+
stable codes in application logic and render the message for people:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
try:
|
|
47
|
+
zenfmt.convert(uploaded_bytes, name="upload.docx")
|
|
48
|
+
except zenfmt.ConversionError as error:
|
|
49
|
+
log.info("conversion failed", extra={"code": error.code})
|
|
50
|
+
print(error.hint)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Security and authority
|
|
54
|
+
|
|
55
|
+
A string is always a path. Passing a path explicitly authorizes zenfmt to read
|
|
56
|
+
that file and, when present, its adjacent digest-bound `.zenfmt.json` manifest.
|
|
57
|
+
Passing bytes or a binary reader grants no filesystem authority: `name` is
|
|
58
|
+
display-only, is never opened, and no adjacent file is inspected. External
|
|
59
|
+
resource references are reported in `conversion.resources` but are never
|
|
60
|
+
fetched. The runtime does not read configuration from the environment, follow
|
|
61
|
+
includes, resolve external entities, discover plugins, or use the network.
|
|
62
|
+
|
|
63
|
+
Conversion is bounded by `zenfmt.Limits`; the defaults cap input, decoded
|
|
64
|
+
text, archive expansion, document structure, reports, embedded resources, and
|
|
65
|
+
artifact output. Services handling large or untrusted documents should lower
|
|
66
|
+
those limits and publish path output inside a per-job directory with suitable
|
|
67
|
+
permissions and quotas. Path publication is transactional and refuses to
|
|
68
|
+
replace an existing artifact ensemble unless `overwrite=True`.
|
|
69
|
+
|
|
70
|
+
The native engine runs inside the Python process with that process's
|
|
71
|
+
authority. A wheel is not a sandbox. Workloads that need a stronger isolation
|
|
72
|
+
boundary should perform conversion in a restricted worker process or
|
|
73
|
+
container. Independent calls are thread-safe; create worker processes before
|
|
74
|
+
starting conversion threads, or use the multiprocessing `spawn` start method.
|
|
75
|
+
|
|
76
|
+
Wheels are published for Linux (manylinux and musl, x86_64 and aarch64),
|
|
77
|
+
macOS 12+ (x86_64 and arm64), and Windows x86_64, for CPython 3.10–3.14.
|
|
78
|
+
Building from the source distribution requires only a
|
|
79
|
+
[Zig](https://ziglang.org/) toolchain — the sdist carries the complete engine
|
|
80
|
+
source under `engine/`.
|
|
81
|
+
|
|
82
|
+
CLI archives and Python wheels are attached to each
|
|
83
|
+
[GitHub release](https://github.com/insanai/zenfmt/releases). This package is
|
|
84
|
+
the Python surface of the [zenfmt monorepo](https://github.com/insanai/zenfmt);
|
|
85
|
+
the engine, design records (ZDS 0014 covers this library), and development
|
|
86
|
+
workflow live there.
|
|
87
|
+
|
|
88
|
+
zenfmt is authored by Vikrant Rathore with assistance from Ronak Rathore.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
//! The zenfmt Python bridge: a private, versioned C ABI over the default
|
|
2
|
+
//! bundle (ZDS 0014).
|
|
3
|
+
//!
|
|
4
|
+
//! The installed `zenfmt` Python package is the only supported consumer.
|
|
5
|
+
//! Symbols use the `zenfmt_py_` prefix, fixed-width integers, and
|
|
6
|
+
//! pointer-length slices; options cross as one versioned JSON object; the
|
|
7
|
+
//! result is an opaque handle whose accessor slices stay valid until the
|
|
8
|
+
//! single `zenfmt_py_result_free`. No callback ever enters the embedding
|
|
9
|
+
//! language, and no pointer into caller memory survives a call.
|
|
10
|
+
|
|
11
|
+
const std = @import("std");
|
|
12
|
+
const builtin = @import("builtin");
|
|
13
|
+
const build_info = @import("zenfmt_build");
|
|
14
|
+
const capabilities = @import("capabilities.zig");
|
|
15
|
+
const result_mod = @import("result.zig");
|
|
16
|
+
|
|
17
|
+
pub const abi_major: u32 = 1;
|
|
18
|
+
pub const abi_minor: u32 = 0;
|
|
19
|
+
|
|
20
|
+
/// `path_encoding` values in `RuntimeInfo`.
|
|
21
|
+
pub const path_encoding_posix_bytes: u32 = 1;
|
|
22
|
+
pub const path_encoding_utf16le: u32 = 2;
|
|
23
|
+
|
|
24
|
+
pub const Slice = extern struct {
|
|
25
|
+
ptr: ?[*]const u8,
|
|
26
|
+
len: u64,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/// A native path: raw bytes on POSIX, UTF-16LE code units on Windows.
|
|
30
|
+
/// `len` counts code units, not bytes.
|
|
31
|
+
pub const PathSlice = extern struct {
|
|
32
|
+
ptr: ?*const anyopaque,
|
|
33
|
+
len: u64,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
pub const RuntimeInfo = extern struct {
|
|
37
|
+
abi_major: u32,
|
|
38
|
+
abi_minor: u32,
|
|
39
|
+
pointer_bits: u32,
|
|
40
|
+
path_encoding: u32,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
pub const Request = extern struct {
|
|
44
|
+
options_json: Slice,
|
|
45
|
+
input_bytes: Slice,
|
|
46
|
+
input_path: PathSlice,
|
|
47
|
+
output_path: PathSlice,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
pub const ResourceView = extern struct {
|
|
51
|
+
rel_path: Slice,
|
|
52
|
+
bytes: Slice,
|
|
53
|
+
digest_hex: Slice,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
pub const Result = result_mod.Result;
|
|
57
|
+
|
|
58
|
+
fn slice(bytes: []const u8) Slice {
|
|
59
|
+
return .{ .ptr = bytes.ptr, .len = bytes.len };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export fn zenfmt_py_abi_version() u32 {
|
|
63
|
+
return (abi_major << 16) | abi_minor;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export fn zenfmt_py_runtime_info(out: ?*RuntimeInfo) void {
|
|
67
|
+
const info = out orelse return;
|
|
68
|
+
info.* = .{
|
|
69
|
+
.abi_major = abi_major,
|
|
70
|
+
.abi_minor = abi_minor,
|
|
71
|
+
.pointer_bits = @bitSizeOf(usize),
|
|
72
|
+
.path_encoding = if (builtin.os.tag == .windows)
|
|
73
|
+
path_encoding_utf16le
|
|
74
|
+
else
|
|
75
|
+
path_encoding_posix_bytes,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export fn zenfmt_py_zenfmt_version(out_len: ?*u64) ?[*]const u8 {
|
|
80
|
+
const len = out_len orelse return null;
|
|
81
|
+
len.* = build_info.version.len;
|
|
82
|
+
return build_info.version.ptr;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export fn zenfmt_py_capabilities(out_len: ?*u64) ?[*]const u8 {
|
|
86
|
+
const len = out_len orelse return null;
|
|
87
|
+
len.* = capabilities.json.len;
|
|
88
|
+
return capabilities.json.ptr;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export fn zenfmt_py_convert(request: ?*const Request) ?*Result {
|
|
92
|
+
const value = request orelse return null;
|
|
93
|
+
return result_mod.convertThreaded(std.heap.smp_allocator, value);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export fn zenfmt_py_result_status(result: ?*const Result) u32 {
|
|
97
|
+
const value = result orelse return result_mod.status_invalid_request;
|
|
98
|
+
return value.status;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export fn zenfmt_py_result_exit_class(result: ?*const Result) u32 {
|
|
102
|
+
const value = result orelse return 0;
|
|
103
|
+
return value.exit_class;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export fn zenfmt_py_result_reports_json(
|
|
107
|
+
result: ?*const Result,
|
|
108
|
+
out_len: ?*u64,
|
|
109
|
+
) ?[*]const u8 {
|
|
110
|
+
const value = result orelse return null;
|
|
111
|
+
const len = out_len orelse return null;
|
|
112
|
+
len.* = value.reports_json.len;
|
|
113
|
+
return value.reports_json.ptr;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export fn zenfmt_py_result_manifest_json(
|
|
117
|
+
result: ?*const Result,
|
|
118
|
+
out_len: ?*u64,
|
|
119
|
+
) ?[*]const u8 {
|
|
120
|
+
const value = result orelse return null;
|
|
121
|
+
const len = out_len orelse return null;
|
|
122
|
+
if (value.conversion) |*conversion| {
|
|
123
|
+
const manifest_json = conversion.manifest_json orelse return null;
|
|
124
|
+
len.* = manifest_json.len;
|
|
125
|
+
return manifest_json.ptr;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export fn zenfmt_py_result_source_format(
|
|
131
|
+
result: ?*const Result,
|
|
132
|
+
out_len: ?*u64,
|
|
133
|
+
) ?[*]const u8 {
|
|
134
|
+
const value = result orelse return null;
|
|
135
|
+
const len = out_len orelse return null;
|
|
136
|
+
if (value.conversion) |*conversion| {
|
|
137
|
+
const format = conversion.source_format orelse return null;
|
|
138
|
+
len.* = format.len;
|
|
139
|
+
return format.ptr;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export fn zenfmt_py_result_output_format(
|
|
145
|
+
result: ?*const Result,
|
|
146
|
+
out_len: ?*u64,
|
|
147
|
+
) ?[*]const u8 {
|
|
148
|
+
const value = result orelse return null;
|
|
149
|
+
const len = out_len orelse return null;
|
|
150
|
+
if (value.conversion) |*conversion| {
|
|
151
|
+
const format = conversion.output_format orelse return null;
|
|
152
|
+
len.* = format.len;
|
|
153
|
+
return format.ptr;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export fn zenfmt_py_result_artifact(
|
|
159
|
+
result: ?*const Result,
|
|
160
|
+
out_len: ?*u64,
|
|
161
|
+
) ?[*]const u8 {
|
|
162
|
+
const value = result orelse return null;
|
|
163
|
+
const len = out_len orelse return null;
|
|
164
|
+
const ensemble = value.ensemble() orelse return null;
|
|
165
|
+
len.* = ensemble.artifact.len;
|
|
166
|
+
return ensemble.artifact.ptr;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export fn zenfmt_py_result_artifact_name(
|
|
170
|
+
result: ?*const Result,
|
|
171
|
+
out_len: ?*u64,
|
|
172
|
+
) ?[*]const u8 {
|
|
173
|
+
const value = result orelse return null;
|
|
174
|
+
const len = out_len orelse return null;
|
|
175
|
+
const ensemble = value.ensemble() orelse return null;
|
|
176
|
+
len.* = ensemble.artifact_name.len;
|
|
177
|
+
return ensemble.artifact_name.ptr;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export fn zenfmt_py_result_resource_count(result: ?*const Result) u64 {
|
|
181
|
+
const value = result orelse return 0;
|
|
182
|
+
const ensemble = value.ensemble() orelse return 0;
|
|
183
|
+
return ensemble.resources.len;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export fn zenfmt_py_result_resource(
|
|
187
|
+
result: ?*const Result,
|
|
188
|
+
index: u64,
|
|
189
|
+
out: ?*ResourceView,
|
|
190
|
+
) u32 {
|
|
191
|
+
const value = result orelse return 1;
|
|
192
|
+
const view = out orelse return 1;
|
|
193
|
+
const ensemble = value.ensemble() orelse return 1;
|
|
194
|
+
const i = std.math.cast(usize, index) orelse return 1;
|
|
195
|
+
if (i >= ensemble.resources.len) return 1;
|
|
196
|
+
const resource = &ensemble.resources[i];
|
|
197
|
+
view.* = .{
|
|
198
|
+
.rel_path = slice(resource.rel_path),
|
|
199
|
+
.bytes = slice(resource.bytes),
|
|
200
|
+
.digest_hex = slice(&resource.digest_hex),
|
|
201
|
+
};
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export fn zenfmt_py_result_free(result: ?*Result) void {
|
|
206
|
+
const value = result orelse return;
|
|
207
|
+
value.destroy();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
test {
|
|
211
|
+
_ = @import("capabilities.zig");
|
|
212
|
+
_ = @import("request.zig");
|
|
213
|
+
_ = @import("result.zig");
|
|
214
|
+
_ = @import("abi_test.zig");
|
|
215
|
+
_ = @import("fuzz_test.zig");
|
|
216
|
+
_ = @import("oom_test.zig");
|
|
217
|
+
}
|