From: short <> Date: Sun, 16 Jul 2000 00:23:15 +0000 (+0000) Subject: First import, working well, no averaging implemented yet. X-Git-Tag: init X-Git-Url: http://git.jankratochvil.net/?p=timeplan.git;a=commitdiff_plain;h=6c24f75178d70ec0619444aad85c409af4273f58 First import, working well, no averaging implemented yet. --- 6c24f75178d70ec0619444aad85c409af4273f58 diff --git a/Makefile b/Makefile new file mode 100644 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 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+:\ !@ +R MT+:^[^!] +R MT+:^!(.*)$ 1 + +R nonfree+:-free$ diff --git a/timeplan.c b/timeplan.c new file mode 100644 index 0000000..6c0979d --- /dev/null +++ b/timeplan.c @@ -0,0 +1,584 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 ] [-f|--formtotal ]\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;itemnext) + 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;sortinext) + 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;mnext) { +#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=LENGTH(acts)) { + fprintf(ERRH1"Too many '+'-delimited actions (%d)"WHERE1,ERRH2,acti,WHERE2); + exit(EXIT_FAILURE); + } + } + timetot=t-last; + for (i=0;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); +}