X-Git-Url: https://git.jankratochvil.net/?p=timeplan.git;a=blobdiff_plain;f=timeplan.c;h=ebf202dd238538341c752d9364a8243c7bd17deb;hp=e48910c14446a7c81846ddaee34cb3c22e70678e;hb=refs%2Ftags%2Frel1_0_1;hpb=1b355f73a1315b7858a13058a01bbb397da53029 diff --git a/timeplan.c b/timeplan.c index e48910c..ebf202d 100644 --- a/timeplan.c +++ b/timeplan.c @@ -10,7 +10,7 @@ #include #define ACTS_MAX 20 -#define HASH_SIZE 211 +#define HASH_SIZE 971 #define CONFIGFILE "/.timeplanrc" /* HOME prepended */ #define DEF_TIMEPLAN "/.timeplan" /* HOME prepended */ #define MARK_DAY "_days" @@ -25,15 +25,17 @@ #define ERRNO2 strerror(errno) #define FAKEUSE =0 -static const char version[]="This is TimePlan, version 1.0\n"; +static const char version[]="This is TimePlan, version 1.0.1\n"; static const char *pname; static char *finame; static FILE *fi; -static int verbose=0,tree=0; +static int verbose=0,tree=0,doaverage=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 unsigned lifetime_days=0; +static unsigned long lifetime_seq=1; static void usage(void) { @@ -50,6 +52,7 @@ Usage: timeplan [-u|--unsort] [-s|--stores] [-T|--timetot] [-t|--tree]\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\ + -a, --average\t\tDisplay all times as average-per-day value\n\ -c, --condition\tDefine condition variable\n\ -f, --formtotal\tFormat \"Total\" column (\"text *val/val:width text\")\n\ -r, --rule\t\tAdd to the end of .rc file this rule (':'->'\\t')\n\ @@ -65,6 +68,7 @@ static const struct option longopts[]={ {"stores" ,0,0,'s'}, {"timetot" ,0,0,'T'}, {"tree" ,0,0,'t'}, +{"average" ,0,0,'a'}, {"condition",1,0,'c'}, {"formtotal",1,0,'f'}, {"rule" ,1,0,'r'}, @@ -81,22 +85,25 @@ static int calctime(int timetot,int at,int of) static struct action { struct action *next; int timetot,stores; + unsigned long last_seq; 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; +int tot,mins FAKEUSE,hours FAKEUSE,days FAKEUSE,origtot; char *s,fmt[]="%?d"; if (action) { tot=(action->timetot+30)/60; + if (doaverage && lifetime_days) + tot/=lifetime_days; mins=tot; hours=mins/60; days=hours/24; mins%=60; hours%=24; - origtot=tot; } - else origtot=0; + else tot=0; + origtot=tot; for (s=formtotal;*s;s++) switch (*s) { case '*': case '/': { @@ -131,9 +138,9 @@ dump: else origtot++; } if (action) - printf("=%02d/%02d:%02d\t%4d\t%s\n",days,hours,mins, + printf("=%03d/%02d:%02d %4d\t%s\n",days,hours,mins, action->stores,action->what); - return(origtot); + return origtot; } #define A (*Ap) @@ -157,7 +164,7 @@ struct action **sorta FAKEUSE,**sorti FAKEUSE; int totalwidth=dumpaction(NULL); while (totalwidth-->5) putchar(' '); - puts("Total Da Hr Mi\tStor\tDescription"); + puts("Total Day Hr Mi Stor\tDescription"); if (!(sorta=malloc(sizeof(*sorta)*hashtable_tot))) { fprintf(ERRH1"malloc() of %d pointers"ERRNO1,ERRH2,hashtable_tot,ERRNO2); exit(EXIT_FAILURE); @@ -184,6 +191,7 @@ static unsigned calchash(const char *s) unsigned r=57; while (*s) r=r*7+11*toupper(*s++); + r=r%HASH_SIZE; return r; } @@ -191,8 +199,10 @@ static void storeone(char *what,int length) { struct action **actionp,*action; + if (!*what) + return; if (verbose) printf("storeone: %d: %s\n",length,what); - for (actionp=hashtable+(calchash(what)%HASH_SIZE);(action=*actionp);actionp=&action->next) + for (actionp=hashtable+calchash(what);(action=*actionp);actionp=&action->next) if (!strcasecmp(action->what,what)) break; if (!action) { if (!(action=malloc(sizeof(*action)+strlen(what)))) { @@ -206,6 +216,13 @@ struct action **actionp,*action; *actionp=action; hashtable_tot++; } + else { + if (action->last_seq==lifetime_seq) { + if (verbose) puts("storeone: preventing duplicate"); + return; /* prevent duplicates like: TV-Music --> TV-Fun-Music-Fun */ + } + } + action->last_seq=lifetime_seq; action->timetot+=length; action->stores++; } @@ -222,8 +239,8 @@ 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); + if (!strcasecmp(text,cond->text)) return 1; + return 0; } static void addlist(struct textlist ***tail,const char *text,int line) @@ -342,6 +359,7 @@ static char *modify(char *what) { regmatch_t matches[10]; int i,m; +int doprep,willprep; static char modbuf1[sizeof(buf)],modbuf2[sizeof(modbuf1)]; char *src=what,*dstbase=modbuf1,*dst=dstbase; const char *start FAKEUSE,*end FAKEUSE,*patt; @@ -349,117 +367,207 @@ const struct textlist *item; if (verbose) printf("modify: %s\n",what); modify_load(); - for (m=0,item=modifies;item;m++,item=item->next) { + for (m=0,item=modifies,willprep=1;;m++,item=item->next) { enum { PATT_START,PATT_MID,PATT_END,PATT_TERM } pattpos; - 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) printf("matched: %s -> %s\n",item->text,modistructs[m].dst); - pattpos=PATT_START; patt=NULL; - while (pattpos!=PATT_TERM) { - switch (pattpos) { - case PATT_START: - start=src; - end=src+matches->rm_so; - pattpos=PATT_MID; patt=modistructs[m].dst; - break; - case PATT_MID: - if (!*patt) { - pattpos=PATT_END; patt=NULL; - continue; - } - else if (*patt=='@') { - start=src; end=src+strlen(src); + for (doprep=willprep;doprep>=0;doprep--) { + if (doprep) { + *dst++='-'; + for (patt=src;;patt++) { + if (*patt=='-' || !*patt) { + if (dst[-1]!='-') + *dst++='-'; + if (!*patt) + break; } - 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 + *dst++=*patt; + } + } + else { + if (!item) + return src; + i=regexec(&modistructs[m].regex,src,LENGTH(matches),matches,0); + if (i==REG_NOMATCH) { + willprep=0; + break; + } + if (i) { + fprintf(ERRH1"regexec() failed for \"%s\""ERRNO1,ERRH2,item->text,ERRNO2); + exit(EXIT_FAILURE); + } + willprep=1; + if (verbose) printf("matched: %s -> %s\n",item->text,modistructs[m].dst); + pattpos=PATT_START; patt=NULL; + while (pattpos!=PATT_TERM) { + switch (pattpos) { + case PATT_START: + start=src; + end=src+matches->rm_so; + pattpos=PATT_MID; patt=modistructs[m].dst; + break; + case PATT_MID: + if (!*patt) { + pattpos=PATT_END; patt=NULL; + continue; + } + else 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; + } + patt++; + break; + case PATT_END: + start=src+matches->rm_eo; + end=src+strlen(src); + pattpos=PATT_TERM; /* assumed: patt=NULL; */ + break; + default: + assert(0); } - else { - start=patt; end=patt+1; + if ((dst-dstbase+(end-start))>=sizeof(modbuf1)-1/* -1 for '-' during prepping */) { + fprintf(ERRH1"Maximum buffer size exceeded during substition for \"%s\""WHERE1, + ERRH2,item->text,WHERE2); + exit(EXIT_FAILURE); } - patt++; - break; - case PATT_END: - start=src+matches->rm_eo; - end=src+strlen(src); - pattpos=PATT_TERM; /* assumed: patt=NULL; */ - break; - default: - assert(0); + memcpy(dst,start,end-start); + dst+=end-start; + } } - 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); +/* SWAP buffers: */ + if (dst==dstbase || (dst==dstbase+1 && *dstbase=='-')) + return NULL; + *dst='\0'; + if (src==what) { + assert(dstbase==modbuf1); + src=dstbase; + dst=dstbase=modbuf2; } - 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; + else { + char *swap; + assert((src==modbuf1 && dstbase==modbuf2) + ||(src==modbuf2 && dstbase==modbuf1)); + swap=src; src=dstbase; dst=dstbase=swap; + } + } /* for (doprep) */ + } + /* NOTREACHED */ +} + +static struct modifycache { + struct modifycache *next; + char src[1]; /* dst[1] follows after '\0' */ + } *mcachetable[HASH_SIZE]; +static unsigned long modifycache_hits; + +static char *modify_cached(char *what) +{ +struct modifycache **mcachep,*mcache; +char *dst; +size_t whatl; + + if (!*what) + return(NULL); + whatl=strlen(what); + if (verbose) printf("modify_cached: %s\n",what); + for (mcachep=mcachetable+calchash(what);(mcache=*mcachep);mcachep=&mcache->next) + if (!strcasecmp(mcache->src,what)) break; + if (!mcache) { + dst=modify(what); + if (!(mcache=malloc(sizeof(*mcache)+whatl+1+(dst?strlen(dst):0)))) { + fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,what,ERRNO2); + exit(EXIT_FAILURE); } + mcache->next=NULL; + memcpy(mcache->src,what,whatl+1); + if (dst) + strcpy(mcache->src+whatl+1,dst); + else + mcache->src[whatl+1]='\0'; /* dst will be empty */ + *mcachep=mcache; + } + else { + if (verbose) printf("cache hit.\n"); + /* if (verbose) would be performance hit */ + modifycache_hits++; + } + if (!mcache->src[whatl+1]) + return(NULL); + return(mcache->src+whatl+1); +} + +static void modify_cached_stats(void) +{ +struct modifycache **mcachep,*mcache; +unsigned long depth,maxdepth=0,entries_total=0; +#define MODIFYCACHE_CALLS (modifycache_hits+entries_total) + + for (mcachep=mcachetable;mcachepnext) + depth++; + if (depth>maxdepth) + maxdepth=depth; + entries_total+=depth; } - return src; + printf("modify_cached cache stats: hits=%u.%02u%% (%lu/%lu), HASH_SIZE=%d, maxdepth=%lu\n", + (unsigned)( 100*modifycache_hits/MODIFYCACHE_CALLS ), + (unsigned)((10000*modifycache_hits/MODIFYCACHE_CALLS)%100), + modifycache_hits,MODIFYCACHE_CALLS, + HASH_SIZE,maxdepth); +#undef MODIFYCACHE_CALLS } static void store(char *what,int length) { -char *ce; +char ce_trash,*ce,*ceo=&ce_trash; - if (!(what=modify(what))) { + if (!(what=modify_cached(what))) { if (verbose) puts("discarded."); return; } if (verbose) printf("store: %d: %s\n",length,what); - while ((ce=(tree?strrchr:strchr)(what,'-'))) { + while ((ce=(tree?strrchr(what,'-'):strchr(what,'-')))) { if (!tree) *ce='\0'; storeone(what,length); - if (!tree) what=ce+1; - else *ce='\0'; + if (!tree) { *ce='-'; what=ce+1; } + else { *ceo='-'; *(ceo=ce)='\0'; } } storeone(what,length); + if (tree) *ceo='-'; + lifetime_seq++; } -static void hit(time_t t) +static void hit(time_t t,char *bufaction) { 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 (verbose) printf("hit: %ld: %s\n",t,bufaction); if (last!=-1) { if (t6 || tm.tm_mday<1 || tm.tm_mday>31 - || tm.tm_mon <1 || tm.tm_mon >12 + || tm.tm_mon <0 || tm.tm_mon >11 || tm.tm_year<80 || tm.tm_year>150 ) { fprintf(ERRH1"Incorrect timestamp \"%s\""WHERE1,ERRH2,buf,WHERE2); @@ -596,13 +707,17 @@ const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"}; #undef WANTED basetime=t; store(MARK_DAY,0); + lifetime_days++; 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 + if (!(isdigit(buf[0]) && isdigit(buf[1]) && buf[2]==':' && isdigit(buf[3]) && isdigit(buf[4]) + && (buf[5]=='-' || (buf[5]==':' && isdigit(buf[6]) && isdigit(buf[7]) && buf[8]=='-')) + ) + || (sec=0,sscanf(buf,(buf[5]=='-'?"%d:%d-":"%d:%d:%d-"),&hour,&min,&sec)!=2+(buf[5]==':')) || hour<0 || hour>23 || min <0 || min >59 + || sec <0 || sec >59 ) { fprintf(ERRH1"Incorrect day-time \"%s\""WHERE1,ERRH2,buf,WHERE2); exit(EXIT_FAILURE); @@ -611,8 +726,8 @@ const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"}; fprintf(ERRH1"Day-time found but no basetime timestamp set"WHERE1,ERRH2,WHERE2); exit(EXIT_FAILURE); } - currtime=basetime+(hour*60+min)*60; - hit(currtime); + currtime=basetime+(hour*60+min)*60+sec; + hit(currtime,buf+(buf[5]=='-'?6:9)); } if (!feof(fi) || ferror(fi)) { @@ -622,6 +737,8 @@ const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"}; if (fi!=stdin && fclose(fi)) fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2); + if (verbose) + modify_cached_stats(); dumphashtable(); - return(EXIT_SUCCESS); + return EXIT_SUCCESS; }