pyOpenVBA 3.0.1__tar.gz → 3.2.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 (28) hide show
  1. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/.gitignore +4 -0
  2. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/PKG-INFO +37 -9
  3. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/README.md +36 -8
  4. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/docs/architecture.md +12 -16
  5. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/docs/roadmap.md +1 -1
  6. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/pyproject.toml +1 -1
  7. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/__init__.py +1 -1
  8. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/__main__.py +45 -15
  9. pyopenvba-3.0.1/src/pyopenvba/excel.py → pyopenvba-3.2.0/src/pyopenvba/_host.py +97 -96
  10. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/access_read.py +15 -22
  11. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/cfb.py +50 -50
  12. pyopenvba-3.2.0/src/pyopenvba/excel.py +76 -0
  13. pyopenvba-3.2.0/src/pyopenvba/powerpoint.py +69 -0
  14. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/vba.py +392 -77
  15. pyopenvba-3.2.0/src/pyopenvba/word.py +68 -0
  16. pyopenvba-3.0.1/src/pyopenvba/powerpoint.py +0 -479
  17. pyopenvba-3.0.1/src/pyopenvba/word.py +0 -478
  18. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/LICENSE.md +0 -0
  19. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/docs/ms-ovba-implementation-guide_v2.md +0 -0
  20. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/_templates/__init__.py +0 -0
  21. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/_templates/blank_files/blank_database.accdb +0 -0
  22. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/_templates/blank_files/blank_document.docm +0 -0
  23. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/_templates/blank_files/blank_presentation.pptm +0 -0
  24. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsb +0 -0
  25. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/_templates/blank_files/blank_workbook.xlsm +0 -0
  26. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/exceptions.py +0 -0
  27. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/src/pyopenvba/vba_pcode.py +0 -0
  28. {pyopenvba-3.0.1 → pyopenvba-3.2.0}/tests/fuzz_corpus/README.md +0 -0
@@ -55,3 +55,7 @@ demo/_isolate.py
55
55
  # tests/live_access_test/_corpus_generate.ps1)
56
56
  tests/live_access_test/re_corpus/
57
57
 
58
+
59
+ # Stray local test logs and Access lock files
60
+ _pytest.log
61
+ *.laccdb
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyOpenVBA
3
- Version: 3.0.1
3
+ Version: 3.2.0
4
4
  Summary: Read and write VBA macros inside Excel, Word, and PowerPoint files in pure Python, no dependencies.
5
5
  Project-URL: Homepage, https://github.com/WilliamSmithEdward/pyOpenVBA
6
6
  Project-URL: Repository, https://github.com/WilliamSmithEdward/pyOpenVBA
@@ -54,8 +54,6 @@ Supports:
54
54
  * Word (`.docm`, `.dotm`, `.doc`)
55
55
  * Access (`.accdb`) - **read-only**
56
56
 
57
- <a href="https://github.com/sponsors/WilliamSmithEdward"><img src="https://img.shields.io/badge/Sponsor-%E2%9D%A4-pink?style=for-the-badge" alt="Sponsor WilliamSmithEdward"></a>
58
-
59
57
  ---
60
58
 
61
59
  ## Why use this?
@@ -257,6 +255,12 @@ with ExcelFile("workbook.xlsm") as wb:
257
255
  wb.save("out.xlsm")
258
256
  ```
259
257
 
258
+ Class sources are accepted in any form: a bare body (the header is
259
+ synthesized), a `.cls` file exported straight from the VBE (the
260
+ `VERSION ... CLASS` preamble is stripped and the required
261
+ `Attribute VB_Base` line is added automatically), or a full
262
+ stream-form source.
263
+
260
264
  ---
261
265
 
262
266
  ## Edit your macros as files on disk (recommended workflow)
@@ -441,17 +445,21 @@ See [docs/roadmap.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/
441
445
 
442
446
  ```
443
447
  src/pyopenvba/
444
- __init__.py public API (ExcelFile, WordFile, PowerPointFile,
448
+ __init__.py public API (ExcelFile, WordFile, PowerPointFile, AccessReader,
445
449
  pull/push, pull_word/push_word, pull_ppt/push_ppt,
446
- VBAModuleKind, synthesize_class_header, exceptions)
447
- excel.py ExcelFile facade (ZIP / CFB dispatch, pull/push helpers)
448
- word.py WordFile facade
449
- powerpoint.py PowerPointFile facade
450
+ pull_access, VBAModuleKind, synthesize_class_header,
451
+ exceptions)
452
+ _host.py VBAHostFile: shared open/edit/pull/push/save pipeline
453
+ excel.py ExcelFile (thin VBAHostFile subclass + create_new template)
454
+ word.py WordFile (thin VBAHostFile subclass + create_new template)
455
+ powerpoint.py PowerPointFile (thin VBAHostFile subclass + create_new template)
456
+ access_read.py AccessReader (read-only ACE/Jet page + LVAL reader)
450
457
  vba.py VBA project parser + MS-OVBA codec
458
+ vba_pcode.py VBA7 p-code disassembler
451
459
  cfb.py MS-CFB (Compound File Binary) parser/writer
452
460
  exceptions.py custom exception hierarchy
453
461
  _templates/ baked-in empty .xlsm/.xlsb/.docm/.pptm bytes for create_new()
454
- __main__.py `python -m pyopenvba {pull,push,ls}` CLI
462
+ __main__.py `python -m pyopenvba {pull,push,ls,disasm,access-*}` CLI
455
463
  ```
456
464
 
457
465
  For deeper documentation:
@@ -477,6 +485,15 @@ pyright src tests
477
485
  pytest -p no:randomly
478
486
  ```
479
487
 
488
+ On a Windows machine with desktop Excel installed you can additionally run
489
+ the live compile-and-run gate (skipped by default and in CI). It builds a
490
+ workbook with pyOpenVBA, runs its macro in real Excel under a popup-aware
491
+ harness, and fails on any VBE dialog:
492
+
493
+ ```powershell
494
+ $env:RUN_LIVE_EXCEL = "1"; pytest tests/test_live_excel_gate.py
495
+ ```
496
+
480
497
  CI runs the test matrix on Python 3.10 / 3.11 / 3.12 / 3.13 across
481
498
  Linux, plus 3.12 on Windows and macOS, on every push and pull request.
482
499
  Releases are published to PyPI automatically when a `v*.*.*` tag is
@@ -487,3 +504,14 @@ pushed.
487
504
  ## License
488
505
 
489
506
  [MIT](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/LICENSE.md).
507
+
508
+ ---
509
+
510
+ ## Support Open Source
511
+
512
+ pyOpenVBA is open-source software. If it saves you time or helps your team keep
513
+ VBA workbooks maintainable, support helps keep the project moving.
514
+
515
+ - [GitHub Sponsors](https://github.com/sponsors/WilliamSmithEdward)
516
+ - [PayPal](https://www.paypal.com/donate/?business=ML855BRLNR838&no_recurring=0&item_name=VBA+has+always+treated+me+well.+It+was+how+I+first+grew+professional+as+a+programmer%2C+I%27m+happy+to+show+it+some+love+%E2%9D%A4%EF%B8%8F&currency_code=USD)
517
+ - [Cash App](https://cash.app/$williamesmithjcil)
@@ -18,8 +18,6 @@ Supports:
18
18
  * Word (`.docm`, `.dotm`, `.doc`)
19
19
  * Access (`.accdb`) - **read-only**
20
20
 
21
- <a href="https://github.com/sponsors/WilliamSmithEdward"><img src="https://img.shields.io/badge/Sponsor-%E2%9D%A4-pink?style=for-the-badge" alt="Sponsor WilliamSmithEdward"></a>
22
-
23
21
  ---
24
22
 
25
23
  ## Why use this?
@@ -221,6 +219,12 @@ with ExcelFile("workbook.xlsm") as wb:
221
219
  wb.save("out.xlsm")
222
220
  ```
223
221
 
222
+ Class sources are accepted in any form: a bare body (the header is
223
+ synthesized), a `.cls` file exported straight from the VBE (the
224
+ `VERSION ... CLASS` preamble is stripped and the required
225
+ `Attribute VB_Base` line is added automatically), or a full
226
+ stream-form source.
227
+
224
228
  ---
225
229
 
226
230
  ## Edit your macros as files on disk (recommended workflow)
@@ -405,17 +409,21 @@ See [docs/roadmap.md](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/
405
409
 
406
410
  ```
407
411
  src/pyopenvba/
408
- __init__.py public API (ExcelFile, WordFile, PowerPointFile,
412
+ __init__.py public API (ExcelFile, WordFile, PowerPointFile, AccessReader,
409
413
  pull/push, pull_word/push_word, pull_ppt/push_ppt,
410
- VBAModuleKind, synthesize_class_header, exceptions)
411
- excel.py ExcelFile facade (ZIP / CFB dispatch, pull/push helpers)
412
- word.py WordFile facade
413
- powerpoint.py PowerPointFile facade
414
+ pull_access, VBAModuleKind, synthesize_class_header,
415
+ exceptions)
416
+ _host.py VBAHostFile: shared open/edit/pull/push/save pipeline
417
+ excel.py ExcelFile (thin VBAHostFile subclass + create_new template)
418
+ word.py WordFile (thin VBAHostFile subclass + create_new template)
419
+ powerpoint.py PowerPointFile (thin VBAHostFile subclass + create_new template)
420
+ access_read.py AccessReader (read-only ACE/Jet page + LVAL reader)
414
421
  vba.py VBA project parser + MS-OVBA codec
422
+ vba_pcode.py VBA7 p-code disassembler
415
423
  cfb.py MS-CFB (Compound File Binary) parser/writer
416
424
  exceptions.py custom exception hierarchy
417
425
  _templates/ baked-in empty .xlsm/.xlsb/.docm/.pptm bytes for create_new()
418
- __main__.py `python -m pyopenvba {pull,push,ls}` CLI
426
+ __main__.py `python -m pyopenvba {pull,push,ls,disasm,access-*}` CLI
419
427
  ```
420
428
 
421
429
  For deeper documentation:
@@ -441,6 +449,15 @@ pyright src tests
441
449
  pytest -p no:randomly
442
450
  ```
443
451
 
452
+ On a Windows machine with desktop Excel installed you can additionally run
453
+ the live compile-and-run gate (skipped by default and in CI). It builds a
454
+ workbook with pyOpenVBA, runs its macro in real Excel under a popup-aware
455
+ harness, and fails on any VBE dialog:
456
+
457
+ ```powershell
458
+ $env:RUN_LIVE_EXCEL = "1"; pytest tests/test_live_excel_gate.py
459
+ ```
460
+
444
461
  CI runs the test matrix on Python 3.10 / 3.11 / 3.12 / 3.13 across
445
462
  Linux, plus 3.12 on Windows and macOS, on every push and pull request.
446
463
  Releases are published to PyPI automatically when a `v*.*.*` tag is
@@ -451,3 +468,14 @@ pushed.
451
468
  ## License
452
469
 
453
470
  [MIT](https://github.com/WilliamSmithEdward/pyOpenVBA/blob/main/LICENSE.md).
471
+
472
+ ---
473
+
474
+ ## Support Open Source
475
+
476
+ pyOpenVBA is open-source software. If it saves you time or helps your team keep
477
+ VBA workbooks maintainable, support helps keep the project moving.
478
+
479
+ - [GitHub Sponsors](https://github.com/sponsors/WilliamSmithEdward)
480
+ - [PayPal](https://www.paypal.com/donate/?business=ML855BRLNR838&no_recurring=0&item_name=VBA+has+always+treated+me+well.+It+was+how+I+first+grew+professional+as+a+programmer%2C+I%27m+happy+to+show+it+some+love+%E2%9D%A4%EF%B8%8F&currency_code=USD)
481
+ - [Cash App](https://cash.app/$williamesmithjcil)
@@ -18,20 +18,17 @@ knows about the layer below it but not the layer above.
18
18
 
19
19
  ```
20
20
  +--------------------------------------------------------+
21
- | excel.py ExcelFile facade |
22
- | - ZIP / raw-CFB dispatch by extension |
23
- | - save() pipeline (safety gates, structural rewrites)|
24
- | - pull / push disk workflow |
21
+ | excel.py / word.py / powerpoint.py host facades |
22
+ | - thin subclasses of _host.VBAHostFile |
23
+ | - per-host constants: container extensions, |
24
+ | vbaProject.bin entry path, message wording |
25
+ | - create_new() from a baked-in template |
25
26
  +--------------------------------------------------------+
26
- | word.py WordFile facade |
27
+ | _host.py VBAHostFile shared implementation |
27
28
  | - ZIP / raw-CFB dispatch by extension |
28
- | - identical save() pipeline as ExcelFile |
29
- | - pull / push disk workflow |
30
- +--------------------------------------------------------+
31
- | powerpoint.py PowerPointFile facade |
32
- | - ZIP / raw-CFB dispatch by extension |
33
- | - identical save() pipeline as ExcelFile |
29
+ | - save() pipeline (safety gates, structural rewrites)|
34
30
  | - pull / push disk workflow |
31
+ | - class-source normalization at set_module / push |
35
32
  +--------------------------------------------------------+
36
33
  | access_read.py AccessReader facade (READ-ONLY) |
37
34
  | - ACE / Jet 4 page reader (.accdb, .mdb) |
@@ -87,8 +84,9 @@ Other files:
87
84
  - `cfb.py` is **completely VBA-agnostic** and could be lifted into a
88
85
  separate library. It knows nothing about MS-OVBA.
89
86
  - `vba.py` operates on a `CFB` instance but never opens files on disk.
90
- - `excel.py` is the only module that touches the filesystem, ZIP
91
- containers, or workbook paths.
87
+ - `_host.py` is the only module that touches the filesystem, ZIP
88
+ containers, or host file paths; the three facades only supply
89
+ constants and `create_new()` templates.
92
90
  - Cross-layer leaks are a code smell. Adding a `pathlib.Path` import to
93
91
  `vba.py` or `cfb.py` is a red flag.
94
92
 
@@ -407,9 +405,7 @@ files in the same commit:
407
405
  | New layer / module | This file (sections 1 and 2), README architecture box |
408
406
  | New roadmap gate or status change | `docs/roadmap.md` |
409
407
  | New mutation safety gate | This file section 4, README "Safety guards" |
410
- | New supported file extension (Excel) | `excel.py` dispatch, README table, this file section 5 |
411
- | New supported file extension (Word) | `word.py` dispatch, README table, this file section 5 |
412
- | New supported file extension (PowerPoint) | `powerpoint.py` dispatch, README table, this file section 5 |
408
+ | New supported file extension | Facade `_zip_formats` / `_cfb_formats`, `__main__._HOST_BY_SUFFIX`, README table, this file section 5 |
413
409
  | New test file or fuzz target | This file section 8 |
414
410
  | Spec/behavior change worth telling other implementers | `docs/ms-ovba-implementation-guide_v2.md` |
415
411
 
@@ -1,7 +1,7 @@
1
1
  # pyOpenVBA Roadmap
2
2
 
3
3
  This roadmap tracks pyOpenVBA's progress against the 26 hard gates defined in
4
- [`xlsm_feature_completeness_gates.md`](../xlsm_feature_completeness_gates.md).
4
+ [`xlsm_feature_completeness_gates.md`](xlsm_feature_completeness_gates.md).
5
5
  Per-gate status keys:
6
6
 
7
7
  - **PASS** — implemented; gate tests in [`tests/test_gates.py`](../tests/test_gates.py) pass.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pyOpenVBA"
7
- version = "3.0.1"
7
+ version = "3.2.0"
8
8
  description = "Read and write VBA macros inside Excel, Word, and PowerPoint files in pure Python, no dependencies."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -204,4 +204,4 @@ __all__ = [
204
204
  "push_word",
205
205
  ]
206
206
 
207
- __version__ = "2.0.0"
207
+ __version__ = "3.2.0"
@@ -3,13 +3,16 @@ Command-line entry point.
3
3
 
4
4
  Usage::
5
5
 
6
- python -m pyopenvba pull <workbook> <dest_dir>
7
- python -m pyopenvba push <src_dir> <workbook> [--out <new_path>] [--strict]
8
- python -m pyopenvba ls <workbook>
6
+ python -m pyopenvba pull <office_file> <dest_dir>
7
+ python -m pyopenvba push <src_dir> <office_file> [--out <new_path>] [--strict]
8
+ python -m pyopenvba ls <office_file>
9
9
  python -m pyopenvba access-ls <accdb>
10
10
  python -m pyopenvba access-pull <accdb> <dest_dir>
11
11
  python -m pyopenvba access-disasm <accdb> [--module <name>] [--with-source]
12
- python -m pyopenvba disasm <workbook|doc|pptx> [--module <name>] [--with-source]
12
+ python -m pyopenvba disasm <office_file> [--module <name>] [--with-source]
13
+
14
+ ``<office_file>`` may be any supported Excel, Word, or PowerPoint file;
15
+ the host is selected by extension (see ``_HOST_BY_SUFFIX``).
13
16
  """
14
17
 
15
18
  from __future__ import annotations
@@ -18,26 +21,51 @@ import argparse
18
21
  import sys
19
22
  from pathlib import Path
20
23
 
21
- from pyopenvba import ExcelFile, PowerPointFile, WordFile, pull, push
24
+ from pyopenvba import ExcelFile, PowerPointFile, WordFile
25
+
26
+
27
+ def _resolve_host(path: Path) -> type | None:
28
+ """Return the host file class for ``path`` by extension, or ``None``
29
+ (after printing an error) when the extension is unsupported."""
30
+ host_cls = _HOST_BY_SUFFIX.get(path.suffix.lower())
31
+ if host_cls is None:
32
+ print(
33
+ f"error: unsupported file type {path.suffix.lower()!r}; expected one of "
34
+ f"{sorted(_HOST_BY_SUFFIX)}",
35
+ file=sys.stderr,
36
+ )
37
+ return host_cls
22
38
 
23
39
 
24
40
  def _cmd_pull(args: argparse.Namespace) -> int:
25
- written = pull(args.workbook, args.dest, overwrite=not args.no_overwrite)
41
+ host_cls = _resolve_host(args.workbook)
42
+ if host_cls is None:
43
+ return 2
44
+ with host_cls(args.workbook) as host:
45
+ written = host.pull_modules(args.dest, overwrite=not args.no_overwrite)
26
46
  for p in written:
27
47
  print(p)
28
48
  return 0
29
49
 
30
50
 
31
51
  def _cmd_push(args: argparse.Namespace) -> int:
32
- updated = push(args.src, args.workbook, out=args.out, strict=args.strict)
52
+ host_cls = _resolve_host(args.workbook)
53
+ if host_cls is None:
54
+ return 2
55
+ with host_cls(args.workbook) as host:
56
+ updated = host.push_modules(args.src, strict=args.strict)
57
+ host.save(args.out)
33
58
  for name in updated:
34
59
  print(name)
35
60
  return 0
36
61
 
37
62
 
38
63
  def _cmd_ls(args: argparse.Namespace) -> int:
39
- with ExcelFile(args.workbook) as wb:
40
- for m in wb.vba_project().modules:
64
+ host_cls = _resolve_host(args.workbook)
65
+ if host_cls is None:
66
+ return 2
67
+ with host_cls(args.workbook) as host:
68
+ for m in host.vba_project().modules:
41
69
  print(f"{m.kind.name:8s} {m.name}")
42
70
  return 0
43
71
 
@@ -55,10 +83,9 @@ def _cmd_access_pull(args: argparse.Namespace) -> int:
55
83
  from pyopenvba.access_read import AccessReader
56
84
 
57
85
  db = AccessReader(args.database)
58
- args.dest.mkdir(parents=True, exist_ok=True)
59
- for m in db.iter_vba_modules():
60
- out = args.dest / f"{m.name}.bas"
61
- out.write_text(m.source, encoding="utf-8")
86
+ # pull_modules classifies standard modules as .bas and class modules
87
+ # as .cls, matching the Excel/Word/PowerPoint pull commands.
88
+ for out in db.pull_modules(args.dest):
62
89
  print(out)
63
90
  return 0
64
91
 
@@ -90,16 +117,19 @@ def _cmd_access_disasm(args: argparse.Namespace) -> int:
90
117
  return 0
91
118
 
92
119
 
120
+ # Every extension listed here is accepted by the mapped host class;
121
+ # keep this in lockstep with each facade's _ZIP_FORMATS / _CFB_FORMATS.
93
122
  _HOST_BY_SUFFIX: dict[str, type] = {
94
123
  ".xlsm": ExcelFile,
95
124
  ".xlsb": ExcelFile,
96
- ".xltm": ExcelFile,
97
125
  ".xlam": ExcelFile,
126
+ ".xls": ExcelFile,
98
127
  ".docm": WordFile,
99
128
  ".dotm": WordFile,
129
+ ".doc": WordFile,
100
130
  ".pptm": PowerPointFile,
101
131
  ".potm": PowerPointFile,
102
- ".ppam": PowerPointFile,
132
+ ".ppt": PowerPointFile,
103
133
  }
104
134
 
105
135