#include <regex.h>
#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"
#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)
{
-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\
{"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'},
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 '/': {
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)
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);
{
unsigned r=57;
- while (*s) r=r*7+11*(*s++);
+ while (*s) r=r*7+11*toupper(*s++);
+ r=r%HASH_SIZE;
return r;
}
{
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)))) {
*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++;
}
int line;
char text[1];
};
-struct textlist *conditions,**conditionstail=&conditions;
+static 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);
+ if (!strcasecmp(text,cond->text)) return 1;
+ return 0;
}
static void addlist(struct textlist ***tail,const char *text,int line)
{
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;
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;mcachep<mcachetable+HASH_SIZE;mcachep++) {
+ for (depth=0,mcache=*mcachep;mcache;mcache=mcache->next)
+ 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 (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';
for (i=0;i<acti;i++)
store(acts[i],calctime(timetot,i,acti));
}
- strcpy(bufbackup,buf);
+ strcpy(bufbackup,bufaction);
last=t;
}
{
int optc;
time_t basetime=-1,currtime;
-int hour,min;
+int hour,min,sec;
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 't':
tree=1;
break;
+ case 'a':
+ doaverage=1;
+ break;
case 'c':
if (iscondition(optarg))
fprintf(ERRH1"Condition \"%s\" already set!\n",ERRH2,optarg);
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_mon <0 || tm.tm_mon >11
|| tm.tm_year<80 || tm.tm_year>150
) {
fprintf(ERRH1"Incorrect timestamp \"%s\""WHERE1,ERRH2,buf,WHERE2);
#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);
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 (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;
}