docxdriver 0.3.3__tar.gz → 0.4.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.
- {docxdriver-0.3.3 → docxdriver-0.4.0}/Cargo.lock +13 -5
- {docxdriver-0.3.3 → docxdriver-0.4.0}/Cargo.toml +1 -1
- {docxdriver-0.3.3 → docxdriver-0.4.0}/PKG-INFO +32 -26
- docxdriver-0.4.0/README.md +87 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/Cargo.toml +6 -0
- docxdriver-0.4.0/crates/docxdriver-core/LICENSE +21 -0
- docxdriver-0.4.0/crates/docxdriver-core/src/facade.rs +200 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/lib.rs +4 -1
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-python/Cargo.toml +2 -0
- docxdriver-0.4.0/crates/docxdriver-python/README.md +87 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0/crates/docxdriver-python}/python/docxdriver/__init__.py +2 -1
- docxdriver-0.4.0/crates/docxdriver-python/python/docxdriver/document.py +126 -0
- docxdriver-0.4.0/crates/docxdriver-python/python/docxdriver/plan.py +570 -0
- docxdriver-0.4.0/crates/docxdriver-python/src/lib.rs +199 -0
- docxdriver-0.4.0/crates/docxdriver-python/tests/test_document.py +75 -0
- docxdriver-0.4.0/crates/docxdriver-python/tests/test_plan.py +40 -0
- {docxdriver-0.3.3/crates/docxdriver-python → docxdriver-0.4.0}/python/docxdriver/__init__.py +2 -1
- docxdriver-0.4.0/python/docxdriver/document.py +126 -0
- docxdriver-0.4.0/python/docxdriver/plan.py +570 -0
- docxdriver-0.3.3/README.md +0 -81
- docxdriver-0.3.3/crates/docxdriver-core/examples/list_scaling.rs +0 -71
- docxdriver-0.3.3/crates/docxdriver-core/examples/plan_scaling.rs +0 -43
- docxdriver-0.3.3/crates/docxdriver-core/src/bin/generate_typed_schema.rs +0 -172
- docxdriver-0.3.3/crates/docxdriver-core/src/html/README.md +0 -14
- docxdriver-0.3.3/crates/docxdriver-core/tests/README.md +0 -35
- docxdriver-0.3.3/crates/docxdriver-core/tests/fixtures/projection-parity/features.json +0 -33
- docxdriver-0.3.3/crates/docxdriver-core/tests/fixtures/projection-parity/parity.docx +0 -0
- docxdriver-0.3.3/crates/docxdriver-core/tests/fixtures/projection-parity/prior-projection.json +0 -1393
- docxdriver-0.3.3/crates/docxdriver-core/tests/fixtures/ts-reference.docx +0 -0
- docxdriver-0.3.3/crates/docxdriver-core/tests/formatting_fidelity.rs +0 -1148
- docxdriver-0.3.3/crates/docxdriver-core/tests/html_e2e.rs +0 -983
- docxdriver-0.3.3/crates/docxdriver-core/tests/interop.rs +0 -99
- docxdriver-0.3.3/crates/docxdriver-core/tests/package_layout.rs +0 -126
- docxdriver-0.3.3/crates/docxdriver-core/tests/projection_parity.rs +0 -531
- docxdriver-0.3.3/crates/docxdriver-core/tests/spike.rs +0 -44
- docxdriver-0.3.3/crates/docxdriver-core/tests/typed_api.rs +0 -2553
- docxdriver-0.3.3/crates/docxdriver-python/README.md +0 -81
- docxdriver-0.3.3/crates/docxdriver-python/python/docxdriver/repl/__init__.py +0 -7
- docxdriver-0.3.3/crates/docxdriver-python/python/docxdriver/repl/host.py +0 -562
- docxdriver-0.3.3/crates/docxdriver-python/python/docxdriver/repl/prelude.py +0 -3
- docxdriver-0.3.3/crates/docxdriver-python/python/docxdriver/repl/quotes.py +0 -325
- docxdriver-0.3.3/crates/docxdriver-python/src/lib.rs +0 -62
- docxdriver-0.3.3/crates/docxdriver-python/tests/test_host.py +0 -357
- docxdriver-0.3.3/python/docxdriver/repl/__init__.py +0 -7
- docxdriver-0.3.3/python/docxdriver/repl/host.py +0 -562
- docxdriver-0.3.3/python/docxdriver/repl/prelude.py +0 -3
- docxdriver-0.3.3/python/docxdriver/repl/quotes.py +0 -325
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/README.md +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/assets/blank.docx +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/api.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/comments.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/equations.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/headers.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/layout.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/lists.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/mod.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/reading.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/readonly.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/revisions.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/sections.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/table_structure.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/tables.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/commands/typed.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/document.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/assets.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/build.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/chrome.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/compact.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/ctx.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/input.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/mathml_omml.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/mod.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/omml_latex.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/omml_mathml.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/parse.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/render.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/styles.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/html/surface.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/media.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/outcome.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/package.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-core/src/segments.rs +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/crates/docxdriver-python/.gitignore +0 -0
- {docxdriver-0.3.3 → docxdriver-0.4.0}/pyproject.toml +0 -0
|
@@ -416,7 +416,7 @@ dependencies = [
|
|
|
416
416
|
|
|
417
417
|
[[package]]
|
|
418
418
|
name = "docxdriver-cli"
|
|
419
|
-
version = "0.
|
|
419
|
+
version = "0.4.0"
|
|
420
420
|
dependencies = [
|
|
421
421
|
"clap",
|
|
422
422
|
"docxdriver-core",
|
|
@@ -427,7 +427,7 @@ dependencies = [
|
|
|
427
427
|
|
|
428
428
|
[[package]]
|
|
429
429
|
name = "docxdriver-core"
|
|
430
|
-
version = "0.
|
|
430
|
+
version = "0.4.0"
|
|
431
431
|
dependencies = [
|
|
432
432
|
"hex",
|
|
433
433
|
"latex2mathml",
|
|
@@ -444,7 +444,7 @@ dependencies = [
|
|
|
444
444
|
|
|
445
445
|
[[package]]
|
|
446
446
|
name = "docxdriver-mcp"
|
|
447
|
-
version = "0.
|
|
447
|
+
version = "0.4.0"
|
|
448
448
|
dependencies = [
|
|
449
449
|
"axum",
|
|
450
450
|
"cap-std",
|
|
@@ -460,18 +460,26 @@ dependencies = [
|
|
|
460
460
|
"tokio-util",
|
|
461
461
|
]
|
|
462
462
|
|
|
463
|
+
[[package]]
|
|
464
|
+
name = "docxdriver-monty-repl"
|
|
465
|
+
version = "0.4.0"
|
|
466
|
+
dependencies = [
|
|
467
|
+
"docxdriver-core",
|
|
468
|
+
]
|
|
469
|
+
|
|
463
470
|
[[package]]
|
|
464
471
|
name = "docxdriver-python"
|
|
465
|
-
version = "0.
|
|
472
|
+
version = "0.4.0"
|
|
466
473
|
dependencies = [
|
|
467
474
|
"docxdriver-core",
|
|
468
475
|
"pyo3",
|
|
476
|
+
"serde",
|
|
469
477
|
"serde_json",
|
|
470
478
|
]
|
|
471
479
|
|
|
472
480
|
[[package]]
|
|
473
481
|
name = "docxdriver-wasm"
|
|
474
|
-
version = "0.
|
|
482
|
+
version = "0.4.0"
|
|
475
483
|
dependencies = [
|
|
476
484
|
"docxdriver-core",
|
|
477
485
|
"js-sys",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docxdriver
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python SDK for the native Rust DOCX engine
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -18,38 +18,44 @@ Install from PyPI:
|
|
|
18
18
|
python -m pip install docxdriver
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
contents as Python `bytes`.
|
|
24
|
-
|
|
25
|
-
The optional sandbox REPL harness is available as a subpackage in the same
|
|
26
|
-
distribution:
|
|
21
|
+
`Document` works on `.docx` bytes; typed plan builders live in
|
|
22
|
+
`docxdriver.plan`:
|
|
27
23
|
|
|
28
24
|
```python
|
|
29
|
-
from
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from docxdriver import Document, create
|
|
27
|
+
from docxdriver.plan import Plan, ReplaceText
|
|
28
|
+
|
|
29
|
+
doc = Document(Path("contract.docx").read_bytes())
|
|
30
|
+
doc.markup() # body with the ids plans address
|
|
31
|
+
doc.comments(); doc.revisions(); doc.styles(); doc.assets()
|
|
32
|
+
doc.find("thirty")
|
|
33
|
+
review = doc.review(Plan(author="Reviewer", ops=[
|
|
34
|
+
ReplaceText(at="0F537164", select="thirty", with_="sixty"),
|
|
35
|
+
]))
|
|
36
|
+
review.report # preview: each op's outcome
|
|
37
|
+
Path("contract.docx").write_bytes(doc.commit(review.key).data)
|
|
38
|
+
|
|
39
|
+
new = create(paragraphs=["Hello"]) # or create(html="<p>Hello</p>")
|
|
30
40
|
```
|
|
31
41
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
The host confines reads and writes to the provided workspace, verifies source
|
|
39
|
-
hashes and candidate output before commit, and performs atomic file replacement
|
|
40
|
-
with read-back verification. Commit keys are bounded and single-use. Cancellation
|
|
41
|
-
is drained before another host call so a write callback cannot be abandoned.
|
|
42
|
+
Commit keys are single-use. Natively `review.key` carries the key; in a
|
|
43
|
+
sandbox it is `None` and the host shows the key only to the model, so a
|
|
44
|
+
commit proves the review was read. Sandbox hosts written in Python build on
|
|
45
|
+
the stateless `review_plan(data, plan_json)` and
|
|
46
|
+
`commit_plan(data, plan_json, key)`, keeping each plan under a key they issue.
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
`permissive`; `controlled` and `authoritative` fail closed.
|
|
48
|
+
Engine diagnostics raise `ValueError`. The lower-level `execute_request`,
|
|
49
|
+
`canonical_plan_json`, `run_command`, `run_plan` and `run_request` remain.
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
`plan.py` and `document.py` stay within Monty's Python subset. Rust hosts get
|
|
52
|
+
the same text as `docxdriver_monty_repl::PLAN` and `DOCUMENT`, so sandbox code
|
|
53
|
+
and native code are interchangeable. The op dataclasses are generated from the
|
|
54
|
+
core schema; regenerate from the repository root with
|
|
55
|
+
`python scripts/generate-python-plan.py`.
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```
|
|
57
|
+
Version 0.4.0 removed the `docxdriver.repl` sandbox host from this package. Its
|
|
58
|
+
source remains in `experimental/python-repl-host` for hosts to vendor.
|
|
53
59
|
|
|
54
60
|
Build and install a wheel from the repository root:
|
|
55
61
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# docxdriver
|
|
2
|
+
|
|
3
|
+
Python SDK for the Rust DOCX engine. It supports Python 3.10 and later and has no
|
|
4
|
+
Python runtime dependencies.
|
|
5
|
+
|
|
6
|
+
Install from PyPI:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
python -m pip install docxdriver
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`Document` works on `.docx` bytes; typed plan builders live in
|
|
13
|
+
`docxdriver.plan`:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from docxdriver import Document, create
|
|
18
|
+
from docxdriver.plan import Plan, ReplaceText
|
|
19
|
+
|
|
20
|
+
doc = Document(Path("contract.docx").read_bytes())
|
|
21
|
+
doc.markup() # body with the ids plans address
|
|
22
|
+
doc.comments(); doc.revisions(); doc.styles(); doc.assets()
|
|
23
|
+
doc.find("thirty")
|
|
24
|
+
review = doc.review(Plan(author="Reviewer", ops=[
|
|
25
|
+
ReplaceText(at="0F537164", select="thirty", with_="sixty"),
|
|
26
|
+
]))
|
|
27
|
+
review.report # preview: each op's outcome
|
|
28
|
+
Path("contract.docx").write_bytes(doc.commit(review.key).data)
|
|
29
|
+
|
|
30
|
+
new = create(paragraphs=["Hello"]) # or create(html="<p>Hello</p>")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Commit keys are single-use. Natively `review.key` carries the key; in a
|
|
34
|
+
sandbox it is `None` and the host shows the key only to the model, so a
|
|
35
|
+
commit proves the review was read. Sandbox hosts written in Python build on
|
|
36
|
+
the stateless `review_plan(data, plan_json)` and
|
|
37
|
+
`commit_plan(data, plan_json, key)`, keeping each plan under a key they issue.
|
|
38
|
+
|
|
39
|
+
Engine diagnostics raise `ValueError`. The lower-level `execute_request`,
|
|
40
|
+
`canonical_plan_json`, `run_command`, `run_plan` and `run_request` remain.
|
|
41
|
+
|
|
42
|
+
`plan.py` and `document.py` stay within Monty's Python subset. Rust hosts get
|
|
43
|
+
the same text as `docxdriver_monty_repl::PLAN` and `DOCUMENT`, so sandbox code
|
|
44
|
+
and native code are interchangeable. The op dataclasses are generated from the
|
|
45
|
+
core schema; regenerate from the repository root with
|
|
46
|
+
`python scripts/generate-python-plan.py`.
|
|
47
|
+
|
|
48
|
+
Version 0.4.0 removed the `docxdriver.repl` sandbox host from this package. Its
|
|
49
|
+
source remains in `experimental/python-repl-host` for hosts to vendor.
|
|
50
|
+
|
|
51
|
+
Build and install a wheel from the repository root:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
python -m pip install "maturin>=1.9,<2"
|
|
55
|
+
maturin build --profile python --locked --manifest-path crates/docxdriver-python/Cargo.toml --out /tmp/docxdriver-wheels
|
|
56
|
+
python -m pip install /tmp/docxdriver-wheels/*.whl
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Publish to PyPI
|
|
60
|
+
|
|
61
|
+
The `Publish Python SDK` GitHub Actions workflow builds a source distribution
|
|
62
|
+
and wheels for Linux x86_64 and aarch64, macOS universal2, and Windows x86_64
|
|
63
|
+
when a `v*` tag is pushed. The tag must match the version in the root
|
|
64
|
+
`Cargo.toml` workspace package.
|
|
65
|
+
|
|
66
|
+
Before the first release, configure a PyPI trusted publisher for the
|
|
67
|
+
`docxdriver` project with these values:
|
|
68
|
+
|
|
69
|
+
- Owner: `jhnoel`
|
|
70
|
+
- Repository: `docxdriver`
|
|
71
|
+
- Workflow filename: `pypi.yml`
|
|
72
|
+
- GitHub environment: `pypi`
|
|
73
|
+
|
|
74
|
+
Create the `pypi` GitHub environment under the repository settings if you want
|
|
75
|
+
to add release approvals. Then update the workspace version, commit the change,
|
|
76
|
+
and push the matching tag (for example, `v0.2.7`). GitHub Actions publishes the
|
|
77
|
+
built distributions using PyPI's trusted publishing; no long-lived PyPI token is
|
|
78
|
+
needed.
|
|
79
|
+
|
|
80
|
+
The `python` Cargo profile keeps release optimizations and enables panic unwinding
|
|
81
|
+
so PyO3 can turn Rust panics into Python exceptions. ABI3 wheels support Python
|
|
82
|
+
3.10 and later on the built operating system and architecture. To run the Python
|
|
83
|
+
SDK and host tests after installing the wheel:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
python -m unittest discover -s crates/docxdriver-python/tests
|
|
87
|
+
```
|
|
@@ -4,7 +4,13 @@ description = "Artifact-first DOCX engine: (bytes, DocxCommand JSON) -> (bytes,
|
|
|
4
4
|
version.workspace = true
|
|
5
5
|
edition.workspace = true
|
|
6
6
|
license.workspace = true
|
|
7
|
+
repository.workspace = true
|
|
7
8
|
readme = "README.md"
|
|
9
|
+
keywords = ["docx", "ooxml", "word", "document", "track-changes"]
|
|
10
|
+
categories = ["text-processing", "parser-implementations"]
|
|
11
|
+
# Library sources and the runtime template only; tests, examples and the
|
|
12
|
+
# schema generator stay in the repository.
|
|
13
|
+
include = ["/src/**/*.rs", "!/src/bin/**", "/assets/blank.docx", "/README.md", "/LICENSE"]
|
|
8
14
|
|
|
9
15
|
[dependencies]
|
|
10
16
|
xmloxide = { version = "0.4.3", default-features = false }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 docxdriver contributors
|
|
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,200 @@
|
|
|
1
|
+
//! Typed document API over the Request engine: one `.docx` in memory,
|
|
2
|
+
//! read, searched, reviewed and committed without JSON envelopes.
|
|
3
|
+
//!
|
|
4
|
+
//! ```text
|
|
5
|
+
//! let doc = Document::open(&bytes);
|
|
6
|
+
//! doc.markup(View::Markup)? body with the ids plans address
|
|
7
|
+
//! doc.comments()? / revisions()? / styles()? / assets()?
|
|
8
|
+
//! doc.find("thirty", Case::Sensitive)?
|
|
9
|
+
//! let plan = doc.plan(json)? bound to these bytes
|
|
10
|
+
//! let review = doc.review(&plan)? key + report, nothing written
|
|
11
|
+
//! let bytes = doc.commit(&plan, &review.key)?
|
|
12
|
+
//! ```
|
|
13
|
+
//!
|
|
14
|
+
//! The preview key binds bytes and plan, so commit refuses anything that
|
|
15
|
+
//! was not reviewed against these exact bytes. No state lives between calls.
|
|
16
|
+
|
|
17
|
+
use serde_json::Value;
|
|
18
|
+
|
|
19
|
+
use crate::api::{
|
|
20
|
+
execute_command, execute_plan, parse_plan_json, Command, CommandRequest, CommandResult,
|
|
21
|
+
CreateCommand, Diagnostic, FindCommand, Plan, PlanReport, PlanRequest, PlanResult,
|
|
22
|
+
PreviewKey, ReadCommand, SourceHash,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/// Which text of the body to project.
|
|
26
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
27
|
+
pub enum View {
|
|
28
|
+
/// Tracked changes shown as `<ins>`/`<del>`.
|
|
29
|
+
Markup,
|
|
30
|
+
/// As if every change were accepted.
|
|
31
|
+
Final,
|
|
32
|
+
/// As if every change were rejected.
|
|
33
|
+
Original,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Case handling for [`Document::find`].
|
|
37
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
38
|
+
pub enum Case {
|
|
39
|
+
Sensitive,
|
|
40
|
+
Insensitive,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// What [`create`] builds a new document from.
|
|
44
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
45
|
+
pub enum Source {
|
|
46
|
+
Paragraphs(Vec<String>),
|
|
47
|
+
Html(String),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// A previewed plan: commit it with `key`; `report` says what each op did.
|
|
51
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
52
|
+
pub struct Review {
|
|
53
|
+
pub key: PreviewKey,
|
|
54
|
+
pub report: PlanReport,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// A `.docx` held as bytes.
|
|
58
|
+
#[derive(Debug, Clone, Copy)]
|
|
59
|
+
pub struct Document<'a> {
|
|
60
|
+
bytes: &'a [u8],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Builds a new `.docx`.
|
|
64
|
+
pub fn create(source: Source) -> Result<Vec<u8>, Diagnostic> {
|
|
65
|
+
let command = match source {
|
|
66
|
+
Source::Paragraphs(paragraphs) => CreateCommand { paragraphs, html: None },
|
|
67
|
+
Source::Html(html) => CreateCommand { paragraphs: Vec::new(), html: Some(html) },
|
|
68
|
+
};
|
|
69
|
+
match run(None, Command::Create(command)) {
|
|
70
|
+
CommandResult::Completed { bytes: Some(bytes), .. } => Ok(bytes),
|
|
71
|
+
CommandResult::Completed { .. } => Err(engine("create returned no document")),
|
|
72
|
+
CommandResult::Rejected { diagnostic, .. } => Err(diagnostic),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
impl<'a> Document<'a> {
|
|
77
|
+
pub fn open(bytes: &'a [u8]) -> Self {
|
|
78
|
+
Self { bytes }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// The body as HTML-like markup with stable paragraph ids.
|
|
82
|
+
pub fn markup(&self, view: View) -> Result<String, Diagnostic> {
|
|
83
|
+
let view = match view {
|
|
84
|
+
View::Markup => "markup",
|
|
85
|
+
View::Final => "final",
|
|
86
|
+
View::Original => "original",
|
|
87
|
+
};
|
|
88
|
+
let result = self.read("document", Some(view))?;
|
|
89
|
+
match result.get("markup") {
|
|
90
|
+
Some(Value::String(markup)) => Ok(markup.clone()),
|
|
91
|
+
_ => Err(engine("document read returned no markup")),
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Equations by paragraph, e.g. `{"at": "0F537164", "equation": 1, ...}`,
|
|
96
|
+
/// the addresses `replace_equation`/`delete_equation` take.
|
|
97
|
+
pub fn equations(&self) -> Result<Vec<Value>, Diagnostic> {
|
|
98
|
+
Ok(array(self.read("document", None)?, "equations"))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Comment threads with anchors and replies.
|
|
102
|
+
pub fn comments(&self) -> Result<Vec<Value>, Diagnostic> {
|
|
103
|
+
self.list("comments")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// Tracked changes; their ids feed `AcceptRevision`/`RejectRevision`.
|
|
107
|
+
pub fn revisions(&self) -> Result<Vec<Value>, Diagnostic> {
|
|
108
|
+
self.list("revisions")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Style definitions.
|
|
112
|
+
pub fn styles(&self) -> Result<Vec<Value>, Diagnostic> {
|
|
113
|
+
self.list("styles")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Embedded images and media.
|
|
117
|
+
pub fn assets(&self) -> Result<Vec<Value>, Diagnostic> {
|
|
118
|
+
self.list("assets")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Paragraphs containing `query`, with their ids.
|
|
122
|
+
pub fn find(&self, query: &str, case: Case) -> Result<Vec<Value>, Diagnostic> {
|
|
123
|
+
let command = FindCommand {
|
|
124
|
+
query: query.into(),
|
|
125
|
+
ignore_case: case == Case::Insensitive,
|
|
126
|
+
view: None,
|
|
127
|
+
};
|
|
128
|
+
let result = completed(run(Some(self.bytes), Command::Find(command)))?;
|
|
129
|
+
Ok(array(result, "matches"))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Parses a plan's JSON and binds it to these bytes; any `base` given
|
|
133
|
+
/// is replaced. E.g. `{"author":"A","change_mode":"track","ops":[...]}`.
|
|
134
|
+
pub fn plan(&self, json: &str) -> Result<Plan, Diagnostic> {
|
|
135
|
+
let mut value: Value = serde_json::from_str(json).map_err(|error| invalid(error.to_string()))?;
|
|
136
|
+
let Some(object) = value.as_object_mut() else {
|
|
137
|
+
return Err(invalid("a plan is a JSON object".into()));
|
|
138
|
+
};
|
|
139
|
+
object.insert("base".into(), Value::String(SourceHash::from_bytes(self.bytes).as_str()));
|
|
140
|
+
parse_plan_json(&value.to_string())
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Previews `plan`; writes nothing.
|
|
144
|
+
pub fn review(&self, plan: &Plan) -> Result<Review, Diagnostic> {
|
|
145
|
+
match self.execute(plan, None) {
|
|
146
|
+
PlanResult::Previewed { preview_key, report, .. } => Ok(Review { key: preview_key, report }),
|
|
147
|
+
PlanResult::Committed { .. } => Err(engine("preview committed")),
|
|
148
|
+
PlanResult::Rejected { diagnostic, .. } => Err(diagnostic),
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Applies a reviewed plan, returning the new document's bytes.
|
|
153
|
+
pub fn commit(&self, plan: &Plan, key: &PreviewKey) -> Result<Vec<u8>, Diagnostic> {
|
|
154
|
+
match self.execute(plan, Some(key.clone())) {
|
|
155
|
+
PlanResult::Committed { bytes, .. } => Ok(bytes),
|
|
156
|
+
PlanResult::Previewed { .. } => Err(engine("commit only previewed")),
|
|
157
|
+
PlanResult::Rejected { diagnostic, .. } => Err(diagnostic),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn execute(&self, plan: &Plan, preview_key: Option<PreviewKey>) -> PlanResult {
|
|
162
|
+
execute_plan(self.bytes, &PlanRequest { plan: plan.clone(), preview_key })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// One listing read, e.g. "comments" → result["comments"].
|
|
166
|
+
fn list(&self, part: &str) -> Result<Vec<Value>, Diagnostic> {
|
|
167
|
+
Ok(array(self.read(part, None)?, part))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn read(&self, part: &str, view: Option<&str>) -> Result<Value, Diagnostic> {
|
|
171
|
+
let command = ReadCommand { read_kind: Some(part.into()), view: view.map(Into::into) };
|
|
172
|
+
completed(run(Some(self.bytes), Command::Read(command)))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fn run(input: Option<&[u8]>, command: Command) -> CommandResult {
|
|
177
|
+
execute_command(input, &CommandRequest { command, expected_source: None })
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fn completed(result: CommandResult) -> Result<Value, Diagnostic> {
|
|
181
|
+
match result {
|
|
182
|
+
CommandResult::Completed { result, .. } => Ok(result.unwrap_or(Value::Null)),
|
|
183
|
+
CommandResult::Rejected { diagnostic, .. } => Err(diagnostic),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn array(mut result: Value, field: &str) -> Vec<Value> {
|
|
188
|
+
match result.get_mut(field).map(Value::take) {
|
|
189
|
+
Some(Value::Array(items)) => items,
|
|
190
|
+
_ => Vec::new(),
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn invalid(message: String) -> Diagnostic {
|
|
195
|
+
Diagnostic { code: "invalid_plan".into(), message, path: None, span: None }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fn engine(message: &str) -> Diagnostic {
|
|
199
|
+
Diagnostic { code: "engine".into(), message: message.into(), path: None, span: None }
|
|
200
|
+
}
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
//! The stable contract is the source-bound typed [`Request`] and [`RequestResult`]
|
|
4
4
|
//! API. Requests are either a typed one-command envelope or a validated Plan;
|
|
5
5
|
//! plans support canonical JSON/TOML, deterministic preview keys, atomic folds,
|
|
6
|
-
//! typed diagnostics, and complete output metadata.
|
|
6
|
+
//! typed diagnostics, and complete output metadata. [`Document`] is the same
|
|
7
|
+
//! engine as plain methods on one document's bytes.
|
|
7
8
|
|
|
8
9
|
mod api;
|
|
9
10
|
mod commands;
|
|
10
11
|
mod document;
|
|
12
|
+
mod facade;
|
|
11
13
|
mod html;
|
|
12
14
|
mod media;
|
|
13
15
|
mod outcome;
|
|
@@ -15,3 +17,4 @@ mod package;
|
|
|
15
17
|
mod segments;
|
|
16
18
|
|
|
17
19
|
pub use api::*;
|
|
20
|
+
pub use facade::{create, Case, Document, Review, Source, View};
|
|
@@ -4,6 +4,7 @@ description = "Native Python bindings for docxdriver-core"
|
|
|
4
4
|
version.workspace = true
|
|
5
5
|
edition.workspace = true
|
|
6
6
|
license.workspace = true
|
|
7
|
+
publish = false
|
|
7
8
|
readme = "README.md"
|
|
8
9
|
|
|
9
10
|
[lib]
|
|
@@ -13,4 +14,5 @@ crate-type = ["cdylib", "rlib"]
|
|
|
13
14
|
[dependencies]
|
|
14
15
|
docxdriver-core = { path = "../docxdriver-core" }
|
|
15
16
|
pyo3 = { version = "0.27.2", features = ["abi3-py310"] }
|
|
17
|
+
serde = "1"
|
|
16
18
|
serde_json = "1"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# docxdriver
|
|
2
|
+
|
|
3
|
+
Python SDK for the Rust DOCX engine. It supports Python 3.10 and later and has no
|
|
4
|
+
Python runtime dependencies.
|
|
5
|
+
|
|
6
|
+
Install from PyPI:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
python -m pip install docxdriver
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`Document` works on `.docx` bytes; typed plan builders live in
|
|
13
|
+
`docxdriver.plan`:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from docxdriver import Document, create
|
|
18
|
+
from docxdriver.plan import Plan, ReplaceText
|
|
19
|
+
|
|
20
|
+
doc = Document(Path("contract.docx").read_bytes())
|
|
21
|
+
doc.markup() # body with the ids plans address
|
|
22
|
+
doc.comments(); doc.revisions(); doc.styles(); doc.assets()
|
|
23
|
+
doc.find("thirty")
|
|
24
|
+
review = doc.review(Plan(author="Reviewer", ops=[
|
|
25
|
+
ReplaceText(at="0F537164", select="thirty", with_="sixty"),
|
|
26
|
+
]))
|
|
27
|
+
review.report # preview: each op's outcome
|
|
28
|
+
Path("contract.docx").write_bytes(doc.commit(review.key).data)
|
|
29
|
+
|
|
30
|
+
new = create(paragraphs=["Hello"]) # or create(html="<p>Hello</p>")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Commit keys are single-use. Natively `review.key` carries the key; in a
|
|
34
|
+
sandbox it is `None` and the host shows the key only to the model, so a
|
|
35
|
+
commit proves the review was read. Sandbox hosts written in Python build on
|
|
36
|
+
the stateless `review_plan(data, plan_json)` and
|
|
37
|
+
`commit_plan(data, plan_json, key)`, keeping each plan under a key they issue.
|
|
38
|
+
|
|
39
|
+
Engine diagnostics raise `ValueError`. The lower-level `execute_request`,
|
|
40
|
+
`canonical_plan_json`, `run_command`, `run_plan` and `run_request` remain.
|
|
41
|
+
|
|
42
|
+
`plan.py` and `document.py` stay within Monty's Python subset. Rust hosts get
|
|
43
|
+
the same text as `docxdriver_monty_repl::PLAN` and `DOCUMENT`, so sandbox code
|
|
44
|
+
and native code are interchangeable. The op dataclasses are generated from the
|
|
45
|
+
core schema; regenerate from the repository root with
|
|
46
|
+
`python scripts/generate-python-plan.py`.
|
|
47
|
+
|
|
48
|
+
Version 0.4.0 removed the `docxdriver.repl` sandbox host from this package. Its
|
|
49
|
+
source remains in `experimental/python-repl-host` for hosts to vendor.
|
|
50
|
+
|
|
51
|
+
Build and install a wheel from the repository root:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
python -m pip install "maturin>=1.9,<2"
|
|
55
|
+
maturin build --profile python --locked --manifest-path crates/docxdriver-python/Cargo.toml --out /tmp/docxdriver-wheels
|
|
56
|
+
python -m pip install /tmp/docxdriver-wheels/*.whl
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Publish to PyPI
|
|
60
|
+
|
|
61
|
+
The `Publish Python SDK` GitHub Actions workflow builds a source distribution
|
|
62
|
+
and wheels for Linux x86_64 and aarch64, macOS universal2, and Windows x86_64
|
|
63
|
+
when a `v*` tag is pushed. The tag must match the version in the root
|
|
64
|
+
`Cargo.toml` workspace package.
|
|
65
|
+
|
|
66
|
+
Before the first release, configure a PyPI trusted publisher for the
|
|
67
|
+
`docxdriver` project with these values:
|
|
68
|
+
|
|
69
|
+
- Owner: `jhnoel`
|
|
70
|
+
- Repository: `docxdriver`
|
|
71
|
+
- Workflow filename: `pypi.yml`
|
|
72
|
+
- GitHub environment: `pypi`
|
|
73
|
+
|
|
74
|
+
Create the `pypi` GitHub environment under the repository settings if you want
|
|
75
|
+
to add release approvals. Then update the workspace version, commit the change,
|
|
76
|
+
and push the matching tag (for example, `v0.2.7`). GitHub Actions publishes the
|
|
77
|
+
built distributions using PyPI's trusted publishing; no long-lived PyPI token is
|
|
78
|
+
needed.
|
|
79
|
+
|
|
80
|
+
The `python` Cargo profile keeps release optimizations and enables panic unwinding
|
|
81
|
+
so PyO3 can turn Rust panics into Python exceptions. ABI3 wheels support Python
|
|
82
|
+
3.10 and later on the built operating system and architecture. To run the Python
|
|
83
|
+
SDK and host tests after installing the wheel:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
python -m unittest discover -s crates/docxdriver-python/tests
|
|
87
|
+
```
|
{docxdriver-0.3.3 → docxdriver-0.4.0/crates/docxdriver-python}/python/docxdriver/__init__.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Python SDK for the docxdriver document engine."""
|
|
2
2
|
import json
|
|
3
|
-
from ._core import __version__, canonical_plan_json, execute_request
|
|
3
|
+
from ._core import __version__, canonical_plan_json, commit_plan, execute_request, review_plan
|
|
4
|
+
from .document import Document, Review, create
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def run_request(data, request):
|