/* epics/share/src/util $Id$ * Author: Roger A. Cole * Date: mm-dd-yy * * Experimental Physics and Industrial Control System (EPICS) * * Copyright 1991, the Regents of the University of California, * and the University of Chicago Board of Governors. * * This software was produced under U.S. Government contracts: * (W-7405-ENG-36) at the Los Alamos National Laboratory, * and (W-31-109-ENG-38) at Argonne National Laboratory. * * Initial development by: * The Controls and Automation Group (AT-8) * Ground Test Accelerator * Accelerator Technology Division * Los Alamos National Laboratory * * Co-developed with * The Controls and Computing Group * Accelerator Systems Division * Advanced Photon Source * Argonne National Laboratory * * Modification Log: * ----------------- * 1.1 07/12/89 rac initial version * 1.2 09/15/89 rac allow multiple input files; change * the calling sequence so -o specifies * output file; print non-printing chars * as . * 1.3 11/02/89 rac change printing of <; allow default * input from stdin. If first line on page * is FF, don't print empty page. * 1.4 11/03/89 rac fix bug in input from stdin. * 1.5 12/04/89 rac handle long lines without truncation * 1.6 02/11/90 rac handle module name and subr name in heading; * put page number in footing. * 1.7 skipped * 1.8 08/31/90 rac handle /subhead text--- divider lines * 1.9 09/17/90 rac fix long line bug; allow more pages * 1.10 07-28-91 rac installed in EPICS * 1.11 07-28-91 rac add -w option for page width * */ /*+/mod*********************************************************************** * TITLE racPrint - filter for printing * * SYNOPSIS * racPrint [-l lm] [-o output] [-t tab] [-P point] [-w width] input ... * * lm is the number of columns for left margin; default 0. * tab is the tab width, in columns; default 8. * input is an ascii file; several may be specified; if none * is specified, stdin is used. * output is an ascii file containing the input file broken * into pages, with pages in reverse order; * default is stdout. * point is a point size for PostScript printers * width is page width, in characters * * DESCRIPTION * The input file is broken into pages and written in reverse page * order to the output file. The following processing is done: * o a 3 line heading is written, containing time and date, file * path, file name, modification date, and subroutien name is * printed * o a 3 line footing containing file number and page number is * printed * o a left margin of 8 columns is added, plus an additional 7 * columns for line numbering. (If -l is specified, it * controls the left margin.) * o tabs are expanded * o form-feed characters are honored * o long lines are folded * o non-printing characters are printed as , where * code is the hexadecimal code for the character. Where * a < occurs, it is printed as a <; hopefully, the context * will avoid confusion with . * EXAMPLE * % racPrint source.c | lpr * * BUGS * o should guard against printing binary files * o files which exactly fill the last page result in an extra, blank * page being printed. * o Doesn't print info following ^L if doesn't follow ^L * *-*/ /*----------------------------------------------------------------------------- * APOLOGY: I offer my humble apologies to whomever comes across this code-- * it was my first program in C, and it shows it. R. Cole *----------------------------------------------------------------------------*/ /* WISH LIST: * - generate a 'table of contents' from names from divider lines * - generate table of contents for file names and extractable entities * - add a -help switch * - handle the backspace character * - handle "pre-formatted" files, such as man pages output * - handle extremely long path names, or at least check for overflow * - allow a `-H heading' option to provide a meaningful heading when * input is from stdin */ #include #include #include #include #include #define MAXCHAR 95 /* max characters per line */ #define MAXLINE 66 /* max lines per page */ #define MAXPAGE 500 /* max pages at a time */ #define MAXTOC 10 /* max table of contents pages */ #define lineBufDim 10000 /* dimension for input line and ovf buffers */ #define STRCAT_CHK(str,sub_str) strcat_chk(str,sizeof str,sub_str) typedef struct { int page_no; /* page number */ int nlines; /* number of lines on this page */ char lines[MAXLINE][MAXCHAR+1]; /* lines of the page */ } PAGE; struct FILE_INF { FILE *ptr; /* file pointer from 'fopen' */ char path[80]; /* pathname to file */ char date_mod[30]; /* time and date last modified */ short line_no; /* line number in file */ char name[20]; /* file name */ char subr[30]; /* subroutine name from subr heading */ int fd; /* file descriptor for use in 'stat' call */ }; char date[40]; /* date and time */ char header[80]; /* heading line */ int page_no; /* number of current page within file */ int file_no = 0; /* file number being processed */ int npages = 0; /* number of pages */ PAGE pages[MAXPAGE]; /* pages from file */ int pages_sub = 0; /* subscript into pages structure */ PAGE toc[MAXTOC]; /* table of contents */ int toc_sub = 0; /* subscript into toc structure */ int left_margin = 0; /* width of left margin in output */ int line_numbering = 1; /* flag to number lines in output */ int page_width = 80; /* page width, in characters */ int point_size = 0; /* PostScript point size if non-zero */ int tab_width = 8; /* columns per 'tab stop' */ char overflow[lineBufDim]; /* overflow record buffer */ int overflow_flag = 0; /* overflow record buffer in use */ /*+***************************************************************** * NAME main - racPrint main program * *-*/ main(argc, argv) int argc; /* number of command line args */ char *argv[]; /* command line args */ { void get_time_and_date(); /* get time and date string */ int i; /* temp for loops */ struct FILE_INF in_file; /* input file */ struct FILE_INF out_file; /* output file */ char line[MAXCHAR+1]; /* line of file */ int get_next_page(); /* get a page from the file */ get_time_and_date(date); /* ------------------------------------------------------------- * get the option arguments from the command line * -------------------------------------------------------------*/ get_option_args(argc, argv, &out_file); /* ------------------------------------------------------------- * get the input file names from the command line, processing each * in turn. * -------------------------------------------------------------*/ while (get_input_file(argc, argv, &in_file) != NULL) { /* ------------------------------------------------------------- * Read pages from input file. If 'buffer' becomes full, print * the accumulated pages in reverse order. * -------------------------------------------------------------*/ in_file.line_no = 0; while (get_next_page(&in_file) != EOF) { if (pages_sub == MAXPAGE) { /* dump and start fresh */ for (i=pages_sub-1; i>=0; i--) print_page(&out_file,i); pages_sub = 0; } } if (in_file.ptr != stdin) /* don't close stdin */ fclose(in_file.ptr); } for (i=pages_sub-1; i>=0; i--) { print_page(&out_file,i); } print_banner_page(&in_file,&out_file); /* flush and close files */ fflush(out_file.ptr); fclose(out_file.ptr); exit(0); } /*+/subr************************************************************ * NAME print_banner_page - write a banner page to the output file * *-*/ print_banner_page(p_in_file,p_out_file) struct FILE_INF *p_in_file; /* pointer to input file */ struct FILE_INF *p_out_file; /* pointer to output file */ { char banner[80]; /* temp for banner line */ char *env; /* pointer to string from getenv */ char *getenv(); /* get environment variables */ int i; /* temp for loops */ for (i=0; i<79; i++) banner[i] = '/'; banner[79] = '\0'; env = getenv("LOGNAME"); for (i=0; iptr, "%s\n",banner); else if (i==15) fprintf(p_out_file->ptr, "////////// For: %s\n",env); else if (i==17) fprintf(p_out_file->ptr, "////////// Date: %s\n",date); else fprintf(p_out_file->ptr, "////////// \n"); } return; } /*+/subr************************************************************ * NAME print_page - write a page to the output file * *-*/ print_page(p_out_file,page_number) struct FILE_INF *p_out_file; /* pointer to file */ int page_number; /* page number to print 0-n */ { int i; /* temp for loops */ for (i=0; iptr); return; } /*+/subr************************************************************ * NAME line_store - store a line into a page * * DESCRIPTION * The record is stored into the line buffer, expanding tabs. * * If the record isn't an overflow record, left margin and * record number are added to the line buffer. If the * record is too long to fit in the line buffer, the overflow * flag is set and the excess characters are copied into the * overflow record. * * If the record is an overflow record, the left margin and * padding for the record number field are added to the line * buffer. If the record is too long to fit in the line buffer, * the overflow flag and overflow record will be handled as above. * * RETURNS * 0 if no form-feed encountered * 1 if form-feed encountered and line printed * 2 if form-feed encountered and no line printed * *-*/ line_store (line, rec, line_no) char line[]; /* line to receive record, with tabs expanded and left margin set */ char rec[]; /* record to store */ int line_no; /* line number to attach; if zero, no line number will appear. */ { char c; /* temp for character */ int ff=0; /* form feed encountered flag */ char hex_code[3]; /* temp for hex code for char */ int l_col=0; /* column index in line */ int lmar; /* left margin to store actual record */ int r_col=0; /* column index in record */ /* ------------------------------------------------------------- * If the first thing on a line is a ^L (i.e., ff), then don't * do anything; just exit with `ff' indicated. * -------------------------------------------------------------*/ if (rec[0] == '\f') { ff = 2; return ff; } while (l_col < left_margin) line[l_col++] = ' '; /* blank fill left margin */ if (!overflow_flag) { if (line_no > 0) { /* 'print' the line number */ sprintf (&line[l_col], "%.5d", line_no); l_col += 5; line[l_col++] = ' '; line[l_col++] = ' '; } } else { sprintf(&line[l_col], " "); l_col += 5; line[l_col++] = ' '; line[l_col++] = ' '; } /* ------------------------------------------------------------- * Move the record into the line buffer, expanding tabs. If record is * too long, the overflow is invoked. Line buffer will always be * terminated with \n\0. Non-printing characters will be shown as * , where hh is the hex code for the character. Where < occurs * in the text, it will be shown as itself. * -------------------------------------------------------------*/ #define store_c(c) \ if (l_col < page_width-1)\ line[l_col++] = c;\ else\ line[lmar-2] = '*'; overflow_flag = 0; /* reset overflow flag */ lmar = l_col; /* save this to make tab expansion 'sane' */ while ((c=rec[r_col++]) != NULL && !overflow_flag) { if (l_col < page_width-2) { switch (c) { case '\t': line[l_col++] = ' '; while (l_col < page_width-1 && ((l_col-lmar) % tab_width) != 0) line[l_col++] = ' '; break; case '\f': ff = 1; break; case '\n': break; case '<': store_c(c); break; default: if (isprint(c)) line[l_col++] = c; else { store_c('<'); sprintf(hex_code,"%02.2X",c); store_c(hex_code[0]); store_c(hex_code[1]); store_c('>'); } break; } } else { r_col--; strcpy(overflow, &rec[r_col]); overflow_flag = 1; } } line[l_col++] = '\n'; line[l_col++] = '\0'; return ff; } /*+/subr************************************************************ * NAME get_next_page - format the next page for printing * * DESCRIPTION * * RETURNS * * SEE ALSO * * EXAMPLE * *-*/ int get_next_page(p_in_file) struct FILE_INF *p_in_file; /* pointer to input file */ { char buf_in[lineBufDim];/* input buffer */ int buf_in_len; /* length of input line */ int col = 0; /* temp for column in line */ int eof = 0; /* end-of-file flag */ int ff; /* form feed flag */ int line = 0; /* temp for line in page */ line = 3; /* ------------------------------------------------------------- * read the lines from the file for one page, leaving room for 'footing' * If the overflow flag is set, input line is taken from overflow * buffer instead of file. * -------------------------------------------------------------*/ while (line < MAXLINE-3 && eof == 0) { if (overflow_flag) { strcpy(buf_in, overflow); } else if (fgets(buf_in, lineBufDim, p_in_file->ptr) != NULL ) { p_in_file->line_no++; } else eof = EOF; /* no more to read */ if (!eof) { if (strncmp(buf_in, "* NAME", 6) == 0) { sscanf(&buf_in[6], "%29s", p_in_file->subr); } else if (strncmp(buf_in, "/*/subhead ", 11) == 0) { int i; int c; i = 0; while (i< 29 && (c=buf_in[11+i])!='-') p_in_file->subr[i++] = c; p_in_file->subr[i] = '\0'; } ff = line_store(pages[pages_sub].lines[line++], buf_in, line_numbering ? p_in_file->line_no : 0); if (ff == 2 && line == 4) { /* FF on first line of page */ line--; /* throw away the FF; already new page */ } else if (ff) { /* if FF encountered, pad rest of page */ if (ff == 2) /* if no line printed, reset line_no */ line--; while (line < MAXLINE-3) pages[pages_sub].lines[line++][0] = '\n'; } } } /*---------------------------------------------------------------------------- * set up the footing *---------------------------------------------------------------------------*/ for(; linedate_mod[0] != '\0') ? "last modified:" : " ", p_in_file->date_mod); for (col=strlen(pages[pages_sub].lines[line]); colname); col++) pages[pages_sub].lines[line][col] = ' '; sprintf(&(pages[pages_sub].lines[line][page_width-2-strlen(p_in_file->name)]), "%s\n", p_in_file->name); line = 1; /* line 1 has path and subroutine name */ col = 0; while (colpath); for (col=strlen(pages[pages_sub].lines[line]); colsubr); col++) pages[pages_sub].lines[line][col] = ' '; sprintf(&(pages[pages_sub].lines[line][page_width-2-strlen(p_in_file->subr)]), "%s\n", p_in_file->subr); line++; /* line 2 is blank */ strcpy(pages[pages_sub].lines[line],"\n"); pages[pages_sub].page_no = ++npages; pages_sub++; page_no++; return eof; } /*+/subr************************************************************ * NAME get_option_args - get option args from command line * * DESCRIPTION * Gets the command line options, except for input file. If * an error is encountered, exit(1) is done. * *-*/ get_option_args(argc, argv, p_out_file) int argc; /* number of command line args */ char *argv[]; /* command line args */ struct FILE_INF *p_out_file; /* pointer to output file */ { int c; /* option letter */ int errflg = 0; /* error flag for options */ int i; /* temp for string length */ extern char *optarg; /* argument for an option */ extern int optind; /* index in argv of next option */ /* ------------------------------------------------------------- * Get any options from the command line and process them. If the * output file is specified, open it; abort if can't open it. * -------------------------------------------------------------*/ p_out_file->ptr = stdout; /* default for output is stdout */ while ((c = getopt (argc, argv, "l:t:o:P:w:")) != -1) { switch(c) { case'l': left_margin = atoi(optarg); /* -l left_margin */ case't': tab_width = atoi(optarg); /* -t tab_width */ break; case'o': if ((p_out_file->ptr = fopen (optarg, "w")) == NULL) { fprintf(stderr, "racPrint: can't open %s\n", optarg); exit(1); } break; case'P': point_size = atoi(optarg); /* -P PostScript point_size */ break; case'w': page_width = atoi(optarg); /* -w page_width */ if (page_width > MAXCHAR) page_width = MAXCHAR; break; case'?': errflg++; } } if (errflg) usage(); return; } usage() { fprintf(stderr, "Usage: racPrint [-l l_mar] [-t tabwid] [-o out_file]\ [-w width] [-P point] input_file\n"); exit(2); } /*+/subr************************************************************ * NAME get_input_file - get next input file from command line * * DESCRIPTION * Gets the next input file argument from the command line. If * an error is encountered, exit(1) is done. * * RETURNS * 0 if no input file arg found * 1 if there is an input file * *-*/ get_input_file(argc, argv, p_in_file) int argc; /* number of command line args */ char *argv[]; /* command line args */ struct FILE_INF *p_in_file; /* pointer to input file */ { char *ctime(); /* convert 'time_t' to 'ascii' */ char *getenv(); /* get environment variable value */ int i; /* temp for string length */ extern char *optarg; /* argument for an option */ extern int optind; /* index in argv of next option */ struct stat p_fstat; /* pointer to status buffer */ char *p_ftime; /* pointer to date and time of last mod */ /* ------------------------------------------------------------- * Set up pointers and do the opens for the next input file. * The file descriptor for the file is also captured and stored. * -------------------------------------------------------------*/ if (optind>=argc && file_no==0) { p_in_file->ptr = stdin; strcpy(p_in_file->name, "stdin"); strcpy(p_in_file->path, " "); p_in_file->date_mod[0] = '\0'; page_no=1; file_no++; strcpy(p_in_file->subr, " "); return(1); } else if (optindptr = fopen(argv[optind], "r")) == NULL) { fprintf(stderr, "racPrint: can't open %s\n", argv[optind]); exit(1); } strcpy(p_in_file->name,argv[optind]); strcpy(p_in_file->path,getenv("PWD")); /* get working directory */ STRCAT_CHK(p_in_file->path,"/"); /* append /filename to it */ STRCAT_CHK(p_in_file->path,p_in_file->name); p_in_file->fd = fileno(p_in_file->ptr); /* get file descriptor */ i = fstat(p_in_file->fd,&p_fstat); /* get file stat info */ p_ftime = ctime(&p_fstat.st_mtime); /* convert to ascii \n\0 */ p_ftime[strlen(p_ftime)-1] = '\0'; /* remove the \n */ strcpy(p_in_file->date_mod,p_ftime); /* date last modified */ optind++; page_no=1; file_no++; strcpy(p_in_file->subr, " "); return(1); } else return(0); } /*+/subr************************************************************ * NAME strcat_chk - concatenate strings, checking bounds * * DESCRIPTION * The sub_string is concatenated to the end of string. If * string isn't large enough, only the part of sub_string which * will fit is concatenated. Strings are \0 terminated. * *-*/ strcat_chk (string, size_of_string, sub_string) char string[]; /* 'master' string */ int size_of_string; /* declared length of string */ char sub_string[]; /* substring to concatenate */ { int i; i = 0; while (string[i] && i