Publish hermes-skills-productivity via gitea-publish skill

This commit is contained in:
figmar
2026-08-09 08:19:26 +08:00
commit aad5c86543
152 changed files with 55812 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
"""
Helper for running LibreOffice (soffice) in environments where AF_UNIX
sockets may be blocked (e.g., sandboxed VMs). Detects the restriction
at runtime and applies an LD_PRELOAD shim if needed.
Usage:
from office.soffice import run_soffice
result = run_soffice(["--headless", "--convert-to", "pdf", "input.docx"])
Call soffice through run_soffice, not through subprocess with get_soffice_env():
the env dict carries the shim but names no user profile, and a non-root sandbox
cannot bootstrap the default one -- soffice aborts with "User installation could
not be completed" and converts nothing. get_soffice_env() stays public for the
callers that build their own argv (they must pass -env:UserInstallation too).
"""
import contextlib
import os
import socket
import subprocess
import tempfile
from collections.abc import Iterable
from pathlib import Path
def get_soffice_env() -> dict:
env = os.environ.copy()
env["SAL_USE_VCLPLUGIN"] = "svp"
if _needs_shim():
shim = _ensure_shim()
env["LD_PRELOAD"] = str(shim)
return env
def run_soffice(args: Iterable[str], **kwargs) -> subprocess.CompletedProcess:
args = list(args)
with contextlib.ExitStack() as stack:
if not any(str(a).startswith("-env:UserInstallation") for a in args):
profile = stack.enter_context(
tempfile.TemporaryDirectory(prefix="lo_profile_", ignore_cleanup_errors=True)
)
args = [f"-env:UserInstallation={Path(profile).as_uri()}"] + args
return subprocess.run(["soffice"] + args, env=get_soffice_env(), **kwargs)
_SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so"
def _needs_shim() -> bool:
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.close()
return False
except OSError:
return True
def _ensure_shim() -> Path:
if _SHIM_SO.exists():
return _SHIM_SO
src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
src.write_text(_SHIM_SOURCE, encoding="utf-8")
subprocess.run(
["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
check=True,
capture_output=True,
)
src.unlink()
return _SHIM_SO
_SHIM_SOURCE = r"""
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
static int (*real_socket)(int, int, int);
static int (*real_socketpair)(int, int, int, int[2]);
static int (*real_listen)(int, int);
static int (*real_accept)(int, struct sockaddr *, socklen_t *);
static int (*real_close)(int);
static int (*real_read)(int, void *, size_t);
/* Per-FD bookkeeping (FDs >= 1024 are passed through unshimmed). */
static int is_shimmed[1024];
static int peer_of[1024];
static int wake_r[1024]; /* accept() blocks reading this */
static int wake_w[1024]; /* close() writes to this */
static int listener_fd = -1; /* FD that received listen() */
__attribute__((constructor))
static void init(void) {
real_socket = dlsym(RTLD_NEXT, "socket");
real_socketpair = dlsym(RTLD_NEXT, "socketpair");
real_listen = dlsym(RTLD_NEXT, "listen");
real_accept = dlsym(RTLD_NEXT, "accept");
real_close = dlsym(RTLD_NEXT, "close");
real_read = dlsym(RTLD_NEXT, "read");
for (int i = 0; i < 1024; i++) {
peer_of[i] = -1;
wake_r[i] = -1;
wake_w[i] = -1;
}
}
/* ---- socket ---------------------------------------------------------- */
int socket(int domain, int type, int protocol) {
if (domain == AF_UNIX) {
int fd = real_socket(domain, type, protocol);
if (fd >= 0) return fd;
/* socket(AF_UNIX) blocked fall back to socketpair(). */
int sv[2];
if (real_socketpair(domain, type, protocol, sv) == 0) {
if (sv[0] >= 0 && sv[0] < 1024) {
is_shimmed[sv[0]] = 1;
peer_of[sv[0]] = sv[1];
int wp[2];
if (pipe(wp) == 0) {
wake_r[sv[0]] = wp[0];
wake_w[sv[0]] = wp[1];
}
}
return sv[0];
}
errno = EPERM;
return -1;
}
return real_socket(domain, type, protocol);
}
/* ---- listen ---------------------------------------------------------- */
int listen(int sockfd, int backlog) {
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
listener_fd = sockfd;
return 0;
}
return real_listen(sockfd, backlog);
}
/* ---- accept ---------------------------------------------------------- */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
/* Block until close() writes to the wake pipe. */
if (wake_r[sockfd] >= 0) {
char buf;
real_read(wake_r[sockfd], &buf, 1);
}
errno = ECONNABORTED;
return -1;
}
return real_accept(sockfd, addr, addrlen);
}
/* ---- close ----------------------------------------------------------- */
int close(int fd) {
if (fd >= 0 && fd < 1024 && is_shimmed[fd]) {
int was_listener = (fd == listener_fd);
is_shimmed[fd] = 0;
if (wake_w[fd] >= 0) { /* unblock accept() */
char c = 0;
write(wake_w[fd], &c, 1);
real_close(wake_w[fd]);
wake_w[fd] = -1;
}
if (wake_r[fd] >= 0) { real_close(wake_r[fd]); wake_r[fd] = -1; }
if (peer_of[fd] >= 0) { real_close(peer_of[fd]); peer_of[fd] = -1; }
if (was_listener)
_exit(0); /* conversion done exit */
}
return real_close(fd);
}
"""
if __name__ == "__main__":
import sys
result = run_soffice(sys.argv[1:])
sys.exit(result.returncode)
+308
View File
@@ -0,0 +1,308 @@
"""
Excel Formula Recalculation Script
Recalculates all formulas in an Excel file using LibreOffice
"""
import contextlib
import json
import os
import platform
import re
import shutil
import subprocess
import sys
import tempfile
import time
import zipfile
from pathlib import Path
from office.soffice import get_soffice_env, run_soffice
from openpyxl import load_workbook
MACRO_FILENAME = "Module1.xba"
SOFFICE_MISSING = "soffice not found on PATH; LibreOffice is required to recalculate"
MAX_LOCATIONS = 100
EXTERNAL_REF_RE = re.compile(r"""(?<![\w"\[])'?\[\d+\][^!"\[\]]*'?!""")
RECALCULATE_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
Sub RecalculateAndSave()
ThisComponent.calculateAll()
ThisComponent.store()
ThisComponent.close(True)
End Sub
</script:module>"""
def has_gtimeout():
try:
subprocess.run(
["gtimeout", "--version"], capture_output=True, timeout=1, check=False
)
return True
except (FileNotFoundError, subprocess.TimeoutExpired):
return False
def _stamp(path):
st = os.stat(path)
return st.st_mtime_ns, st.st_size
def setup_libreoffice_macro(profile_dir: Path, timeout=30):
url = profile_dir.as_uri()
try:
run_soffice(
["--headless", "--terminate_after_init", f"-env:UserInstallation={url}"],
capture_output=True,
timeout=timeout,
)
except FileNotFoundError:
return None, SOFFICE_MISSING
except subprocess.TimeoutExpired:
return None, "LibreOffice timed out creating its profile; formulas were NOT recalculated"
macro_dir = profile_dir / "user" / "basic" / "Standard"
if not macro_dir.exists():
return None, "LibreOffice did not create a usable profile; formulas were NOT recalculated"
try:
(macro_dir / MACRO_FILENAME).write_text(RECALCULATE_MACRO, encoding="utf-8")
except OSError as e:
return None, f"Could not install the recalculation macro: {e}"
return url, None
def external_links_at_risk(filename):
try:
with zipfile.ZipFile(filename) as archive:
names = archive.namelist()
except (zipfile.BadZipFile, OSError):
return []
if not any(n.startswith("xl/externalLinks/") for n in names):
return []
with contextlib.ExitStack() as stack:
formulas = load_workbook(filename, data_only=False)
stack.callback(formulas.close)
values = load_workbook(filename, data_only=True)
stack.callback(values.close)
external_names = [
name
for name, dn in formulas.defined_names.items()
if isinstance(getattr(dn, "value", None), str) and EXTERNAL_REF_RE.search(dn.value)
]
name_re = (
re.compile(r"\b(" + "|".join(re.escape(n) for n in external_names) + r")\b")
if external_names
else None
)
at_risk = []
for sheet in formulas.sheetnames:
ws = formulas[sheet]
if not hasattr(ws, "iter_rows"):
continue
cached = values[sheet]
for row in ws.iter_rows():
for cell in row:
v = cell.value
if not (isinstance(v, str) and v.startswith("=")):
continue
reaches_out = EXTERNAL_REF_RE.search(v) or (name_re and name_re.search(v))
if reaches_out and cached[cell.coordinate].value is None:
at_risk.append(f"{sheet}!{cell.coordinate}")
return at_risk
def recalc(filename, timeout=30, force=False):
if not Path(filename).exists():
return {"error": f"File {filename} does not exist"}
abs_path = str(Path(filename).absolute())
if not os.access(abs_path, os.W_OK):
return {"error": f"{filename} is not writable; recalculation rewrites the file in place"}
try:
get_soffice_env()
except Exception as e:
return {"error": f"Could not prepare the LibreOffice environment: {e}"}
if not force:
try:
at_risk = external_links_at_risk(filename)
except Exception as e:
return {"error": f"Could not inspect {filename} for external links: {e}"}
if at_risk:
shown = at_risk[:MAX_LOCATIONS]
return {
"error": (
"Refusing to recalculate: this workbook links to another workbook, and "
f"{len(at_risk)} linked cell(s) have lost their cached value (openpyxl strips "
"these on save). Recalculating would resolve them to #NAME? and delete the "
"external links for good. Copy those cells' values from the original file "
"before saving, or pass --force to accept the loss. Charts and conditional "
"formats can hold external references too, so this list may not be exhaustive."
),
"external_link_cells": shown,
"external_link_cells_truncated": max(0, len(at_risk) - len(shown)),
}
with tempfile.TemporaryDirectory(
prefix="recalc-lo-profile-", ignore_cleanup_errors=True
) as profile_dir:
return _recalc_with_profile(filename, abs_path, timeout, Path(profile_dir))
def _recalc_with_profile(filename, abs_path, timeout, profile_dir: Path):
started = time.monotonic()
profile_url, err = setup_libreoffice_macro(profile_dir, timeout=timeout)
if err:
return {"error": err}
timeout = max(5, int(timeout - (time.monotonic() - started)))
before = _stamp(abs_path)
cmd = [
"soffice",
"--headless",
"--norestore",
f"-env:UserInstallation={profile_url}",
"vnd.sun.star.script:Standard.Module1.RecalculateAndSave?language=Basic&location=application",
abs_path,
]
if platform.system() == "Linux" and shutil.which("timeout"):
cmd = ["timeout", str(timeout)] + cmd
elif platform.system() == "Darwin" and has_gtimeout():
cmd = ["gtimeout", str(timeout)] + cmd
timed_out = f"LibreOffice timed out after {timeout}s; formulas were NOT recalculated. Re-run with a longer timeout."
try:
result = subprocess.run(
cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", env=get_soffice_env(), timeout=timeout + 15
)
except subprocess.TimeoutExpired:
return {"error": timed_out}
except FileNotFoundError:
return {"error": SOFFICE_MISSING}
if result.returncode == 124:
return {"error": timed_out}
if result.returncode != 0:
detail = (result.stderr or "").strip() or f"soffice exited {result.returncode}"
return {"error": f"LibreOffice failed to recalculate: {detail}"}
if _stamp(abs_path) == before:
return {
"error": (
"LibreOffice exited cleanly but never rewrote the file, so nothing was "
"recalculated. Check that no other LibreOffice instance is running, then retry."
)
}
try:
wb = load_workbook(filename, data_only=True)
excel_errors = [
"#VALUE!",
"#DIV/0!",
"#REF!",
"#NAME?",
"#NULL!",
"#NUM!",
"#N/A",
]
error_details = {err: [] for err in excel_errors}
total_errors = 0
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
if not hasattr(ws, "iter_rows"):
continue
for row in ws.iter_rows():
for cell in row:
if cell.value is not None and isinstance(cell.value, str):
for err in excel_errors:
if err in cell.value:
location = f"{sheet_name}!{cell.coordinate}"
error_details[err].append(location)
total_errors += 1
break
result = {
"status": "success" if total_errors == 0 else "errors_found",
"total_errors": total_errors,
"error_summary": {},
}
for err_type, locations in error_details.items():
if locations:
entry = {"count": len(locations), "locations": locations[:MAX_LOCATIONS]}
if len(locations) > MAX_LOCATIONS:
entry["locations_truncated"] = len(locations) - MAX_LOCATIONS
result["error_summary"][err_type] = entry
wb.close()
wb_formulas = load_workbook(filename, data_only=False)
formula_count = 0
for sheet_name in wb_formulas.sheetnames:
ws = wb_formulas[sheet_name]
if not hasattr(ws, "iter_rows"):
continue
for row in ws.iter_rows():
for cell in row:
if (
cell.value
and isinstance(cell.value, str)
and cell.value.startswith("=")
):
formula_count += 1
wb_formulas.close()
result["total_formulas"] = formula_count
return result
except Exception as e:
return {"error": str(e)}
def main():
args = [a for a in sys.argv[1:] if a != "--force"]
force = "--force" in sys.argv[1:]
if not args:
print("Usage: python recalc.py <excel_file> [timeout_seconds] [--force]")
print("\nRecalculates all formulas in an Excel file using LibreOffice")
print("\nReturns JSON with error details:")
print(" - status: 'success' or 'errors_found'")
print(" - total_errors: Total number of Excel errors found")
print(" - total_formulas: Number of formulas in the file")
print(" - error_summary: Breakdown by error type with locations")
print(" - #VALUE!, #DIV/0!, #REF!, #NAME?, #NULL!, #NUM!, #N/A")
print("\nOn any failure the JSON has an 'error' key and no 'status'.")
print("--force recalculates even when it would destroy external links.")
sys.exit(1)
filename = args[0]
timeout = int(args[1]) if len(args) > 1 else 30
result = recalc(filename, timeout, force=force)
print(json.dumps(result, indent=2))
sys.exit(1 if "error" in result else 0)
if __name__ == "__main__":
main()