Simple Time Tracker

This commit is contained in:
Simon Lieb 2019-10-02 19:01:25 +02:00
commit 53948fa278
8 changed files with 555 additions and 0 deletions

20
LICENSE Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
© 2016 Simon Lieb <pips@e5150.fr>
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.

49
Makefile Normal file
View file

@ -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

17
README.md Normal file
View file

@ -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

5
TODO.md Normal file
View file

@ -0,0 +1,5 @@
* play with <sys/tree.h> instead of home made tree funcs
* add argument to list previous/arbitraty days
* easier past task management than `date -d <date> +%s && vim ~/.ttime`
* add some more complex task description (i.e: task@project)
* use task@project to group task in reports

48
arg.h Normal file
View file

@ -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

19
config.mk Normal file
View file

@ -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

35
stt.1 Normal file
View file

@ -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.

362
stt.c Normal file
View file

@ -0,0 +1,362 @@
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <pwd.h>
#include <time.h>
#include <string.h>
#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;
}