#!/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())