commit 53948fa278f756d9ed51983ea365b8672e12faa2 Author: Simon Lieb Date: Wed Oct 2 19:01:25 2019 +0200 Simple Time Tracker 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; +}