Simple Time Tracker
This commit is contained in:
commit
53948fa278
8 changed files with 555 additions and 0 deletions
20
LICENSE
Normal file
20
LICENSE
Normal 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
49
Makefile
Normal 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
17
README.md
Normal 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
5
TODO.md
Normal 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
48
arg.h
Normal 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
19
config.mk
Normal 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
35
stt.1
Normal 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
362
stt.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue