[comp.sources.misc] v14i037: `mn' mail summary/tally utility

wiml@milton.u.washington.edu (William Lewis) (07/27/90)

Posting-number: Volume 14, Issue 37
Submitted-by: wiml@milton.u.washington.edu (William Lewis)
Archive-name: mn/part01

I wrote this because it's useful, then after a fit of documentation
decided that something with a man page might as well be posted...

Mn is a utility to read a mailbox file and summarize its contents, 
say for use in a login script. See the README for more details
(it's right at the top of the shar ...) 


----------cut here-----------------cut here------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	mn.c
#	mn.1
#	Makefile
# This archive created: Mon Jul 23 22:58:52 1990
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'README'" '(925 characters)'
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
sed 's/^X//' << \SHAR_EOF > 'README'
XMn reads and summarizes, formats, or tallies a (Berkeley-style?)
Xmailbox. It is useful both in .logins and as a target of rsh. Some
Xoutput formats are also machine-parsable, and could be used in
Xshell scripts or the like. See the man page for details.
X
XMn requires no configuration switches. Probably the only less-than-portable
Xconstructs are the use of <sys/ioctl.h> and the TIOCGWINSZ ioctl to
Xfind the width of the current terminal, and a few assumptions about the
Xorder of header lines in the mailbox file. Mn has been compiled and tested
Xon a Vax running 4.3BSD, a Sequent Symmetry under DYNIX, a Vax under
XUltrix and an IBM running AIX, although testing under the latter two 
Xwas minimal =8) 
X 
X  To compile, simply type `make all' which will produce mn (the executeable)
Xand mn.man (the nroff'd man file).
X
X  Please send bug reports, fixes, comments, complaints about bandwidth, etc.
Xto wiml@milton.u.washington.edu.
SHAR_EOF
if test 925 -ne "`wc -c < 'README'`"
then
	echo shar: "error transmitting 'README'" '(should have been 925 characters)'
fi
fi
echo shar: "extracting 'mn.c'" '(11701 characters)'
if test -f 'mn.c'
then
	echo shar: "will not over-write existing file 'mn.c'"
else
sed 's/^X//' << \SHAR_EOF > 'mn.c'
X/*   maynot -- mail notification program */
X/*
X    mn -- see man page. Written by Wim Lewis c. May 1990. 
X    Reads a (berkeley-style) mailbox and produces various forms
Xof output, mainly for human consumption.
X
X*/
X/*
X    options:
X
X    -f   Formatting option. Formats available:
X    -n   Notify option. Prints "You have mail", "You have new mail",
X          "You have unread mail" or "You have mail" instead of other output.
X    -N   -n to stderr, after normal output.
X    -t,T Verbose verion of -n. Print number of messages in each category.
X    -h   Suppress header & trailer lines.
X    -w   Decrement line width.
X    -ix  Include messages with status letters `x'.
X    -ex  Exclude messages with status letters `x'.
X
X     this comment is probably outdated    
X*/
X/*
X    This program may be freely distributed, modified, hacked, warped, 
Xignored, etc., as long as the author's name and this notice are kept
Xintact in the source, and as long as recipients are free to do likewise
X*/
X
X/* path for last-ditch attempt to find mailbox  */
X/* customize if necessary */
X#define MAILBOXPATHFORM "/usr/spool/mail/%s" 
X
X#include<stdio.h>
X#include<ctype.h>
X#include<sys/ioctl.h>  /* for get-line-length call */
Xextern char *getenv(), *strcpy();
X
X#define SUBOFF  8 /* length of string `Subject:' */
X#define FROMOFF 5 /* length of string `From '    */
X#define STATOFF 8 /* length of string `Status: ' */
X
X/* output styles */
X#define OFROM 0      /* old "from"(1)-style output */
X#define NFROM 1      /* "nfrom"-style output */
X#define SIMPLE 2     /* simple output, originally a debug mode */
X#define ADDRONLY 3   /* address-only format */
X#define QUIET 4      /* no output, for -t, -n switches */
X#define NFROM_S 5    /* like NFROM but without continuation */
X
X#define LINLEN 250  /* input line buffer size */
X
X#define MAXNCOND 10 /* max # conditional phrases */
X#define MAXCONDL 5  /* max conditional length */
X
X#define DEFAULT_OWIDTH 80  /* assumed output screen width */
X
Xextern char *index(), *rindex(), *malloc();   /* minimal header files? */
Xchar *dynamicize();
X
Xmain(argc, argv)  int argc; char **argv;
X{int state;         /* 0 = waiting for 'from', 1=in header, 2=in body */
X char buf[LINLEN];  /* holds lines read from mailbox */
X char *from, *sub;  /* holds addr, subj of current article */
X FILE *np = stderr; /* stream to place tally on */
X FILE *inp = NULL;  /* stream to input from */
X int noheader = 0;  /* whether to print decorative headers if appropriate*/
X int style = NFROM; /* output style */
X int notify = 0;    /* whether to notify read/unread/new mail */
X int stafl = 0;     /* flags "quiet"(Status:less) trans. from header to body */
X int unread=0, newmail=0;  /* counters for notify mode */
X int nummsgs=0;     /* number of messages */
X char cond[MAXNCOND][MAXCONDL];  /* conditional expressions */
X int numconds = 0;  /* number of conditional phrases */
X int owidth;        /* width of output device */
X int remains = 0;   /* remaining length on an NFROM summary line */
X int i;             /* miscellaneous index variable */
X
X
X /* first get the tty line length */
X {struct winsize ttyb; int fd;
X  fd = open("/dev/tty", 0);
X  if(fd < 1) owidth = DEFAULT_OWIDTH;
X  else {
X  ioctl(fd, TIOCGWINSZ, &ttyb);
X  owidth = ttyb.ws_col;
X  }
X  if(owidth<20) owidth = DEFAULT_OWIDTH;  /* sanity check */
X }
X
X for(i=1; i<argc; i++)  /* process all arguments */
X {
X  if(argv[i][0] != '-' || strlen(argv[i]) == 1)    /* filenames...*/
X  {
X   if(inp)
X   {
X    fprintf(stderr, "Extra filename arguments rejected: %s\n", argv[i]);
X    continue;
X   }
X   inp = (argv[i][0] == '-' && argv[i][1] == 0) ? stdin : fopen(argv[i], "r");
X   if(!inp)
X   {
X    fprintf(stderr, "Filename argument rejected -- %s: ", argv[i]);
X    perror("fopen");
X    inp = stdin;
X   }
X   continue;
X  }
X  switch(argv[i][1])
X  {
X   case 'h': noheader = 1; break;
X   case 'f':
X    switch(argv[i][2])   /* select output mode */
X    {
X     case 'n': style=NFROM;	break;
X     case 'N': style=NFROM_S;	break;
X     case 's': style=SIMPLE;	break;
X     case 'a': style=ADDRONLY;	break;
X     case 'x': style=QUIET;	break;
X     case 'f':
X     case 0:   style=OFROM;	break;
X     default:  fprintf(stderr, "Format '%s' unknown.\n", argv[i]+2); break;
X    }
X    break;
X   case 'n':
X    style=QUIET;   /* falls through */
X   case 'N':
X    notify = 1;
X    break;
X   case 't':
X    style=QUIET;    /* falls through */
X   case 'T':
X    notify = 2;
X    break;
X   case 'w':
X    owidth--;      /* decrement output width */
X    break;
X   case 'e':
X   case 'i':
X    strncpy(cond[numconds++], argv[i]+1, MAXCONDL);
X    cond[numconds-1][0] = (cond[numconds-1][0] == 'e')? 0 : 1;
X    break;
X   default:
X    printf("Unrecognized option -%s\n", argv[i]);
X    break;
X  }
X }
X
X if(!inp   /* no filenames were specified on the command line */
X  && !(getenv("MAIL") && (inp = fopen(getenv("MAIL"), "r"))) /* env var */
X )
X {char *cp;   /* try to open /usr/spool/mail/user */
X  cp = getenv("USER");
X  if(!cp) cp = getenv("LOGNAME");
X  if(cp)
X  {char mbuf[100];
X   sprintf(mbuf, MAILBOXPATHFORM, cp);
X   inp = fopen(mbuf, "r");
X  }
X  if(!inp)
X  {
X   fprintf(stderr, "No files specified on the command line, aborting...\n");
X   exit(-1);
X  }
X }
X
X state = 0;           /* initialize */
X from = sub = NULL;
X
X 
X while(!feof(inp))
X {
X   fgets(buf, LINLEN, inp);    /* get a line from the mailbox */
X   if(*buf == '\n' && !buf[1]) /* blank lines are special */
X   { 
X    if(state==1) 
X    {
X     state=2;
X     if(stafl != -1) stafl = 1;   /* flag end of header */
X     else 
X     {
X      stafl = 0;
X      continue; 
X     }
X    }
X    else state=0;
X   }
X#ifdef DEBUG
X      /*ACME universal debugging statement*/
X   printf("%d %d %d %s", state,stafl,remains, buf); 
X#endif
X   if(!strncmp(buf, "From ", FROMOFF) && state==0) /* grab origin addr */
X   { 
X    state=1;               /* beginning of message header */
X    if(from) free(from);
X    if(style == OFROM) { from=dynamicize(buf); continue; }  /* special case */
X    for(i = FROMOFF; buf[i] && !isspace(buf[i]); i++)
X       ; /* search for first blank */
X    buf[i] = (char)0;  /* terminate the string there */
X    from = dynamicize(buf+FROMOFF);
X   }
X   if(state==1 && !(style==OFROM || style==ADDRONLY) &&
X       !strncmp("Subject:", buf, SUBOFF)) /* grab the subj string */
X   {
X    if(sub) free(sub);
X    for(i=strlen(buf) - 1; i > SUBOFF && isspace(buf[i]); i--)
X       ; /* search for last nonblank */
X    buf[i+1] = (char)0;  /* trim trailing blanks */
X    for(i=SUBOFF; buf[i] && isspace(buf[i]); i++)
X       ;
X    sub = dynamicize(buf+i);    /* trim leading blanks too */
X   }
X   if((state==1 && !strncmp("Status: ", buf, STATOFF)) || stafl==1) /*end hdr*/
X   {int flag; char tych;  /* tych == type character */
X    if(stafl == 1)
X     buf[STATOFF] = (char)0;
X    else
X    {
X     for(i=strlen(buf)-1; i>STATOFF && isspace(buf[i]); i--)
X       ; /* search for last nonblank */
X     buf[i+1] = (char)0; /* trim blanks */
X    }
X    stafl --;
X
X    flag = 1;
X    for(i=0; i<numconds; i++)  /* evaluate the conditional */
X    {int j;
X     for(j=1; cond[i][j]; j++)
X      if(!index(buf+STATOFF, cond[i][j])) goto nextc; /* kludge for break^2 */
X     flag = cond[i][0];
X     nextc:  ;
X    }
X    if(!flag) continue;   
X
X    nummsgs++; tych = 'N';
X    if(!index(buf+STATOFF, 'O'))  /* Old mail? */
X     newmail++; else tych = 'U';
X    if(!index(buf+STATOFF, 'R'))  /* read mail? */
X     unread++;  else tych = (tych == 'U')?'O':'?';
X
X    switch(style)   /* output this msg. in correct style */
X    {
X     case SIMPLE:
X      printf("<%s> %s %s\n", buf+STATOFF, from, sub);
X      break;
X     case OFROM:
X      printf("%s", from);
X      break;
X     case ADDRONLY:
X      puts(from);
X      break;
X     case QUIET:
X      break;
X     case NFROM:
X     case NFROM_S:
X      /* Format of NFROM output:
X          Columns 0 - owidth/3: Address
X          4 columns: ':', ' ', status, ' '
X          The rest: subject */
X      /* The hard part: figuring out which part of a too-long address
X          the user wants to see. Current algorithm: justify so that
X          whatever is immediately before the last '@' sign is in view.
X         This fails for addresses of the form  user%host%host%host@host. */
X      if(nummsgs == 1 && !noheader)  /* print the header line before 1st msg */
X      {
X       for(i=0; i< (owidth/2) -4; i++) putchar('-');
X       printf(" M A I L ");
X       for(i=0; i< ((1+owidth)/2) -5; i++) putchar('-');
X       putchar('\n');
X      }
X      if(remains) { putchar('\n'); remains = 0; }  /* null message body? */
X     {char *cp;
X      cp = rindex(from, '@');    /* find end of area to be shown */
X      if(cp == NULL) cp = from+(strlen(from) -1);
X      cp  -= (owidth/3);  /* find beginning of area to be shown */
X      if(cp<from) cp=from;  /* don't overshoot */
X      printf("%*.*s: %c %.*s", owidth/3, owidth/3, cp,
X	     tych, owidth - ( (owidth/3) +5), sub);
X     }
X      if(style == NFROM_S || (strlen(sub) >= (owidth-((owidth/3)+5))))
X      {
X       remains = 0;     /* don't fill the rest of the line */
X       putchar('\n');   /* and terminate it */
X      }
X      else 
X      {
X       remains = (owidth-((owidth/3)+5)) - strlen(sub);  /* calc remainder */
X       putchar('/');    /* mark end of subject line */
X      }
X      break;
X     default:
X      puts("Unimplemented style"); break;  /* this should never happen ;-) */
X    }  /* end of style-output switch() */
X   }  /* end of if(Status:) test */
X   if(remains > 0 && (state == 0 || state == 2)) /*need to fill rest of line?*/
X   {char *cp; int spflag = 0;    /* stick this line onto end of line */
X				/* (NFROM mode) */
X    				/* spflag is used to compress blank space */
X    for(cp = buf; *cp; cp++)
X    {
X	/* put char., compressing out blanks */
X     if(!spflag && isspace(*cp)) { spflag = 1; putchar(' '); remains--; }
X     if(spflag && !isspace(*cp)) spflag = 0;
X     if(!spflag) { putchar(*cp); remains--; } 
X     if(!remains)		/* and check for termination */
X     {
X      putchar('\n');
X      break;
X     }
X    }  /* end of fill-lines loop */
X   }  /* end of fill-line test */
X }  /* end of read-mailbox loop, now clean up */
X if(remains)  /* if we're still filling up a line */
X  putchar('\n');  /* end that line */
X if((style == NFROM || style == NFROM_S) && nummsgs && !noheader)
X  {for(i=0; i<owidth; i++) putchar('-');  /* draw second "header" line */
X   putchar('\n'); }			  /* and terminate it */
X if(notify && nummsgs)     /* next we print out message tally */
X {                    /* the complex booleans are for English's grammar */
X  if(notify == 2 && unread >= newmail) unread -= newmail;
X  fputs("You have",np);
X  if(notify == 2 && newmail) pnum(np, newmail);
X  if(newmail) fputs(" new",np);
X  if(newmail && notify == 2) fprintf(np, " message%s", newmail!=1? "s":"");
X  if(newmail && unread && notify == 2) fputs(" and",np);
X  if(unread && notify==2) pnum(np, unread);
X  if(unread && ((notify==1 && newmail==0) || notify==2)) fputs(" unread",np);
X  if(notify == 1 || !(newmail+unread)) fputs(" mail",np);
X  if(notify == 2 && unread) fprintf(np, " message%s", unread!=1? "s":"");
X  fputs(".\n",np);   /* Whew! */
X }
X}
X
Xstatic char *nums[] = 
X{ "no", "one", "two", "three", "four", "five", "six", "seven", "eight",
X"nine", "ten" };
X
Xpnum(s, n) FILE *s; int n;  /* print num to stream preceded by space */
X{
X  fputc(' ',s);
X  if(n < 0) fprintf(s,"%d",n);
X  else if(n < 11) fputs(nums[n], s);
X  else fprintf(s,"%d",n);
X}
X
X/* dynamicize() takes a string and moves it to dynamic memory, performing
X   error checking (crashes out on error) */
Xchar *dynamicize(str) char *str;
X{char *cp;
X  cp = malloc((unsigned)(strlen(str)+1));
X  if(!cp) 
X  {
X    fputs("Out of memory", stderr);
X    exit(-2);
X  }
X  strcpy(cp, str);
X  return(cp);
X}
SHAR_EOF
if test 11701 -ne "`wc -c < 'mn.c'`"
then
	echo shar: "error transmitting 'mn.c'" '(should have been 11701 characters)'
fi
fi
echo shar: "extracting 'mn.1'" '(3833 characters)'
if test -f 'mn.1'
then
	echo shar: "will not over-write existing file 'mn.1'"
else
sed 's/^X//' << \SHAR_EOF > 'mn.1'
X.TH mn 1 "June 1990"
X.SH NAME
Xmn \- Summarize contents of mailbox
X.SH SYNOPSIS
X.B "mn \fR[\fB-nNvh\fR] [\fB-f\fR[\fBnfsax\fR]]"
X.B "\fR[\fB-\fR[\fBie\fR]\fInnn\fR]"
X.B \fR[\fIfilename\fR]
X.SH DESCRIPTION
X.B Mn
Xreads a mailbox and gives a summary of mail waiting
Xfor the user. For each message,
X.B mn
Xcan display the subject, sender's address, and whether or not
Xthe message has been read. 
X.B Mn
Xcan also be made to ignore or skip messages according to which
Xstatus flags are set.
X.PP
XThere are several options:
X.TP
X.B \-f\fIn
XSets the output format to that specified by
Xthe letter \fIn\fR. Several output formats are available:
X.RS
X.IP f
XImitates the format of \fBfrom\fR(1).
X.IP n
XNice format, designed for human-readability, with justified
Xaddress, status and subject fields, plus as much of the message
Xas will fit on the rest of the line.
XThis is the default setting.
X.IP N
XLike \fBn\fR, but does not include parts of the message body.
X.IP s
XSimple format. Mainly useful for piping output to another
Xprogram without losing any information available with \fBn\fR.
X.IP a
XAddress-only format. Similar to \fBf\fR, but only prints the
Xaddress portion of the line.
X.IP x
XProduces no output. This option is particularly pointless.
X.RE
X.TP
X.B \-n
XInstead of listing each message, the mailbox is read and a single
Xline of the form "You have new/unread mail" is printed to stderr.
X.TP
X.B \-N
XLike \fB\-n\fR, but does not suppress listing of the individual messages.
X.TP
X.B \-t\fR, \fB\-T
XLike \fB\-n\fR, but prints a tally of new and unread messages (in English).
X.TP
X.B \-h
XSuppresses the decorative lines around the message listing, if
Xany. Only has effect with the option \fB\-fn\fR.
X.TP
X.B -w
XCauses \fBmn\fR to avoid using the last column, for terminals
Xthat wrap on the last column. Multiple -w switches can be
Xused to decrement the width by an arbitrary amount.
X.TP
X.B \-e\fInnn
XExclude messages with specified status flags. Messages with
Xstatus flags corresponding to \fInnn\fR are not listed.
X.TP
X.B \-i\fInnn
XInclude messages with specified status flags. This is the reverse
Xof \fB\-e\fR. Initially, by default, all messages are included.
XLater \fB\-e\fR and \fB\-i\fR switches override previous
Xones, allowing a complex boolean message filter to be built up
X(this would find more use if there were more than two flags to
Xtest.)
X.PP
XThe name \fB - \fR can be used to denote the standard input.
X.PP
X.B Mn
Xis designed to be used in a user's .login, .profile or similar
Xplaces, hence the lack of a complex default behavior. 
X.SH EXAMPLES
XThe following reads the user's mailbox and prints
Xout a list of all new or unread messages:
X.IP
X.B prmail | mn - -fn -eR
X.PP
XRead the user's mailbox and inform them of waiting messages:
X.IP
X.B mn $MAIL -n
X.PP
XCreate a list of the addresses of everyone whose messages are in
Xthe user's old-mail box:
X.IP
X.B mn -fa ~/mbox > list
X.SH FILES
XIf no file is specified on the command line, \fBmn\fR searches for
Xan environment variable named \fIMAIL\fB, containing the name of
Xthe mailbox. Failing this, 
X\fBmn\fR
Xlooks for environment variables \fIUSER\fR or \fILOGNAME\fR, appends
Xtheir contents to the string \fB/usr/spool/mail/\fR, and attempts
Xto open the file. After that it gives up.
X.SH SEE ALSO
X.BR from (1),
X.BR prmail (1),
X.BR mail (1).
X.SH BUGS
XLines greater than about 250 characters will get silently folded.
XLines almost exactly 250 characters long may confuse \fBmn\fR. 
X.PP
XThe algorithm used to determine which part of the originating
Xaddress to display when using the \fB\-fn\fR switch could use some
Xwork. Specifically, addresses of the form 
X.B user%host%host%host@host
Xare displayed incorrectly.
X.PP
XCertain assumptions are made about the order of the header lines in
Xthe mailbox file which may break \fBmn\fR on some systems.
X.SH AUTHOR
XWim Lewis (wiml@milton.u.washington.edu)
SHAR_EOF
if test 3833 -ne "`wc -c < 'mn.1'`"
then
	echo shar: "error transmitting 'mn.1'" '(should have been 3833 characters)'
fi
fi
echo shar: "extracting 'Makefile'" '(242 characters)'
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
sed 's/^X//' << \SHAR_EOF > 'Makefile'
X#
X#   Makefile for maynot mail notification
X#
Xall: mn mn.man
X#
Xmn: mn.o
X	cc -o mn mn.o
Xmn.man: mn.1
X	nroff -man mn.1 >mn.man
X#
Xlint:
X	lint -bhapc mn.c
X#
Xshar:	mn.c mn.1 README Makefile
X	shar -v -c -b -p X README mn.c mn.1 Makefile >mn.shar
X#
SHAR_EOF
if test 242 -ne "`wc -c < 'Makefile'`"
then
	echo shar: "error transmitting 'Makefile'" '(should have been 242 characters)'
fi
fi
exit 0
#	End of shell archive

--
wiml@milton.acs.washington.edu      Seattle, Washington  | No sig under
(William Lewis)  |  47 41' 15" N   122 42' 58" W  |||||||| construction