#!/usr/bin/env bash # Zeiterfassung für den Planer. # Schreibt nach devlog/zeiten.csv: datum;start;ende;minuten;meilenstein;art;notiz set -euo pipefail WURZEL="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CSV="$WURZEL/devlog/zeiten.csv" KOPF='datum;start;ende;minuten;meilenstein;art;notiz' OFFEN="$WURZEL/devlog/.zeit-offen" hilfe() { cat <<'ENDE' Verwendung: zeit.sh [argumente] start [Notiz] Eintrag öffnen (Art: claude) stop [Notiz] Offenen Eintrag schließen nachtrag Eigene Zeit nachtragen (Art: review, test, einrichtung) summe [Meilenstein] Summen je Meilenstein und Art status Zeigt, ob ein Eintrag offen ist hilfe Diese Übersicht ENDE } fehler() { printf 'Fehler: %s\n' "$1" >&2 exit 1 } # Semikolons und Zeilenumbrüche würden die CSV zerlegen. saeubern() { printf '%s' "$1" | tr '\n\r;' ' ' | sed 's/ */ /g; s/^ //; s/ $//' } csv_anlegen() { mkdir -p "$(dirname "$CSV")" if [ ! -f "$CSV" ]; then printf '%s\n' "$KOPF" > "$CSV" fi } # Minuten seit Mitternacht, damit auch ohne GNU date gerechnet werden kann. zu_minuten() { local uhr="$1" local h="${uhr%%:*}" local m="${uhr##*:}" printf '%s' "$((10#$h * 60 + 10#$m))" } befehl_start() { local meilenstein="${1:-}" [ -n "$meilenstein" ] || fehler 'Meilenstein fehlt. Beispiel: zeit.sh start M1 "Gerüst"' shift local notiz notiz="$(saeubern "${*:-}")" if [ -f "$OFFEN" ]; then local alt alt="$(cat "$OFFEN")" fehler "Es läuft bereits ein Eintrag ($alt). Erst 'zeit.sh stop'." fi csv_anlegen mkdir -p "$(dirname "$OFFEN")" printf '%s;%s;%s;%s\n' "$(date '+%Y-%m-%d')" "$(date '+%H:%M')" \ "$(saeubern "$meilenstein")" "$notiz" > "$OFFEN" printf 'Gestartet: %s um %s Uhr%s\n' "$meilenstein" "$(date '+%H:%M')" \ "${notiz:+ – $notiz}" } befehl_stop() { [ -f "$OFFEN" ] || fehler "Kein offener Eintrag. Erst 'zeit.sh start '." local zeile datum start meilenstein notiz_start zeile="$(cat "$OFFEN")" IFS=';' read -r datum start meilenstein notiz_start <<< "$zeile" local ende notiz ende="$(date '+%H:%M')" notiz="$(saeubern "${*:-}")" [ -n "$notiz" ] || notiz="$notiz_start" local von bis minuten von="$(zu_minuten "$start")" bis="$(zu_minuten "$ende")" minuten=$((bis - von)) # Über Mitternacht hinweg. [ "$minuten" -ge 0 ] || minuten=$((minuten + 1440)) csv_anlegen printf '%s;%s;%s;%s;%s;claude;%s\n' \ "$datum" "$start" "$ende" "$minuten" "$meilenstein" "$notiz" >> "$CSV" rm -f "$OFFEN" printf 'Gestoppt: %s, %s Minuten (%s–%s)\n' "$meilenstein" "$minuten" "$start" "$ende" } befehl_nachtrag() { local meilenstein="${1:-}" minuten="${2:-}" art="${3:-}" [ -n "$meilenstein" ] || fehler 'Meilenstein fehlt.' [ -n "$minuten" ] || fehler 'Minuten fehlen.' [ -n "$art" ] || fehler 'Art fehlt (review, test, einrichtung).' case "$minuten" in ''|*[!0-9]*) fehler "Minuten müssen eine ganze Zahl sein, nicht '$minuten'." ;; esac shift 3 local notiz notiz="$(saeubern "${*:-}")" [ -n "$notiz" ] || fehler 'Notiz fehlt.' csv_anlegen printf '%s;;;%s;%s;%s;%s\n' \ "$(date '+%Y-%m-%d')" "$minuten" "$(saeubern "$meilenstein")" \ "$(saeubern "$art")" "$notiz" >> "$CSV" printf 'Nachgetragen: %s Minuten auf %s (%s)\n' "$minuten" "$meilenstein" "$art" } befehl_summe() { local filter="${1:-}" [ -f "$CSV" ] || fehler "Noch keine Zeiten erfasst ($CSV fehlt)." awk -F';' -v filter="$filter" ' NR == 1 { next } $5 == "" { next } filter != "" && $5 != filter { next } { schluessel = $5 ";" $6 summe[schluessel] += $4 je_meilenstein[$5] += $4 gesamt += $4 if (!($5 in gesehen)) { gesehen[$5] = 1; reihenfolge[++anzahl] = $5 } } END { if (gesamt == 0) { print "Keine Einträge gefunden."; exit } printf "%-14s %-14s %10s %10s\n", "Meilenstein", "Art", "Minuten", "Stunden" printf "%s\n", "------------------------------------------------------" for (i = 1; i <= anzahl; i++) { m = reihenfolge[i] for (s in summe) { split(s, teil, ";") if (teil[1] == m) { printf "%-14s %-14s %10d %10.2f\n", m, teil[2], summe[s], summe[s] / 60 } } printf "%-14s %-14s %10d %10.2f\n", m, "= Summe", je_meilenstein[m], je_meilenstein[m] / 60 printf "%s\n", "------------------------------------------------------" } printf "%-29s %10d %10.2f\n", "Gesamt", gesamt, gesamt / 60 } ' "$CSV" } befehl_status() { if [ -f "$OFFEN" ]; then local zeile datum start meilenstein notiz zeile="$(cat "$OFFEN")" IFS=';' read -r datum start meilenstein notiz <<< "$zeile" printf 'Offen seit %s %s Uhr: %s%s\n' "$datum" "$start" "$meilenstein" "${notiz:+ – $notiz}" else printf 'Kein offener Eintrag.\n' fi } befehl="${1:-hilfe}" shift || true case "$befehl" in start) befehl_start "$@" ;; stop) befehl_stop "$@" ;; nachtrag) befehl_nachtrag "$@" ;; summe) befehl_summe "$@" ;; status) befehl_status ;; hilfe|-h|--hilfe|--help) hilfe ;; *) fehler "Unbekannter Befehl '$befehl'. 'zeit.sh hilfe' zeigt die Übersicht." ;; esac