feat: install user console commands and document Office independence

This commit is contained in:
admin committed 2026-10-16 12:30:46 +02:00
1 parent 6438558ea3
commit f447603af4
6 files changed
+179 -10

No files matched your search

+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Install per-user launchers without sudo or changing shell configuration."""
import argparse
from datetime import datetime, timezone
import os
from pathlib import Path
import subprocess
import sys
import uuid
def install(bin_dir: Path) -> None:
root = Path(__file__).resolve().parent.parent
bin_dir = bin_dir.expanduser().absolute()
bin_dir.mkdir(parents=True, exist_ok=True)
changed = []
try:
for name in ('einvoice', 'jr-einvoice'):
source = root / 'bin' / name
target = bin_dir / name
if target.is_symlink() and target.resolve() == source:
print(f'Bereits eingerichtet: {target}', flush=True)
continue
backup = None
if target.exists() or target.is_symlink():
if target.is_dir():
raise RuntimeError(f'Ziel ist ein Verzeichnis: {target}')
stamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')
backup = target.with_name(f'{name}.before-einvoice-{stamp}-{uuid.uuid4().hex[:8]}')
if target.is_symlink():
backup.symlink_to(os.readlink(target))
else:
import shutil
shutil.copy2(target, backup)
print(f'Bisheriger Befehl gesichert: {backup}', flush=True)
temporary = target.with_name(f'.{name}-{uuid.uuid4().hex}.next')
try:
temporary.symlink_to(source)
os.replace(temporary, target)
finally:
temporary.unlink(missing_ok=True)
changed.append((target, backup))
print(f'Eingerichtet: {target} -> {source}', flush=True)
# A different cwd verifies that symlinks resolve to the checkout correctly.
for name in ('einvoice', 'jr-einvoice'):
subprocess.run([str(bin_dir / name), '--help'], cwd=bin_dir,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
print('OK: Beide Konsolenbefehle geprüft.', flush=True)
except BaseException:
for target, backup in reversed(changed):
if backup is None:
target.unlink(missing_ok=True)
else:
os.replace(backup, target)
raise
if str(bin_dir) not in os.environ.get('PATH', '').split(os.pathsep):
import shlex
print(f'Für diese Shell ausführen: export PATH={shlex.quote(str(bin_dir))}:"$PATH"')
print('Ab jetzt: einvoice --help | einvoice --update --status | jr-einvoice --help')
print('Falls die Shell noch den alten Pfad merkt: rehash (zsh) bzw. hash -r (bash).')
def main():
parser = argparse.ArgumentParser(description='einvoice und jr-einvoice im Benutzer-Suchpfad einrichten.')
parser.add_argument('--bin-dir', type=Path, default=Path.home() / '.local/bin')
args = parser.parse_args()
try:
install(args.bin_dir)
except (Exception, KeyboardInterrupt) as exc:
print(f'FEHLER: {exc}; geänderte Befehle wurden zurückgesetzt.', file=sys.stderr)
if isinstance(exc, subprocess.CalledProcessError) and exc.stderr:
print(exc.stderr.strip(), file=sys.stderr)
return 1
return 0
if __name__ == '__main__':
raise SystemExit(main())