commands/elvis/fmt.c
/* fmt.c */

/* usage: fmt [-width] [files]...
 *
 * Fmt rearrages text in order to make each line have roughly the
 * same width.  Indentation and word spacing is preserved.
 *
 * The default width is 72 characters, but you can override that via -width.
 * If no files are given on the command line, then it reads stdin.
 */

#include <stdio.h>

#ifndef TRUE
# define TRUE	1
# define FALSE	0
#endif



int	width = 72;	/* the desired line width */
int	isblank;	/* is the current output line blank? */
int	indent;		/* width of the indentation */
char	ind[512];	/* indentation text */
char	word[1024];	/* word buffer */

/* This function displays a usage message and quits */
void usage()
{
	fprintf(stderr, "usage: fmt [-width] [files]...\n");
	exit(2);
}



/* This function outputs a single word.  It takes care of spacing and the
 * newlines within a paragraph.
 */
void putword()
{
	int		i;		/* index into word[], or whatever */
	int		ww;		/* width of the word */
	int		sw;		/* width of spacing after word */
	static int	psw;		/* space width of previous word */
	static int	tab;		/* the width of text already written */


	/* separate the word and its spacing */
	for (ww = 0; word[ww] && word[ww] != ' '; ww++)
	{
	}
	sw = strlen(word) - ww;
	word[ww] = '\0';

	/* if no spacing (that is, the word was at the end of the line) then
	 * assume 1 space unless the last char of the word was punctuation
	 */
	if (sw == 0)
	{
		sw = 1;
		if (word[ww - 1] == '.' || word[ww - 1] == '?' || word[ww - 1] == '!')
			sw = 2;
	}

	/* if this is the first word on the line... */
	if (isblank)
	{
		/* output the indentation first */
		fputs(ind, stdout);
		tab = indent;
	}
	else /* text has already been written to this output line */
	{
		/* will the word fit on this line? */
		if (psw + ww + tab <= width)
		{
			/* yes - so write the previous word's spacing */
			for (i = 0; i < psw; i++)
			{
				putchar(' ');
			}
			tab += psw;
		}
		else
		{
			/* no, so write a newline and the indentation */
			putchar('\n');
			fputs(ind, stdout);
			tab = indent;
		}
	}

	/* write the word itself */
	fputs(word, stdout);
	tab += ww;

	/* remember this word's spacing */
	psw = sw;

	/* this output line isn't blank anymore. */
	isblank = FALSE;
}



/* This function reformats text. */
void fmt(in)
	FILE	*in;		/* the name of the input stream */
{
	int	ch;		/* character from input stream */
	int	prevch;		/* the previous character in the loop */
	int	i;		/* index into ind[] or word[] */
	int	inword;		/* boolean: are we between indent & newline? */


	/* for each character in the stream... */
	for (indent = -1, isblank = TRUE, inword = FALSE, i = 0, prevch = '\n';
	     (ch = getc(in)) != EOF;
	     prevch = ch)
	{
		/* is this the end of a line? */
		if (ch == '\n')
		{
			/* if end of last word in the input line */
			if (inword)
			{
				/* if it really is a word */
				if (i > 0)
				{
					/* output it */
					word[i] = '\0';
					putword();
				}
			}
			else /* blank line in input */
			{
				/* finish the previous paragraph */
				if (!isblank)
				{
					putchar('\n');
					isblank = TRUE;
				}

				/* output a blank line */
				putchar('\n');
			}

			/* continue with next input line... */
			indent = -1;
			i = 0;
			inword = FALSE;
			continue;
		}

		/* if we're expecting indentation now... */
		if (indent < 0)
		{
			/* if this is part of the indentation... */
			if (ch == ' ' || ch == '\t')
			{
				/* remember it */
				ind[i++] = ch;
			}
			else /* end of indentation */
			{
				/* mark the end of the indentation string */
				ind[i] = '\0';

				/* calculate the width of the indentation */
				for (i = indent = 0; ind[i]; i++)
				{
					if (ind[i] == '\t')
						indent = (indent | 7) + 1;
					else
						indent++;
				}

				/* reset the word index */
				i = 0;

				/* reprocess that last character */
				ungetc(ch, in);
			}

			/* continue in the for-loop */
			continue;
		}

		/* if we get here, we're either in a word or in the space
		 * after a word.
		 */
		inword = TRUE;

		/* is this the start of a new word? */
		if (ch != ' ' && prevch == ' ')
		{
			/* yes!  output the previous word */
			word[i] = '\0';
			putword();

			/* reset `i' to the start of the word[] buffer */
			i = 0;
		}
		word[i++] = ch;
	}

	/* if necessary, write a final newline */
	if (!isblank)
	{
		putchar('\n');
		isblank = TRUE;
	}
}





int main(argc, argv)
	int	argc;
	char	**argv;
{
	FILE	*in;	/* an input stream */
	int	error;	/* if non-zero, then an error occurred */
	int	i;


	/* handle the -width flag, if given */
	if (argc > 1 && argv[1][0] == '-')
	{
		width = atoi(argv[1] + 1);
		if (width <= 0)
		{
			usage();
		}
		argc--;
		argv++;
	}

	/* if no filenames given, then process stdin */
	if (argc == 1)
	{
		fmt(stdin);
	}
	else /* one or more filenames given */
	{
		for (error = 0, i = 1; i < argc; i++)
		{
			in = fopen(argv[i], "r");
			if (!in)
			{
				perror(argv[i]);
				error = 3;
			}
			else
			{
				fmt(in);
				fclose(in);
			}
		}
	}

	/* exit, possibly indicating an error */
	exit(error);
	/*NOTREACHED*/
}
