wordlive 0.8.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.
Files changed (60) hide show
  1. wordlive-0.8.0/.github/workflows/docs.yml +60 -0
  2. wordlive-0.8.0/.github/workflows/release.yml +81 -0
  3. wordlive-0.8.0/.gitignore +16 -0
  4. wordlive-0.8.0/.python-version +1 -0
  5. wordlive-0.8.0/PKG-INFO +134 -0
  6. wordlive-0.8.0/README.md +116 -0
  7. wordlive-0.8.0/docs/cli.md +932 -0
  8. wordlive-0.8.0/docs/concepts.md +209 -0
  9. wordlive-0.8.0/docs/cookbook.md +903 -0
  10. wordlive-0.8.0/docs/design.md +123 -0
  11. wordlive-0.8.0/docs/errors.md +163 -0
  12. wordlive-0.8.0/docs/getting-started.md +155 -0
  13. wordlive-0.8.0/docs/index.md +68 -0
  14. wordlive-0.8.0/docs/python-api.md +169 -0
  15. wordlive-0.8.0/docs/stylesheets/extra.css +10 -0
  16. wordlive-0.8.0/feature-plan.md +325 -0
  17. wordlive-0.8.0/issues.md +442 -0
  18. wordlive-0.8.0/mkdocs.yml +86 -0
  19. wordlive-0.8.0/pyproject.toml +39 -0
  20. wordlive-0.8.0/scripts/e2e_test.py +810 -0
  21. wordlive-0.8.0/spec.md +247 -0
  22. wordlive-0.8.0/src/wordlive/__init__.py +85 -0
  23. wordlive-0.8.0/src/wordlive/_anchors.py +938 -0
  24. wordlive-0.8.0/src/wordlive/_app.py +82 -0
  25. wordlive-0.8.0/src/wordlive/_com.py +86 -0
  26. wordlive-0.8.0/src/wordlive/_comments.py +152 -0
  27. wordlive-0.8.0/src/wordlive/_document.py +488 -0
  28. wordlive-0.8.0/src/wordlive/_edit.py +69 -0
  29. wordlive-0.8.0/src/wordlive/_findreplace.py +149 -0
  30. wordlive-0.8.0/src/wordlive/_images.py +110 -0
  31. wordlive-0.8.0/src/wordlive/_lists.py +207 -0
  32. wordlive-0.8.0/src/wordlive/_sections.py +217 -0
  33. wordlive-0.8.0/src/wordlive/_selection.py +119 -0
  34. wordlive-0.8.0/src/wordlive/_styles.py +137 -0
  35. wordlive-0.8.0/src/wordlive/_tables.py +272 -0
  36. wordlive-0.8.0/src/wordlive/cli/__init__.py +1 -0
  37. wordlive-0.8.0/src/wordlive/cli/__main__.py +5 -0
  38. wordlive-0.8.0/src/wordlive/cli/commands.py +1495 -0
  39. wordlive-0.8.0/src/wordlive/cli/main.py +83 -0
  40. wordlive-0.8.0/src/wordlive/constants.py +155 -0
  41. wordlive-0.8.0/src/wordlive/exceptions.py +145 -0
  42. wordlive-0.8.0/src/wordlive/py.typed +0 -0
  43. wordlive-0.8.0/tests/__init__.py +0 -0
  44. wordlive-0.8.0/tests/conftest.py +640 -0
  45. wordlive-0.8.0/tests/test_anchors.py +462 -0
  46. wordlive-0.8.0/tests/test_cli.py +1396 -0
  47. wordlive-0.8.0/tests/test_comments.py +121 -0
  48. wordlive-0.8.0/tests/test_cursor.py +61 -0
  49. wordlive-0.8.0/tests/test_edit_scope.py +84 -0
  50. wordlive-0.8.0/tests/test_findreplace.py +145 -0
  51. wordlive-0.8.0/tests/test_images.py +253 -0
  52. wordlive-0.8.0/tests/test_lists.py +153 -0
  53. wordlive-0.8.0/tests/test_paragraphs.py +116 -0
  54. wordlive-0.8.0/tests/test_ranges.py +100 -0
  55. wordlive-0.8.0/tests/test_sections.py +167 -0
  56. wordlive-0.8.0/tests/test_smoke.py +46 -0
  57. wordlive-0.8.0/tests/test_styles.py +202 -0
  58. wordlive-0.8.0/tests/test_tables.py +250 -0
  59. wordlive-0.8.0/tests/test_track_changes.py +62 -0
  60. wordlive-0.8.0/uv.lock +640 -0
@@ -0,0 +1,60 @@
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - 'docs/**'
8
+ - 'mkdocs.yml'
9
+ - 'src/wordlive/**'
10
+ - 'pyproject.toml'
11
+ - '.github/workflows/docs.yml'
12
+ pull_request:
13
+ paths:
14
+ - 'docs/**'
15
+ - 'mkdocs.yml'
16
+ - 'src/wordlive/**'
17
+ - 'pyproject.toml'
18
+ - '.github/workflows/docs.yml'
19
+ workflow_dispatch:
20
+
21
+ permissions:
22
+ contents: read
23
+ pages: write
24
+ id-token: write
25
+
26
+ concurrency:
27
+ group: pages
28
+ cancel-in-progress: false
29
+
30
+ jobs:
31
+ build:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+
36
+ - uses: actions/setup-python@v5
37
+ with:
38
+ python-version: '3.13'
39
+ cache: pip
40
+
41
+ - name: Install docs deps
42
+ run: pip install -e ".[docs]"
43
+
44
+ - name: Build site
45
+ run: mkdocs build --strict
46
+
47
+ - uses: actions/upload-pages-artifact@v3
48
+ with:
49
+ path: site
50
+
51
+ deploy:
52
+ if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
53
+ needs: build
54
+ runs-on: ubuntu-latest
55
+ environment:
56
+ name: github-pages
57
+ url: ${{ steps.deployment.outputs.page_url }}
58
+ steps:
59
+ - id: deployment
60
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,81 @@
1
+ name: release
2
+
3
+ # Push a version tag to publish: git tag v0.8.0 && git push origin v0.8.0
4
+ on:
5
+ push:
6
+ tags:
7
+ - 'v*'
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: '3.13'
21
+
22
+ - name: Verify tag matches the package version
23
+ run: |
24
+ version=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
25
+ tag="${GITHUB_REF_NAME#v}"
26
+ echo "pyproject version: $version"
27
+ echo "release tag: $GITHUB_REF_NAME -> $tag"
28
+ if [ "$version" != "$tag" ]; then
29
+ echo "::error::Tag '$GITHUB_REF_NAME' does not match pyproject version '$version'. Bump the version in pyproject.toml or retag."
30
+ exit 1
31
+ fi
32
+
33
+ - name: Build sdist + wheel
34
+ run: |
35
+ python -m pip install --upgrade build twine
36
+ python -m build
37
+ twine check dist/*
38
+
39
+ - uses: actions/upload-artifact@v4
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+
44
+ pypi-publish:
45
+ needs: build
46
+ runs-on: ubuntu-latest
47
+ # Must match the PyPI trusted-publisher "Environment name".
48
+ environment:
49
+ name: pypi
50
+ url: https://pypi.org/p/wordlive
51
+ permissions:
52
+ id-token: write # OIDC token for trusted publishing — no API token needed
53
+ steps:
54
+ - uses: actions/download-artifact@v4
55
+ with:
56
+ name: dist
57
+ path: dist/
58
+
59
+ - name: Publish to PyPI
60
+ uses: pypa/gh-action-pypi-publish@release/v1
61
+
62
+ github-release:
63
+ needs: pypi-publish
64
+ runs-on: ubuntu-latest
65
+ permissions:
66
+ contents: write # to create the Release and upload assets
67
+ steps:
68
+ - uses: actions/download-artifact@v4
69
+ with:
70
+ name: dist
71
+ path: dist/
72
+
73
+ - name: Create GitHub Release from the tag
74
+ env:
75
+ GH_TOKEN: ${{ github.token }}
76
+ run: >-
77
+ gh release create "$GITHUB_REF_NAME"
78
+ --repo "$GITHUB_REPOSITORY"
79
+ --title "$GITHUB_REF_NAME"
80
+ --generate-notes
81
+ dist/*
@@ -0,0 +1,16 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # MkDocs build output
13
+ site/
14
+
15
+ # Personal notes
16
+ notes.md
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: wordlive
3
+ Version: 0.8.0
4
+ Summary: Drive a running Microsoft Word instance from Python — xlwings, but for Word, and LLM-friendly.
5
+ Author-email: "Thomas.Villani" <thomas.villani@gmail.com>
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: click>=8.1
8
+ Requires-Dist: pywin32>=306; sys_platform == 'win32'
9
+ Provides-Extra: dev
10
+ Requires-Dist: pytest-mock>=3.12; extra == 'dev'
11
+ Requires-Dist: pytest>=8; extra == 'dev'
12
+ Provides-Extra: docs
13
+ Requires-Dist: mkdocs-include-markdown-plugin>=6.2; extra == 'docs'
14
+ Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
15
+ Requires-Dist: mkdocs>=1.6; extra == 'docs'
16
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # wordlive
20
+
21
+ Drive a running Microsoft Word instance from Python — `xlwings`, but for Word.
22
+ Built for both human scripting and LLM agents. Windows-only.
23
+
24
+ ## Install
25
+
26
+ ```
27
+ pip install wordlive
28
+ ```
29
+
30
+ (Requires Python 3.13+ and `pywin32` on Windows.)
31
+
32
+ ## Python
33
+
34
+ ```python
35
+ import wordlive as wl
36
+
37
+ with wl.attach() as word:
38
+ doc = word.documents.active
39
+
40
+ # Reads
41
+ outline = doc.outline()
42
+ bookmarks = doc.bookmarks.list()
43
+
44
+ # Polite writes — preserves the user's cursor and view, atomic Ctrl-Z.
45
+ with doc.edit("Update address block"):
46
+ doc.bookmarks["Address"].set_text("123 Main St")
47
+ doc.content_controls["Signatory"].set_text("Jane Doe")
48
+ doc.heading("Introduction").insert_paragraph_after("New context paragraph.")
49
+ ```
50
+
51
+ ## CLI
52
+
53
+ JSON in, JSON out — designed to drop straight into an LLM tool-use loop:
54
+
55
+ ```
56
+ wordlive status
57
+ wordlive outline # heading structure (heading:N)
58
+ wordlive outline --all # every paragraph (para:N) — alias of `paragraphs`
59
+ wordlive paragraphs # same: para:N, level, offsets, text
60
+ wordlive read bookmark Address
61
+ wordlive write bookmark Address --text "123 Main St"
62
+
63
+ # Insert a new paragraph relative to ANY anchor (heading, paragraph, bookmark, …):
64
+ wordlive insert --anchor-id heading:1 --text "..." # after (default)
65
+ wordlive insert --anchor-id para:3 --text "..." --before
66
+
67
+ # Address anchors by ID (the IDs `outline`/`paragraphs` emit — `heading:N`, `para:N`, `bookmark:NAME`, `cc:NAME`):
68
+ wordlive replace --anchor-id heading:3 --text "Updated section text"
69
+ wordlive go-to --anchor-id bookmark:Address
70
+
71
+ # Explicit cursor surface (the non-preferred mode — deliberately moves the cursor):
72
+ wordlive cursor read # where is the cursor? which para:N?
73
+ wordlive cursor write --text "inserted here" # type at the cursor
74
+
75
+ # Styles + paragraph formatting (atomic-undo):
76
+ wordlive style list
77
+ wordlive style apply --anchor-id heading:3 --name "Heading 2"
78
+ wordlive format-paragraph --anchor-id heading:3 --alignment center --space-before 6
79
+
80
+ # Tables (cells are anchors: table:N:R:C):
81
+ wordlive table list
82
+ wordlive table read 1
83
+ wordlive replace --anchor-id table:1:2:2 --text "$450"
84
+ wordlive table add-row --table 1 --values '["Lodging", "$600"]'
85
+
86
+ # Collaboration: comments + track changes (the polite, non-destructive surface):
87
+ wordlive comment add --anchor-id heading:3 --text "Please expand this." --author Bot
88
+ wordlive comment list
89
+ wordlive comment resolve --index 1
90
+ wordlive track on # record edits as revisions; `track off` to stop
91
+
92
+ # Lists & numbering (any anchor's paragraphs):
93
+ wordlive list apply --anchor-id heading:6 --type numbered
94
+ wordlive list restart --anchor-id heading:6
95
+
96
+ # Sections, headers & footers (header:S:WHICH / footer:S:WHICH):
97
+ wordlive section list
98
+ wordlive header write --section 1 --text "ACME Corporation"
99
+ wordlive footer read --section 1
100
+
101
+ # Batch multiple ops in a single Ctrl-Z:
102
+ wordlive exec --script ops.json
103
+ ```
104
+
105
+ Where `ops.json` looks like:
106
+
107
+ ```json
108
+ {
109
+ "label": "Update report",
110
+ "ops": [
111
+ {"op": "write_bookmark", "name": "Address", "text": "123 Main St"},
112
+ {"op": "write_cc", "name": "Signatory", "text": "Jane Doe"},
113
+ {"op": "insert_paragraph", "anchor_id": "heading:3", "text": "New risk paragraph."},
114
+ {"op": "replace", "anchor_id": "heading:3", "text": "Updated section text"},
115
+ {"op": "apply_style", "anchor_id": "heading:3", "name": "Heading 2"},
116
+ {"op": "format_paragraph", "anchor_id": "heading:3", "alignment": "center", "space_before": 6}
117
+ ]
118
+ }
119
+ ```
120
+
121
+ Exit codes: `0` ok, `2` anchor-not-found, `3` Word-busy, `4` Word-not-running, `1` other.
122
+
123
+ ## Design
124
+
125
+ - **Politeness first** — operations preserve the user's `Selection`, view, and
126
+ scroll. The user keeps editing alongside you.
127
+ - **Semantic anchors over `Selection`** — operations target bookmarks, content
128
+ controls, or headings — never the live cursor unless you ask.
129
+ - **Atomic undo** — every `doc.edit()` opens a Word `UndoRecord`, so a single
130
+ Ctrl-Z reverts the whole block.
131
+ - **Escape hatch** — every wrapper exposes `.com` for the raw COM object;
132
+ you're never blocked by missing coverage.
133
+
134
+ See [`spec.md`](spec.md) for the full design.
@@ -0,0 +1,116 @@
1
+ # wordlive
2
+
3
+ Drive a running Microsoft Word instance from Python — `xlwings`, but for Word.
4
+ Built for both human scripting and LLM agents. Windows-only.
5
+
6
+ ## Install
7
+
8
+ ```
9
+ pip install wordlive
10
+ ```
11
+
12
+ (Requires Python 3.13+ and `pywin32` on Windows.)
13
+
14
+ ## Python
15
+
16
+ ```python
17
+ import wordlive as wl
18
+
19
+ with wl.attach() as word:
20
+ doc = word.documents.active
21
+
22
+ # Reads
23
+ outline = doc.outline()
24
+ bookmarks = doc.bookmarks.list()
25
+
26
+ # Polite writes — preserves the user's cursor and view, atomic Ctrl-Z.
27
+ with doc.edit("Update address block"):
28
+ doc.bookmarks["Address"].set_text("123 Main St")
29
+ doc.content_controls["Signatory"].set_text("Jane Doe")
30
+ doc.heading("Introduction").insert_paragraph_after("New context paragraph.")
31
+ ```
32
+
33
+ ## CLI
34
+
35
+ JSON in, JSON out — designed to drop straight into an LLM tool-use loop:
36
+
37
+ ```
38
+ wordlive status
39
+ wordlive outline # heading structure (heading:N)
40
+ wordlive outline --all # every paragraph (para:N) — alias of `paragraphs`
41
+ wordlive paragraphs # same: para:N, level, offsets, text
42
+ wordlive read bookmark Address
43
+ wordlive write bookmark Address --text "123 Main St"
44
+
45
+ # Insert a new paragraph relative to ANY anchor (heading, paragraph, bookmark, …):
46
+ wordlive insert --anchor-id heading:1 --text "..." # after (default)
47
+ wordlive insert --anchor-id para:3 --text "..." --before
48
+
49
+ # Address anchors by ID (the IDs `outline`/`paragraphs` emit — `heading:N`, `para:N`, `bookmark:NAME`, `cc:NAME`):
50
+ wordlive replace --anchor-id heading:3 --text "Updated section text"
51
+ wordlive go-to --anchor-id bookmark:Address
52
+
53
+ # Explicit cursor surface (the non-preferred mode — deliberately moves the cursor):
54
+ wordlive cursor read # where is the cursor? which para:N?
55
+ wordlive cursor write --text "inserted here" # type at the cursor
56
+
57
+ # Styles + paragraph formatting (atomic-undo):
58
+ wordlive style list
59
+ wordlive style apply --anchor-id heading:3 --name "Heading 2"
60
+ wordlive format-paragraph --anchor-id heading:3 --alignment center --space-before 6
61
+
62
+ # Tables (cells are anchors: table:N:R:C):
63
+ wordlive table list
64
+ wordlive table read 1
65
+ wordlive replace --anchor-id table:1:2:2 --text "$450"
66
+ wordlive table add-row --table 1 --values '["Lodging", "$600"]'
67
+
68
+ # Collaboration: comments + track changes (the polite, non-destructive surface):
69
+ wordlive comment add --anchor-id heading:3 --text "Please expand this." --author Bot
70
+ wordlive comment list
71
+ wordlive comment resolve --index 1
72
+ wordlive track on # record edits as revisions; `track off` to stop
73
+
74
+ # Lists & numbering (any anchor's paragraphs):
75
+ wordlive list apply --anchor-id heading:6 --type numbered
76
+ wordlive list restart --anchor-id heading:6
77
+
78
+ # Sections, headers & footers (header:S:WHICH / footer:S:WHICH):
79
+ wordlive section list
80
+ wordlive header write --section 1 --text "ACME Corporation"
81
+ wordlive footer read --section 1
82
+
83
+ # Batch multiple ops in a single Ctrl-Z:
84
+ wordlive exec --script ops.json
85
+ ```
86
+
87
+ Where `ops.json` looks like:
88
+
89
+ ```json
90
+ {
91
+ "label": "Update report",
92
+ "ops": [
93
+ {"op": "write_bookmark", "name": "Address", "text": "123 Main St"},
94
+ {"op": "write_cc", "name": "Signatory", "text": "Jane Doe"},
95
+ {"op": "insert_paragraph", "anchor_id": "heading:3", "text": "New risk paragraph."},
96
+ {"op": "replace", "anchor_id": "heading:3", "text": "Updated section text"},
97
+ {"op": "apply_style", "anchor_id": "heading:3", "name": "Heading 2"},
98
+ {"op": "format_paragraph", "anchor_id": "heading:3", "alignment": "center", "space_before": 6}
99
+ ]
100
+ }
101
+ ```
102
+
103
+ Exit codes: `0` ok, `2` anchor-not-found, `3` Word-busy, `4` Word-not-running, `1` other.
104
+
105
+ ## Design
106
+
107
+ - **Politeness first** — operations preserve the user's `Selection`, view, and
108
+ scroll. The user keeps editing alongside you.
109
+ - **Semantic anchors over `Selection`** — operations target bookmarks, content
110
+ controls, or headings — never the live cursor unless you ask.
111
+ - **Atomic undo** — every `doc.edit()` opens a Word `UndoRecord`, so a single
112
+ Ctrl-Z reverts the whole block.
113
+ - **Escape hatch** — every wrapper exposes `.com` for the raw COM object;
114
+ you're never blocked by missing coverage.
115
+
116
+ See [`spec.md`](spec.md) for the full design.