Cosmetic: Ordering changed for better -O2 disable by hand
[timeplan.git] / timeplan.c
index f8fd739..357f9d0 100644 (file)
@@ -29,11 +29,13 @@ static const char version[]="This is TimePlan, version 1.0\n";
 static const char *pname;
 static char *finame;
 static FILE *fi;
 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 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)
 {
 
 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\
   -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\
   -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'},
 {"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'},
 {"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;
 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)
 {
        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;
 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;
                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 '/': {
        for (s=formtotal;*s;s++)
                switch (*s) {
                        case '*': case '/': {
@@ -131,9 +138,9 @@ dump:
                                else origtot++;
                        }
        if (action)
                                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);
                        action->stores,action->what);
-       return(origtot);
+       return origtot;
 }
 
 #define A (*Ap)
 }
 
 #define A (*Ap)
@@ -157,7 +164,7 @@ struct action **sorta FAKEUSE,**sorti FAKEUSE;
 int totalwidth=dumpaction(NULL);
 
                while (totalwidth-->5) putchar(' ');
 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);
                if (!(sorta=malloc(sizeof(*sorta)*hashtable_tot))) {
                        fprintf(ERRH1"malloc() of %d pointers"ERRNO1,ERRH2,hashtable_tot,ERRNO2);
                        exit(EXIT_FAILURE);
@@ -183,7 +190,7 @@ static unsigned calchash(const char *s)
 {
 unsigned r=57;
 
 {
 unsigned r=57;
 
-       while (*s) r=r*7+11*(*s++);
+       while (*s) r=r*7+11*toupper(*s++);
        return r;
 }
 
        return r;
 }
 
@@ -191,6 +198,8 @@ static void storeone(char *what,int length)
 {
 struct action **actionp,*action;
 
 {
 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)
                if (!strcasecmp(action->what,what)) break;
        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;
@@ -206,6 +215,13 @@ struct action **actionp,*action;
                *actionp=action;
                hashtable_tot++;
                }
                *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++;
 }
        action->timetot+=length;
        action->stores++;
 }
@@ -222,8 +238,8 @@ static int iscondition(const char *text)
 struct textlist *cond;
 
        for (cond=conditions;cond;cond=cond->next)
 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)
 }
 
 static void addlist(struct textlist ***tail,const char *text,int line)
@@ -342,6 +358,7 @@ static char *modify(char *what)
 {
 regmatch_t matches[10];
 int i,m;
 {
 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;
 static char modbuf1[sizeof(buf)],modbuf2[sizeof(modbuf1)];
 char *src=what,*dstbase=modbuf1,*dst=dstbase;
 const char *start FAKEUSE,*end FAKEUSE,*patt;
@@ -349,83 +366,108 @@ const struct textlist *item;
 
        if (verbose) printf("modify: %s\n",what);
        modify_load();
 
        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;
 
 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;
+               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=='@') {
-                                               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
+                                               *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) */
                }
                }
-       return src;
+       /* NOTREACHED */
 }
 
 static void store(char *what,int length)
 }
 
 static void store(char *what,int length)
@@ -444,22 +486,23 @@ char *ce;
                else *ce='\0';
                }
        storeone(what,length);
                else *ce='\0';
                }
        storeone(what,length);
+       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;
 
 {
 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 (t<last) {
                        fprintf(ERRH1"Time goes backward"WHERE1,ERRH2,WHERE2);
                        t=last;
                        }
        if (last!=-1) {
                if (t<last) {
                        fprintf(ERRH1"Time goes backward"WHERE1,ERRH2,WHERE2);
                        t=last;
                        }
-               acts[0]=bufbackup+6;
+               acts[0]=bufbackup;
                acti=1;
                while ((s=strchr(acts[acti-1],'+'))) {
                        *s='\0';
                acti=1;
                while ((s=strchr(acts[acti-1],'+'))) {
                        *s='\0';
@@ -473,7 +516,7 @@ int timetot,acti,i;
                for (i=0;i<acti;i++)
                        store(acts[i],calctime(timetot,i,acti));
                }
                for (i=0;i<acti;i++)
                        store(acts[i],calctime(timetot,i,acti));
                }
-       strcpy(bufbackup,buf);
+       strcpy(bufbackup,bufaction);
        last=t;
 }
 
        last=t;
 }
 
@@ -481,10 +524,10 @@ int main(int argc,char **argv)
 {
 int optc;
 time_t basetime=-1,currtime;
 {
 int optc;
 time_t basetime=-1,currtime;
-int hour,min;
+int hour,min,sec;
 
        pname=argv[0];
 
        pname=argv[0];
-       while ((optc=getopt_long(argc,argv,"b:pw:qusTtc:f:r:vhV",longopts,NULL))!=EOF) switch (optc) {
+       while ((optc=getopt_long(argc,argv,"b:pw:qusTtac:f:r:vhV",longopts,NULL))!=EOF) switch (optc) {
                
                case 'u':
                        sortby=SORT_NO;
                
                case 'u':
                        sortby=SORT_NO;
@@ -498,6 +541,9 @@ int hour,min;
                case 't':
                        tree=1;
                        break;
                case 't':
                        tree=1;
                        break;
+               case 'a':
+                       doaverage=1;
+                       break;
                case 'c':
                        if (iscondition(optarg))
                                fprintf(ERRH1"Condition \"%s\" already set!\n",ERRH2,optarg);
                case 'c':
                        if (iscondition(optarg))
                                fprintf(ERRH1"Condition \"%s\" already set!\n",ERRH2,optarg);
@@ -596,13 +642,17 @@ const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"};
 #undef WANTED
                        basetime=t;
                        store(MARK_DAY,0);
 #undef WANTED
                        basetime=t;
                        store(MARK_DAY,0);
+                       lifetime_days++;
                        continue;
                        }
 
                        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
                 || 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);
                    ) {
                        fprintf(ERRH1"Incorrect day-time \"%s\""WHERE1,ERRH2,buf,WHERE2);
                        exit(EXIT_FAILURE);
@@ -611,8 +661,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);
                        }
                        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))  {
                }
 
        if (!feof(fi) || ferror(fi))  {
@@ -623,5 +673,5 @@ const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"};
                fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
 
        dumphashtable();
                fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
 
        dumphashtable();
-       return(EXIT_SUCCESS);
+       return EXIT_SUCCESS;
 }
 }