diff --git a/TODO.txt b/TODO.txt index 8313855..d3fdddf 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,13 +1,12 @@ TODO ---- -* Bessere Fehlerkontrolle -* Ausgabe (besonders farbige) im Terminal verbessern. -* Ausgabe deaktivierbar machen. -* Parameterübergabe um einzelne Kunden zu bearbeiten. - * Parameter für den Versand der Rechnung. +* Bessere Fehlerkontrolle (besonders beim Einlesen) + * Längenprüfung + * Backticks * Parameterübergabe für ein Ausgabeverzeichnis. * Ansonsten vielleicht PDF verschieben * Nicht mehr benötigte Dateien (*.log, *.aux, etc.) der Rechnung löschen. -* Den Begleittext dürch eine Variable ersetzen. +* Den Begleittext im Latex-File durch eine Variable ersetzen. +* Das Program in einem temporären Verzeichnis arbeiten lassen. diff --git a/absender.lco b/absender.lco new file mode 100644 index 0000000..96b1de5 --- /dev/null +++ b/absender.lco @@ -0,0 +1,23 @@ +% eigene Variablen definieren und belegen +\newkomavar[stadt]{fromtown} +\newkomavar[adresse]{fromstreet} +\newkomavar[amtsgericht]{fromcourt} +\newkomavar[ustid]{fromustid} +\newkomavar[iban1]{fromiban1} +\newkomavar[iban2]{fromiban2} +\setkomavar{fromtown}{01234 Musterstadt} +\setkomavar{fromstreet}{Musterstr.\,1} +\setkomavar{fromcourt}{Amtsgericht Musterstadt} +\setkomavar{fromustid}{DE123456789} +\setkomavar{fromiban1}{DE01 2345 6789} +\setkomavar{fromiban2}{2345 6789 01} + +\setkomavar{fromname}{Musterfirma GmbH} +\setkomavar{fromaddress}{Musterstr.\,1\\01234 Musterstadt} +\setkomavar{frommobilephone}[\faMobilePhone~]{01\,23 / 456 78 90} +\setkomavar{fromurl}{https://musterfirma.com/} +\setkomavar{fromemail}[\faEnvelopeO~]{rechnung@musterfirma.com} +\setkomavar{place}{Musterstadt} +\setkomavar{placeseparator}{, } +\setkomavar{signature}{Max Mustermann} +\setkomavar{frombank}{Musterbank AG Musterstadt} diff --git a/octorech.lco b/octorech.lco deleted file mode 100644 index cd40ac8..0000000 --- a/octorech.lco +++ /dev/null @@ -1,9 +0,0 @@ -\setkomavar{fromname}{OctoPi.Consulting} -\setkomavar{fromaddress}{Zitzmannstr.\,15\\07743 Jena} -\setkomavar{frommobilephone}[\faMobilePhone~]{01\,63 / 615 61 98} -\setkomavar{fromurl}{https://octopi.consulting/} -\setkomavar{fromemail}[\faEnvelopeO~]{rechnung@octopi.consulting} -\setkomavar{place}{Jena} -\setkomavar{placeseparator}{, } -\setkomavar{signature}{Jens Kubieziel} -\setkomavar{frombank}{Commerzbank AG Jena} diff --git a/readme.txt b/readme.txt index 1ea13d9..db15157 100644 --- a/readme.txt +++ b/readme.txt @@ -9,13 +9,40 @@ Beschreibung Aus einer Kundendatei (rechnung.ini) werden blockweise Kundendaten gelesen und in Variablen gespeichert. Am Ende des Blocks wird eine Kopie des Templates (template.ltx) erstellt und die Platzhalter in der Kopie durch die -entsprechenden Variablen erstellt. Diese Kopie wird zweimal mittels pdflatex -übersetzt. +entsprechenden Variablen ersetzt. Aus dieser Kopie wird durch einen +Latexcompiler eine Rechnung im PDF-Format erstellt. Installation ------------ -Kopieren Sie die Dateien template.ltx, octorech.lco, rechnung.ini und +Kopieren Sie die Dateien template.ltx, absender.lco, rechnung.ini und rechnung.{sh|py} in ein Verzeichnis Ihrer Wahl. Passen Sie die Dateien -rechnung.ini und octorech.lco an Ihre Bedürfnisse an. +rechnung.ini und absender.lco an Ihre Bedürfnisse an. +Optionen +-------- + +-h + + Gibt eine kurze Hilfe aus und beendet das Programm. + +-f kundendatei + + Die Kundendaten werden aus 'kundendatei' gelesen. + +-i + + Installiert bei Bedarf das Latex-Paket rechnung.sty. Das Paket wird aus + dem Repo geklont, mittels latex gebaut und in das Verzeichnis + $HOME/texmf/latex/tex/rechnung/ kopiert. + +-l lco-datei + + Verwendet 'lco-datei' für verschiedene Variablen des Absenders. + +-q + + Es werden nur noch Fehlermeldungen ausgegeben. + +-v + Zeigt die Programmversion an. diff --git a/rechnung.ini b/rechnung.ini index 0975a3f..2257515 100644 --- a/rechnung.ini +++ b/rechnung.ini @@ -5,7 +5,7 @@ [start] kunde=Firma A -adresse1= Strasse A; 012345 Stadt A; +adresse= Strasse A; 012345 Stadt A; anrede=Sehr geehrte Damen und Herren, anzahl=1 position=Dienstleistung A mit etwas mehr Text diff --git a/rechnung.sh b/rechnung.sh index 0bde787..55a87e7 100755 --- a/rechnung.sh +++ b/rechnung.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/sh - # file: rechnung.sh # date: 02.05.2021 @@ -10,6 +10,19 @@ set -u set -e +# ein paar Sicherheitsvorkehrungen +# -------------------------------- + +OLDIFS=$IFS +IFS=' ' + +unset -f command +unset -f unalias +\unalias -a +OLDPATH=$PATH +PATH=/bin:/usr/bin +export PATH + # Benötigte Programme # ------------------- @@ -20,13 +33,14 @@ CUT_BIN=$(which cut 2>/dev/null || echo "cut") DATE_BIN=$(which date 2>/dev/null || echo "date") TPUT_BIN=$(which tput 2>/dev/null || echo "tput") PDFLATEX_BIN=$(which pdflatex 2>/dev/null || echo "pdflatex") +KPSEWHICH_BIN=$(which kpsewhich 2>/dev/null || echo "kpsewhich") GIT_BIN=$(which git 2>/dev/null || echo "git") LATEX_BIN=$(which latex 2>/dev/null || echo "latex") RUBBER_BIN=$(which rubber 2>/dev/null || echo "rubber") # Nicht auf rubber prüfen; Programm ist optional # Nicht auf git prüfen; wird nur benötigt, wenn rechnung.sty nicht existiert. # Nicht auf latex prüfen; wird nur benötigt, wenn rechnung.sty nicht texistiert. -EXECUTABLES_TO_CHECK="$BC_BIN $TR_BIN $SED_BIN $CUT_BIN $TPUT_BIN $DATE_BIN $PDFLATEX_BIN" +EXECUTABLES_TO_CHECK="$BC_BIN $TR_BIN $SED_BIN $CUT_BIN $TPUT_BIN $DATE_BIN $PDFLATEX_BIN $KPSEWHICH_BIN" GENPDF="" if [ -x "$RUBBER_BIN" ]; then @@ -39,17 +53,20 @@ fi # Variablen definieren # -------------------- -PROGRAMM_NAME=$0 +PROGRAMM_NAME=$(basename "$0") PROGRAMM_VERSION="0.0.1" -#VERBOSITY=0 +VERBOSITY=1 +STY_HOME="$HOME/texmf/tex/latex" STY_RECHNUNG="rechnung.sty" RECHNUNGSFILE="rechnung.ini" TEMPLATEFILE="template.ltx" +LCOFILE="absender.lco" +LCO=$(basename $LCOFILE .lco) +INSTALL_LCO_FILE=0 SEDFILE="sed.txt" LINE_BUFFER="" KEY_BUFFER="" VALUE_BUFFER="" -ERROR=0 KUNDE="" ADRESSE="" ANREDE="" @@ -78,28 +95,45 @@ print_start() { msg=$* - if [ "$COLORED_OUTPUT" -eq 1 ]; then - printf "%s[+]%s %s" "$GREEN" "$NORMAL" "$msg" + if [ "$VERBOSITY" -eq 0 ]; then + return else - printf " Failed" + if [ "$COLORED_OUTPUT" -eq 1 ]; then + printf "%s[+]%s %s" "$GREEN" "$NORMAL" "$msg" + else + printf " Failed" + fi fi } print_failed() { - if [ "$COLORED_OUTPUT" -eq 1 ]; then - printf "%s Failed%s\n" "$RED" "$NORMAL" + msg=$* + + if [ "$VERBOSITY" -eq 0 ]; then + return else - printf " Failed\n" + if [ "$COLORED_OUTPUT" -eq 1 ]; then + printf "%s Failed%s\n" "$RED" "$NORMAL" + else + printf " Failed\n" + fi + if [ -n "$msg" ]; then + print_error "$msg" + fi fi } print_ok() { - if [ "$COLORED_OUTPUT" -eq 1 ]; then - printf "%s OK%s\n" "$GREEN" "$NORMAL" + if [ "$VERBOSITY" -eq 0 ]; then + return else - printf " OK\n" + if [ "$COLORED_OUTPUT" -eq 1 ]; then + printf "%s OK%s\n" "$GREEN" "$NORMAL" + else + printf " OK\n" + fi fi } @@ -107,10 +141,14 @@ print_deko() { msg=$* - if [ "$COLORED_OUTPUT" -eq 1 ]; then - echo "${BLUE}${msg}${NORMAL}" + if [ "$VERBOSITY" -eq 0 ]; then + return else - echo "$msg" + if [ "$COLORED_OUTPUT" -eq 1 ]; then + echo "${BLUE}${msg}${NORMAL}" + else + echo "$msg" + fi fi } @@ -118,10 +156,14 @@ print_info() { msg=$* - if [ "$COLORED_OUTPUT" -eq 1 ]; then - echo "${GREEN}[+]$NORMAL $msg" + if [ "$VERBOSITY" -eq 0 ]; then + return else - echo "[-] $msg" + if [ "$COLORED_OUTPUT" -eq 1 ]; then + echo "${GREEN}[+]$NORMAL $msg" + else + echo "[-] $msg" + fi fi } @@ -129,10 +171,14 @@ print_warn() { msg=$* - if [ "$COLORED_OUTPUT" -eq 1 ]; then - echo "${YELLOW}[-]$NORMAL $msg" + if [ "$VERBOSITY" -eq 0 ]; then + return else - echo "[-] $msg" + if [ "$COLORED_OUTPUT" -eq 1 ]; then + echo "${YELLOW}[-]$NORMAL $msg" + else + echo "[-] $msg" + fi fi } @@ -147,39 +193,96 @@ print_error() { fi } +failed_and_exit() { + + print_failed + exit "$*" +} + # Diverse Checks # -------------- check_readable() { - + + # testet, ob die übergebene datei kein symlink ist, existiert und lesbar + # ist. schlägt einer der tests fehl, wird das programm beendet. + print_start "Datei $1 ist lesbar ..." - if [ -f "$1" ] && [ -r "$1" ]; then + if test -h "$1"; then + target=$(get_link_target "$1") + print_failed "$1 ist ein symbolischer Link auf $target" + exit 1 + fi + if test -f "$1" && test -r "$1"; then print_ok else - print_failed - quit 1 + failed_and_exit 1 fi } -check_binaries() { +check_sty() ( - program="" + # sucht in einer subshell nach dem paket rechnung.sty. gibt true oder + # false zurück. + + print_start "Suche nach $STY_RECHNUNG ..." + + if kpsewhich "$STY_RECHNUNG" > /dev/null 2>&1 ; then + print_ok + return 0 + else + print_failed + return 1 + fi +) + +check_binaries() ( + + # die funktion prüft in einer subshell, ob alle benötigten binaries + # installiert sind. sollte das binary ein symlink auf eine ander datei + # sein, wird geprüft, ob sich diese im PATH befindet und nicht ebenfalls + # ein symlink ist. schlägt eine der prüfungen fehl, wird das programm + # beendet. + + program= binaries=$* for program in $binaries; do print_start "Suche nach $program ..." - if [ ! -x "$program" ]; then - print_failed - print_error "Das Programm $program wird benötigt, aber nicht gefunden." + if test ! -x "$program"; then + print_failed "Das Programm $program wird benötigt, aber nicht gefunden." quit 3 else + if [ -h "$program" ]; then + program=$(get_link_target "$program") + fi + if ! which "$program" > /dev/null 2>&1 ; then + print_failed "$program ist nicht im Pfad" + fi print_ok fi done -} +) + +get_link_target() ( + + # die funktion bekommt einen symbolischen link und folgt ihm bis zum + # ende. mit dem echo-befehl wird das ziel an die aufrufende funktion + # zurück gegeben. + + program=$1 + + while [ -h "$program" ]; do + program=$(readlink "$program") + done + echo "$program" +) is_option() ( + # die funktion überprüft in einer subshell, ob die übergebene zeile ein + # = als trennzeichen enthält. gibt ja oder falsch zurück. + line="$*" if [ "${line#*=}" != "$line" ]; then @@ -189,6 +292,43 @@ is_option() ( fi ) +check_rechnunsdaten() ( + + # die funktion überprüft in einer subshell, ob alle variablen belegt + # sind. ist eine variable leer, wird der name der variablen ausgegeben + # und ein fehler (1) zurück gegeben. + + print_start "Prüfe Rechnungsdaten ..." + if test -z "$KUNDE"; then + print_failed "Das Feld Kunde ist leer" + return 1 + elif test -z "$ADRESSE"; then + print_failed "Das Feld Adresse ist leer" + return 1 + elif test -z "$ANREDE"; then + print_failed "Das Feld Anrede ist leer" + return 1 + elif test -z "$ANZAHL"; then + print_failed "Das Feld Anzahl ist leer" + return 1 + elif test -z "$POSITION"; then + print_failed "Das Feld Position ist leer" + return 1 + elif test -z "$SHORT"; then + print_failed "Das Feld Short ist leer" + return 1 + elif test -z "$BETRAG"; then + print_failed "Das Feld Betrag ist leer" + return 1 + elif test -z "$LFDNR"; then + print_failed "Das Feld LfdNr ist leer" + return 1 + else + print_ok + return 0 + fi +) + # Whitespaces entfernen # in Buffer speichern # --------------------- @@ -217,16 +357,53 @@ trim_value() { set +f } +# LaTeX-Paket rechnung.sty installieren +# ------------------------------------- + +install_sty() ( + + old_pwd=$(pwd) + + check_binaries "$GIT_BIN $LATEX_BIN" + print_start "Hole Repo von $STY_URL ..." + + if $GIT_BIN clone $STY_URL >/dev/null 2>&1; then + print_ok + cd rechnung + mkdir -p "$STY_HOME/rechnung/" + print_start "Kompiliere $STY_RECHNUNG ..." + + if $LATEX_BIN rechnung.ins >/dev/null 2>&1; then + print_ok + print_start "Kopiere $STY_RECHNUNG nach $STY_HOME/rechnung ..." + + if cp rechnung.sty "$STY_HOME/rechnung/"; then + print_ok + else + failed_and_exit 3 + fi + cd "$old_pwd" + rm -rf rechnung/ + print_info "Paket $STY_RECHNUNG erfolgreich installiert" + else + print_error "Installation fehlgeschlagen" + exit 4 + fi + else + print_error "Klonen des Repos fehlgeschlagen" + exit 5 + fi +) + # wenn ein neuer Kunde beginnt, alle Variablen neu initialisieren # --------------------------------------------------------------- reset_values() { - print_info "Initialisiere Felder für Rechnung neu." + print_info "Beginne neue Rechnung" LINE_BUFFER="" KEY_BUFFER="" VALUE_BUFFER="" - ERROR=0 KUNDE="" ADRESSE="" ANREDE="" @@ -255,19 +432,29 @@ set_value() { betrag) BETRAG="$VALUE_BUFFER";; lfdnr) LFDNR="$VALUE_BUFFER";; short) SHORT="$VALUE_BUFFER";; - *) print_warn "Unbekanntes Feld: $KEY_BUFFER"; ERROR=1;; + *) print_warn "Unbekanntes Feld: $KEY_BUFFER";; esac } -make_invoice() { +make_invoice() ( + + # die funktion erstellt in einer subshell die rechnung. schlägt die + # prüfung der rechnungsdaten fehl, kehrt sie zur aufrufenden shell + # zurück. ansonst wird eine kopie des latex-templates erstellt, die + # darin befindlichen platzhalter durch die entsprechenden variablen zu + # ersetzen und das ganze zu kompilieren. + + + if ! check_rechnunsdaten ; then + return 1 + fi dead_days="14" if [ "$(date +%u)" -gt 5 ]; then - dead_days="16" + dead_days="16" fi month_ascii=$(date '+%B') -# inv_day=$(date '+%j') inv_date=$(date '+%Y%m%d') inv_dead=$(date -d "+${dead_days}days" '+%d.\\,%m.\\,%Y') rechnungsnummer="$inv_date--$LFDNR" @@ -278,6 +465,7 @@ make_invoice() { print_info "Erstelle Rechnung: $filename" { + printf "s!#LCOFILE#!%s!\n" "$LCO" printf "s!#SUBJECT#!%s!\n" "$subject" printf "s!#ANREDE#!%s!\n" "$ANREDE" printf "s!#ANSCHRIFT#!%s!\n" "$anschrift" @@ -286,20 +474,22 @@ make_invoice() { printf "s!#POSITION#!%s!\n" "$POSITION" printf "s!#BETRAG#!%s!\n" "$BETRAG" printf "s!#DEADLINE#!%s!\n" "$inv_dead" - } > $SEDFILE + } >$SEDFILE cp "$TEMPLATEFILE" "$filename" $SED_BIN -i -f "$SEDFILE" "$filename" - if ! $GENPDF "$filename"; then - print_error "Rechnung fehlgeschlagen" + if ! $GENPDF "$filename" 1>&2 >/dev/null; then + print_error "Kompilierung von $filename fehlgeschlagen" + logfile=$(echo "$filename" | sed -e s/ltx/log/) + print_error "$(grep "Error" "$logfile")" else - print_info "Rechnung erfolgreich erstellt" + print_info "Rechnung $filename erfolgreich erstellt" fi if [ -x "$RUBBER_BIN" ]; then - "$RUBBER_BIN" --clean "$filename" + "$RUBBER_BIN" --clean "$filename" fi -} +) proced_ini_line() { @@ -320,12 +510,7 @@ proced_ini_line() { # ende einer rechnung elif [ "$line" = "[ende]" ]; then - if [ $ERROR -ne 0 ]; then - print_warn "Kunde \"$KUNDE\" enthält einen Fehler. Erstelle keine Rechung." - return - else - make_invoice - fi + make_invoice # alle anderen zeilen else @@ -338,6 +523,16 @@ proced_ini_line() { fi } +display_help() ( + + echo "Usage: $PROGRAMM_NAME [-f rechnungsfile] [-h] [-l lco-file]" +) + +display_version() ( + + echo "$PROGRAMM_NAME version $PROGRAMM_VERSION" +) + quit() { printf "\n" printf "%s" "$BLUE" @@ -348,7 +543,21 @@ quit() { # --- Programmstart --- # # --------------------- # -# Verschiedene Basic Checks +# CLI Optionen auswerten +while getopts f:hi:l:qv opt +do + case $opt in + f) RECHNUNGSFILE=$OPTARG;; + h) display_help; exit 0;; + i) INSTALL_LCO_FILE=1;; + l) LCOFILE=$OPTARG;; + q) VERBOSITY=0;; + v) display_version; exit 0;; + *) display_help; exit 1;; + esac +done + +# Farbe aktivieren wenn vorhanden if [ -n "$NUMBER_OF_COLORS" ] && [ "$NUMBER_OF_COLORS" -ge 8 ]; then COLORED_OUTPUT=1 fi @@ -357,6 +566,7 @@ print_deko " ========================" print_deko " = $PROGRAMM_NAME v$PROGRAMM_VERSION =" print_deko " ========================" +# Verschiedene Basic Checks if [ "$(id -u)" -eq 0 ]; then print_error "$0: Das Skript darf nicht als Root ausgeführt werden." exit 1 @@ -364,13 +574,16 @@ fi check_binaries "$EXECUTABLES_TO_CHECK" check_readable "$RECHNUNGSFILE" check_readable "$TEMPLATEFILE" - -if ! kpsewhich $STY_RECHNUNG > /dev/null; then - print_error "$STY_RECHNUNG nicht gefunden." - exit +check_readable "$LCOFILE" +if ! check_sty; then + print_warn "Verwenden sie -i um $STY_RECHNUNG zu installieren" + if [ $INSTALL_LCO_FILE -eq 0 ]; then + exit 7 + else + install_sty + fi fi - # Kundendatei einlesen und rechnungen erstellen while IFS= read -r rawline do @@ -378,4 +591,3 @@ do proced_ini_line "$LINE_BUFFER" done < "$RECHNUNGSFILE" - diff --git a/template.ltx b/template.ltx index a9e15a7..4e12d7f 100644 --- a/template.ltx +++ b/template.ltx @@ -17,7 +17,7 @@ parskip=half,% numericaldate=off,%% Datum numerisch ausgeben refline=narrow,%% Geschaeftszeile im Satzspiegel firstfoot=on,%% Footerbereich -octorech]{scrlttr2} + #LCOFILE#]{scrlttr2} \usepackage[ngerman]{babel} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} @@ -35,13 +35,14 @@ octorech]{scrlttr2} \makeatletter \@addtoplength[-]{firstfootvpos}{.75cm} % Fuß ein Stück nach oben \makeatother + \setkomavar{firstfoot}{\footnotesize% \rule[3pt]{\textwidth}{.4pt} \\ \begin{tabularx}{\textwidth}{XXl} - OctoPi.Consulting & Amtsgericht Jena & \textbf{Bankverbindung}\\ - Jens Kubieziel & USt.-IdNr.: & IBAN: DE50 8204 0000\\ - Zitzmannstr.\,15 & DE298453137 & \hfill{}0264 3955 00\\ - 07743 Jena & & Commerzbank AG Jena + \usekomavar{fromname} & \usekomavar{fromcourt} & \textbf{Bankverbindung}\\ + \usekomavar{signature} & USt.-IdNr.: & IBAN: \usekomavar{fromiban1}\\ + \usekomavar{fromstreet} & \usekomavar{fromustid} & \hfill{}\usekomavar{fromiban2}\\ + \usekomavar{fromtown} & & \usekomavar{frombank} \end{tabularx}% }