#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\ [-r|--rule :]\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\ -r, --rule\t\tAdd to the end of .rc file this rule (':'->'\\t')\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'}, {"rule" ,1,0,'r'}, {"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,*modifiescmdl,**modifiescmdltail=&modifiescmdl; 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); *modifiestail=modifiescmdl; /* add all command-line arguments to the end */ 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) printf("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 FAKEUSE,*end FAKEUSE,*patt; const struct textlist *item; if (verbose) printf("modify: %s\n",what); modify_load(); for (m=0,item=modifies;item;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); } 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); } 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); }