1: static char *rcsid = "$Header$"; 2: /* 3: * plog - record, edit, print, sort progress of a project 4: * 5: * Author: Peter J. Nicklin 6: */ 7: #include <ctype.h> 8: #include <signal.h> 9: #include <stdio.h> 10: #include "bin.h" 11: #include "date.h" 12: #include "from.h" 13: #include "getarg.h" 14: #include "null.h" 15: #include "path.h" 16: #include "spms.h" 17: #include "system.h" 18: #include "yesno.h" 19: 20: #define SUBJECT_MAX_FORWARD_LOOK 5 /* max no. of lines to read ahead */ 21: /* for "Subject: " */ 22: #define TITLELENGTH 72 /* length of subject title */ 23: /* 24: * type of operation to be executed by plog 25: */ 26: #define APPENDLOG 1 27: #define EDITLOG 2 28: #define PRINTLOG 3 29: #define PRINTLOGTITLE 4 30: #define SORTLOG 5 31: 32: char *PGN = "plog"; /* program name */ 33: int PRINT_TITLE = YES; /* print message titles? */ 34: 35: main(argc, argv) 36: int argc; 37: char **argv; 38: { 39: extern int PPDEBUG; /* project pathname debug flag */ 40: char command[PATHSIZE+8]; /* shell command buffer */ 41: char *cwp; /* current working project */ 42: char *getcwp(); /* get current working project */ 43: char *pathcat(); /* pathname concatenation */ 44: char *pathname; /* regular pathname */ 45: char plog[PATHSIZE]; /* PROJECTLOG pathname */ 46: char *strcpy(); /* string copy */ 47: FILE *ifp; /* input file stream */ 48: FILE *mustfopen(); /* must open file or die */ 49: FILE *ofp; /* output file stream */ 50: FILE *popen(); /* open pipe */ 51: int action; /* what plog has to do */ 52: int decoderange(); /* decode message range */ 53: int naction = 0; /* number of non-default actions */ 54: int range[2]; /* range of messages to print */ 55: int sortlog(); /* sort project log by date */ 56: int status = 0; /* exit status */ 57: int xppath(); /* expand project pathname */ 58: PATH pathbuf; /* pathname struct buffer */ 59: void print(); /* print PROJECTLOG */ 60: void print_head(); /* print PROJECTLOG message headings */ 61: 62: action = APPENDLOG; 63: range[0] = range[1] = 0; 64: 65: { 66: register char *s; /* option pointer */ 67: while (--argc > 0 && (**++argv == '-' || **argv == '+')) 68: { 69: if (**argv == '-') 70: { 71: for (s = argv[0]+1; *s != '\0'; s++) 72: switch (*s) 73: { 74: case 'D': 75: PPDEBUG = YES; 76: break; 77: case 'e': 78: action = EDITLOG; 79: naction++; 80: break; 81: case 'h': 82: PRINT_TITLE = NO; 83: break; 84: case 'p': 85: action = PRINTLOG; 86: naction++; 87: if (decoderange(++s, range) == NO) 88: status = 1; 89: goto endif; 90: case 's': 91: action = SORTLOG; 92: naction++; 93: break; 94: default: 95: warn("bad option -%c", *s); 96: status = 1; 97: goto endif; 98: } 99: } 100: else { 101: for (s = argv[0]+1; *s != '\0'; s++) 102: switch (*s) 103: { 104: case 'h': 105: action = PRINTLOGTITLE; 106: naction++; 107: break; 108: default: 109: warn("bad option +%c", *s); 110: status = 1; 111: goto endif; 112: } 113: } 114: endif: continue; 115: } 116: } 117: if (status == 1 || argc > 1) 118: { 119: fatal("usage: plog [-e] [{+-}h] [-p[low[-high]]] [-s] [projectname]"); 120: } 121: if (naction > 1) 122: { 123: fatal("choose only one of -e, -p, -s, or +h options"); 124: } 125: if (argc == 0) 126: { 127: if ((cwp = getcwp()) == NULL) 128: fatal("no project environment"); 129: pathname = cwp; 130: } 131: else { 132: if (xppath(*argv, &pathbuf) == -1) 133: { 134: patherr(*argv); 135: exit(1); 136: } 137: switch (pathbuf.p_mode & P_IFMT) 138: { 139: case P_IFNEW: 140: case P_IFREG: 141: case P_IFPDIR: 142: fatal("%s: no such project", *argv); 143: case P_IFHOME: 144: case P_IFPROOT: 145: pathname = pathbuf.p_path; 146: break; 147: } 148: } 149: pathcat(plog, pathname, PROJECTLOG); 150: 151: switch (action) 152: { 153: case APPENDLOG: 154: printf("Mailing to %s\n", plog); 155: sprintf(command, "Mail %s", plog); 156: status = system(command); 157: break; 158: case EDITLOG: 159: printf("Editing %s\n", plog); 160: sprintf(command, "Mail -f %s", plog); 161: status = system(command); 162: break; 163: case PRINTLOG: 164: ifp = mustfopen(plog, "r"); 165: if (!isatty(fileno(stdout)) || (ofp = popen("more","w")) == NULL) 166: ofp = stdout; 167: 168: print(ifp, ofp, range); 169: 170: if (ofp != stdout) 171: pclose(ofp); 172: break; 173: case PRINTLOGTITLE: 174: ifp = mustfopen(plog, "r"); 175: if (!isatty(fileno(stdout)) || (ofp = popen("more","w")) == NULL) 176: ofp = stdout; 177: 178: print_head(ifp, ofp); 179: 180: if (ofp != stdout) 181: pclose(ofp); 182: break; 183: case SORTLOG: 184: printf("Sorting %s\n", plog); 185: status = sortlog(plog); 186: break; 187: } 188: exit(status); 189: } 190: 191: 192: 193: /* 194: * decoderange() determines the range of message numbers to be printed. 195: * Prints an error message and returns NO if syntax error, otherwise YES. 196: */ 197: decoderange(srange, range) 198: char *srange; /* range string to decode */ 199: int range[]; /* decoded message range */ 200: { 201: register char *s; /* range string pointer */ 202: register int high = 0; /* high end of message range */ 203: register int low = 0; /* low end of message range */ 204: 205: for (s = srange; isdigit(*s); s++) 206: low = 10*low + (*s - '0'); 207: if (*s == '-') 208: for (s++; isdigit(*s); s++) 209: high = 10*high + (*s - '0'); 210: if (*s != '\0') 211: { 212: warn("%s: bad message range", srange); 213: return(NO); 214: } 215: range[0] = low; 216: range[1] = high; 217: return(YES); 218: } 219: 220: 221: 222: /* 223: * print() copies input stream to output stream, printing a title at 224: * the beginning of each log entry. 225: */ 226: void 227: print(ifp, ofp, range) 228: register FILE *ifp; /* input file stream */ 229: register FILE *ofp; /* output file stream */ 230: int range[]; /* range of messages to print */ 231: { 232: register int high; /* top of range */ 233: register int low; /* bottom of range */ 234: register int msgno; /* current message number */ 235: char *fgets(); /* get a line from input stream */ 236: char linebuf[BUFSIZ]; /* input line buffer */ 237: FROM *isfrom(); /* is line a "From " line? */ 238: void print_title(); /* print message title */ 239: 240: msgno = 0; 241: low = range[0]; 242: high = range[1]; 243: 244: while (fgets(linebuf, BUFSIZ, ifp) != NULL) 245: if (isfrom(linebuf) != NULL) 246: { 247: msgno++; 248: if (msgno < low) 249: continue; 250: else if (msgno > high && high != 0) 251: break; 252: if (PRINT_TITLE == YES) 253: { 254: print_title(linebuf, ifp, ofp); 255: } 256: else { 257: fputs(linebuf, ofp); 258: } 259: } 260: else { 261: if (msgno < low) 262: continue; 263: fputs(linebuf, ofp); 264: } 265: } 266: 267: 268: 269: /* 270: * print_head() prints the log entry headings only. 271: */ 272: void 273: print_head(ifp, ofp) 274: register FILE *ifp; /* input file stream */ 275: register FILE *ofp; /* output file stream */ 276: { 277: register int msgno; /* current message number */ 278: register char *sp; /* subject field pointer */ 279: char *fgets(); /* get a line from input stream */ 280: char linebuf[BUFSIZ]; /* input line buffer */ 281: char *skipword(); /* skip to next word */ 282: char *subject; /* beginning of subject field */ 283: FROM *fromline; /* "From " line struct */ 284: FROM *isfrom(); /* is line a "From " line? */ 285: int i; /* read-ahead buffer counter */ 286: int strlen(); /* string length */ 287: int strncmp(); /* compare strings for n chars */ 288: 289: msgno = 0; 290: while (fgets(linebuf, BUFSIZ, ifp) != NULL) 291: if ((fromline = isfrom(linebuf)) != NULL) 292: { 293: msgno++; 294: fprintf(ofp, "%3d %-10s %s", msgno, fromline->from, 295: fromline->date); 296: for (i = 0; i < SUBJECT_MAX_FORWARD_LOOK; i++) 297: { 298: if (fgets(linebuf, BUFSIZ, ifp) == NULL) 299: break; 300: if (strncmp("Subject: ", linebuf, 9) == 0) 301: { 302: sp = subject = skipword(linebuf); 303: while (*sp != '\n' && *sp != '\0') 304: sp++; 305: *sp = '\0'; 306: fprintf(ofp, " \"%s\"", subject); 307: } 308: else if (isfrom(linebuf) != NULL) 309: { 310: /* "From " line not welcome here */ 311: fseek(ifp, (long) -strlen(linebuf), 1); 312: break; 313: } 314: } 315: putc('\n', ofp); 316: } 317: } 318: 319: 320: 321: /* 322: * printsubject() pretty-prints a "Subject: " field. 323: */ 324: void 325: printsubject(linebuf, ofp) 326: char *linebuf; /* line containing subject */ 327: register FILE *ofp; /* output file stream */ 328: { 329: register int nblank; /* number of blanks to pad title */ 330: char *skipword(); /* skip to next word */ 331: int strlen(); /* string length */ 332: 333: nblank = (TITLELENGTH - (strlen(linebuf) - 9)) / 2; 334: /* length of "Subject: " = 9 chars */ 335: while (nblank-- > 0) 336: putc(' ', ofp); 337: fputs(skipword(linebuf), ofp); 338: } 339: 340: 341: 342: /* 343: * print_title() prints a subject title between two dashed lines. 344: * If a "Subject:" field cannot be found within SUBJECT_MAX_FORWARD_LOOK 345: * lines of the "From" line, then, a single dash line is printed. 346: */ 347: #define PUTDASHLINE(fp) {int i = TITLELENGTH; \ 348: while (i-- > 0) putc('-', fp); putc('\n', fp);} 349: void 350: print_title(linebuf, ifp, ofp) 351: char linebuf[]; /* line buffer containing "From" */ 352: register FILE *ifp; /* input file stream */ 353: register FILE *ofp; /* output file stream */ 354: { 355: char *fgets(); /* get a line from input stream */ 356: char frombuf[BUFSIZ]; /* "From " line buffer */ 357: char *strcpy(); /* string copy */ 358: int i; /* read-ahead buffer counter */ 359: int strncmp(); /* compare strings for n chars */ 360: long ftell(); /* offset relative to file beginning */ 361: long markifp; /* mark position of file */ 362: void printsubject(); /* printprint "Subject: " field */ 363: 364: PUTDASHLINE(ofp); 365: 366: markifp = ftell(ifp); 367: strcpy(frombuf, linebuf); 368: for (i = 0; i < SUBJECT_MAX_FORWARD_LOOK; i++) 369: { 370: if (fgets(linebuf, BUFSIZ, ifp) == NULL) 371: break; 372: if (strncmp("Subject: ", linebuf, 9) == 0) 373: { 374: printsubject(linebuf, ofp); 375: PUTDASHLINE(ofp); 376: break; 377: } 378: } 379: fputs(frombuf, ofp); 380: fseek(ifp, markifp, 0); 381: } 382: 383: 384: 385: /* 386: * sortlog() sorts the project log by date. Returns status 0 if 387: * successful, otherwise 1. 388: */ 389: sortlog(logname) 390: char *logname; /* name of project log file */ 391: { 392: register FILE *ifp; /* input file stream */ 393: register int (*hstat)(); /* hangup status */ 394: register int (*istat)(); /* interrupt status */ 395: register int (*qstat)(); /* quit status */ 396: register long nc = 0; /* number of characters read */ 397: char *fgets(); /* get a line from input stream */ 398: char linebuf[BUFSIZ]; /* input line buffer */ 399: char *pathcat(); /* pathname concatenation */ 400: char *pathhead(); /* remove pathname tail */ 401: char *strcpy(); /* string copy */ 402: FILE *mustfopen(); /* must open file or die */ 403: FILE *ofp; /* output file stream */ 404: FROM *from; /* broken down "From " line */ 405: FROM *initfrom(); /* initialize "From " pointer array */ 406: FROM *isfrom(); /* is line a "From " line? */ 407: FROM *lastfrom; /* previous "From " line */ 408: FROM *savefrom(); /* save "From " lines */ 409: int len; /* length of input line */ 410: int outfrom(); /* output "From " messages */ 411: int parsedate(); /* parse ctime(3) generated date */ 412: char sortlog[PATHSIZE]; /* temporary projectlog for sorting */ 413: int status = 0; /* return status */ 414: int strlen(); /* string length */ 415: void sortfrom(); /* sort "From " lines */ 416: 417: ifp = mustfopen(logname, "r"); 418: 419: lastfrom = initfrom(); 420: 421: for (;;) 422: { 423: if (fgets(linebuf, BUFSIZ, ifp) == NULL) 424: break; 425: len = strlen(linebuf); 426: nc += len; 427: if ((from = isfrom(linebuf)) != NULL) 428: { 429: if (parsedate(from->date, &from->bdt) == NO) 430: { 431: warn("%s: bad date", from->date); 432: status = 1; 433: } 434: else { 435: from->m_seek = nc - len; 436: lastfrom->m_len = from->m_seek - lastfrom->m_seek; 437: if ((lastfrom = savefrom(from)) == NULL) 438: { 439: warn("out of memory"); 440: return(1); 441: } 442: } 443: } 444: } 445: lastfrom->m_len = nc - lastfrom->m_seek; 446: if (status > 0) 447: return(status); 448: sortfrom(); 449: pathcat(sortlog, pathhead(strcpy(sortlog, logname)), "temp_log"); 450: if (FILEXIST(sortlog)) 451: { 452: warn("%s sort in progress - try later", PROJECTLOG); 453: return(1); 454: } 455: 456: hstat = signal(SIGHUP, SIG_IGN); 457: istat = signal(SIGINT, SIG_IGN); 458: qstat = signal(SIGQUIT, SIG_IGN); 459: 460: ofp = mustfopen(sortlog, "w"); 461: if (outfrom(ifp, ofp) == YES) 462: { 463: fclose(ifp); 464: fclose(ofp); 465: RENAME(sortlog, logname); 466: } 467: else { 468: warn("write error in %s: sort failed", sortlog); 469: fclose(ifp); 470: fclose(ofp); 471: unlink(sortlog); 472: status = 1; 473: } 474: 475: signal(SIGINT, hstat); 476: signal(SIGINT, istat); 477: signal(SIGQUIT, qstat); 478: 479: return(status); 480: } 481: 482: 483: 484: /* 485: * skipword() skips a liberal (blank, tab delimited) word and returns a 486: * pointer to the next word. 487: */ 488: char * 489: skipword(bp) 490: register char *bp; /* buffer pointer */ 491: { 492: for (; *bp != '\0' && isspace(*bp); bp++) 493: continue; 494: for (; *bp != '\0' && !isspace(*bp); bp++) 495: continue; 496: for (; *bp != '\0' && isspace(*bp); bp++) 497: continue; 498: return(bp); 499: }