version 1.9, 1998/02/16 07:54:29 |
version 1.10, 1998/04/25 23:02:28 |
|
|
/* $OpenBSD$ */ |
/* $OpenBSD$ */ |
/* $NetBSD: fmt.c,v 1.4 1995/09/01 01:29:41 jtc Exp $ */ |
|
|
|
/* |
/* Sensible version of fmt |
* Copyright (c) 1980, 1993 |
|
* The Regents of the University of California. All rights reserved. |
|
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Syntax: fmt [ options ] [ goal [ max ] ] [ filename ... ] |
* modification, are permitted provided that the following conditions |
* |
* are met: |
* Since the documentation for the original fmt is so poor, here |
* 1. Redistributions of source code must retain the above copyright |
* is an accurate description of what this one does. It's usually |
|
* the same. The *mechanism* used may differ from that suggested |
|
* here. Note that we are *not* entirely compatible with fmt, |
|
* because fmt gets so many things wrong. |
|
* |
|
* 1. Tabs are expanded, assuming 8-space tab stops. |
|
* If the `-t <n>' option is given, we assume <n>-space |
|
* tab stops instead. |
|
* Trailing blanks are removed from all lines. |
|
* x\b == nothing, for any x other than \b. |
|
* Other control characters are simply stripped. This |
|
* includes \r. |
|
* 2. Each line is split into leading whitespace and |
|
* everything else. Maximal consecutive sequences of |
|
* lines with the same leading whitespace are considered |
|
* to form paragraphs, except that a blank line is always |
|
* a paragraph to itself. |
|
* If the `-p' option is given then the first line of a |
|
* paragraph is permitted to have indentation different |
|
* from that of the other lines. |
|
* If the `-m' option is given then a line that looks |
|
* like a mail message header, if it is not immediately |
|
* preceded by a non-blank non-message-header line, is |
|
* taken to start a new paragraph, which also contains |
|
* any subsequent lines with non-empty leading whitespace. |
|
* 3. The "everything else" is split into words; a word |
|
* includes its trailing whitespace, and a word at the |
|
* end of a line is deemed to be followed by a single |
|
* space, or two spaces if it ends with a sentence-end |
|
* character. (See the `-d' option for how to change that.) |
|
* If the `-s' option has been given, then a word's trailing |
|
* whitespace is replaced by what it would have had if it |
|
* had occurred at end of line. |
|
* 4. Each paragraph is sent to standard output as follows. |
|
* We output the leading whitespace, and then enough words |
|
* to make the line length as near as possible to the goal |
|
* without exceeding the maximum. (If a single word would |
|
* exceed the maximum, we output that anyway.) Of course |
|
* the trailing whitespace of the last word is ignored. |
|
* We then emit a newline and start again if there are any |
|
* words left. |
|
* Note that for a blank line this translates as "We emit |
|
* a newline". |
|
* If the `-l <n>' option is given, then leading whitespace |
|
* is modified slightly: <n> spaces are replaced by a tab. |
|
* Indented paragraphs (see above under `-p') make matters |
|
* more complicated than this suggests. Actually every paragraph |
|
* has two `leading whitespace' values; the value for the first |
|
* line, and the value for the most recent line. (While processing |
|
* the first line, the two are equal. When `-p' has not been |
|
* given, they are always equal.) The leading whitespace |
|
* actually output is that of the first line (for the first |
|
* line of *output*) or that of the most recent line (for |
|
* all other lines of output). |
|
* When `-m' has been given, message header paragraphs are |
|
* taken as having first-leading-whitespace empty and |
|
* subsequent-leading-whitespace two spaces. |
|
* |
|
* Multiple input files are formatted one at a time, so that a file |
|
* never ends in the middle of a line. |
|
* |
|
* There's an alternative mode of operation, invoked by giving |
|
* the `-c' option. In that case we just center every line, |
|
* and most of the other options are ignored. This should |
|
* really be in a separate program, but we must stay compatible |
|
* with old `fmt'. |
|
* |
|
* QUERY: Should `-m' also try to do the right thing with quoted text? |
|
* QUERY: `-b' to treat backslashed whitespace as old `fmt' does? |
|
* QUERY: Option meaning `never join lines'? |
|
* QUERY: Option meaning `split in mid-word to avoid overlong lines'? |
|
* (Those last two might not be useful, since we have `fold'.) |
|
* |
|
* Differences from old `fmt': |
|
* |
|
* - We have many more options. Options that aren't understood |
|
* generate a lengthy usage message, rather than being |
|
* treated as filenames. |
|
* - Even with `-m', our handling of message headers is |
|
* significantly different. (And much better.) |
|
* - We don't treat `\ ' as non-word-breaking. |
|
* - Downward changes of indentation start new paragraphs |
|
* for us, as well as upward. (I think old `fmt' behaves |
|
* in the way it does in order to allow indented paragraphs, |
|
* but this is a broken way of making indented paragraphs |
|
* behave right.) |
|
* - Given the choice of going over or under |goal_length| |
|
* by the same amount, we go over; old `fmt' goes under. |
|
* - We treat `?' as ending a sentence, and not `:'. Old `fmt' |
|
* does the reverse. |
|
* - We return approved return codes. Old `fmt' returns |
|
* 1 for some errors, and *the number of unopenable files* |
|
* when that was all that went wrong. |
|
* - We have fewer crashes and more helpful error messages. |
|
* - We don't turn spaces into tabs at starts of lines unless |
|
* specifically requested. |
|
* - New `fmt' is somewhat smaller and slightly faster than |
|
* old `fmt'. |
|
* |
|
* Bugs: |
|
* |
|
* None known. There probably are some, though. |
|
* |
|
* Portability: |
|
* |
|
* I believe this code to be pretty portable. It does require |
|
* that you have `getopt'. If you need to include "getopt.h" |
|
* for this (e.g., if your system didn't come with `getopt' |
|
* and you installed it yourself) then you should arrange for |
|
* NEED_getopt_h to be #defined. |
|
* |
|
* Everything here should work OK even on nasty 16-bit |
|
* machines and nice 64-bit ones. However, it's only really |
|
* been tested on my FreeBSD machine. Your mileage may vary. |
|
*/ |
|
|
|
/* Copyright (c) 1997 Gareth McCaughan. All rights reserved. |
|
* |
|
* Redistribution and use of this code, in source or binary forms, |
|
* with or without modification, are permitted subject to the following |
|
* conditions: |
|
* |
|
* - Redistribution of source code must retain the above copyright |
* notice, this list of conditions and the following disclaimer. |
* notice, this list of conditions and the following disclaimer. |
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* 3. All advertising materials mentioning features or use of this software |
|
* must display the following acknowledgement: |
|
* This product includes software developed by the University of |
|
* California, Berkeley and its contributors. |
|
* 4. Neither the name of the University nor the names of its contributors |
|
* may be used to endorse or promote products derived from this software |
|
* without specific prior written permission. |
|
* |
* |
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
* - If you distribute modified source code it must also include |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
* a notice saying that it has been modified, and giving a brief |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
* description of what changes have been made. |
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
* |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
* Disclaimer: I am not responsible for the results of using this code. |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
* If it formats your hard disc, sends obscene messages to |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
* your boss and kills your children then that's your problem |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
* not mine. I give absolutely no warranty of any sort as to |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
* what the program will do, and absolutely refuse to be held |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
* liable for any consequences of your using it. |
* SUCH DAMAGE. |
* Thank you. Have a nice day. |
*/ |
*/ |
|
|
|
/* RCS change log: |
|
* Revision 1.5 1998/03/02 18:02:21 gjm11 |
|
* Minor changes for portability. |
|
* |
|
* Revision 1.4 1997/10/01 11:51:28 gjm11 |
|
* Repair broken indented-paragraph handling. |
|
* Add mail message header stuff. |
|
* Improve comments and layout. |
|
* Make usable with non-BSD systems. |
|
* Add revision display to usage message. |
|
* |
|
* Revision 1.3 1997/09/30 16:24:47 gjm11 |
|
* Add copyright notice, rcsid string and log message. |
|
* |
|
* Revision 1.2 1997/09/30 16:13:39 gjm11 |
|
* Add options: -d <chars>, -l <width>, -p, -s, -t <width>, -h . |
|
* Parse options with `getopt'. Clean up code generally. |
|
* Make comments more accurate. |
|
* |
|
* Revision 1.1 1997/09/30 11:29:57 gjm11 |
|
* Initial revision |
|
*/ |
|
|
#ifndef lint |
#ifndef lint |
static char copyright[] = |
static const char rcsid[] = |
"@(#) Copyright (c) 1980, 1993\n\ |
"$OpenBSD$"; |
The Regents of the University of California. All rights reserved.\n"; |
static const char copyright[] = |
|
"Copyright (c) 1997 Gareth McCaughan. All rights reserved.\n"; |
#endif /* not lint */ |
#endif /* not lint */ |
|
|
#ifndef lint |
/* Cater for BSD and non-BSD systems. |
#if 0 |
* I hate the C preprocessor. |
static char sccsid[] = "@(#)fmt.c 8.1 (Berkeley) 7/20/93"; |
*/ |
|
|
|
#undef HAVE_errx |
|
#undef HAVE_sysexits |
|
|
|
#ifdef unix |
|
# include <sys/param.h> |
|
# ifdef BSD |
|
# define HAVE_errx |
|
# if BSD >= 199306 |
|
# define HAVE_sysexits |
|
# endif |
|
# endif |
|
#endif |
|
|
|
#ifdef HAVE_errx |
|
# include <err.h> |
#else |
#else |
static char rcsid[] = "$OpenBSD$"; |
# define errx(rc,str) { fprintf(stderr,"fmt: %s\n",str); exit(rc); } |
#endif |
#endif |
#endif /* not lint */ |
|
|
|
|
#ifdef HAVE_sysexits |
|
# include <sysexits.h> |
|
#else |
|
# define EX_USAGE 1 |
|
# define EX_NOINPUT 1 |
|
# define EX_SOFTWARE 1 |
|
# define EX_OSERR 1 |
|
#endif |
|
|
#include <ctype.h> |
#include <ctype.h> |
#include <err.h> |
|
#include <locale.h> |
|
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
|
|
#ifdef __GNUC__ |
#ifdef NEED_getopt_h |
#define inline __inline |
# include "getopt.h" |
#else /* !__GNUC__ */ |
#endif |
#define inline |
|
#endif /* !__GNUC__ */ |
|
|
|
/* |
/* Something that, we hope, will never be a genuine line length, |
* fmt -- format the concatenation of input files or standard input |
* indentation etc. |
* onto standard output. Designed for use with Mail ~| |
|
* |
|
* Syntax : fmt [ goal [ max ] ] [ name ... ] |
|
* Authors: Kurt Shoens (UCB) 12/7/78; |
|
* Liz Allen (UMCP) 2/24/83 [Addition of goal length concept]. |
|
*/ |
*/ |
|
#define SILLY ((size_t)-1) |
|
|
/* LIZ@UOM 6/18/85 -- Don't need LENGTH any more. |
/* I used to use |strtoul| for this, but (1) not all systems have it |
* #define LENGTH 72 Max line length in output |
* and (2) it's probably better to use |strtol| to detect negative |
|
* numbers better. |
|
* If |fussyp==0| then we don't complain about non-numbers |
|
* (returning 0 instead), but we do complain about bad numbers. |
*/ |
*/ |
#define NOSTR ((char *) 0) /* Null string pointer for lint */ |
size_t get_positive(const char *s, const char *err_mess, int fussyP) { |
|
char *t; |
|
long result = strtol(s,&t,0); |
|
if (*t) { if (fussyP) goto Lose; else return 0; } |
|
if (result<=0) { Lose: errx(EX_USAGE, err_mess); } |
|
return (size_t) result; |
|
} |
|
|
/* LIZ@UOM 6/18/85 --New variables goal_length and max_length */ |
/* Just for the sake of linguistic purity: */ |
#define GOAL_LENGTH 65 |
|
#define MAX_LENGTH 75 |
|
int goal_length; /* Target or goal line length in output */ |
|
int max_length; /* Max line length in output */ |
|
int pfx; /* Current leading blank count */ |
|
int lineno; /* Current input line */ |
|
int mark; /* Last place we saw a head line */ |
|
int center; /* Did they ask to center lines? */ |
|
|
|
char *headnames[] = {"To", "Subject", "Cc", 0}; |
#ifdef BRITISH |
|
# define CENTER "centre" |
|
#else |
|
# define CENTER "center" |
|
#endif |
|
|
void fmt __P((FILE *)); |
/* Global variables */ |
void setout __P((void)); |
|
void prefix __P((char *)); |
|
void split __P((char *)); |
|
void pack __P((char *, int)); |
|
void oflush __P((void)); |
|
void tabulate __P((char *)); |
|
void leadin __P((void)); |
|
char *savestr __P((char *)); |
|
inline char *extstr __P((char *, int *, int)); |
|
int ispref __P((char *, char *)); |
|
int ishead __P((char *)); |
|
|
|
/* |
static int centerP=0; /* Try to center lines? */ |
* Drive the whole formatter by managing input files. Also, |
static size_t goal_length=0; /* Target length for output lines */ |
* cause initialization of the output stuff and flush it out |
static size_t max_length=0; /* Maximum length for output lines */ |
* at the end. |
static int coalesce_spaces_P=0; /* Coalesce multiple whitespace -> ' ' ? */ |
*/ |
static int allow_indented_paragraphs=0; /* Can first line have diff. ind.? */ |
|
static int tab_width=8; /* Number of spaces per tab stop */ |
|
static int output_tab_width=0; /* Ditto, when squashing leading spaces */ |
|
static char *sentence_enders=".?!"; /* Double-space after these */ |
|
static int grok_mail_headers=0; /* treat embedded mail headers magically? */ |
|
|
int |
static int n_errors=0; /* Number of failed files. Return on exit. */ |
main(argc, argv) |
static char *output_buffer=0; /* Output line will be built here */ |
int argc; |
static size_t x; /* Horizontal position in output line */ |
char **argv; |
static size_t x0; /* Ditto, ignoring leading whitespace */ |
{ |
static size_t pending_spaces; /* Spaces to add before next word */ |
register FILE *fi; |
static int output_in_paragraph=0; /* Any of current para written out yet? */ |
register int errs = 0; |
|
int number; /* LIZ@UOM 6/18/85 */ |
|
|
|
(void) setlocale(LC_CTYPE, ""); |
/* Prototypes */ |
|
|
goal_length = GOAL_LENGTH; |
static void process_named_file (const char *); |
max_length = MAX_LENGTH; |
static void process_stream (FILE *, const char *); |
setout(); |
static size_t indent_length (const char *, size_t); |
lineno = 1; |
static int might_be_header (const char *); |
mark = -10; |
static void new_paragraph (size_t, size_t); |
/* |
static void output_word (size_t, size_t, const char *, size_t, size_t); |
* LIZ@UOM 6/18/85 -- Check for goal and max length arguments |
static void output_indent (size_t); |
*/ |
static void center_stream (FILE *, const char *); |
if (argc > 1 && !strcmp(argv[1], "-c")) { |
static char * get_line (FILE *, size_t *); |
center++; |
static void * xrealloc (void *, size_t); |
argc--; |
|
argv++; |
|
} |
|
if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) { |
|
argv++; |
|
argc--; |
|
goal_length = abs(number); |
|
if (argc > 1 && (1 == (sscanf(argv[1], "%d", &number)))) { |
|
argv++; |
|
argc--; |
|
max_length = abs(number); |
|
} |
|
} |
|
if (max_length <= goal_length) |
|
errx(1, "Max length (%d) must be greater than goal length: %d", |
|
max_length, goal_length); |
|
if (argc < 2) { |
|
fmt(stdin); |
|
oflush(); |
|
exit(0); |
|
} |
|
while (--argc) { |
|
if ((fi = fopen(*++argv, "r")) == NULL) { |
|
perror(*argv); |
|
errs++; |
|
continue; |
|
} |
|
fmt(fi); |
|
fclose(fi); |
|
} |
|
oflush(); |
|
exit(errs); |
|
} |
|
|
|
/* |
#define XMALLOC(x) xrealloc(0,x) |
* Read up characters from the passed input file, forming lines, |
|
* doing ^H processing, expanding tabs, stripping trailing blanks, |
/* Here is perhaps the right place to mention that this code is |
* and sending each line down for analysis. |
* all in top-down order. Hence, |main| comes first. |
*/ |
*/ |
void |
int |
fmt(fi) |
main(int argc, char *argv[]) { |
FILE *fi; |
int ch; /* used for |getopt| processing */ |
{ |
|
static char *linebuf, *canonb; |
|
static int lbufsize, cbufsize; |
|
register char *cp, *cp2, cc; |
|
register int c, col; |
|
#define CHUNKSIZE 1024 |
|
|
|
canonb = malloc(CHUNKSIZE); |
/* 1. Grok parameters. */ |
if (canonb == 0) |
|
errx(1, "Ran out of memory"); |
|
|
|
if (center) { |
while ((ch = getopt(argc, argv, "cd:hl:mpst:")) != EOF) switch(ch) { |
register int len; |
case 'c': |
|
centerP = 1; |
|
continue; |
|
case 'd': |
|
sentence_enders = XMALLOC(strlen(optarg)+1); |
|
strcpy(sentence_enders, optarg); |
|
continue; |
|
case 'l': |
|
output_tab_width |
|
= get_positive(optarg, "output tab width must be positive", 1); |
|
continue; |
|
case 'm': |
|
grok_mail_headers = 1; |
|
continue; |
|
case 'p': |
|
allow_indented_paragraphs = 1; |
|
continue; |
|
case 's': |
|
coalesce_spaces_P = 1; |
|
continue; |
|
case 't': |
|
tab_width = get_positive(optarg, "tab width must be positive", 1); |
|
continue; |
|
case 'h': default: |
|
fprintf(stderr, |
|
"Usage: fmt [-cmps] [-d chars] [-l num] [-t num] [goal [maximum]] [file...]\n" |
|
"Options: -c " CENTER " each line instead of formatting\n" |
|
" -d <chars> double-space after <chars> at line end\n" |
|
" -l <n> turn each <n> spaces at start of line into a tab\n" |
|
" -m try to make sure mail header lines stay separate\n" |
|
" -p allow indented paragraphs\n" |
|
" -s coalesce whitespace inside lines\n" |
|
" -t <n> have tabs every <n> columns\n"); |
|
exit(ch=='h' ? 0 : EX_USAGE); |
|
} |
|
argc -= optind; argv += optind; |
|
|
linebuf = extstr(linebuf, &lbufsize, NULL); |
/* [ goal [ maximum ] ] */ |
for (;;) { |
|
len = 0; |
|
for (;;) { |
|
if (!fgets(linebuf + len, lbufsize - len, fi)) |
|
break; |
|
len = strlen(linebuf); |
|
if (linebuf[len-1] == '\n' || feof(fi)) |
|
break; |
|
linebuf = extstr(linebuf, &lbufsize, NULL); |
|
} |
|
if (len == 0) |
|
return; |
|
cp = linebuf; |
|
while (*cp && isspace(*cp)) |
|
cp++; |
|
cp2 = cp + strlen(cp) - 1; |
|
while (cp2 > cp && isspace(*cp2)) |
|
cp2--; |
|
if (cp == cp2) |
|
putchar('\n'); |
|
col = cp2 - cp; |
|
for (c = 0; c < (goal_length-col)/2; c++) |
|
putchar(' '); |
|
while (cp <= cp2) |
|
putchar(*cp++); |
|
putchar('\n'); |
|
} |
|
} |
|
c = getc(fi); |
|
while (c != EOF) { |
|
/* |
|
* Collect a line, doing ^H processing. |
|
* Leave tabs for now. |
|
*/ |
|
cp = linebuf; |
|
while (c != '\n' && c != EOF) { |
|
if (cp - linebuf >= lbufsize) { |
|
int offset = cp - linebuf; |
|
linebuf = extstr(linebuf, &lbufsize, NULL); |
|
cp = linebuf + offset; |
|
} |
|
if (c == '\b') { |
|
if (cp > linebuf) |
|
cp--; |
|
c = getc(fi); |
|
continue; |
|
} |
|
if (!isprint(c) && c != '\t') { |
|
c = getc(fi); |
|
continue; |
|
} |
|
*cp++ = c; |
|
c = getc(fi); |
|
} |
|
|
|
/* |
if (argc>0 |
* Toss anything remaining on the input line. |
&& (goal_length=get_positive(*argv,"goal length must be positive", 0)) |
*/ |
!= 0) { |
while (c != '\n' && c != EOF) |
--argc; ++argv; |
c = getc(fi); |
if (argc>0 |
|
&& (goal_length=get_positive(*argv,"max length must be positive", 0)) |
|
!= 0) { |
|
if (max_length<goal_length) |
|
errx(EX_USAGE, "max length must be >= goal length"); |
|
} |
|
} |
|
if (goal_length==0) goal_length = 65; |
|
if (max_length==0) max_length = goal_length+10; |
|
output_buffer = XMALLOC(max_length+1); /* really needn't be longer */ |
|
|
if (cp != NULL) { |
/* 2. Process files. */ |
*cp = '\0'; |
|
} else { |
|
putchar('\n'); |
|
c = getc(fi); |
|
continue; |
|
} |
|
|
|
/* |
if (argc>0) { |
* Expand tabs on the way to canonb. |
while (argc-->0) process_named_file(*argv++); |
*/ |
} |
col = 0; |
else { |
cp = linebuf; |
process_stream(stdin, "standard input"); |
cp2 = canonb; |
} |
while ((cc = *cp++)) { |
|
if (cc != '\t') { |
|
col++; |
|
if (cp2 - canonb >= cbufsize) { |
|
int offset = cp2 - canonb; |
|
canonb = extstr(canonb, &cbufsize, NULL); |
|
cp2 = canonb + offset; |
|
} |
|
*cp2++ = cc; |
|
continue; |
|
} |
|
do { |
|
if (cp2 - canonb >= cbufsize) { |
|
int offset = cp2 - canonb; |
|
canonb = extstr(canonb, &cbufsize, NULL); |
|
cp2 = canonb + offset; |
|
} |
|
*cp2++ = ' '; |
|
col++; |
|
} while ((col & 07) != 0); |
|
} |
|
|
|
/* |
/* We're done. */ |
* Swipe trailing blanks from the line. |
|
*/ |
|
for (cp2--; cp2 >= canonb && *cp2 == ' '; cp2--) |
|
; |
|
*++cp2 = '\0'; |
|
prefix(canonb); |
|
if (c != EOF) |
|
c = getc(fi); |
|
} |
|
} |
|
|
|
/* |
return n_errors ? EX_NOINPUT : 0; |
* Take a line devoid of tabs and other garbage and determine its |
|
* blank prefix. If the indent changes, call for a linebreak. |
|
* If the input line is blank, echo the blank line on the output. |
|
* Finally, if the line minus the prefix is a mail header, try to keep |
|
* it on a line by itself. |
|
*/ |
|
void |
|
prefix(line) |
|
char line[]; |
|
{ |
|
register char *cp, **hp; |
|
register int np, h; |
|
|
|
if (*line == '\0') { |
} |
oflush(); |
|
putchar('\n'); |
|
return; |
|
} |
|
for (cp = line; *cp == ' '; cp++) |
|
; |
|
np = cp - line; |
|
|
|
/* |
/* Process a single file, given its name. |
* The following horrible expression attempts to avoid linebreaks |
*/ |
* when the indent changes due to a paragraph. |
static void |
*/ |
process_named_file(const char *name) { |
if (np != pfx && (np > pfx || abs(pfx-np) > 8)) |
FILE *f=fopen(name, "r"); |
oflush(); |
if (!f) { perror(name); ++n_errors; } |
if ((h = ishead(cp))) |
else { |
oflush(), mark = lineno; |
process_stream(f, name); |
if (lineno - mark < 3 && lineno - mark > 0) |
fclose(f); |
for (hp = &headnames[0]; *hp != NULL; hp++) |
} |
if (ispref(*hp, cp)) { |
|
h = 1; |
|
oflush(); |
|
break; |
|
} |
|
if (!h && (h = (*cp == '.'))) |
|
oflush(); |
|
pfx = np; |
|
if (h) |
|
pack(cp, strlen(cp)); |
|
else |
|
split(cp); |
|
if (h) |
|
oflush(); |
|
lineno++; |
|
} |
} |
|
|
/* |
/* Types of mail header continuation lines: |
* Split up the passed line into output "words" which are |
|
* maximal strings of non-blanks with the blank separation |
|
* attached at the end. Pass these words along to the output |
|
* line packer. |
|
*/ |
*/ |
void |
typedef enum { |
split(line) |
hdr_ParagraphStart = -1, |
char line[]; |
hdr_NonHeader = 0, |
{ |
hdr_Header = 1, |
register char *cp, *cp2; |
hdr_Continuation = 2 |
static char *word; |
} HdrType; |
static int wordsize; |
|
int wordl; /* LIZ@UOM 6/18/85 */ |
|
|
|
if (strlen(line) >= wordsize) |
/* Process a stream. This is where the real work happens, |
word = extstr(word, &wordsize, strlen(line) + 1); |
* except that centering is handled separately. |
|
*/ |
|
static void |
|
process_stream(FILE *stream, const char *name) { |
|
size_t last_indent=SILLY; /* how many spaces in last indent? */ |
|
size_t para_line_number=0; /* how many lines already read in this para? */ |
|
size_t first_indent=SILLY; /* indentation of line 0 of paragraph */ |
|
HdrType prev_header_type=hdr_ParagraphStart; |
|
/* ^-- header_type of previous line; -1 at para start */ |
|
char *line; |
|
size_t length; |
|
|
cp = line; |
if (centerP) { center_stream(stream, name); return; } |
while (*cp) { |
while ((line=get_line(stream,&length)) != NULL) { |
cp2 = word; |
size_t np=indent_length(line, length); |
wordl = 0; /* LIZ@UOM 6/18/85 */ |
{ HdrType header_type=hdr_NonHeader; |
|
if (grok_mail_headers && prev_header_type!=hdr_NonHeader) { |
|
if (np==0 && might_be_header(line)) |
|
header_type = hdr_Header; |
|
else if (np>0 && prev_header_type>hdr_NonHeader) |
|
header_type = hdr_Continuation; |
|
} |
|
/* We need a new paragraph if and only if: |
|
* this line is blank, |
|
* OR it's a mail header, |
|
* OR it's not a mail header AND the last line was one, |
|
* OR the indentation has changed |
|
* AND the line isn't a mail header continuation line |
|
* AND this isn't the second line of an indented paragraph. |
|
*/ |
|
if ( length==0 |
|
|| header_type==hdr_Header |
|
|| (header_type==hdr_NonHeader && prev_header_type>hdr_NonHeader) |
|
|| (np!=last_indent |
|
&& header_type != hdr_Continuation |
|
&& (!allow_indented_paragraphs || para_line_number != 1)) ) { |
|
new_paragraph(output_in_paragraph ? last_indent : first_indent, np); |
|
para_line_number = 0; |
|
first_indent = np; |
|
last_indent = np; |
|
if (header_type==hdr_Header) last_indent=2; /* for cont. lines */ |
|
if (length==0) { |
|
putchar('\n'); |
|
prev_header_type=hdr_ParagraphStart; |
|
continue; |
|
} |
|
} |
|
else { |
|
/* If this is an indented paragraph other than a mail header |
|
* continuation, set |last_indent|. |
|
*/ |
|
if (np != last_indent && header_type != hdr_Continuation) |
|
last_indent=np; |
|
} |
|
prev_header_type = header_type; |
|
} |
|
|
/* |
{ size_t n=np; |
* Collect a 'word,' allowing it to contain escaped white |
while (n<length) { |
* space. |
/* Find word end and count spaces after it */ |
*/ |
size_t word_length=0, space_length=0; |
while (*cp && *cp != ' ') { |
while (n+word_length < length && line[n+word_length] != ' ') |
if (*cp == '\\' && isspace(cp[1])) |
++word_length; |
*cp2++ = *cp++, wordl++; |
space_length = word_length; |
*cp2++ = *cp++; |
while (n+space_length < length && line[n+space_length] == ' ') |
wordl++;/* LIZ@UOM 6/18/85 */ |
++space_length; |
} |
/* Send the word to the output machinery. */ |
|
output_word(first_indent, last_indent, |
/* |
line+n, word_length, space_length-word_length); |
* Guarantee a space at end of line. Two spaces after end of |
n += space_length; |
* sentence punctuation. |
} |
*/ |
} |
if (*cp == '\0') { |
++para_line_number; |
*cp2++ = ' '; |
} |
if (strchr(".:!", cp[-1])) |
new_paragraph(output_in_paragraph ? last_indent : first_indent, 0); |
*cp2++ = ' '; |
if (ferror(stream)) { perror(name); ++n_errors; } |
} |
|
while (*cp == ' ') |
|
*cp2++ = *cp++; |
|
*cp2 = '\0'; |
|
/* |
|
* LIZ@UOM 6/18/85 pack(word); |
|
*/ |
|
pack(word, wordl); |
|
} |
|
} |
} |
|
|
/* |
/* How long is the indent on this line? |
* Output section. |
|
* Build up line images from the words passed in. Prefix |
|
* each line with correct number of blanks. The buffer "outbuf" |
|
* contains the current partial line image, including prefixed blanks. |
|
* "outp" points to the next available space therein. When outp is NOSTR, |
|
* there ain't nothing in there yet. At the bottom of this whole mess, |
|
* leading tabs are reinserted. |
|
*/ |
*/ |
static char *outbuf; /* Sandbagged output line image */ |
static size_t |
static int obufsize; /* Size of outbuf */ |
indent_length(const char *line, size_t length) { |
static char *outp; /* Pointer in above */ |
size_t n=0; |
|
while (n<length && *line++ == ' ') ++n; |
|
return n; |
|
} |
|
|
/* |
/* Might this line be a mail header? |
* Initialize the output section. |
* We deem a line to be a possible header if it matches the |
|
* Perl regexp /^[A-Z][-A-Za-z0-9]*:\s/. This is *not* the same |
|
* as in RFC whatever-number-it-is; we want to be gratuitously |
|
* conservative to avoid mangling ordinary civilised text. |
*/ |
*/ |
void |
static int |
setout() |
might_be_header(const char *line) { |
{ |
if (!isupper(*line++)) return 0; |
outp = NOSTR; |
while (*line && (isalnum(*line) || *line=='-')) ++line; |
|
return (*line==':' && isspace(line[1])); |
} |
} |
|
|
/* |
/* Begin a new paragraph with an indent of |indent| spaces. |
* Pack a word onto the output line. If this is the beginning of |
|
* the line, push on the appropriately-sized string of blanks first. |
|
* If the word won't fit on the current line, flush and begin a new |
|
* line. If the word is too long to fit all by itself on a line, |
|
* just give it its own and hope for the best. |
|
* |
|
* LIZ@UOM 6/18/85 -- If the new word will fit in at less than the |
|
* goal length, take it. If not, then check to see if the line |
|
* will be over the max length; if so put the word on the next |
|
* line. If not, check to see if the line will be closer to the |
|
* goal length with or without the word and take it or put it on |
|
* the next line accordingly. |
|
*/ |
*/ |
|
static void |
/* |
new_paragraph(size_t old_indent, size_t indent) { |
* LIZ@UOM 6/18/85 -- pass in the length of the word as well |
if (x0) { |
* pack(word) |
if (old_indent>0) output_indent(old_indent); |
* char word[]; |
fwrite(output_buffer, 1, x0, stdout); |
*/ |
putchar('\n'); |
void |
} |
pack(word, wl) |
x=indent; x0=0; pending_spaces=0; |
char word[]; |
output_in_paragraph = 0; |
int wl; |
|
{ |
|
register char *cp; |
|
register int s, t; |
|
|
|
if (outp == NOSTR) |
|
leadin(); |
|
/* |
|
* LIZ@UOM 6/18/85 -- change condition to check goal_length; s is the |
|
* length of the line before the word is added; t is now the length |
|
* of the line after the word is added |
|
* t = strlen(word); |
|
* if (t+s <= LENGTH) |
|
*/ |
|
s = outp - outbuf; |
|
t = wl + s; |
|
if (t + 1 > obufsize) { |
|
outbuf = extstr(outbuf, &obufsize, t + 1); |
|
outp = outbuf + s; |
|
} |
|
if ((t <= goal_length) || |
|
((t <= max_length) && (t - goal_length <= goal_length - s))) { |
|
/* |
|
* In like flint! |
|
*/ |
|
for (cp = word; *cp; *outp++ = *cp++) |
|
; |
|
return; |
|
} |
|
if (s > pfx) { |
|
oflush(); |
|
leadin(); |
|
} |
|
for (cp = word; *cp; *outp++ = *cp++) |
|
; |
|
} |
} |
|
|
/* |
/* Output spaces or tabs for leading indentation. |
* If there is anything on the current output line, send it on |
|
* its way. Set outp to NOSTR to indicate the absence of the current |
|
* line prefix. |
|
*/ |
*/ |
void |
static void |
oflush() |
output_indent(size_t n_spaces) { |
{ |
if (output_tab_width) { |
if (outp == NOSTR) |
while (n_spaces >= output_tab_width) { |
return; |
putchar('\t'); |
*outp = '\0'; |
n_spaces -= output_tab_width; |
tabulate(outbuf); |
} |
outp = NOSTR; |
} |
|
while (n_spaces-- > 0) putchar(' '); |
} |
} |
|
|
/* |
/* Output a single word, or add it to the buffer. |
* Take the passed line buffer, insert leading tabs where possible, and |
* indent0 and indent1 are the indents to use on the first and subsequent |
* output on standard output (finally). |
* lines of a paragraph. They'll often be the same, of course. |
*/ |
*/ |
void |
static void |
tabulate(line) |
output_word(size_t indent0, size_t indent1, const char *word, size_t length, size_t spaces) { |
char line[]; |
size_t new_x = x+pending_spaces+length; |
{ |
size_t indent = output_in_paragraph ? indent1 : indent0; |
register char *cp; |
|
register int b, t; |
|
|
|
/* |
/* If either |spaces==0| (at end of line) or |coalesce_spaces_P| |
* Toss trailing blanks in the output line. |
* (squashing internal whitespace), then add just one space; |
*/ |
* except that if the last character was a sentence-ender we |
cp = line + strlen(line) - 1; |
* actually add two spaces. |
while (cp >= line && *cp == ' ') |
*/ |
cp--; |
if (coalesce_spaces_P || spaces==0) |
*++cp = '\0'; |
spaces = strchr(sentence_enders, word[length-1]) ? 2 : 1; |
|
|
/* |
if (new_x<=goal_length) { |
* Count the leading blank space and tabulate. |
/* After adding the word we still aren't at the goal length, |
*/ |
* so clearly we add it to the buffer rather than outputing it. |
for (cp = line; *cp == ' '; cp++) |
*/ |
; |
memset(output_buffer+x0, ' ', pending_spaces); |
b = cp-line; |
x0 += pending_spaces; x += pending_spaces; |
t = b >> 3; |
memcpy(output_buffer+x0, word, length); |
b &= 07; |
x0 += length; x += length; |
if (t > 0) |
pending_spaces = spaces; |
do |
} |
putc('\t', stdout); |
else { |
while (--t); |
/* Adding the word takes us past the goal. Print the line-so-far, |
if (b > 0) |
* and the word too iff either (1) the lsf is empty or (2) that |
do |
* makes us nearer the goal but doesn't take us over the limit, |
putc(' ', stdout); |
* or (3) the word on its own takes us over the limit. |
while (--b); |
* In case (3) we put a newline in between. |
while (*cp) |
*/ |
putc(*cp++, stdout); |
if (indent>0) output_indent(indent); |
putc('\n', stdout); |
fwrite(output_buffer, 1, x0, stdout); |
|
if (x0==0 || (new_x <= max_length && new_x-goal_length <= goal_length-x)) { |
|
printf("%*s", pending_spaces, ""); |
|
goto write_out_word; |
|
} |
|
else { |
|
/* If the word takes us over the limit on its own, just |
|
* spit it out and don't bother buffering it. |
|
*/ |
|
if (indent+length > max_length) { |
|
putchar('\n'); |
|
if (indent>0) output_indent(indent); |
|
write_out_word: |
|
fwrite(word, 1, length, stdout); |
|
x0 = 0; x = indent1; pending_spaces = 0; |
|
} |
|
else { |
|
memcpy(output_buffer, word, length); |
|
x0 = length; x = length+indent1; pending_spaces = spaces; |
|
} |
|
} |
|
putchar('\n'); |
|
output_in_paragraph = 1; |
|
} |
} |
} |
|
|
/* |
/* Process a stream, but just center its lines rather than trying to |
* Initialize the output line with the appropriate number of |
* format them neatly. |
* leading blanks. |
|
*/ |
*/ |
void |
static void |
leadin() |
center_stream(FILE *stream, const char *name) { |
{ |
char *line; |
register int b; |
size_t length; |
register char *cp; |
while ((line=get_line(stream, &length)) != 0) { |
|
size_t l=length; |
if (obufsize == 0 || (outp != NULL && outp - outbuf <= pfx)) |
while (l>0 && isspace(*line)) { ++line; --l; } |
outbuf = extstr(outbuf, &obufsize, pfx); |
length=l; |
for (b = 0, cp = outbuf; b < pfx; b++) |
while (l<goal_length) { putchar(' '); l+=2; } |
*cp++ = ' '; |
fwrite(line, 1, length, stdout); |
outp = cp; |
putchar('\n'); |
|
} |
|
if (ferror(stream)) { perror(name); ++n_errors; } |
} |
} |
|
|
/* |
/* Get a single line from a stream. Expand tabs, strip control |
* Save a string in dynamic space. |
* characters and trailing whitespace, and handle backspaces. |
* This little goodie is needed for |
* Return the address of the buffer containing the line, and |
* a headline detector in head.c |
* put the length of the line in |lengthp|. |
|
* This can cope with arbitrarily long lines, and with lines |
|
* without terminating \n. |
|
* If there are no characters left or an error happens, we |
|
* return 0. |
|
* Don't confuse |spaces_pending| here with the global |
|
* |pending_spaces|. |
*/ |
*/ |
char * |
static char * |
savestr(str) |
get_line(FILE *stream, size_t *lengthp) { |
char str[]; |
static char *buf=NULL; |
{ |
static size_t length=0; |
char *top; |
size_t len=0; |
|
int ch; |
|
size_t spaces_pending=0; |
|
|
top = strdup(str); |
if (buf==NULL) { length=100; buf=XMALLOC(length); } |
if (top == NOSTR) |
while ((ch=getc(stream)) != '\n' && ch != EOF) { |
errx(1, "Ran out of memory"); |
if (ch==' ') ++spaces_pending; |
return (top); |
else if (isprint(ch)) { |
|
while (len+spaces_pending >= length) { |
|
length*=2; buf=xrealloc(buf, length); |
|
} |
|
while (spaces_pending > 0) { --spaces_pending; buf[len++]=' '; } |
|
buf[len++] = ch; |
|
} |
|
else if (ch=='\t') |
|
spaces_pending += tab_width - (len+spaces_pending)%tab_width; |
|
else if (ch=='\b') { if (len) --len; } |
|
} |
|
*lengthp=len; |
|
return (len>0 || ch!=EOF) ? buf : 0; |
} |
} |
|
|
/* |
/* (Re)allocate some memory, exiting with an error if we can't. |
* Is s1 a prefix of s2?? |
|
*/ |
*/ |
int |
static void * |
ispref(s1, s2) |
xrealloc(void *ptr, size_t nbytes) { |
register char *s1, *s2; |
void *p = realloc(ptr, nbytes); |
{ |
if (p == NULL) errx(EX_OSERR, "out of memory"); |
|
return p; |
while (*s1++ == *s2) |
|
; |
|
return (*s1 == '\0'); |
|
} |
|
|
|
inline char * |
|
extstr(str, size, gsize) |
|
char *str; |
|
int *size; |
|
int gsize; |
|
{ |
|
do { |
|
*size += CHUNKSIZE; |
|
} while (gsize && *size < gsize); |
|
|
|
if ((str = realloc(str, *size)) == NULL) |
|
errx(1, "Ran out of memory"); |
|
|
|
return(str); |
|
} |
} |