bday

view bdengine.c @ 2:9ec037775c38

removed code for &include in input files
author meillo@marmaro.de
date Mon, 17 Dec 2007 08:02:15 +0100
parents 22b6e71de68e
children dc2f94280b01
line source
1 /*
2 birthday.c
4 Birthday/Anniversary display on login
6 (c) 1996 AS Mortimer
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version. You may also
12 distribute it under the Artistic License, as comes with Perl.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 You should also have recieved a copy of the Artistic license with
23 this program.
25 $Id: bdengine.c,v 1.14 2001/10/21 07:03:49 andy Exp $
27 We're getting there. At the moment, the file used by default is ~/.birthdays
28 under UNIX, or C:\PERSONAL\BDAYS.LST under DOS, but this can be overridden on
29 the command line. The file has the following format:
31 name/event/whatever=date flags
32 where:
33 date is dd/mm, dd/mm/yy (assumes 20th century!) or dd/mm/yyyy
34 flags is ONE or ZERO of
35 o bd for a birthday (default)
36 o bir for a birthday (exactly equivalent to `bd')
37 o ann for an anniversary
38 o ev for an event
39 and zero or more of
40 o w<n> to set the warn-in-advance time to n days (don't include the
41 brackets! :)
42 o to<date>
43 o for<days>
44 to specify the length of time taken by an event, for example a
45 holiday.
47 Comment lines are preceeded by #.
49 Note: If you deviate from this format, I cannot guarantee anything about
50 it's behaviour. In most cases, it will just quietly ignore the error,
51 which probably isn't ideal behaviour. Oh, well.
53 2003/05/20: Automatic reallocation of output buffer in listsrings() by
54 Sebastian Schmidt <yath@yath.eu.org>.
56 */
60 /* ========== */
63 #include <stdio.h>
64 #include <stdarg.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <time.h>
69 #include <sys/types.h>
70 #include <unistd.h>
71 #include <pwd.h>
73 #include "birthday.h"
75 /* ========== */
79 /*
80 xmalloc/xrealloc functions, and fatal exit function
81 Note: the x* functions are lifted straight from the GNU libc info docs
82 $Id: xmalloc.c,v 1.2 1999/01/16 17:08:59 andy Exp $
83 */
85 void *xmalloc (size_t size) {
86 register void *value = malloc (size);
87 if (value == 0) {
88 fprintf(stderr, "virtual memory exhausted\n");
89 exit(1);
90 }
91 return value;
92 }
95 void *xrealloc (void *ptr, size_t size) {
96 register void *value = realloc (ptr, size);
97 if (value == 0) {
98 fprintf(stderr, "virtual memory exhausted\n");
99 exit(1);
100 }
101 return value;
102 }
104 /* ========== */
111 int skptok(int j, char *ptr);
112 int evcmp(const void *e1, const void *e2);
113 char *deffname(void);
115 /*struct event *dir_include(char *dir, char *parm);*/
117 /* ========== Global variables */
119 struct date today;
120 int iDWarn = DEF_WARN;
121 int iMaxWarn = MAX_WARN;
122 int iMinWarn = MIN_WARN;
124 const unsigned MLENDAT[]={31,-1,31,30,31,30,31,31,30,31,30,31};
126 const struct _ftable FTABLE[] = {
127 {"bir",F_TBIRTHDAY},
128 {"bd", F_TBIRTHDAY},
129 {"ann",F_TANNIVERSARY},
130 {"ev", F_TEVENT},
131 {"mes", F_TMESSAGE},
132 {"w", F_WTIME_P},
133 {"to", F_TODATE},
134 {"for", F_FORDAYS},
135 {NULL, 0}
136 };
140 /* ========== */
145 /* compare the first strlen(a) characters of a and b */
146 #define strbegcmp(a,b) strncmp(a,b,strlen(a))
148 /* like strcat(), but lets the buffer automagically grow :-)
149 * (needs local variable "size" with the buffer size) */
150 #define append(where, what) do { \
151 if (strlen(what) > (size - strlen(where))) { \
152 xrealloc(where, size + 128 + strlen(what)); \
153 size += 128 + strlen(what); \
154 } \
155 strcat(where, what); \
156 } while(0)
158 /* ========== */
160 /* returns delta(d) in days, weeks, months, etc
161 * the returned buffer is malloc()ed, do not forget to free() it */
162 char *tdelta(struct date *d) {
163 int dy, wk, mn, yr;
164 char *tmp;
165 char *buf = xmalloc(128);
166 int size = 128;
167 *buf = 0;
169 switch (delta(d)) {
170 case 0:
171 append(buf, "today");
172 return buf;
173 case 1:
174 append(buf, "tomorrow");
175 return buf;
176 default:
177 /* like delta(), we ignore the year */
178 yr=-before(*d,today);
179 mn=d->month - today.month;
180 dy=d->day - today.day;
182 if (dy < 0) {
183 dy += mlen(today.month, today.year);
184 mn--;
185 }
186 if (mn < 0) {
187 mn += 12;
188 yr++;
189 }
191 wk = (dy/7);
192 dy%=7;
194 append(buf, "in ");
195 tmp = ttime(yr, mn, wk, dy);
196 append(buf, tmp);
197 free(tmp);
199 if (*(buf + strlen(buf) - 1) == 's')
200 append(buf, "'");
201 else
202 append(buf, "'s");
204 append(buf, " time");
206 return buf;
207 }
208 }
214 /*
215 void donum(n,txt) {
216 do {
217 if (n > 0) {
218 snprintf(tmp, sizeof(tmp), "%d", n);
219 append(buf, tmp);
220 append(buf, " " txt);
221 if (n != 1)
222 append(buf, "s");
223 terms--;
224 if (orgterms > 1) {
225 if (terms == 1)
226 append(buf, " and ");
227 else if (terms > 1)
228 append(buf, ", ");
229 }
230 }
231 } while(0)
232 }
233 */
236 #define donum(n,txt) do { \
237 if (n > 0) { \
238 snprintf(tmp, sizeof(tmp), "%d", n); \
239 append(buf, tmp); \
240 append(buf, " " txt); \
241 if (n != 1) \
242 append(buf, "s"); \
243 terms--; \
244 if (orgterms > 1) { \
245 if (terms == 1) \
246 append(buf, " and "); \
247 else if (terms > 1) \
248 append(buf, ", "); \
249 } \
250 } \
251 } while(0)
254 /* returns allocated buffer, don't forget to free() */
255 char* ttime(int yr, int mn, int wk, int dy) {
256 char* buf = xmalloc(128);
257 int size = 128;
258 int terms, orgterms;
259 char tmp[128];
261 *buf = 0; /* Initialize buffer */
262 terms = orgterms = (yr!=0) + (mn!=0) + (wk!=0) + (dy!=0);
264 donum(yr, "year");
265 donum(mn, "month");
266 donum(wk, "week");
267 donum(dy, "day");
269 return buf;
270 }
271 #undef donum
278 /* lists the birthdays in their string format, one by one, and passes the string
279 to a function. */
280 void liststrings(struct event *evl, prnfunc outf) {
281 int i,j;
282 char *buf, *tmp;
283 int size;
285 for (i = 0; evl[i].text != NULL; i++) {
286 buf = xmalloc(128);
287 *buf = '\0';
288 size = 128;
290 if (evl[i].warn == -1 && delta(&(evl[i].date))==0) {
291 append(buf, evl[i].text);
292 } else if (evl[i].enddate.day == 0) {
293 if (delta(&(evl[i].date)) <= warnperiod(evl[i])) {
294 append(buf, evl[i].text);
295 append(buf, " ");
296 tmp = tdelta(&(evl[i].date));
297 append(buf, tmp);
298 free(tmp);
299 }
300 } else {
301 if (delta(&(evl[i].date)) <= warnperiod(evl[i])) {
302 append(buf, evl[i].text);
303 append(buf, " for ");
304 /* +1 because, if the difference between two dates is one day,
305 then the length of an event on those days is two days */
306 j = ddiff(&(evl[i].date),&(evl[i].enddate)) + 1;
307 tmp = ttime(0, 0, j/7, j%7);
308 append(buf, tmp);
309 free(tmp);
310 append(buf, " ");
311 tmp = tdelta(&(evl[i].date));
312 append(buf, tmp);
313 } else if (delta(&(evl[i].enddate)) <= warnperiod(evl[i])) {
314 append(buf, evl[i].text);
315 append(buf, " ");
316 j = delta(&(evl[i].enddate));
317 if (j) {
318 append(buf, "for ");
319 tmp = ttime(0, 0, j/7, j%7);
320 append(buf, tmp);
321 free(tmp);
322 append(buf, " longer");
323 } else {
324 append(buf, "finishes today");
325 }
326 }
327 }
328 if (*buf) {
329 append(buf, ".");
330 outf(buf);
331 }
332 free(buf);
333 }
334 }
342 char* deffname(void) {
343 char buf[256];
345 strcpy(buf, getpwuid(getuid())->pw_dir);
346 strcat(buf, "/" DEFAULT_FILE);
348 return strdup(buf);
349 }
356 /* sort the events by the time before the next time they come up, putting those
357 where the start has passed but we are still in the time-period first */
358 int evcmp(const void *p1, const void *p2) {
359 struct event *e1=(struct event *)p1;
360 struct event *e2=(struct event *)p2;
361 unsigned d1,d2;
363 /* if the delta for the enddate is less than that for the start date, then we
364 have passed the start date but not yet the end date, and so we should
365 display the enddate; otherwise, we should display the start date */
367 d1=delta(&(e1->date));
368 if (e1->enddate.day && delta(&(e1->enddate)) < d1)
369 d1=delta(&(e1->enddate));
371 d2=delta(&(e2->date));
372 if (e2->enddate.day && delta(&(e2->enddate)) < d2)
373 d2=delta(&(e2->enddate));
375 if (d1 < d2) return -1;
376 if (d1 > d2) return 1;
378 return strcmp(e1->text, e2->text);
379 }
386 /* difference in days between two dates */
387 /* it is assumed that D1 < D2, and so the result is always positive */
388 unsigned ddiff(struct date *D1, struct date *D2) {
389 struct date d1,d2;
390 int dd,m;
392 /* make working copies */
393 d1=*D1;
394 d2=*D2;
396 /* sort out zero years */
397 if (d1.year == 0 || d2.year==0) {
398 if (d1.year != d2.year) {
399 if (d1.year == 0) {
400 if (before(d1,d2))
401 d1.year=d2.year;
402 else
403 d1.year=d2.year-1;
404 } else {
405 if (before(d1,d2))
406 d2.year=d1.year;
407 else
408 d2.year=d1.year+1;
409 }
410 } else { /* both years zero */
411 if (before(d1,d2))
412 d1.year=d2.year=today.year;
413 else {
414 d1.year=today.year;
415 d2.year=d1.year+1;
416 }
417 }
418 }
420 /* now we can actually do the comparison ... */
421 dd=0;
423 /* to start with, we work in months */
424 for (m=d1.month; m < d2.month + (d2.year-d1.year)*12; m++)
425 dd += mlen(((m-1)%12)+1, d1.year + m/12);
427 /* and then we renormalise for the days within the months */
428 /* the first month was included in our calculations */
429 dd -= d1.day;
430 /* but the last one wasn't */
431 dd += d2.day;
433 return dd;
434 }
443 /* actually until the next anniversary of ... */
444 unsigned delta(struct date *date) {
445 struct date d;
446 unsigned dt, mn;
448 memcpy(&d, date, sizeof(struct date));
450 /* past the end of the year */
451 if (before(d, today)) {
452 d.year = 1;
453 } else {
454 d.year = 0;
455 }
457 for (mn = today.month, dt=0; mn < d.month + 12*d.year; mn++)
458 dt += mlen(((mn-1)%12) + 1,today.year + mn/12);
460 dt -= today.day;
461 dt += d.day;
463 return dt;
464 }
471 void gettoday(void) {
472 struct tm *tm;
473 time_t t;
475 time(&t);
476 tm = localtime(&t);
477 today.day = tm->tm_mday;
478 today.month = tm->tm_mon + 1; /* 1-12 instead of 0-11 */
479 today.year = tm->tm_year;
480 today.year += 1900;
481 }
487 struct event *readlist(char *fname) {
488 FILE *file;
489 int i,j,k,l,d;
490 struct event *evl;
491 char buf[1024], buf2[1024];
492 char *ptr;
493 unsigned flags;
495 /* initialise */
496 if (fname==NULL) {
497 fname=deffname();
498 }
500 gettoday();
502 if (fname[0] == '-' && fname[1] == 0) {
503 /* read from stdin */
504 file=stdin;
505 } else {
506 /* now read it */
507 if((file=fopen(fname, "rt"))==NULL) {
508 fprintf(stderr, "Unable to open file \"%s\"\n", fname);
509 exit(1);
510 }
511 }
514 for (i = 0, evl=NULL; fgets(buf, sizeof(buf), file) != NULL; i++) {
515 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
517 /* ignore comments and empty lines */
518 if (*buf == '#' || *buf == '\n') {
519 i--;
520 continue;
521 }
523 /* parse string in buf */
524 ptr = strrchr(buf, '='); /* allow '=' in text */
526 /* not a valid line, so ignore it! Cool, huh? */
527 if (ptr == NULL) {
528 fprintf(stderr, "WARNING: Invalid line in input file:\n%s", buf);
529 i--;
530 continue;
531 }
533 *(ptr++) = 0;
535 j = sscanf(ptr, "%u-%u-%u", &(evl[i].date.year), &(evl[i].date.month), &(evl[i].date.day));
536 /* if our year is only two digits, add 1900 to it ... */
537 if(evl[i].date.year < 100) evl[i].date.year+=1900;
538 /* ... unless it wasn't read, in which case set it to zero */
539 if(j==2) evl[i].date.year=0;
541 /* parse flags */
543 evl[i].warn=iDWarn;
544 evl[i].enddate.day=evl[i].enddate.month=evl[i].enddate.year=0;
546 flags=j=0;
547 while(j = skptok(j, ptr),ptr[j]!=0) {
548 for (k = 0; FTABLE[k].txt != NULL && strncmp(FTABLE[k].txt, ptr + j, strlen(FTABLE[k].txt)); k++);
549 switch (FTABLE[k].flag) {
550 case F_WTIME_P: /* w<n> -- sets warning time */
551 sscanf(ptr + j, "w %u", &(evl[i].warn));
552 break;
553 case F_FORDAYS: /* for<days> -- sets the duration of the event */
554 sscanf(ptr + j, "for %d", &d);
555 evl[i].enddate=evl[i].date;
556 for (l=1; l < d; l++) {
557 evl[i].enddate.day++;
558 if (evl[i].enddate.day > mlen(evl[i].enddate.month,
559 evl[i].enddate.year)) {
560 evl[i].enddate.month++;
561 evl[i].enddate.day=1;
562 }
563 if (evl[i].enddate.month > 12) {
564 evl[i].enddate.year++;
565 evl[i].enddate.month=1;
566 }
567 }
568 break;
569 case F_TODATE:
570 l = sscanf(ptr + j, "to %u-%u-%u", &(evl[i].enddate.year), &(evl[i].enddate.month), &(evl[i].enddate.day));
571 if (evl[i].enddate.year < 100) {
572 evl[i].enddate.year+=1900;
573 }
574 if (l == 2) {
575 evl[i].enddate.year=0;
576 }
577 break;
578 case 0:
579 break;
580 default:
581 flags|=FTABLE[k].flag;
582 break;
583 }
584 }
587 /* construct event text */
589 switch(flags & F_MTYPE) {
590 case F_TBIRTHDAY:
591 default: /* assume it's a birthday */
592 if (evl[i].date.year != 0) {
593 int tmp_age=ydelta(evl[i].date, today);
594 if (tmp_age!=1) {
595 sprintf(buf2, "%s is %d years old", buf, tmp_age);
596 } else {
597 sprintf(buf2, "%s is %d year old", buf, tmp_age);
598 }
599 } else {
600 sprintf(buf2, "%s has a birthday", buf);
601 }
602 break;
603 case F_TANNIVERSARY:
604 if (evl[i].date.year != 0) {
605 sprintf(buf2, "%s %d years ago", buf, ydelta(evl[i].date, today));
606 } else {
607 strcpy(buf2, buf);
608 }
609 break;
610 case F_TEVENT:
611 /* if a year was specified, and this warning isn't for it, ignore! */
612 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) &&
613 (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
614 i--;
615 continue;
616 }
617 strcpy(buf2, buf);
618 break;
619 case F_TMESSAGE:
620 /* Like an event, except that it only comes up on the given date, and no text at all is appended */
621 if ((evl[i].date.year != 0 && ydelta(evl[i].date, today) != 0) &&
622 (evl[i].enddate.year == 0 || ydelta(evl[i].enddate, today) != 0)) {
623 i--;
624 continue;
625 }
626 strcpy(buf2, buf);
627 evl[i].warn=-1; /* special code! */
628 break;
629 }
630 evl[i].text = strdup(buf2);
631 }
633 evl = (struct event *) xrealloc(evl, sizeof(struct event) * (i + 1));
634 evl[i].date.day=evl[i].date.month=evl[i].date.year=0;
635 evl[i].text = (char *) NULL;
637 fclose(file);
638 free(fname);
640 /* NB uses i from above */
641 qsort(evl, i, sizeof(struct event), evcmp);
642 return evl;
643 }
652 int skptok(int j, char *ptr) {
653 for (; ptr[j] != 0 && ptr[j] != ' ' && ptr[j] != '\t' ; j++);
654 for (; ptr[j] != 0 && (ptr[j] == ' ' || ptr[j] == '\t'); j++);
656 return j;
657 }