dsh-office-kit 0.3.0 → 0.3.2
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.
- package/assets/skills/office-documents/render_docx.py +7 -0
- package/assets/skills/office-documents/scripts/a11y_audit.py +7 -0
- package/assets/skills/office-documents/scripts/accept_tracked_changes.py +7 -0
- package/assets/skills/office-documents/scripts/add_tracked_replacements.py +7 -0
- package/assets/skills/office-documents/scripts/append_blocks.py +7 -0
- package/assets/skills/office-documents/scripts/apply_template_styles.py +7 -0
- package/assets/skills/office-documents/scripts/captions_and_crossrefs.py +7 -0
- package/assets/skills/office-documents/scripts/comments_add.py +7 -0
- package/assets/skills/office-documents/scripts/comments_apply_patch.py +7 -0
- package/assets/skills/office-documents/scripts/comments_extract.py +7 -0
- package/assets/skills/office-documents/scripts/comments_strip.py +7 -0
- package/assets/skills/office-documents/scripts/content_controls.py +7 -0
- package/assets/skills/office-documents/scripts/core_properties.py +7 -0
- package/assets/skills/office-documents/scripts/docx_kit.py +7 -0
- package/assets/skills/office-documents/scripts/docx_ooxml_patch.py +7 -0
- package/assets/skills/office-documents/scripts/docx_table_to_csv.py +7 -0
- package/assets/skills/office-documents/scripts/fields_materialize.py +7 -0
- package/assets/skills/office-documents/scripts/fields_report.py +7 -0
- package/assets/skills/office-documents/scripts/flatten_ref_fields.py +7 -0
- package/assets/skills/office-documents/scripts/footnotes_report.py +7 -0
- package/assets/skills/office-documents/scripts/google_docs_title_sanitize.py +7 -0
- package/assets/skills/office-documents/scripts/heading_audit.py +7 -0
- package/assets/skills/office-documents/scripts/images_audit.py +7 -0
- package/assets/skills/office-documents/scripts/insert_note.py +7 -0
- package/assets/skills/office-documents/scripts/insert_ref_fields.py +7 -0
- package/assets/skills/office-documents/scripts/insert_toc.py +7 -0
- package/assets/skills/office-documents/scripts/internal_nav.py +7 -0
- package/assets/skills/office-documents/scripts/make_fixtures.py +7 -0
- package/assets/skills/office-documents/scripts/merge_docx_append.py +7 -0
- package/assets/skills/office-documents/scripts/privacy_scrub.py +7 -0
- package/assets/skills/office-documents/scripts/redact_docx.py +7 -0
- package/assets/skills/office-documents/scripts/render_and_diff.py +7 -0
- package/assets/skills/office-documents/scripts/replace_text.py +7 -0
- package/assets/skills/office-documents/scripts/section_audit.py +7 -0
- package/assets/skills/office-documents/scripts/set_protection.py +7 -0
- package/assets/skills/office-documents/scripts/style_lint.py +7 -0
- package/assets/skills/office-documents/scripts/style_normalize.py +7 -0
- package/assets/skills/office-documents/scripts/table_geometry.py +7 -0
- package/assets/skills/office-documents/scripts/watermark_add.py +7 -0
- package/assets/skills/office-documents/scripts/watermark_audit_remove.py +7 -0
- package/assets/skills/office-documents/scripts/xlsx_to_docx_table.py +7 -0
- package/assets/skills/office-presentations/container_tools/add_slides.py +7 -0
- package/assets/skills/office-presentations/container_tools/core_properties.py +7 -0
- package/assets/skills/office-presentations/container_tools/create_montage.py +7 -0
- package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py +7 -0
- package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py +7 -0
- package/assets/skills/office-presentations/container_tools/native_chart_title_gate.py +7 -0
- package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py +7 -0
- package/assets/skills/office-presentations/container_tools/native_table_arithmetic_gate.py +7 -0
- package/assets/skills/office-presentations/container_tools/ooxml_helpers.py +7 -0
- package/assets/skills/office-presentations/container_tools/preview_render.py +7 -0
- package/assets/skills/office-presentations/container_tools/render_slides.py +7 -0
- package/assets/skills/office-presentations/container_tools/replace_text.py +7 -0
- package/assets/skills/office-presentations/container_tools/template_source_fidelity_gate.py +7 -0
- package/assets/skills/office-presentations/template_following_scripts/check_template_fidelity.py +7 -0
- package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py +7 -0
- package/assets/skills/office-presentations/template_following_scripts/make_contact_sheet.py +7 -0
- package/assets/skills/office-presentations/template_following_scripts/prepare_template_starter_deck.py +7 -0
- package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py +7 -0
- package/assets/skills/office-spreadsheets/container_tools/add_chart.py +7 -0
- package/assets/skills/office-spreadsheets/container_tools/add_image.py +7 -0
- package/assets/skills/office-spreadsheets/container_tools/append_rows.py +7 -0
- package/assets/skills/office-spreadsheets/container_tools/diff_workbooks.py +7 -0
- package/assets/skills/office-spreadsheets/container_tools/inspect_workbook.py +7 -0
- package/assets/skills/office-spreadsheets/container_tools/recalc_gate.py +19 -7
- package/assets/skills/office-spreadsheets/container_tools/set_cells.py +7 -0
- package/assets/skills/office-spreadsheets/container_tools/sheet_ops.py +7 -0
- package/assets/skills/office-spreadsheets/container_tools/style_cells.py +7 -0
- package/assets/skills/shared/check_runtime.py +7 -0
- package/dist/index.js +31 -18
- package/package.json +1 -1
- package/postinstall.mjs +41 -18
- package/python/office_cli.py +52 -9
|
@@ -13,6 +13,13 @@ import json
|
|
|
13
13
|
import sys
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
17
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
18
|
+
try:
|
|
19
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
20
|
+
except (AttributeError, ValueError):
|
|
21
|
+
pass
|
|
22
|
+
|
|
16
23
|
try:
|
|
17
24
|
from PIL import Image, ImageDraw, ImageFont
|
|
18
25
|
except ImportError: # pragma: no cover - whitelist dependency, but fail clearly
|
package/assets/skills/office-presentations/container_tools/inspect_presentation_layout_geometry.py
CHANGED
|
@@ -42,6 +42,13 @@ from ooxml_helpers import ( # noqa: E402
|
|
|
42
42
|
text_of,
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
46
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
47
|
+
try:
|
|
48
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
49
|
+
except (AttributeError, ValueError):
|
|
50
|
+
pass
|
|
51
|
+
|
|
45
52
|
TITLE_OUTSIDE_MARGIN_IN = 0.0
|
|
46
53
|
DEFAULT_TITLE_FLOOR_PT = 28.0
|
|
47
54
|
DEFAULT_BODY_FLOOR_PT = 14.0
|
package/assets/skills/office-presentations/container_tools/inspect_presentation_package_integrity.py
CHANGED
|
@@ -16,6 +16,13 @@ import zipfile
|
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
from xml.etree import ElementTree
|
|
18
18
|
|
|
19
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
20
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
21
|
+
try:
|
|
22
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
23
|
+
except (AttributeError, ValueError):
|
|
24
|
+
pass
|
|
25
|
+
|
|
19
26
|
REL_NS = "{http://schemas.openxmlformats.org/package/2006/relationships}"
|
|
20
27
|
CT_NS = "{http://schemas.openxmlformats.org/package/2006/content-types}"
|
|
21
28
|
P_NS = "{http://schemas.openxmlformats.org/presentationml/2006/main}"
|
|
@@ -20,6 +20,13 @@ import zipfile
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from xml.etree import ElementTree
|
|
22
22
|
|
|
23
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
24
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
25
|
+
try:
|
|
26
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
27
|
+
except (AttributeError, ValueError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
23
30
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
24
31
|
|
|
25
32
|
C_NS = "{http://schemas.openxmlformats.org/drawingml/2006/chart}"
|
package/assets/skills/office-presentations/container_tools/native_quantitative_chart_gate.py
CHANGED
|
@@ -22,6 +22,13 @@ from decimal import Decimal, InvalidOperation
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from xml.etree import ElementTree
|
|
24
24
|
|
|
25
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
26
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
27
|
+
try:
|
|
28
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
29
|
+
except (AttributeError, ValueError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
25
32
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
26
33
|
|
|
27
34
|
C_NS = "{http://schemas.openxmlformats.org/drawingml/2006/chart}"
|
|
@@ -24,6 +24,13 @@ import sys
|
|
|
24
24
|
from decimal import Decimal, InvalidOperation
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
|
|
27
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
28
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
29
|
+
try:
|
|
30
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
31
|
+
except (AttributeError, ValueError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
27
34
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
28
35
|
|
|
29
36
|
TOTAL_PATTERN = re.compile(r"\btotal\b", re.IGNORECASE)
|
|
@@ -23,6 +23,13 @@ import sys
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from typing import Any, Iterable, Iterator
|
|
25
25
|
|
|
26
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
27
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
28
|
+
try:
|
|
29
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
30
|
+
except (AttributeError, ValueError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
26
33
|
EMU_PER_INCH = 914400
|
|
27
34
|
|
|
28
35
|
# Anything that renders about a full em wide and wraps mid-token.
|
|
@@ -48,6 +48,13 @@ from ooxml_helpers import ( # noqa: E402
|
|
|
48
48
|
WIDE_CHAR,
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
52
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
53
|
+
try:
|
|
54
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
55
|
+
except (AttributeError, ValueError):
|
|
56
|
+
pass
|
|
57
|
+
|
|
51
58
|
INK = (0.13, 0.15, 0.18)
|
|
52
59
|
MUTED = (0.42, 0.45, 0.50)
|
|
53
60
|
RULE = (0.80, 0.82, 0.85)
|
|
@@ -23,6 +23,13 @@ import sys
|
|
|
23
23
|
import tempfile
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
|
|
26
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
27
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
28
|
+
try:
|
|
29
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
30
|
+
except (AttributeError, ValueError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
26
33
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
27
34
|
|
|
28
35
|
|
|
@@ -26,6 +26,13 @@ import json
|
|
|
26
26
|
import sys
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
30
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
31
|
+
try:
|
|
32
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
33
|
+
except (AttributeError, ValueError):
|
|
34
|
+
pass
|
|
35
|
+
|
|
29
36
|
|
|
30
37
|
def _emit(payload: dict, code: int = 0) -> int:
|
|
31
38
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
@@ -30,6 +30,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
|
30
30
|
|
|
31
31
|
from ooxml_helpers import box, is_picture, slide_size_in # noqa: E402
|
|
32
32
|
|
|
33
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
34
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
35
|
+
try:
|
|
36
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
37
|
+
except (AttributeError, ValueError):
|
|
38
|
+
pass
|
|
39
|
+
|
|
33
40
|
EMU_PER_INCH = 914400
|
|
34
41
|
DEFAULT_TOLERANCE_EMU = 9144 # 0.01 in
|
|
35
42
|
|
package/assets/skills/office-presentations/template_following_scripts/check_template_fidelity.py
CHANGED
|
@@ -20,6 +20,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools
|
|
|
20
20
|
|
|
21
21
|
from ooxml_helpers import box, has_visible_text, is_picture, overlap_ratio, text_of # noqa: E402
|
|
22
22
|
|
|
23
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
24
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
25
|
+
try:
|
|
26
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
27
|
+
except (AttributeError, ValueError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
23
30
|
PLACEHOLDER_PATTERNS = [
|
|
24
31
|
re.compile(pattern, re.IGNORECASE)
|
|
25
32
|
for pattern in (
|
package/assets/skills/office-presentations/template_following_scripts/inspect_template_deck.py
CHANGED
|
@@ -23,6 +23,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools
|
|
|
23
23
|
|
|
24
24
|
from ooxml_helpers import box, has_visible_text, is_chart, is_picture, is_table, placeholder_kind, slide_size_in, text_of # noqa: E402
|
|
25
25
|
|
|
26
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
27
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
28
|
+
try:
|
|
29
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
30
|
+
except (AttributeError, ValueError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
26
33
|
|
|
27
34
|
def _theme(presentation) -> dict:
|
|
28
35
|
fonts: set[str] = set()
|
|
@@ -15,6 +15,13 @@ import math
|
|
|
15
15
|
import sys
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
|
|
18
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
19
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
20
|
+
try:
|
|
21
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
22
|
+
except (AttributeError, ValueError):
|
|
23
|
+
pass
|
|
24
|
+
|
|
18
25
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools"))
|
|
19
26
|
|
|
20
27
|
|
|
@@ -23,6 +23,13 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools
|
|
|
23
23
|
|
|
24
24
|
from ooxml_helpers import box, placeholder_kind, slide_size_in # noqa: E402
|
|
25
25
|
|
|
26
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
27
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
28
|
+
try:
|
|
29
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
30
|
+
except (AttributeError, ValueError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
26
33
|
PLACEHOLDER_ALIASES = {
|
|
27
34
|
"title": ("TITLE", "CENTER_TITLE"),
|
|
28
35
|
"subtitle": ("SUBTITLE",),
|
package/assets/skills/office-presentations/template_following_scripts/validate_template_plan.py
CHANGED
|
@@ -26,6 +26,13 @@ import json
|
|
|
26
26
|
import sys
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
30
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
31
|
+
try:
|
|
32
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
33
|
+
except (AttributeError, ValueError):
|
|
34
|
+
pass
|
|
35
|
+
|
|
29
36
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "container_tools"))
|
|
30
37
|
|
|
31
38
|
ALLOWED_ACTIONS = {"rewrite", "rewrite-and-reposition", "replace", "delete", "keep", "fill-placeholder"}
|
|
@@ -35,6 +35,13 @@ import json
|
|
|
35
35
|
import sys
|
|
36
36
|
from pathlib import Path
|
|
37
37
|
|
|
38
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
39
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
40
|
+
try:
|
|
41
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
42
|
+
except (AttributeError, ValueError):
|
|
43
|
+
pass
|
|
44
|
+
|
|
38
45
|
if hasattr(sys.stdout, "reconfigure"):
|
|
39
46
|
sys.stdout.reconfigure(errors="replace")
|
|
40
47
|
|
|
@@ -23,6 +23,13 @@ import json
|
|
|
23
23
|
import sys
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
|
|
26
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
27
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
28
|
+
try:
|
|
29
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
30
|
+
except (AttributeError, ValueError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
26
33
|
if hasattr(sys.stdout, "reconfigure"):
|
|
27
34
|
sys.stdout.reconfigure(errors="replace")
|
|
28
35
|
|
|
@@ -29,6 +29,13 @@ import json
|
|
|
29
29
|
import sys
|
|
30
30
|
from pathlib import Path
|
|
31
31
|
|
|
32
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
33
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
34
|
+
try:
|
|
35
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
36
|
+
except (AttributeError, ValueError):
|
|
37
|
+
pass
|
|
38
|
+
|
|
32
39
|
if hasattr(sys.stdout, "reconfigure"):
|
|
33
40
|
sys.stdout.reconfigure(errors="replace")
|
|
34
41
|
|
|
@@ -21,6 +21,13 @@ import json
|
|
|
21
21
|
import sys
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
25
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
26
|
+
try:
|
|
27
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
28
|
+
except (AttributeError, ValueError):
|
|
29
|
+
pass
|
|
30
|
+
|
|
24
31
|
if hasattr(sys.stdout, "reconfigure"):
|
|
25
32
|
# Legacy console codepages (cp936, cp1252) must not abort a report.
|
|
26
33
|
sys.stdout.reconfigure(errors="replace")
|
|
@@ -22,6 +22,13 @@ import sys
|
|
|
22
22
|
import zipfile
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
|
|
25
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
26
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
27
|
+
try:
|
|
28
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
29
|
+
except (AttributeError, ValueError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
25
32
|
if hasattr(sys.stdout, "reconfigure"):
|
|
26
33
|
# Legacy console codepages (cp936, cp1252) must not abort a report.
|
|
27
34
|
sys.stdout.reconfigure(errors="replace")
|
|
@@ -28,6 +28,13 @@ import subprocess
|
|
|
28
28
|
import sys
|
|
29
29
|
from pathlib import Path
|
|
30
30
|
|
|
31
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
32
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
33
|
+
try:
|
|
34
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
35
|
+
except (AttributeError, ValueError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
31
38
|
if hasattr(sys.stdout, "reconfigure"):
|
|
32
39
|
# Legacy console codepages (cp936, cp1252) must not abort a report.
|
|
33
40
|
sys.stdout.reconfigure(errors="replace")
|
|
@@ -116,23 +123,28 @@ def _referenced_sheets(formula: str) -> list[str]:
|
|
|
116
123
|
return found
|
|
117
124
|
|
|
118
125
|
|
|
119
|
-
def _recalculate(python: str, cli: Path, source: Path, out: Path) -> tuple[bool, str | None]:
|
|
126
|
+
def _recalculate(python: str, cli: Path, source: Path, out: Path) -> tuple[bool, str | None, str | None]:
|
|
120
127
|
command = [python, str(cli), "xlsx", "recalc", "--input", str(source), "--out", str(out)]
|
|
121
128
|
try:
|
|
122
129
|
completed = subprocess.run(command, capture_output=True, text=True, timeout=300)
|
|
123
130
|
except FileNotFoundError as error:
|
|
124
|
-
return False, f"cannot start the helper CLI: {error}"
|
|
131
|
+
return False, f"cannot start the helper CLI: {error}", None
|
|
125
132
|
except subprocess.TimeoutExpired:
|
|
126
|
-
return False, "recalculation timed out after 300 seconds"
|
|
133
|
+
return False, "recalculation timed out after 300 seconds", None
|
|
127
134
|
if completed.returncode == 0 and out.is_file():
|
|
128
|
-
|
|
135
|
+
renderer = None
|
|
136
|
+
try:
|
|
137
|
+
renderer = json.loads(completed.stdout).get("renderer")
|
|
138
|
+
except (json.JSONDecodeError, AttributeError):
|
|
139
|
+
renderer = None
|
|
140
|
+
return True, None, renderer
|
|
129
141
|
message = (completed.stderr or completed.stdout or "").strip()
|
|
130
142
|
try:
|
|
131
143
|
parsed = json.loads(message)
|
|
132
144
|
message = str(parsed.get("error", message))
|
|
133
145
|
except json.JSONDecodeError:
|
|
134
146
|
pass
|
|
135
|
-
return False, message or f"helper CLI exited with code {completed.returncode}"
|
|
147
|
+
return False, message or f"helper CLI exited with code {completed.returncode}", None
|
|
136
148
|
|
|
137
149
|
|
|
138
150
|
def _ignored(entries: str | None) -> dict[str, set[str]]:
|
|
@@ -176,10 +188,10 @@ def main() -> int:
|
|
|
176
188
|
if not cli.is_file():
|
|
177
189
|
recalc_error = f"helper CLI not found: {cli}; pass --cli or use --no-recalc"
|
|
178
190
|
else:
|
|
179
|
-
recalculated, recalc_error = _recalculate(args.python, cli, source, out)
|
|
191
|
+
recalculated, recalc_error, renderer = _recalculate(args.python, cli, source, out)
|
|
180
192
|
if recalculated:
|
|
181
193
|
checked = out
|
|
182
|
-
engine = "
|
|
194
|
+
engine = renderer or "recalculation engine"
|
|
183
195
|
|
|
184
196
|
try:
|
|
185
197
|
cached = _cached_values(checked, cells) if cells else {}
|
|
@@ -26,6 +26,13 @@ import json
|
|
|
26
26
|
import sys
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
30
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
31
|
+
try:
|
|
32
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
33
|
+
except (AttributeError, ValueError):
|
|
34
|
+
pass
|
|
35
|
+
|
|
29
36
|
if hasattr(sys.stdout, "reconfigure"):
|
|
30
37
|
sys.stdout.reconfigure(errors="replace")
|
|
31
38
|
|
|
@@ -24,6 +24,13 @@ import json
|
|
|
24
24
|
import sys
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
|
|
27
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
28
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
29
|
+
try:
|
|
30
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
31
|
+
except (AttributeError, ValueError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
27
34
|
if hasattr(sys.stdout, "reconfigure"):
|
|
28
35
|
sys.stdout.reconfigure(errors="replace")
|
|
29
36
|
|
|
@@ -30,6 +30,13 @@ import json
|
|
|
30
30
|
import sys
|
|
31
31
|
from pathlib import Path
|
|
32
32
|
|
|
33
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
34
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
35
|
+
try:
|
|
36
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
37
|
+
except (AttributeError, ValueError):
|
|
38
|
+
pass
|
|
39
|
+
|
|
33
40
|
if hasattr(sys.stdout, "reconfigure"):
|
|
34
41
|
sys.stdout.reconfigure(errors="replace")
|
|
35
42
|
|
|
@@ -24,6 +24,13 @@ import subprocess
|
|
|
24
24
|
import sys
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
|
|
27
|
+
# Windows consoles default to a legacy code page; every payload is UTF-8 JSON.
|
|
28
|
+
for _stream in (sys.stdout, sys.stderr):
|
|
29
|
+
try:
|
|
30
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
31
|
+
except (AttributeError, ValueError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
27
34
|
DEFAULT_ROOT = Path(__file__).resolve().parents[3]
|
|
28
35
|
RENDERER_HINT = (
|
|
29
36
|
"docx/pptx/xlsx rendering needs LibreOffice, Microsoft Office, or WPS Office; "
|
package/dist/index.js
CHANGED
|
@@ -280,18 +280,13 @@ function statusOf(python) {
|
|
|
280
280
|
remedy: runtimeRemedy()
|
|
281
281
|
};
|
|
282
282
|
}
|
|
283
|
-
/**
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (expanded !== candidate && !isFile(expanded)) continue;
|
|
289
|
-
const inspection = await inspectPython(expanded);
|
|
290
|
-
if (inspection.compatible && inspection.missing.length === 0) return statusOf(inspection);
|
|
291
|
-
if (inspection.compatible && compatible == null) compatible = inspection;
|
|
292
|
-
last = inspection;
|
|
293
|
-
}
|
|
283
|
+
/** Pick the best interpreter: all dependencies first, then compatible-only, else none. */
|
|
284
|
+
function selectRuntime(inspections) {
|
|
285
|
+
const ready = inspections.find((inspection) => inspection.compatible && inspection.missing.length === 0);
|
|
286
|
+
if (ready != null) return statusOf(ready);
|
|
287
|
+
const compatible = inspections.find((inspection) => inspection.compatible);
|
|
294
288
|
if (compatible != null) return statusOf(compatible);
|
|
289
|
+
const last = inspections[inspections.length - 1];
|
|
295
290
|
return {
|
|
296
291
|
state: "no-interpreter",
|
|
297
292
|
packages: [],
|
|
@@ -300,6 +295,17 @@ async function inspectRuntime(configFile = officePaths().configFile) {
|
|
|
300
295
|
...last?.error == null ? {} : { error: last.error }
|
|
301
296
|
};
|
|
302
297
|
}
|
|
298
|
+
/** Inspect candidates in priority order; the first fully-provisioned interpreter wins. */
|
|
299
|
+
async function inspectRuntime(configFile = officePaths().configFile) {
|
|
300
|
+
const seen = [];
|
|
301
|
+
for (const candidate of [...candidates(configFile), ...await launcherInterpreters()]) for (const expanded of await expandLauncher(candidate)) {
|
|
302
|
+
if (expanded !== candidate && !isFile(expanded)) continue;
|
|
303
|
+
const inspection = await inspectPython(expanded);
|
|
304
|
+
seen.push(inspection);
|
|
305
|
+
if (inspection.compatible && inspection.missing.length === 0) return selectRuntime(seen);
|
|
306
|
+
}
|
|
307
|
+
return selectRuntime(seen);
|
|
308
|
+
}
|
|
303
309
|
/** Run postinstall.mjs as a child process; resolve with an error summary or undefined. */
|
|
304
310
|
function runPostinstall() {
|
|
305
311
|
return new Promise((resolve) => {
|
|
@@ -324,20 +330,27 @@ function runPostinstall() {
|
|
|
324
330
|
}
|
|
325
331
|
let cache;
|
|
326
332
|
let healing;
|
|
333
|
+
/** A cached `ok` is only worth reusing while its interpreter still exists on disk. */
|
|
334
|
+
function runtimeAlive(status) {
|
|
335
|
+
return status.state !== "ok" || status.python != null && isFile(status.python.path);
|
|
336
|
+
}
|
|
327
337
|
/**
|
|
328
338
|
* Resolve a runtime that is proven to work, self-healing via postinstall.mjs when it
|
|
329
339
|
* is missing or incomplete. Never returns a bare interpreter without dependencies.
|
|
340
|
+
* Pass `force` for an explicit status check that must not trust the process cache.
|
|
330
341
|
*/
|
|
331
|
-
function ensureRuntime(configFile = officePaths().configFile) {
|
|
332
|
-
if (
|
|
342
|
+
function ensureRuntime(configFile = officePaths().configFile, force = false) {
|
|
343
|
+
if (!force && cache != null) {
|
|
344
|
+
if (cache.status.state === "ok" ? runtimeAlive(cache.status) : Date.now() - cache.at < FAILURE_RETRY_DELAY) return Promise.resolve(cache.status);
|
|
345
|
+
}
|
|
333
346
|
healing ??= (async () => {
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
347
|
+
const probed = await inspectRuntime(configFile);
|
|
348
|
+
if (probed.state === "ok") {
|
|
336
349
|
cache = {
|
|
337
|
-
status:
|
|
350
|
+
status: probed,
|
|
338
351
|
at: Date.now()
|
|
339
352
|
};
|
|
340
|
-
return
|
|
353
|
+
return probed;
|
|
341
354
|
}
|
|
342
355
|
const healError = await runPostinstall();
|
|
343
356
|
const status = {
|
|
@@ -519,7 +532,7 @@ function apply(ctx) {
|
|
|
519
532
|
},
|
|
520
533
|
timeoutMs: 6e5,
|
|
521
534
|
async execute() {
|
|
522
|
-
const runtime = await ensureRuntime(paths.configFile);
|
|
535
|
+
const runtime = await ensureRuntime(paths.configFile, true);
|
|
523
536
|
const report = {
|
|
524
537
|
plugin: {
|
|
525
538
|
name,
|
package/package.json
CHANGED
package/postinstall.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { spawnSync } from 'node:child_process'
|
|
19
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
|
|
20
20
|
import { homedir, tmpdir } from 'node:os'
|
|
21
21
|
import { dirname, join } from 'node:path'
|
|
22
22
|
import process from 'node:process'
|
|
@@ -25,15 +25,15 @@ import { fileURLToPath } from 'node:url'
|
|
|
25
25
|
const PACKAGE_ROOT = dirname(fileURLToPath(import.meta.url))
|
|
26
26
|
const IS_WINDOWS = process.platform === 'win32'
|
|
27
27
|
const PYTHON_VERSION = '3.12.10'
|
|
28
|
-
const RUNTIME_DIR = join(homedir(), '.dsh', 'office', 'runtime')
|
|
29
|
-
const VENV_DIR = join(RUNTIME_DIR, 'venv')
|
|
28
|
+
export const RUNTIME_DIR = join(homedir(), '.dsh', 'office', 'runtime')
|
|
29
|
+
export const VENV_DIR = join(RUNTIME_DIR, 'venv')
|
|
30
30
|
const VENV_PYTHON = join(VENV_DIR, IS_WINDOWS ? 'Scripts/python.exe' : 'bin/python')
|
|
31
31
|
const LEGACY_DATA_DIR = IS_WINDOWS
|
|
32
32
|
? join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'dsh-office-kit')
|
|
33
33
|
: process.platform === 'darwin'
|
|
34
34
|
? join(homedir(), 'Library', 'Application Support', 'dsh-office-kit')
|
|
35
35
|
: join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'dsh-office-kit')
|
|
36
|
-
const LEGACY_VENV_DIR = join(LEGACY_DATA_DIR, 'venv')
|
|
36
|
+
export const LEGACY_VENV_DIR = join(LEGACY_DATA_DIR, 'venv')
|
|
37
37
|
const LEGACY_VENV_PYTHON = join(LEGACY_VENV_DIR, IS_WINDOWS ? 'Scripts/python.exe' : 'bin/python')
|
|
38
38
|
const CONFIG_DIR = join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'dsh-office-kit')
|
|
39
39
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
|
|
@@ -181,6 +181,24 @@ function verifySignature(file) {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Installer switches. PATH is appended by the installer itself (`AppendPath=1`),
|
|
186
|
+
* the install location stays the per-user default (no `TargetDir`), and the
|
|
187
|
+
* `py` launcher is kept so `py -3.12` works.
|
|
188
|
+
*/
|
|
189
|
+
export function installerArguments() {
|
|
190
|
+
return [
|
|
191
|
+
'/quiet',
|
|
192
|
+
'InstallAllUsers=0',
|
|
193
|
+
'AppendPath=1',
|
|
194
|
+
'CompileAll=0',
|
|
195
|
+
'Include_test=0',
|
|
196
|
+
'Include_doc=0',
|
|
197
|
+
'Include_pip=1',
|
|
198
|
+
'SimpleInstall=1',
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
|
|
184
202
|
function installPython() {
|
|
185
203
|
if (!IS_WINDOWS) {
|
|
186
204
|
return false
|
|
@@ -191,17 +209,7 @@ function installPython() {
|
|
|
191
209
|
download(url, installer)
|
|
192
210
|
verifySignature(installer)
|
|
193
211
|
log(`installing Python ${PYTHON_VERSION} into the installer default per-user location`)
|
|
194
|
-
const
|
|
195
|
-
'/quiet',
|
|
196
|
-
'InstallAllUsers=0',
|
|
197
|
-
'AppendPath=1',
|
|
198
|
-
'CompileAll=0',
|
|
199
|
-
'Include_test=0',
|
|
200
|
-
'Include_doc=0',
|
|
201
|
-
'Include_pip=1',
|
|
202
|
-
'SimpleInstall=1',
|
|
203
|
-
]
|
|
204
|
-
const result = spawnSync(installer, args, { stdio: 'inherit', timeout: 15 * 60 * 1000 })
|
|
212
|
+
const result = spawnSync(installer, installerArguments(), { stdio: 'inherit', timeout: 15 * 60 * 1000 })
|
|
205
213
|
rmSync(installer, { force: true })
|
|
206
214
|
if (result.status !== 0) {
|
|
207
215
|
throw new Error(`Python installer exited with status ${result.status}`)
|
|
@@ -368,6 +376,21 @@ function readPackageVersion() {
|
|
|
368
376
|
}
|
|
369
377
|
}
|
|
370
378
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
379
|
+
function startedFromCli() {
|
|
380
|
+
const entry = process.argv[1]
|
|
381
|
+
if (entry == null) {
|
|
382
|
+
return false
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url))
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
return false
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (startedFromCli()) {
|
|
393
|
+
const started = Date.now()
|
|
394
|
+
main()
|
|
395
|
+
log(`finished in ${((Date.now() - started) / 1000).toFixed(1)}s`)
|
|
396
|
+
}
|