#!/usr/bin/env python3 """Starter im Benutzer-Suchpfad ohne sudo oder Änderungen an der Shell einrichten.""" 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', 'einvoice-ui'): 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) # Aus fremdem Verzeichnis die Auflösung der Konsolenstarter prüfen. 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-ui | 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') parser.add_argument('--desktop', action='store_true', help='Zusätzlich einen Eintrag im Anwendungsmenü installieren') args = parser.parse_args() try: install(args.bin_dir) if args.desktop: root = Path(__file__).resolve().parent.parent applications = Path(os.environ.get('XDG_DATA_HOME', Path.home() / '.local/share')) / 'applications' applications.mkdir(parents=True, exist_ok=True) launcher = str(root / 'bin/einvoice-ui').replace('%', '%%') for char in ('\\', '"', '`', '$'): launcher = launcher.replace(char, '\\' + char) entry = applications / 'de.jr-it-services.einvoice.desktop' entry.write_text('[Desktop Entry]\nType=Application\nName=JR eInvoice\n' 'Comment=E-Rechnungen erstellen und als PDF ansehen\n' f'Exec="{launcher}"\nIcon={root / "assets/logo.svg"}\n' 'Terminal=false\nCategories=Office;Finance;\nStartupNotify=true\n', encoding='utf-8') print(f'Anwendungsmenü eingerichtet: {entry}') 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())