From 6479f767b51a152d93355b6e29deb9f32a3211cd Mon Sep 17 00:00:00 2001 From: lhark Date: Mon, 17 Feb 2020 01:07:40 -0500 Subject: [PATCH] New tiemkeeping implementation, in python this time --- bin/work | 255 ++++++++++++++++++++++++++++++++++++++------------- bin/work.old | 71 ++++++++++++++ 2 files changed, 260 insertions(+), 66 deletions(-) create mode 100755 bin/work.old diff --git a/bin/work b/bin/work index d988337..f1a4219 100755 --- a/bin/work +++ b/bin/work @@ -1,71 +1,194 @@ -#!/bin/sh +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- -TIMEFILE="$HOME/timekeeping.csv" +import argparse +from signal import signal, SIGINT +import sys +from time import sleep +from os.path import expanduser, join +from datetime import datetime, timedelta -usage () { - >&2 printf "usage:\n\t%s " "$(basename "$0")" - exit 0 -} +TIME_FILE = join(expanduser("~"), "timekeeping.csv") +TODAY_FILE = join(expanduser("~"), ".timekeeping") -die () { - >&2 echo "Error: $*" - exit 1 -} - -from_ts () { - if ! date --version >/dev/null 2>&1; then # BSD - date -r "$1" '+%H:%M' - else # GNU - date -d "@$1" '+%H:%M' - fi -} - -pause_end () { - if [ "$ACTION" = "pause" ]; then - now="$(date '+%s')" - printf "%s," "$((now - START))" >> "$TIMEFILE" - >&2 printf "\nYou took a %d minutes pause\n" "$(( (now - START) / 60))" - fi -} - -trap pause_end INT - -test -z "$1" && usage -test -e "$TIMEFILE" || touch "$TIMEFILE" - -TODAY="$(date '+%Y-%m-%d')" -START="$(date '+%s')" -ACTION="$1" +is_on_break = False +today_fields = [] +start_time = datetime.now() -case "$1" in - start) - grep -qE "^$TODAY" "$TIMEFILE" && die "you already started your day" - printf "%s,%s," "$TODAY" "$(date '+%s')" >> "$TIMEFILE" - >&2 printf "Started work at %s\n" "$(date '+%H:%M')";; - pause) - grep -qE "^$TODAY" "$TIMEFILE" || die "You haven't even started your day!" - awk -F, "/^$TODAY/"'{if (NF > 4) exit 1}' "$TIMEFILE" || die "You're already done for the day" - awk -F, "/^$TODAY/"'{if (NF > 2) exit 1}' "$TIMEFILE" || die "You've already expanded your daily break allowance" - >&2 echo "Taking a break..." - sleep 9999999;; - end) - test -z "$2" && die "Tell me what you did today" - grep -qE "^$TODAY" "$TIMEFILE" || die "You haven't even started your day!" - start_hour="$(from_ts "$(tail -n1 "$TIMEFILE" | cut -f 2 -d ,)")" - end_hour="$(date '+%H:%M')" - shift 1 - msg="$(printf "%s" "$*" | sed 's/"/""/g')" - line="$(awk -F ',' -v now="$START" -v start="$start_hour" -v end="$end_hour" -v msg="$msg" \ - "/^$TODAY/"'{ - total=now-$2-$3; - h=int(total/3600); - m=int(total/60%60); - ph=int($3/3600); - pm=int($3/60%60); - printf "%s,%s,%s,%s:%s,%s:%s,\"%s\"",$1,start,end,ph,pm,h,m,msg; - }' \ - "$TIMEFILE")" - printf "\$d\nw\n\q" | ed "$TIMEFILE" > /dev/null 2>&1 - printf "%s\n" "$line" >> "$TIMEFILE";; -esac +def die(*args): + print("Error: {}".format(*args), file=sys.stderr) + sys.exit(1) + + +def log(*args): + print(*args, file=sys.stderr) + + +def signal_handler(signal, frame): + global today_fields + now = datetime.now() + hour = now.strftime("%H:%M") + if not is_on_break: + sys.exit(0) + else: + break_time = now - start_time + log("\nBreak ended at {} and lasted {}".format( + hour, td_format(break_time))) + today_fields.append(start_time.strftime("%H:%M")) + today_fields.append(hour) + with open(TODAY_FILE, "w") as f: + f.write(",".join(today_fields)) + sys.exit(0) + + +def td_format(td): + hours, remainder = divmod(td.total_seconds(), 3600) + minutes, seconds = divmod(remainder, 60) + + return '{:d}:{:02d}'.format(int(hours), int(minutes)) + + +def convert_manual_entry(): + for line in sys.stdin: + fields = line.split(",") + if len(fields) < 6: + break + morning = datetime.strptime(fields[1], "%H:%M") + break_start = datetime.strptime(fields[2], "%H:%M") + break_end = datetime.strptime(fields[3], "%H:%M") + evening = datetime.strptime(fields[4], "%H:%M") + break_time = break_end - break_start + full_day = evening - morning + work_day = full_day - break_time + print('{},{},{},{},{},{}'.format( + fields[0], + morning.strftime("%H:%M"), + evening.strftime("%H:%M"), + td_format(break_time), + td_format(work_day), + "".join(fields[5:]).strip())) + + +def get_today_fields(): + try: + with open(TODAY_FILE, "r") as f: + for line in f: + if line.startswith(start_time.strftime("%Y-%m-%d")): + return line.strip().split(",") + except FileNotFoundError: + return [] + return [] + + +def work(args): + fields = get_today_fields() + if len(fields) < 2: + die("you haven't started working yet today") + print("stats: {}".format(fields)) + + +def work_start(args): + fields = get_today_fields() + hour = start_time.strftime("%H:%M") + if fields: + die("You already started working") + with open(TODAY_FILE, "w") as f: + f.write("{},{}".format( + start_time.strftime("%Y-%m-%d"), hour)) + log("Started working at {}".format(hour)) + + +def work_pause(args): + global is_on_break + global today_fields + is_on_break = True + today_fields = get_today_fields() + hour = start_time.strftime("%H:%M") + if not today_fields: + die("no work to take a break from") + log("Taking a break at {}".format(hour)) + # Wait to be stopped by a Ctrl-C + sleep(999999) + + +def work_end(args): + fields = get_today_fields() + hour = start_time.strftime("%H:%M") + if not fields: + die("Why try to leave when you haven't even started") + fields.append(hour) + # Test for even number of timestamp (but fields[0] is the date) + if len(fields) < 3: + die("not enough fields in {}".format(TODAY_FILE)) + elif len(fields) % 2 == 0: + die("odd number of timestamps in {}".format(TODAY_FILE)) + begin_time = None + worked_time = timedelta() + for field in fields[1:]: + try: + if begin_time is None: + begin_time = datetime.strptime(field, "%H:%M") + else: + end_time = datetime.strptime(field, "%H:%M") + worked_time += end_time - begin_time + begin_time = None + except ValueError: + die("couldn't parse field '{}' in {}".format( + field, TODAY_FILE)) + day_start = datetime.strptime(fields[1], "%H:%M") + day_end = datetime.strptime(fields[-1], "%H:%M") + total_time = day_end - day_start + break_time = total_time - worked_time + with open(TIME_FILE, "a") as f: + f.write("{},{},{},{},{},{}\n".format( + fields[0], + day_start.strftime("%H:%M"), + start_time.strftime("%H:%M"), + td_format(break_time), + td_format(worked_time), + args.description)) + # Erase TODAY_FILE + with open(TODAY_FILE, "w") as f: + f.write("") + f.flush() + log("Finished working at {} after working {}".format( + hour, td_format(worked_time))) + + +def work_export(args): + print("export") + + +def work_parse(args): + print("parse") + + +if __name__ == "__main__": + # Handle Ctrl-C + signal(SIGINT, signal_handler) + + parser = argparse.ArgumentParser() + parser.set_defaults(func=work) + commands = parser.add_subparsers(dest="command") + + start_parser = commands.add_parser("start") + pause_parser = commands.add_parser("pause") + end_parser = commands.add_parser("end") + export_parser = commands.add_parser("export") + parse_parser = commands.add_parser("parse") + + start_parser.set_defaults(func=work_start) + pause_parser.set_defaults(func=work_pause) + end_parser.add_argument("description") + end_parser.set_defaults(func=work_end) + export_parser.set_defaults(func=work_export) + parse_parser.set_defaults(func=work_parse) + + args = parser.parse_args() + args.func(args) + + #now = datetime.now() + #with open(TIME_FILE, "r") as f: + # for line in f: + # print(line.strip()) diff --git a/bin/work.old b/bin/work.old new file mode 100755 index 0000000..d988337 --- /dev/null +++ b/bin/work.old @@ -0,0 +1,71 @@ +#!/bin/sh + +TIMEFILE="$HOME/timekeeping.csv" + +usage () { + >&2 printf "usage:\n\t%s " "$(basename "$0")" + exit 0 +} + +die () { + >&2 echo "Error: $*" + exit 1 +} + +from_ts () { + if ! date --version >/dev/null 2>&1; then # BSD + date -r "$1" '+%H:%M' + else # GNU + date -d "@$1" '+%H:%M' + fi +} + +pause_end () { + if [ "$ACTION" = "pause" ]; then + now="$(date '+%s')" + printf "%s," "$((now - START))" >> "$TIMEFILE" + >&2 printf "\nYou took a %d minutes pause\n" "$(( (now - START) / 60))" + fi +} + +trap pause_end INT + +test -z "$1" && usage +test -e "$TIMEFILE" || touch "$TIMEFILE" + +TODAY="$(date '+%Y-%m-%d')" +START="$(date '+%s')" +ACTION="$1" + + +case "$1" in + start) + grep -qE "^$TODAY" "$TIMEFILE" && die "you already started your day" + printf "%s,%s," "$TODAY" "$(date '+%s')" >> "$TIMEFILE" + >&2 printf "Started work at %s\n" "$(date '+%H:%M')";; + pause) + grep -qE "^$TODAY" "$TIMEFILE" || die "You haven't even started your day!" + awk -F, "/^$TODAY/"'{if (NF > 4) exit 1}' "$TIMEFILE" || die "You're already done for the day" + awk -F, "/^$TODAY/"'{if (NF > 2) exit 1}' "$TIMEFILE" || die "You've already expanded your daily break allowance" + >&2 echo "Taking a break..." + sleep 9999999;; + end) + test -z "$2" && die "Tell me what you did today" + grep -qE "^$TODAY" "$TIMEFILE" || die "You haven't even started your day!" + start_hour="$(from_ts "$(tail -n1 "$TIMEFILE" | cut -f 2 -d ,)")" + end_hour="$(date '+%H:%M')" + shift 1 + msg="$(printf "%s" "$*" | sed 's/"/""/g')" + line="$(awk -F ',' -v now="$START" -v start="$start_hour" -v end="$end_hour" -v msg="$msg" \ + "/^$TODAY/"'{ + total=now-$2-$3; + h=int(total/3600); + m=int(total/60%60); + ph=int($3/3600); + pm=int($3/60%60); + printf "%s,%s,%s,%s:%s,%s:%s,\"%s\"",$1,start,end,ph,pm,h,m,msg; + }' \ + "$TIMEFILE")" + printf "\$d\nw\n\q" | ed "$TIMEFILE" > /dev/null 2>&1 + printf "%s\n" "$line" >> "$TIMEFILE";; +esac