Files
einvoice/scripts/install_commands.py

93 lines
4.3 KiB
Python
Executable File

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