14 #define CONFIGFILE "/.timeplanrc" /* HOME prepended */
15 #define DEF_TIMEPLAN "/.timeplan" /* HOME prepended */
16 #define MARK_DAY "_days"
17 #define DEF_FORMTOTAL ":6"
18 #define LENGTH(x) (sizeof((x))/sizeof(*(x)))
19 #define WHERE1 " in \"%s\" on line %d!\n"
20 #define WHERE2 finame,line
22 #define ERRH1 stderr,"%s: "
24 #define ERRNO1 ": %s!\n"
25 #define ERRNO2 strerror(errno)
28 static const char version[]="This is TimePlan, version 1.0\n";
29 static const char *pname;
32 static int verbose=0,tree=0;
33 static char *formtotal=DEF_FORMTOTAL;
34 static char buf[LINE_MAX];
36 static enum { SORT_NO,SORT_TIMETOT,SORT_STORES } sortby=SORT_TIMETOT;
38 static void usage(void)
42 This command summarizes the timelog information:\n\
44 Usage: timeplan [-u|--unsort] [-s|--stores] [-T|--timetot] [-t|--tree]\n\
45 [-c|--condition <cond>] [-f|--formtotal <fmtstring>]\n\
46 [-v|--verbose] [-h|--help] [-V|--version]\n\
48 -u, --unsort\t\tDon't sort the result in any way\n\
49 -s, --stores\t\tSort the result by stores count\n\
50 -T, --timetot\t\tSort the result by total time (default)\n\
51 -t, --tree\t\tOrganize data as hierarchy tree\n\
52 -c, --condition\tDefine condition variable\n\
53 -f, --formtotal\tFormat \"Total\" column (\"text *val/val:width text\")\n\
54 -v, --verbose\t\tInform about phases of transfer\n\
55 -h, --help\t\tPrint a summary of the options\n\
56 -V, --version\t\tPrint the version number\n\
61 static const struct option longopts[]={
66 {"condition",1,0,'c'},
67 {"formtotal",1,0,'f'},
70 {"version" ,0,0,'V'}};
72 static int calctime(int timetot,int at,int of)
74 /* FIXME: better distribution */
78 static struct action {
82 } *hashtable[HASH_SIZE];
83 static int hashtable_tot=0;
85 static int dumpaction(const struct action *action)
87 int tot FAKEUSE,mins FAKEUSE,hours FAKEUSE,days FAKEUSE,origtot;
91 tot=(action->timetot+30)/60;
92 mins=tot; hours=mins/60; days=hours/24;
97 for (s=formtotal;*s;s++)
103 if (s[1]==*s) { s++; goto dump; }
104 for (s2=s+1;isdigit(*s2);s2++);
105 l=strtol(s+1,&end,10);
107 fprintf(ERRH1"Number parse error at column %d of formtotal string!\n",ERRH2,s-formtotal);
115 if (s[1]==*s) { s++; goto dump; }
116 if (!isdigit(s[1])) goto dump;
122 else origtot+=s[1]-'0';
127 if (action) putchar(*s);
131 printf("=%02d/%02d:%02d\t%4d\t%s\n",days,hours,mins,
132 action->stores,action->what);
138 #define FUNC(which) \
139 static int sort_##which(const struct action **Ap,const struct action **Bp) \
140 { return (B->which>A->which)-(A->which>B->which); }
147 static void dumphashtable(void)
150 struct action *action;
151 struct action **sorta FAKEUSE,**sorti FAKEUSE;
153 if (sortby!=SORT_NO) {
154 int totalwidth=dumpaction(NULL);
156 while (totalwidth-->5) putchar(' ');
157 puts("Total Da Hr Mi\tStor\tDescription");
158 if (!(sorta=malloc(sizeof(*sorta)*hashtable_tot))) {
159 fprintf(ERRH1"malloc() of %d pointers"ERRNO1,ERRH2,hashtable_tot,ERRNO2);
164 for (item=0;item<LENGTH(hashtable);item++)
165 for (action=hashtable[item];action;action=action->next)
170 if (sortby==SORT_NO) return;
171 assert(sorti==sorta+hashtable_tot);
172 qsort(sorta,hashtable_tot,sizeof(*sorta),
173 (int (*)(const void *,const void *))(sortby==SORT_TIMETOT ? sort_timetot : sort_stores));
174 for (sorti=sorta;sorti<sorta+hashtable_tot;sorti++)
179 static unsigned calchash(const char *s)
183 while (*s) r=r*7+11*(*s++);
187 static void storeone(char *what,int length)
189 struct action **actionp,*action;
191 if (verbose) printf("storeone: %d: %s\n",length,what);
192 for (actionp=hashtable+(calchash(what)%HASH_SIZE);(action=*actionp);actionp=&action->next)
193 if (!strcasecmp(action->what,what)) break;
195 if (!(action=malloc(sizeof(*action)+strlen(what)))) {
196 fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,what,ERRNO2);
202 strcpy(action->what,what);
206 action->timetot+=length;
211 struct textlist *next;
215 struct textlist *conditions,**conditionstail=&conditions;
217 static int iscondition(const char *text)
219 struct textlist *cond;
221 for (cond=conditions;cond;cond=cond->next)
222 if (!strcasecmp(text,cond->text)) return(1);
226 static void addlist(struct textlist ***tail,const char *text,int line)
228 struct textlist *item;
230 if (!(item=malloc(sizeof(*item)+strlen(text)))) {
231 fprintf(ERRH1"malloc() for \"%s\""ERRNO1,ERRH2,text,ERRNO2);
234 strcpy(item->text,text);
240 static struct textlist *modifies,**modifiestail=&modifies;
241 static int modifies_tot;
242 static struct modistruct {
247 static void modify_load(void)
253 struct textlist *item;
258 if (asprintf(&finame,"%s"CONFIGFILE,getenv("HOME"))==-1) {
259 fprintf(ERRH1"Config filename allocation",ERRH2);
263 if (!(fi=fopen(finame,"rt"))) {
265 fprintf(ERRH1"Config file \"%s\" read"ERRNO1,ERRH2,finame,ERRNO2);
269 while (clearerr(fi),fgets(buf,sizeof(buf),fi)==buf) {
271 if ((s=strchr(buf,'\n')) && !s[1]) *s='\0';
274 fprintf(ERRH1"fgets(3) results not newline-terminated"WHERE1,ERRH2,WHERE2);
277 if (!*buf || *buf=='#') {
282 fprintf(ERRH1"Unrecognized syntax"WHERE1,ERRH2,WHERE2);
289 while (isspace(*s)) s++;
292 fprintf(ERRH1"Invalid character at offset %d"WHERE1,ERRH2,s-buf+1,WHERE2);
295 for (s2=s+1;isalpha(*s2);s2++);
296 if (*s2!='+' && *s2!='-') {
297 fprintf(ERRH1"Only plus ('+') or minus ('-'), not '%c' expected at offset %d"WHERE1,ERRH2,*s2,s2-buf+1,WHERE2);
300 isplus=(*s2=='+'); *s2++='\0';
301 if (iscondition(s)!=isplus)
306 addlist(&modifiestail,s,line);
309 if (!feof(fi) || ferror(fi)) {
310 fprintf(ERRH1"fgets(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
314 fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
315 if (!(modistructs=malloc(sizeof(*modistructs)*modifies_tot))) {
316 fprintf(ERRH1"malloc() of %d modistructs's"ERRNO1,ERRH2,modifies_tot,ERRNO2);
319 for (m=0,item=modifies;m<modifies_tot;m++,item=item->next) {
320 #define line (item->line)
321 if (!(s=strchr(item->text,'\t'))) {
322 fprintf(ERRH1"No delimiting tab-character found"WHERE1,ERRH2,WHERE2);
325 *s++='\0'; modistructs[m].dst=s;
326 if (verbose) fprintf(stderr,"regcomp: %s -> %s\n",item->text,s);
327 if (regcomp(&modistructs[m].regex,item->text,REG_EXTENDED|REG_ICASE)) {
328 fprintf(ERRH1"regcomp() failed for \"%s\" (%s)"WHERE1,ERRH2,item->text,ERRNO2,WHERE2);
337 static char *modify(char *what)
339 regmatch_t matches[10];
341 static char modbuf1[sizeof(buf)],modbuf2[sizeof(modbuf1)];
342 char *src=what,*dstbase=modbuf1,*dst=dstbase;
343 const char *start,*end,*patt;
344 const struct textlist *item;
346 if (verbose) printf("modify: %s\n",what);
348 for (m=0,item=modifies;item;m++,item=item->next) {
349 i=regexec(&modistructs[m].regex,src,LENGTH(matches),matches,0);
350 if (i==REG_NOMATCH) continue;
352 fprintf(ERRH1"regexec() failed for \"%s\""ERRNO1,ERRH2,item->text,ERRNO2);
355 if (verbose) fprintf(stderr,"matched: %s -> %s\n",item->text,modistructs[m].dst);
356 for (patt=modistructs[m].dst;*patt;patt++) {
358 start=src; end=src+strlen(src);
360 else if (*patt>='0' && *patt<='9') {
361 regmatch_t *match=matches+(*patt-'0');
363 || match->rm_eo==-1) {
364 fprintf(ERRH1"Trying to substitute '%c' but no \"matches\" entry not set for \"%s\""WHERE1,
365 ERRH2,*patt,item->text,WHERE2);
368 if (match->rm_so>match->rm_eo) {
369 fprintf(ERRH1"Trying to substitute '%c' start>end (%d>%d) for \"%s\""WHERE1,
370 ERRH2,*patt,match->rm_so,match->rm_eo,item->text,WHERE2);
373 start=src+match->rm_so; end=src+match->rm_eo;
376 start=patt; end=patt+1;
378 if ((dst-dstbase+(end-start))>=sizeof(modbuf1)) {
379 fprintf(ERRH1"Maximum buffer size exceeded during substition for \"%s\""WHERE1,
380 ERRH2,item->text,WHERE2);
383 memcpy(dst,start,end-start);
386 if (dst==dstbase) return(NULL);
389 assert(dstbase==modbuf1);
395 assert((src==modbuf1 && dstbase==modbuf2)
396 ||(src==modbuf2 && dstbase==modbuf1));
397 swap=src; src=dstbase; dst=dstbase=swap;
403 static void store(char *what,int length)
407 if (!(what=modify(what))) {
408 if (verbose) puts("discarded.");
411 if (verbose) printf("store: %d: %s\n",length,what);
412 while ((ce=(tree?strrchr:strchr)(what,'-'))) {
414 storeone(what,length);
415 if (!tree) what=ce+1;
418 storeone(what,length);
421 static void hit(time_t t)
423 static time_t last=-1;
424 static char bufbackup[sizeof(buf)];
425 char *acts[ACTS_MAX],*s;
428 if (verbose) printf("hit: %ld: %s\n",t,buf+6);
431 fprintf(ERRH1"Time goes backward"WHERE1,ERRH2,WHERE2);
436 while ((s=strchr(acts[acti-1],'+'))) {
439 if (acti>=LENGTH(acts)) {
440 fprintf(ERRH1"Too many '+'-delimited actions (%d)"WHERE1,ERRH2,acti,WHERE2);
446 store(acts[i],calctime(timetot,i,acti));
448 strcpy(bufbackup,buf);
452 int main(int argc,char **argv)
455 time_t basetime=-1,currtime;
459 while ((optc=getopt_long(argc,argv,"b:pw:qusTtc:f:vhV",longopts,NULL))!=EOF) switch (optc) {
474 if (iscondition(optarg))
475 fprintf(ERRH1"Condition \"%s\" already set!\n",ERRH2,optarg);
476 else addlist(&conditionstail,optarg,-1);
485 fprintf(stderr,version);
487 default: /* also 'h' */
492 if (asprintf(&finame,"%s"DEF_TIMEPLAN,getenv("HOME"))==-1) {
493 fprintf(ERRH1"Default timeplan filename allocation",ERRH2);
497 else if (optind+1!=argc) usage();
498 else if (!strcmp(argv[optind],"-")) {
502 else finame=argv[optind];
503 if (!fi && !(fi=fopen(finame,"r"))) {
504 fprintf(ERRH1"open \"%s\" for reading"ERRNO1,ERRH2,finame,ERRNO2);
508 while (clearerr(fi),fgets(buf,sizeof(buf),fi)==buf) {
512 if ((s=strchr(buf,'\n')) && !s[1]) *s='\0';
514 fprintf(ERRH1"fgets(3) results not newline-terminated"WHERE1,ERRH2,WHERE2);
518 if (*buf==':') { /* ":12.7.2000 (St)" */
523 const char *days[]={"Ne","Po","Ut","St","Ct","Pa","So"};
525 assert(LENGTH(days)==7);
526 i=sscanf(buf,":%d.%d.%d (%2s)%n",&tm.tm_mday,&tm.tm_mon,&tm.tm_year,wday,&parsed);
527 if ((i!=4 && i!=5) || parsed!=strlen(buf)) { /* See note in sscanf(3) man page */
528 fprintf(ERRH1"Timestamp with incorrect format"WHERE1,ERRH2,WHERE2);
531 for (i=0;i<7;i++) if (!strcmp(days[i],wday)) break;
533 fprintf(ERRH1"Non-parsable week-day name \"%s\""WHERE1,ERRH2,wday,WHERE2);
534 tm.tm_sec=tm.tm_min=tm.tm_hour=0;
535 tm.tm_wday=tm.tm_yday=-1;
536 tm.tm_isdst=0; /* FIXME */
540 if (t==-1 || tm.tm_wday<0 || tm.tm_wday>6
541 || tm.tm_mday<1 || tm.tm_mday>31
542 || tm.tm_mon <1 || tm.tm_mon >12
543 || tm.tm_year<80 || tm.tm_year>150
545 fprintf(ERRH1"Incorrect timestamp \"%s\""WHERE1,ERRH2,buf,WHERE2);
548 if (i<7 && tm.tm_wday!=i)
549 fprintf(ERRH1"Non-matching week-day, given \"%s\", calculated \"%s\""WHERE1,ERRH2,days[i],days[tm.tm_wday],WHERE2);
550 #define WANTED (60*60*24)
551 if (basetime!=-1 && basetime+WANTED!=t)
552 fprintf(ERRH1"Non-continuous timestamp (%ld, wanted %d) \"%s\""WHERE1,ERRH2,t-basetime,WANTED,buf,WHERE2);
559 if (!isdigit(buf[0]) || !isdigit(buf[1]) || buf[2]!=':' || !isdigit(buf[3]) || !isdigit(buf[4]) || buf[5]!='-'
560 || sscanf(buf,"%d:%d-",&hour,&min)!=2
564 fprintf(ERRH1"Incorrect day-time \"%s\""WHERE1,ERRH2,buf,WHERE2);
568 fprintf(ERRH1"Day-time found but no basetime timestamp set"WHERE1,ERRH2,WHERE2);
571 currtime=basetime+(hour*60+min)*60;
575 if (!feof(fi) || ferror(fi)) {
576 fprintf(ERRH1"fgets(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
579 if (fi!=stdin && fclose(fi))
580 fprintf(ERRH1"fclose(3) \"%s\""ERRNO1,ERRH2,finame,ERRNO2);
583 return(EXIT_SUCCESS);