From 53948fa278f756d9ed51983ea365b8672e12faa2 Mon Sep 17 00:00:00 2001 From: Simon Lieb Date: Wed, 2 Oct 2019 19:01:25 +0200 Subject: [PATCH] Simple Time Tracker --- LICENSE | 20 +++ Makefile | 49 ++++++++ README.md | 17 +++ TODO.md | 5 + arg.h | 48 ++++++++ config.mk | 19 +++ stt.1 | 35 ++++++ stt.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 555 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 TODO.md create mode 100644 arg.h create mode 100644 config.mk create mode 100644 stt.1 create mode 100644 stt.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..08d4963 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +© 2016 Simon Lieb + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..38c6b7f --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +# stt - simple time tracker +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = stt.c +OBJ = ${SRC:.c=.o} + +all: options stt + +options: + @echo stt build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.mk + +stt: ${OBJ} + @echo CC -o $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + @echo cleaning + @rm -f stt ${OBJ} stt-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p stt-${VERSION} + @cp -R LICENSE Makefile config.mk ${SRC} stt.1 arg.h README.md stt-${VERSION} + @tar -cf stt-${VERSION}.tar stt-${VERSION} + @gzip stt-${VERSION}.tar + @rm -rf stt-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f stt ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/stt + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/stt + +.PHONY: all options clean dist install uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a7cbc2 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +stt +==== + +Simple Time Tracker + +Install +------- + + # make clean install + +Usage +----- + + $ stt -a task # start task + $ stt -s # stop task + $ stt [-l] # print report + diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..570b8b4 --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +* play with instead of home made tree funcs +* add argument to list previous/arbitraty days +* easier past task management than `date -d +%s && vim ~/.ttime` +* add some more complex task description (i.e: task@project) +* use task@project to group task in reports diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..ba3fb3f --- /dev/null +++ b/arg.h @@ -0,0 +1,48 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..ffa35d2 --- /dev/null +++ b/config.mk @@ -0,0 +1,19 @@ +# stt version +VERSION = 0.1 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local + +# includes and libs +INCS = +LIBS = + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +LDFLAGS = -static -s ${LIBS} + +# compiler and linker +CC = cc diff --git a/stt.1 b/stt.1 new file mode 100644 index 0000000..7d70d0d --- /dev/null +++ b/stt.1 @@ -0,0 +1,35 @@ +.TH stt 1 stt\-VERSION +.SH NAME +stt \- Simple time tracker +.SH SYNOPSIS +.B stt +.RB [ \-v ] +.RB [ \-h ] +.RB [ \-a +.IR task " |" +.RB \-s " |" +.RB \-l ] +.SH DESCRIPTION +.B stt +keeps track of tasks during a work day. +.PP +Without operand, day report is printed, as with +.B -l +option. +.SH OPTIONS +.TP +.BI \-a " task" +Registers given task as active. Will also stop previous active task. +.TP +.B \-s +Stops current active task. +.TP +.B \-l +Prints day report, for each tasks name, start time, end time or state if +still running and duration (in decimal hours) is displayed. +.TP +.B \-h +Prints synopsis help. +.TP +.B \-v +Prints version information. diff --git a/stt.c b/stt.c new file mode 100644 index 0000000..0cd1a84 --- /dev/null +++ b/stt.c @@ -0,0 +1,362 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" + +#ifndef VERSION +#define VERSION "dev" +#endif + +char *argv0; + +static char *timesfile = ".ttimes"; + +struct timesnode { + char *task; + time_t starttime; + time_t endtime; + struct timesnode *left; + struct timesnode *right; + signed int height; +}; + +/* functions declarations */ +static void usage(void); + +FILE *opentimesfile(); + +struct timesnode *loadtimes(FILE *, struct timesnode *); +void writetimes(FILE *, struct timesnode *); +struct timesnode *timesnode_add(struct timesnode *, const char *, time_t, time_t); +struct timesnode *timesnode_stop(struct timesnode *, time_t); +int timesnode_max(int, int); +int timesnode_heigh(struct timesnode *); +int timesnode_getbalance(struct timesnode *); +struct timesnode *timesnode_rightrotate(struct timesnode *); +struct timesnode *timesnode_leftrotate(struct timesnode *); +void timesnode_print(struct timesnode *, time_t); + +void +usage() +{ + fprintf(stderr, "usage: %s [-v] [-h] [-a task | -s | -l]\n", argv0); + exit(1); +} + +FILE * +opentimesfile() +{ + FILE *fp; + + fp = fopen(timesfile, "r+"); + + if (fp == NULL) { + //int oldcwd; + struct passwd *passwd; + + //oldcwd = dirfd(opendir(".")); + + passwd = getpwuid(getuid()); + chdir(passwd->pw_dir); + + fp = fopen(timesfile, "a+"); + + if (fp == NULL) { + perror("fopen failed"); + } + //fchdir(oldcwd); + } + if (fp == NULL) { + fprintf(stderr, "%s: Error opening times file\n", argv0); + exit(1); + } + return fp; +} + +struct timesnode * +loadtimes(FILE * fp, struct timesnode * p) +{ + char *readline, *line, *tmp; + time_t starttime, endtime; + char *task; + size_t linesize; + ssize_t linelen; + + rewind(fp); + + line = readline = NULL; + linesize = 0; + while ((linelen = getline(&readline, &linesize, fp)) != -1) { + line = strdup(readline); + tmp = strsep(&line, ";"); + starttime = atoi(tmp); + + if (starttime == 0) { + continue; + } + tmp = strsep(&line, ";"); + endtime = atoi(tmp); + + task = strdup(line); + task[strlen(task) - 1] = '\0'; + + p = timesnode_add(p, task, starttime, endtime); + } + + return p; +} + +void +writetimes(FILE * fp, struct timesnode * p) +{ + if (p == NULL) { + return; + } + if (p->left != NULL) { + writetimes(fp, p->left); + } + fprintf(fp, "%lld;%lld;%s\n", p->starttime, p->endtime, p->task); + + if (p->right != NULL) { + writetimes(fp, p->right); + } + return; +} + +int +timesnode_max(int a, int b) +{ + return (a > b) ? a : b; +} + +int +timesnode_heigh(struct timesnode * p) +{ + if (p == NULL) { + return 0; + } + return p->height; +} + +int +timesnode_getbalance(struct timesnode * p) +{ + if (p == NULL) { + return 0; + } + return (timesnode_heigh(p->left) + (p->left != NULL)) - (timesnode_heigh(p->right) + (p->right != NULL)); +} + +struct timesnode * +timesnode_rightrotate(struct timesnode * p) +{ + struct timesnode *leftchild; + struct timesnode *subrightchild; + + leftchild = p->left; + subrightchild = leftchild->right; + + leftchild->right = p; + p->left = subrightchild; + + p->height = (p->left != NULL || p->right != NULL) ? 1 + timesnode_max(timesnode_heigh(p->left), timesnode_heigh(p->right)) : 0; + leftchild->height = 1 + timesnode_max(timesnode_heigh(leftchild->left), timesnode_heigh(leftchild->right)); + + return leftchild; +} + +struct timesnode * +timesnode_leftrotate(struct timesnode * p) +{ + struct timesnode *rightchild; + struct timesnode *subleftchild; + + rightchild = p->right; + subleftchild = rightchild->left; + + rightchild->left = p; + p->right = subleftchild; + + p->height = (p->left != NULL || p->right != NULL) ? 1 + timesnode_max(timesnode_heigh(p->left), timesnode_heigh(p->right)) : 0; + rightchild->height = 1 + timesnode_max(timesnode_heigh(rightchild->left), timesnode_heigh(rightchild->right)); + + return rightchild; +} + +struct timesnode * +timesnode_add(struct timesnode * p, const char *task, time_t starttime, time_t endtime) +{ + int balance; + + if (p == NULL) { + p = (struct timesnode *) malloc(sizeof(struct timesnode)); + + p->task = strdup(task); + p->starttime = starttime; + p->endtime = endtime; + p->left = p->right = NULL; + p->height = 0; + + return p; + } else if (p->starttime > starttime) { + p->left = timesnode_add(p->left, task, starttime, endtime); + } else { + p->right = timesnode_add(p->right, task, starttime, endtime); + } + + p->height = 1 + timesnode_max(timesnode_heigh(p->left), timesnode_heigh(p->right)); + + balance = timesnode_getbalance(p); + + if (balance > 1 && starttime < p->left->starttime) { + return timesnode_rightrotate(p); + } + if (balance < -1 && starttime > p->right->starttime) { + return timesnode_leftrotate(p); + } + if (balance > 1 && starttime > p->left->starttime) { + p->left = timesnode_leftrotate(p->left); + return timesnode_rightrotate(p); + } + if (balance < -1 && starttime < p->right->starttime) { + p->right = timesnode_rightrotate(p->right); + return timesnode_leftrotate(p); + } + return p; +} + +struct timesnode * +timesnode_stop(struct timesnode * p, time_t endtime) +{ + if (p == NULL) { + return NULL; + } + if (p->endtime == 0) { + p->endtime = endtime; + } + if (p->left != NULL) { + timesnode_stop(p->left, endtime); + } + if (p->right != NULL) { + timesnode_stop(p->right, endtime); + } + return p; +} + +void +timesnode_print(struct timesnode * p, time_t aftertime) +{ + time_t *nowtime; + int duration = 0; + + if (p == NULL) { + return; + } + if (p->left != NULL) { + timesnode_print(p->left, aftertime); + } + if (p->starttime > aftertime || p->endtime > aftertime || p->endtime == 0) { + printf("task: %s\n", p->task); + printf("started at: %s", ctime(&p->starttime)); + + if (p->endtime != 0) { + printf("ended at: %s", ctime(&p->endtime)); + + duration = difftime(p->endtime, p->starttime); + + printf("duration(hours): %.2f\n\n", duration / 3600.0); + } else { + nowtime = malloc(sizeof(time_t)); + time(nowtime); + printf("still running\n"); + + duration = difftime(*nowtime, p->starttime); + + printf("duration(hours): %.2f\n\n", duration / 3600.0); + free(nowtime); + } + } + if (p->right != NULL) { + timesnode_print(p->right, aftertime); + } +} + +int +main(int argc, char *argv[]) +{ + unsigned int start, stop, list; + char *task; + FILE *fp; + struct timesnode *timestree; + time_t *nowtime; + int changed; + struct tm today; + + timestree = NULL; + task = NULL; + changed = start = stop = 0; + list = 1; + + nowtime = malloc(sizeof(time_t)); + + ARGBEGIN { +case 'a': /* start new task - stop current if any */ + start = 1; + stop = 1; + list = 0; + + task = EARGF(usage()); + break; +case 's': /* stop current task */ + start = 0; + stop = 1; + list = 0; + break; +case 'l': /* list task */ + start = 0; + stop = 0; + list = 1; + break; +case 'v': + fprintf(stderr, "%s-" VERSION " © 2016 Simon Lieb, see LICENSE for details\n", argv0); + return 1; + break; +case 'h': +default: + usage(); + } ARGEND; + + fp = opentimesfile(); + + time(nowtime); + timestree = loadtimes(fp, timestree); + + if (stop) { + timesnode_stop(timestree, *nowtime); + changed = 1; + } + if (task != NULL && start) { + timestree = timesnode_add(timestree, task, *nowtime, 0); + changed = 1; + } + if (changed) { + freopen(timesfile, "w", fp); + writetimes(fp, timestree); + } + if (list) { + localtime_r(nowtime, &today); + + today.tm_sec = 0; + today.tm_min = 0; + today.tm_hour = 0; + + timesnode_print(timestree, mktime(&today)); + } + return 0; +}