v0.5.6
Example document / build (push) Has been cancelled
Python application / build (push) Has been cancelled

This commit is contained in:
Igor20264
2026-09-22 18:49:09 +03:00
parent 7fc112e2ce
commit daa84f8bce
21 changed files with 2761 additions and 23 deletions
+176
View File
@@ -0,0 +1,176 @@
"""Detect installed Word / LibreOffice / OpenOffice for post-save table splits."""
from __future__ import annotations
import os
import shutil
import sys
from dataclasses import dataclass
from pathlib import Path
from .uno_cli import soffice_cli_path
OFFICE_ENGINE_MODES = ("auto", "word", "libre", "openoffice")
DEFAULT_OFFICE_ENGINE = "auto"
@dataclass(frozen=True)
class OfficeInstall:
engine: str # "word" | "libre" | "openoffice"
label: str
soffice_path: str | None = None
python_path: str | None = None
def _program_dirs(kind: str) -> list[Path]:
roots: list[Path] = []
for env in ("ProgramFiles", "ProgramFiles(x86)"):
base = os.environ.get(env)
if base:
roots.append(Path(base))
if sys.platform != "win32":
roots.extend(
[
Path("/usr/lib/libreoffice"),
Path("/usr/lib64/libreoffice"),
Path("/opt/libreoffice"),
Path("/opt/openoffice4"),
Path("/Applications/LibreOffice.app/Contents"),
]
)
candidates: list[Path] = []
if kind == "libre":
for root in roots:
candidates.extend(
[
root / "LibreOffice" / "program",
root / "libreoffice" / "program",
root / "program",
root / "MacOS",
]
)
else:
for root in roots:
candidates.extend(
[
root / "OpenOffice 4" / "program",
root / "openoffice4" / "program",
]
)
return candidates
def _soffice_names() -> tuple[str, ...]:
if sys.platform == "win32":
# soffice.com = CLI/headless; soffice.exe opens the Start Center GUI.
return ("soffice.com", "soffice.exe")
return ("soffice", "soffice.bin")
def _python_names() -> tuple[str, ...]:
if sys.platform == "win32":
return ("python.exe",)
return ("python", "python3")
def _find_soffice_install(kind: str) -> OfficeInstall | None:
label = "LibreOffice" if kind == "libre" else "OpenOffice"
seen: set[str] = set()
for directory in _program_dirs(kind):
for soffice_name in _soffice_names():
soffice = directory / soffice_name
key = str(soffice.resolve()) if soffice.exists() else ""
if not key or key in seen:
continue
seen.add(key)
python_path = None
for py_name in _python_names():
candidate = directory / py_name
if candidate.is_file():
python_path = str(candidate)
break
if python_path is None:
continue
return OfficeInstall(
engine=kind,
label=label,
soffice_path=soffice_cli_path(str(soffice)),
python_path=python_path,
)
which = shutil.which("soffice") or shutil.which("soffice.bin")
if which:
program_dir = Path(which).resolve().parent
python_path = None
for py_name in _python_names():
candidate = program_dir / py_name
if candidate.is_file():
python_path = str(candidate)
break
if python_path:
lower = str(program_dir).lower()
detected = "openoffice" if "openoffice" in lower else "libre"
if kind != detected:
return None
return OfficeInstall(
engine=kind,
label=label,
soffice_path=soffice_cli_path(str(Path(which).resolve())),
python_path=python_path,
)
return None
def detect_office_install(engine: str) -> OfficeInstall | None:
"""Return install info for a concrete engine id."""
if engine == "word":
return _detect_word()
if engine == "libre":
return _find_soffice_install("libre")
if engine == "openoffice":
return _find_soffice_install("openoffice")
return None
def _detect_word() -> OfficeInstall | None:
if sys.platform != "win32":
return None
try:
import win32com.client # type: ignore
except ImportError:
return None
word = None
try:
word = win32com.client.DispatchEx("Word.Application")
return OfficeInstall(engine="word", label="Microsoft Word")
except Exception:
return None
finally:
if word is not None:
try:
word.Quit()
except Exception:
pass
def resolve_office_engine(mode: str) -> tuple[str | None, OfficeInstall | None]:
"""
Resolve ``mode`` to a concrete engine id and optional install record.
``auto`` tries Word, then LibreOffice, then OpenOffice.
"""
if mode not in OFFICE_ENGINE_MODES:
mode = DEFAULT_OFFICE_ENGINE
if mode == "auto":
for candidate in ("word", "libre", "openoffice"):
install = detect_office_install(candidate)
if install is not None:
return candidate, install
return None, None
install = detect_office_install(mode)
if install is None:
return None, None
return mode, install