First import, working well, no averaging implemented yet. init
authorshort <>
Sun, 16 Jul 2000 00:23:15 +0000 (00:23 +0000)
committershort <>
Sun, 16 Jul 2000 00:23:15 +0000 (00:23 +0000)
Makefile [new file with mode: 0644]
rc [new file with mode: 0644]
timeplan.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..c217400
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
+CFLAGS=-O2 -ggdb -Wall -pedantic -ansi -D_GNU_SOURCE
+
+all: timeplan
+
+timeplan: timeplan.o
+
+timeplan.o: timeplan.c
+
+.PHONY: clean
+clean:
+       $(RM) timeplan *.o core
diff --git a/rc b/rc
new file mode 100644 (file)
index 0000000..88ef102
--- /dev/null
+++ b/rc
@@ -0,0 +1,16 @@
+R:^(.*[^ ]) *\(([^()]*)\)$     1-2
+R:^Wash$       Maint-@
+R:^Food$       Maint-@
+R:^Rest$       Sleep-@
+R:^Sleep\>     Maint-@
+R:-Talk$       @-free
+
+R pay+:^[^!].*-pay$    !@
+R pay+:^[^!]   
+R pay+:^!(.*)-pay$     1
+
+R MT+:\<MT\>   !@
+R MT+:^[^!]    
+R MT+:^!(.*)$  1
+
+R nonfree+:-free$      
diff --git a/timeplan.c b/timeplan.c
new file mode 100644 (file)
index 0000000..6c0979d
--- /dev/null
@@ -0,0 +1,584 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <limits.h>
+#include <ctype.h>
+#include <regex.h>
+
+#define ACTS_MAX 20
+#define HASH_SIZE 211
+#define CONFIGFILE "/.timeplanrc" /* HOME prepended */
+#define DEF_TIMEPLAN "/.timeplan" /* HOME prepended */
+#define MARK_DAY "_days"
+#define DEF_FORMTOTAL ":6"
+#define LENGTH(x) (sizeof((x))/sizeof(*(x)))
+#define WHERE1 " in \"%s\" on line %d!\n"
+#define WHERE2 finame,line
+#define NL "\r\n"
+#define ERRH1 stderr,"%s: "
+#define ERRH2 pname
+#define ERRNO1 ": %s!\n"
+#define ERRNO2 strerror(errno)
+#define FAKEUSE =0
+
+static const char version[]="This is TimePlan, version 1.0\n";
+static const char *pname;
+static char *finame;
+static FILE *fi;
+static int verbose=0,tree=0;
+static char *formtotal=DEF_FORMTOTAL;
+static char buf[LINE_MAX];
+static int line=0;
+static enum { SORT_NO,SORT_TIMETOT,SORT_STORES } sortby=SORT_TIMETOT;
+
+static void usage(void)
+{
+       fprintf(stderr,"\
+%s\
+This command summarizes the timelog information:\n\
+\n\
+Usage: timeplan [-u|--unsort] [-s|--stores] [-T|--timetot]  [-t|--tree]\n\
+                [-c|--condition <cond>] [-f|--formtotal <fmtstring>]\n\
+                [-v|--verbose] [-h|--help] [-V|--version]\n\
+\n\
+  -u, --unsort\t\tDon't sort the result in any way\n\
+  -s, --stores\t\tSort the result by stores count\n\
+  -T, --timetot\t\tSort the result by total time (default)\n\
+  -t, --tree\t\tOrganize data as hierarchy tree\n\
+  -c, --condition\tDefine condition variable\n\
+  -f, --formtotal\tFormat \"Total\" column (\"text *val/val:width text\")\n\
+  -v, --verbose\t\tInform about phases of transfer\n\
+  -h, --help\t\tPrint a summary of the options\n\
+  -V, --version\t\tPrint the version number\n\
+",version);
+       exit(EXIT_FAILURE);
+}
+
+static const struct option longopts[]={
+{"unsort"   ,0,0,'u'},
+{"stores"   ,0,0,'s'},
+{"timetot"  ,0,0,'T'},
+{"tree"     ,0,0,'t'},
+{"condition",1,0,'c'},
+{"formtotal",1,0,'f'},
+{"verbose"  ,0,0,'v'},
+{"help"     ,0,0,'h'},
+{"version"  ,0,0,'V'}};
+
+static int calctime(int timetot,int at,int of)
+{
+       /* FIXME: better distribution */
+       return timetot/of;
+}
+
+static struct action {
+       struct action *next;
+       int timetot,stores;
+       char what[1];
+       } *hashtable[HASH_SIZE];
+static int hashtable_tot=0;
+
+static int dumpaction(const struct action *action)
+{
+int tot FAKEUSE,mins FAKEUSE,hours FAKEUSE,days FAKEUSE,origtot;
+char *s,fmt[]="%?d";
+
+       if (action) {
+               tot=(action->timetot+30)/60;
+               mins=tot; hours=mins/60; days=hours/24;
+               mins%=60; hours%=24;
+               origtot=tot;
+               }
+       else origtot=0;
+       for (s=formtotal;*s;s++)
+               switch (*s) {
+                       case '*': case '/': {
+long l;
+char *end,*s2;
+
+                               if (s[1]==*s) { s++; goto dump; }
+                               for (s2=s+1;isdigit(*s2);s2++);
+                               l=strtol(s+1,&end,10);
+                               if (end!=s2) {
+                                       fprintf(ERRH1"Number parse error at column %d of formtotal string!\n",ERRH2,s-formtotal);
+                                       exit(EXIT_FAILURE);
+                                       }
+                               if (*s=='*') tot*=l;
+                               else         tot/=l;
+                               s=s2-1;
+                               break; }
+                       case ':':
+                               if (s[1]==*s) { s++; goto dump; }
+                               if (!isdigit(s[1])) goto dump;
+                               if (action) {
+                                       fmt[1]=s[1];
+                                       printf(fmt,tot);
+                                       tot=origtot;
+                                       }
+                               else origtot+=s[1]-'0';
+                               s+=1;
+                               break;
+                       default:
+dump:
+                               if (action) putchar(*s);
+                               else origtot++;
+                       }
+       if (action)
+               printf("=%02d/%02d:%02d\t%4d\t%s\n",days,hours,mins,
+                       action->stores,action->what);
+       return(origtot);
+}
+
+#define A (*Ap)
+#define B (*Bp)
+#define FUNC(which) \
+       static int sort_##which(const struct action **Ap,const struct action **Bp) \
+       { return (B->which>A->which)-(A->which>B->which); }
+FUNC(timetot)
+FUNC(stores)
+#undef FUNC
+#undef B
+#undef A
+
+static void dumphashtable(void)
+{
+int item;
+struct action *action;
+struct action **sorta FAKEUSE,**sorti FAKEUSE;
+
+       if (sortby!=SORT_NO) {
+int totalwidth=dumpaction(NULL);
+
+               while (totalwidth-->5) putchar(' ');
+               puts("Total Da Hr Mi\tStor\tDescription");
+               if (!(sorta=malloc(sizeof(*sorta)*hashtable_tot))) {
+                       fprintf(ERRH1"malloc() of %d pointers"ERRNO1,ERRH2,hashtable_tot,ERRNO2);
+                       exit(EXIT_FAILURE);
+                       }
+               sorti=sorta;
+               }
+       for (item=0;item<LENGTH(hashtable);item++)
+               for (action=hashtable[item];action;action=action->next)
+                       if (sortby==SORT_NO)
+                               dumpaction(action);
+                       else
+                               *sorti++=action;
+       if (sortby==SORT_NO) return;
+       assert(sorti==sorta+hashtable_tot);
+       qsort(sorta,hashtable_tot,sizeof(*sorta),
+               (int (*)(const void *,const void *))(sortby==SORT_TIMETOT ? sort_timetot : sort_stores));
+       for (sorti=sorta;sorti<sorta+hashtable_tot;sorti++)
+               dumpaction(*sorti);
+       free(sorta);
+}
+
+static unsigned calchash(const char *s)
+{
+unsigned r=57;
+
+       while (*s) r=r*7+11*(*s++);
+       return r;
+}
+
+static void storeone(char *what,int length)
+{
+struct action **actionp,*action;
+
+       if (verbose) printf("storeone: %d: %s\n",length,what);
+       for (actionp=hashtable+(calchash(what)%HASH_SIZE);(action=*actionp);actionp=&action->next)
+               if (!strcasecmp(action->what,what)) break;
+       if (!action) {
+               if (!(action=malloc(sizeof(*action)+strlen(what)))) {
+                       fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,what,ERRNO2);
+                       exit(EXIT_FAILURE);
+                       }
+               action->next=NULL;
+               action->timetot=0;
+               action->stores=0;
+               strcpy(action->what,what);
+               *actionp=action;
+               hashtable_tot++;
+               }
+       action->timetot+=length;
+       action->stores++;
+}
+
+struct textlist {
+       struct textlist *next;
+       int line;
+       char text[1];
+       };
+struct textlist *conditions,**conditionstail=&conditions;
+
+static int iscondition(const char *text)
+{
+struct textlist *cond;
+
+       for (cond=conditions;cond;cond=cond->next)
+               if (!strcasecmp(text,cond->text)) return(1);
+       return(0);
+}
+
+static void addlist(struct textlist ***tail,const char *text,int line)
+{
+struct textlist *item;
+
+       if (!(item=malloc(sizeof(*item)+strlen(text)))) {
+               fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,text,ERRNO2);
+               exit(EXIT_FAILURE);
+               }
+       strcpy(item->text,text);
+       item->next=NULL;
+       **tail=item;
+       *tail=&item->next;
+}
+
+static struct textlist *modifies,**modifiestail=&modifies;
+static int modifies_tot;
+static struct modistruct {
+       regex_t regex;
+       char *dst;
+       } *modistructs;
+
+static void modify_load(void)
+{
+static int inited=0;
+char *finame,*s,*s2;
+FILE *fi;
+int line=0,m;
+struct textlist *item;
+char buf[LINE_MAX];
+
+       if (inited) return;
+       inited=1;
+       if (asprintf(&finame,"%s"CONFIGFILE,getenv("HOME"))==-1) {
+               fprintf(ERRH1"Config filename allocation",ERRH2);
+               exit(EXIT_FAILURE);
+               }
+       errno=0;
+       if (!(fi=fopen(finame,"rt"))) {
+               if (errno!=ENOENT)
+                       fprintf(ERRH1"Config file \"%s\" read"ERRNO1,ERRH2,finame,ERRNO2);
+               return;
+               }
+
+       while (clearerr(fi),fgets(buf,sizeof(buf),fi)==buf) {
+               line++;
+               if ((s=strchr(buf,'\n')) && !s[1]) *s='\0';
+
+               else {
+                       fprintf(ERRH1"fgets(3) results not newline-terminated"WHERE1,ERRH2,WHERE2);
+                       exit(EXIT_FAILURE);
+                       }
+               if (!*buf || *buf=='#') {
+nextline:
+                       continue;
+                       }
+               if (*buf!='R') {
+                       fprintf(ERRH1"Unrecognized syntax"WHERE1,ERRH2,WHERE2);
+                       exit(EXIT_FAILURE);
+                       }
+               s=buf+1;
+               for (;;) {
+int isplus;
+
+                       while (isspace(*s)) s++;
+                       if (*s==':') break;
+                       if (!isalpha(*s)) {
+                               fprintf(ERRH1"Invalid character at offset %d"WHERE1,ERRH2,s-buf+1,WHERE2);
+                               exit(EXIT_FAILURE);
+                               }
+                       for (s2=s+1;isalpha(*s2);s2++);
+                       if (*s2!='+' && *s2!='-') {
+                               fprintf(ERRH1"Only plus ('+') or minus ('-'), not '%c' expected at offset %d"WHERE1,ERRH2,*s2,s2-buf+1,WHERE2);
+                               exit(EXIT_FAILURE);
+                               }
+                       isplus=(*s2=='+'); *s2++='\0';
+                       if (iscondition(s)!=isplus)
+                               goto nextline;
+                       s=s2;
+                       }
+               s++; /* Skip ':' */
+               addlist(&modifiestail,s,line);
+               modifies_tot++;
+               }
+       if (!feof(fi) || ferror(fi))  {
+               fprintf(ERRH1"fgets(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
+               exit(EXIT_FAILURE);
+               }
+       if (fclose(fi))
+               fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
+       if (!(modistructs=malloc(sizeof(*modistructs)*modifies_tot))) {
+               fprintf(ERRH1"malloc() of %d modistructs's"ERRNO1,ERRH2,modifies_tot,ERRNO2);
+               exit(EXIT_FAILURE);
+               }
+       for (m=0,item=modifies;m<modifies_tot;m++,item=item->next) {
+#define line (item->line)
+               if (!(s=strchr(item->text,'\t'))) {
+                       fprintf(ERRH1"No delimiting tab-character found"WHERE1,ERRH2,WHERE2);
+                       exit(EXIT_FAILURE);
+                       }
+               *s++='\0'; modistructs[m].dst=s;
+               if (verbose) fprintf(stderr,"regcomp: %s -> %s\n",item->text,s);
+               if (regcomp(&modistructs[m].regex,item->text,REG_EXTENDED|REG_ICASE)) {
+                       fprintf(ERRH1"regcomp() failed for \"%s\" (%s)"WHERE1,ERRH2,item->text,ERRNO2,WHERE2);
+                       exit(EXIT_FAILURE);
+                       }
+#undef line
+               }
+       assert(!item);
+       free(finame);
+}
+
+static char *modify(char *what)
+{
+regmatch_t matches[10];
+int i,m;
+static char modbuf1[sizeof(buf)],modbuf2[sizeof(modbuf1)];
+char *src=what,*dstbase=modbuf1,*dst=dstbase;
+const char *start,*end,*patt;
+const struct textlist *item;
+
+       if (verbose) printf("modify: %s\n",what);
+       modify_load();
+       for (m=0,item=modifies;item;m++,item=item->next) {
+               i=regexec(&modistructs[m].regex,src,LENGTH(matches),matches,0);
+               if (i==REG_NOMATCH) continue;
+               if (i) {
+                       fprintf(ERRH1"regexec() failed for \"%s\""ERRNO1,ERRH2,item->text,ERRNO2);
+                       exit(EXIT_FAILURE);
+                       }
+               if (verbose) fprintf(stderr,"matched: %s -> %s\n",item->text,modistructs[m].dst);
+               for (patt=modistructs[m].dst;*patt;patt++) {
+                       if (*patt=='@') {
+                               start=src; end=src+strlen(src);
+                               }
+                       else if (*patt>='0' && *patt<='9') {
+regmatch_t *match=matches+(*patt-'0');
+                               if (match->rm_so==-1
+                                || match->rm_eo==-1) {
+                                       fprintf(ERRH1"Trying to substitute '%c' but no \"matches\" entry not set for \"%s\""WHERE1,
+                                                       ERRH2,*patt,item->text,WHERE2);
+                                       exit(EXIT_FAILURE);
+                                       }
+                               if (match->rm_so>match->rm_eo) {
+                                       fprintf(ERRH1"Trying to substitute '%c' start>end (%d>%d) for \"%s\""WHERE1,
+                                                       ERRH2,*patt,match->rm_so,match->rm_eo,item->text,WHERE2);
+                                       exit(EXIT_FAILURE);
+                                       }
+                               start=src+match->rm_so; end=src+match->rm_eo;
+                               }
+                       else {
+                               start=patt; end=patt+1;
+                               }
+                       if ((dst-dstbase+(end-start))>=sizeof(modbuf1)) {
+                               fprintf(ERRH1"Maximum buffer size exceeded during substition for \"%s\""WHERE1,
+                                               ERRH2,item->text,WHERE2);
+                               exit(EXIT_FAILURE);
+                               }
+                       memcpy(dst,start,end-start);
+                       dst+=end-start;
+                       }
+               if (dst==dstbase) return(NULL);
+               *dst='\0';
+               if (src==what) {
+                       assert(dstbase==modbuf1);
+                       src=dstbase;
+                       dst=dstbase=modbuf2;
+                       }
+               else {
+char *swap;
+                       assert((src==modbuf1 && dstbase==modbuf2)
+                                        ||(src==modbuf2 && dstbase==modbuf1));
+                       swap=src; src=dstbase; dst=dstbase=swap;
+                       }
+               }
+       return src;
+}
+
+static void store(char *what,int length)
+{
+char *ce;
+
+       if (!(what=modify(what))) {
+               if (verbose) puts("discarded.");
+               return;
+               }
+       if (verbose) printf("store: %d: %s\n",length,what);
+       while ((ce=(tree?strrchr:strchr)(what,'-'))) {
+               if (!tree) *ce='\0';
+               storeone(what,length);
+               if (!tree) what=ce+1;
+               else *ce='\0';
+               }
+       storeone(what,length);
+}
+
+static void hit(time_t t)
+{
+static time_t last=-1;
+static char bufbackup[sizeof(buf)];
+char *acts[ACTS_MAX],*s;
+int timetot,acti,i;
+
+       if (verbose) printf("hit: %ld: %s\n",t,buf+6);
+       if (last!=-1) {
+               if (t<last) {
+                       fprintf(ERRH1"Time goes backward"WHERE1,ERRH2,WHERE2);
+                       t=last;
+                       }
+               acts[0]=bufbackup+6;
+               acti=1;
+               while ((s=strchr(acts[acti-1],'+'))) {
+                       *s='\0';
+                       acts[acti++]=s+1;
+                       if (acti>=LENGTH(acts)) {
+                               fprintf(ERRH1"Too many '+'-delimited actions (%d)"WHERE1,ERRH2,acti,WHERE2);
+                               exit(EXIT_FAILURE);
+                               }
+                       }
+               timetot=t-last;
+               for (i=0;i<acti;i++)
+                       store(acts[i],calctime(timetot,i,acti));
+               }
+       strcpy(bufbackup,buf);
+       last=t;
+}
+
+int main(int argc,char **argv)
+{
+int optc;
+time_t basetime=-1,currtime;
+int hour,min;
+
+       pname=argv[0];
+       while ((optc=getopt_long(argc,argv,"b:pw:qusTtc:f:vhV",longopts,NULL))!=EOF) switch (optc) {
+               
+               case 'u':
+                       sortby=SORT_NO;
+                       break;
+               case 's':
+                       sortby=SORT_STORES;
+                       break;
+               case 'T':
+                       sortby=SORT_TIMETOT;
+                       break;
+               case 't':
+                       tree=1;
+                       break;
+               case 'c':
+                       if (iscondition(optarg))
+                               fprintf(ERRH1"Condition \"%s\" already set!\n",ERRH2,optarg);
+                       else addlist(&conditionstail,optarg,-1);
+                       break;
+               case 'f':
+                       formtotal=optarg;
+                       break;
+               case 'v':
+                       verbose=1;
+                       break;
+               case 'V':
+                       fprintf(stderr,version);
+                       exit(EXIT_FAILURE);
+               default: /* also 'h' */
+                       usage();
+                       break;
+               }
+       if (optind==argc) {
+               if (asprintf(&finame,"%s"DEF_TIMEPLAN,getenv("HOME"))==-1) {
+                       fprintf(ERRH1"Default timeplan filename allocation",ERRH2);
+                       exit(EXIT_FAILURE);
+                       }
+               }
+       else if (optind+1!=argc) usage();
+       else if (!strcmp(argv[optind],"-")) {
+               finame="<stdin>";
+               fi=stdin;
+               }
+       else finame=argv[optind];
+       if (!fi && !(fi=fopen(finame,"r"))) {
+               fprintf(ERRH1"open \"%s\" for reading"ERRNO1,ERRH2,finame,ERRNO2);
+               exit(EXIT_FAILURE);
+               }
+
+       while (clearerr(fi),fgets(buf,sizeof(buf),fi)==buf) {
+char *s;
+
+               line++;
+               if ((s=strchr(buf,'\n')) && !s[1]) *s='\0';
+               else {
+                       fprintf(ERRH1"fgets(3) results not newline-terminated"WHERE1,ERRH2,WHERE2);
+                       exit(EXIT_FAILURE);
+                       }
+               if (!*buf) continue;
+               if (*buf==':') { /* ":12.7.2000 (St)" */
+struct tm tm;
+char wday[3];
+int i,parsed;
+time_t t;
+const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"};
+
+                       assert(LENGTH(days)==7);
+                       i=sscanf(buf,":%d.%d.%d (%2s)%n",&tm.tm_mday,&tm.tm_mon,&tm.tm_year,wday,&parsed);
+                       if ((i!=4 && i!=5) || parsed!=strlen(buf)) { /* See note in sscanf(3) man page */
+                               fprintf(ERRH1"Timestamp with incorrect format"WHERE1,ERRH2,WHERE2);
+                               exit(EXIT_FAILURE);
+                               }
+                       for (i=0;i<7;i++) if (!strcmp(days[i],wday)) break;
+                       if (i>=7)
+                               fprintf(ERRH1"Non-parsable week-day name \"%s\""WHERE1,ERRH2,wday,WHERE2);
+                       tm.tm_sec=tm.tm_min=tm.tm_hour=0;
+                       tm.tm_wday=tm.tm_yday=-1;
+                       tm.tm_isdst=0; /* FIXME */
+                       tm.tm_mon--;
+                       tm.tm_year-=1900;
+                       t=mktime(&tm);
+                       if (t==-1 || tm.tm_wday<0 || tm.tm_wday>6
+                           || tm.tm_mday<1 || tm.tm_mday>31
+                                       || tm.tm_mon <1 || tm.tm_mon >12
+                                       || tm.tm_year<80 || tm.tm_year>150
+                                 ) {
+                               fprintf(ERRH1"Incorrect timestamp \"%s\""WHERE1,ERRH2,buf,WHERE2);
+                               exit(EXIT_FAILURE);
+                               }
+                       if (i<7 && tm.tm_wday!=i)
+                               fprintf(ERRH1"Non-matching week-day, given \"%s\", calculated \"%s\""WHERE1,ERRH2,days[i],days[tm.tm_wday],WHERE2);
+#define WANTED (60*60*24)
+                       if (basetime!=-1 && basetime+WANTED!=t)
+                               fprintf(ERRH1"Non-continuous timestamp (%ld, wanted %d) \"%s\""WHERE1,ERRH2,t-basetime,WANTED,buf,WHERE2);
+#undef WANTED
+                       basetime=t;
+                       store(MARK_DAY,0);
+                       continue;
+                       }
+
+               if (!isdigit(buf[0]) || !isdigit(buf[1]) || buf[2]!=':' || !isdigit(buf[3]) || !isdigit(buf[4]) || buf[5]!='-'
+                || sscanf(buf,"%d:%d-",&hour,&min)!=2
+                || hour<0 || hour>23
+                || min <0 || min >59
+                   ) {
+                       fprintf(ERRH1"Incorrect day-time \"%s\""WHERE1,ERRH2,buf,WHERE2);
+                       exit(EXIT_FAILURE);
+                       }
+               if (basetime==-1) {
+                       fprintf(ERRH1"Day-time found but no basetime timestamp set"WHERE1,ERRH2,WHERE2);
+                       exit(EXIT_FAILURE);
+                       }
+               currtime=basetime+(hour*60+min)*60;
+               hit(currtime);
+               }
+
+       if (!feof(fi) || ferror(fi))  {
+               fprintf(ERRH1"fgets(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
+               exit(EXIT_FAILURE);
+               }
+       if (fi!=stdin && fclose(fi))
+               fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
+
+       dumphashtable();
+       return(EXIT_SUCCESS);
+}