version 1.20, 2003/12/01 15:34:26 |
version 1.21, 2004/04/01 23:14:19 |
|
|
* (returning 0 instead), but we do complain about bad numbers. |
* (returning 0 instead), but we do complain about bad numbers. |
*/ |
*/ |
static size_t |
static size_t |
get_positive(const char *s, const char *err_mess, int fussyP) { |
get_positive(const char *s, const char *err_mess, int fussyP) |
char *t; |
{ |
long result = strtol(s,&t,0); |
char *t; |
if (*t) { if (fussyP) goto Lose; else return 0; } |
long result = strtol(s, &t, 0); |
if (result<=0) { Lose: errx(EX_USAGE, "%s", err_mess); } |
|
return (size_t) result; |
if (*t) { |
|
if (fussyP) |
|
goto Lose; |
|
else |
|
return 0; |
|
} |
|
if (result <= 0) { |
|
Lose: |
|
errx(EX_USAGE, "%s", err_mess); |
|
} |
|
|
|
return (size_t) result; |
} |
} |
|
|
/* Global variables */ |
/* Global variables */ |
|
|
static int centerP=0; /* Try to center lines? */ |
static int centerP = 0; /* Try to center lines? */ |
static size_t goal_length=0; /* Target length for output lines */ |
static size_t goal_length = 0; /* Target length for output lines */ |
static size_t max_length=0; /* Maximum length for output lines */ |
static size_t max_length = 0; /* Maximum length for output lines */ |
static int coalesce_spaces_P=0; /* Coalesce multiple whitespace -> ' ' ? */ |
static int coalesce_spaces_P = 0; /* Coalesce multiple whitespace -> ' ' ? */ |
static int allow_indented_paragraphs=0; /* Can first line have diff. ind.? */ |
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 tab_width = 8; /* Number of spaces per tab stop */ |
static size_t output_tab_width=0; /* Ditto, when squashing leading spaces */ |
static size_t output_tab_width = 0; /* Ditto, when squashing leading spaces */ |
static const char *sentence_enders=".?!"; /* Double-space after these */ |
static const char *sentence_enders = ".?!"; /* Double-space after these */ |
static int grok_mail_headers=0; /* treat embedded mail headers magically? */ |
static int grok_mail_headers = 0; /* treat embedded mail headers magically? */ |
static int format_troff=0; /* Format troff? */ |
static int format_troff = 0; /* Format troff? */ |
|
|
static int n_errors=0; /* Number of failed files. Return on exit. */ |
static int n_errors = 0; /* Number of failed files. Return on exit. */ |
static char *output_buffer=0; /* Output line will be built here */ |
static char *output_buffer = NULL; /* Output line will be built here */ |
static size_t x; /* Horizontal position in output line */ |
static size_t x; /* Horizontal position in output line */ |
static size_t x0; /* Ditto, ignoring leading whitespace */ |
static size_t x0; /* Ditto, ignoring leading whitespace */ |
static size_t pending_spaces; /* Spaces to add before next word */ |
static size_t pending_spaces; /* Spaces to add before next word */ |
static int output_in_paragraph=0; /* Any of current para written out yet? */ |
static int output_in_paragraph = 0; /* Any of current para written out yet? */ |
|
|
/* Prototypes */ |
/* Prototypes */ |
|
|
static void process_named_file(const char *); |
static void process_named_file(const char *); |
static void process_stream(FILE *, const char *); |
static void process_stream(FILE *, const char *); |
static size_t indent_length(const char *, size_t); |
static size_t indent_length(const char *, size_t); |
static int might_be_header(const unsigned char *); |
static int might_be_header(const unsigned char *); |
static void new_paragraph(size_t, size_t); |
static void new_paragraph(size_t, size_t); |
static void output_word(size_t, size_t, const char *, size_t, size_t); |
static void output_word(size_t, size_t, const char *, size_t, size_t); |
static void output_indent(size_t); |
static void output_indent(size_t); |
static void center_stream(FILE *, const char *); |
static void center_stream(FILE *, const char *); |
static char * get_line(FILE *, size_t *); |
static char *get_line(FILE *, size_t *); |
static void * xrealloc(void *, size_t); |
static void *xrealloc(void *, size_t); |
|
void usage(void); |
|
|
#define XMALLOC(x) xrealloc(0,x) |
#define XMALLOC(x) xrealloc(0, x) |
|
|
/* Here is perhaps the right place to mention that this code is |
/* Here is perhaps the right place to mention that this code is |
* all in top-down order. Hence, |main| comes first. |
* all in top-down order. Hence, |main| comes first. |
*/ |
*/ |
int |
int |
main(int argc, char *argv[]) { |
main(int argc, char *argv[]) |
int ch; /* used for |getopt| processing */ |
{ |
|
int ch; /* used for |getopt| processing */ |
|
|
|
(void)setlocale(LC_CTYPE, ""); |
|
|
(void)setlocale(LC_CTYPE, ""); |
/* 1. Grok parameters. */ |
|
while ((ch = getopt(argc, argv, "0123456789cd:hl:mnpst:w:")) != -1) { |
|
switch (ch) { |
|
case 'c': |
|
centerP = 1; |
|
break; |
|
case 'd': |
|
sentence_enders = optarg; |
|
break; |
|
case 'l': |
|
output_tab_width |
|
= get_positive(optarg, "output tab width must be positive", 1); |
|
break; |
|
case 'm': |
|
grok_mail_headers = 1; |
|
break; |
|
case 'n': |
|
format_troff = 1; |
|
break; |
|
case 'p': |
|
allow_indented_paragraphs = 1; |
|
break; |
|
case 's': |
|
coalesce_spaces_P = 1; |
|
break; |
|
case 't': |
|
tab_width = get_positive(optarg, "tab width must be positive", 1); |
|
break; |
|
case 'w': |
|
goal_length = get_positive(optarg, "width must be positive", 1); |
|
max_length = goal_length; |
|
break; |
|
case '0': case '1': case '2': case '3': case '4': case '5': |
|
case '6': case '7': case '8': case '9': |
|
/* XXX this is not a stylistically approved use of getopt() */ |
|
if (goal_length == 0) { |
|
char *p; |
|
|
/* 1. Grok parameters. */ |
p = argv[optind - 1]; |
|
if (p[0] == '-' && p[1] == ch && !p[2]) |
|
goal_length = get_positive(++p, "width must be nonzero", 1); |
|
else |
|
goal_length = get_positive(argv[optind]+1, |
|
"width must be nonzero", 1); |
|
max_length = goal_length; |
|
} |
|
break; |
|
case 'h': |
|
default: |
|
usage(); |
|
/* NOT REACHED */ |
|
} |
|
} |
|
|
while ((ch = getopt(argc, argv, "0123456789cd:hl:mnpst:w:")) != -1) |
argc -= optind; |
switch(ch) { |
argv += optind; |
case 'c': |
|
centerP = 1; |
|
continue; |
|
case 'd': |
|
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 'n': |
|
format_troff = 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 'w': |
|
goal_length = get_positive(optarg, "width must be positive", 1); |
|
max_length = goal_length; |
|
continue; |
|
case '0': case '1': case '2': case '3': case '4': case '5': |
|
case '6': case '7': case '8': case '9': |
|
/* XXX this is not a stylistically approved use of getopt() */ |
|
if (goal_length==0) { |
|
char *p; |
|
p = argv[optind - 1]; |
|
if (p[0] == '-' && p[1] == ch && !p[2]) |
|
goal_length = get_positive(++p, "width must be nonzero", 1); |
|
else |
|
goal_length = get_positive(argv[optind]+1, |
|
"width must be nonzero", 1); |
|
max_length = goal_length; |
|
} |
|
continue; |
|
case 'h': default: |
|
fprintf(stderr, |
|
"Usage: fmt [-cmps] [-d chars] [-l num] [-t num]\n" |
|
" [-w width | -width | 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" |
|
" -n format lines beginning with a dot\n" |
|
" -p allow indented paragraphs\n" |
|
" -s coalesce whitespace inside lines\n" |
|
" -t <n> have tabs every <n> columns\n" |
|
" -w <n> set maximum width to <n>\n" |
|
" goal set target width to goal\n"); |
|
exit(ch=='h' ? 0 : EX_USAGE); |
|
} |
|
argc -= optind; argv += optind; |
|
|
|
/* [ goal [ maximum ] ] */ |
/* [ goal [ maximum ] ] */ |
|
if (argc > 0 && goal_length == 0 && |
|
(goal_length = get_positive(*argv,"goal length must be positive", 0)) != 0) { |
|
--argc; |
|
++argv; |
|
if (argc > 0 && (max_length = get_positive(*argv,"max length must be positive", 0)) != 0) { |
|
--argc; |
|
++argv; |
|
if (max_length < goal_length) |
|
errx(EX_USAGE, "max length must be >= goal length"); |
|
} |
|
} |
|
|
if (argc>0 && goal_length==0 |
if (goal_length == 0) |
&& (goal_length=get_positive(*argv,"goal length must be positive", 0)) |
goal_length = 65; |
!= 0) { |
if (max_length == 0) |
--argc; ++argv; |
max_length = goal_length+10; |
if (argc>0 |
output_buffer = XMALLOC(max_length+1); /* really needn't be longer */ |
&& (max_length=get_positive(*argv,"max length must be positive", 0)) |
|
!= 0) { |
|
--argc; ++argv; |
|
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 */ |
|
|
|
/* 2. Process files. */ |
/* 2. Process files. */ |
|
|
if (argc>0) { |
if (argc > 0) { |
while (argc-->0) process_named_file(*argv++); |
while (argc-- > 0) |
} |
process_named_file(*argv++); |
else { |
} else { |
process_stream(stdin, "standard input"); |
process_stream(stdin, "standard input"); |
} |
} |
|
|
/* We're done. */ |
/* We're done. */ |
|
return n_errors ? EX_NOINPUT : 0; |
|
|
return n_errors ? EX_NOINPUT : 0; |
|
|
|
} |
} |
|
|
/* Process a single file, given its name. |
/* Process a single file, given its name. |
*/ |
*/ |
static void |
static void |
process_named_file(const char *name) { |
process_named_file(const char *name) |
FILE *f=fopen(name, "r"); |
{ |
if (!f) { perror(name); ++n_errors; } |
FILE *f; |
else { |
|
process_stream(f, name); |
if ((f = fopen(name, "r")) == NULL) { |
fclose(f); |
perror(name); |
} |
++n_errors; |
|
} else { |
|
process_stream(f, name); |
|
fclose(f); |
|
} |
} |
} |
|
|
/* Types of mail header continuation lines: |
/* Types of mail header continuation lines: |
*/ |
*/ |
typedef enum { |
typedef enum { |
hdr_ParagraphStart = -1, |
hdr_ParagraphStart = -1, |
hdr_NonHeader = 0, |
hdr_NonHeader = 0, |
hdr_Header = 1, |
hdr_Header = 1, |
hdr_Continuation = 2 |
hdr_Continuation = 2 |
} HdrType; |
} HdrType; |
|
|
/* Process a stream. This is where the real work happens, |
/* Process a stream. This is where the real work happens, |
* except that centering is handled separately. |
* except that centering is handled separately. |
*/ |
*/ |
static void |
static void |
process_stream(FILE *stream, const char *name) { |
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 n; |
size_t first_indent=SILLY; /* indentation of line 0 of paragraph */ |
size_t np; |
HdrType prev_header_type=hdr_ParagraphStart; |
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; |
|
HdrType header_type; |
|
|
/* ^-- header_type of previous line; -1 at para start */ |
/* ^-- header_type of previous line; -1 at para start */ |
char *line; |
char *line; |
size_t length; |
size_t length; |
|
|
if (centerP) { center_stream(stream, name); return; } |
if (centerP) { |
while ((line=get_line(stream,&length)) != NULL) { |
center_stream(stream, name); |
size_t np=indent_length(line, length); |
return; |
{ 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 troff request, |
|
* 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 |
|
|| (line[0]=='.' && !format_troff) |
|
|| 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; |
|
/* nroff compatibility */ |
|
if (length>0 && line[0]=='.' && !format_troff) { |
|
printf("%.*s\n", (int)length, line); |
|
continue; |
|
} |
|
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; |
while ((line = get_line(stream, &length)) != NULL) { |
while (n<length) { |
np = indent_length(line, length); |
/* Find word end and count spaces after it */ |
header_type = hdr_NonHeader; |
size_t word_length=0, space_length=0; |
if (grok_mail_headers && prev_header_type != hdr_NonHeader) { |
while (n+word_length < length && line[n+word_length] != ' ') |
if (np == 0 && might_be_header(line)) |
++word_length; |
header_type = hdr_Header; |
space_length = word_length; |
else if (np > 0 && prev_header_type>hdr_NonHeader) |
while (n+space_length < length && line[n+space_length] == ' ') |
header_type = hdr_Continuation; |
++space_length; |
} |
/* Send the word to the output machinery. */ |
|
output_word(first_indent, last_indent, |
/* We need a new paragraph if and only if: |
line+n, word_length, space_length-word_length); |
* this line is blank, |
n += space_length; |
* OR it's a troff request, |
} |
* OR it's a mail header, |
} |
* OR it's not a mail header AND the last line was one, |
++para_line_number; |
* OR the indentation has changed |
} |
* AND the line isn't a mail header continuation line |
new_paragraph(output_in_paragraph ? last_indent : first_indent, 0); |
* AND this isn't the second line of an indented paragraph. |
if (ferror(stream)) { perror(name); ++n_errors; } |
*/ |
|
if (length == 0 || (line[0] == '.' && !format_troff) || |
|
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; |
|
|
|
/* nroff compatibility */ |
|
if (length > 0 && line[0] == '.' && !format_troff) { |
|
printf("%.*s\n", (int)length, line); |
|
continue; |
|
} |
|
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; |
|
} |
|
|
|
n = np; |
|
while (n < length) { |
|
/* Find word end and count spaces after it */ |
|
size_t word_length = 0, space_length = 0; |
|
while (n+word_length < length && line[n+word_length] != ' ') |
|
++word_length; |
|
space_length = word_length; |
|
while (n+space_length < length && line[n+space_length] == ' ') |
|
++space_length; |
|
/* Send the word to the output machinery. */ |
|
output_word(first_indent, last_indent, |
|
line+n, word_length, space_length-word_length); |
|
n += space_length; |
|
} |
|
++para_line_number; |
|
} |
|
|
|
new_paragraph(output_in_paragraph ? last_indent : first_indent, 0); |
|
if (ferror(stream)) { |
|
perror(name); |
|
++n_errors; |
|
} |
} |
} |
|
|
/* How long is the indent on this line? |
/* How long is the indent on this line? |
*/ |
*/ |
static size_t |
static size_t |
indent_length(const char *line, size_t length) { |
indent_length(const char *line, size_t length) |
size_t n=0; |
{ |
while (n<length && *line++ == ' ') ++n; |
size_t n = 0; |
return n; |
|
|
while (n < length && *line++ == ' ') |
|
++n; |
|
return n; |
} |
} |
|
|
/* Might this line be a mail header? |
/* Might this line be a mail header? |
|
|
* conservative to avoid mangling ordinary civilised text. |
* conservative to avoid mangling ordinary civilised text. |
*/ |
*/ |
static int |
static int |
might_be_header(const unsigned char *line) { |
might_be_header(const unsigned char *line) |
if (!isupper(*line++)) return 0; |
{ |
while (*line && (isalnum(*line) || *line=='-')) ++line; |
|
return (*line==':' && isspace(line[1])); |
if (!isupper(*line++)) |
|
return 0; |
|
while (*line && (isalnum(*line) || *line == '-')) |
|
++line; |
|
return (*line == ':' && isspace(line[1])); |
} |
} |
|
|
/* Begin a new paragraph with an indent of |indent| spaces. |
/* Begin a new paragraph with an indent of |indent| spaces. |
*/ |
*/ |
static void |
static void |
new_paragraph(size_t old_indent, size_t indent) { |
new_paragraph(size_t old_indent, size_t indent) |
if (x0) { |
{ |
if (old_indent>0) output_indent(old_indent); |
|
fwrite(output_buffer, 1, x0, stdout); |
if (x0) { |
putchar('\n'); |
if (old_indent > 0) |
} |
output_indent(old_indent); |
x=indent; x0=0; pending_spaces=0; |
fwrite(output_buffer, 1, x0, stdout); |
output_in_paragraph = 0; |
putchar('\n'); |
|
} |
|
x = indent; |
|
x0 = 0; |
|
pending_spaces = 0; |
|
output_in_paragraph = 0; |
} |
} |
|
|
/* Output spaces or tabs for leading indentation. |
/* Output spaces or tabs for leading indentation. |
*/ |
*/ |
static void |
static void |
output_indent(size_t n_spaces) { |
output_indent(size_t n_spaces) |
if (output_tab_width) { |
{ |
while (n_spaces >= output_tab_width) { |
|
putchar('\t'); |
if (output_tab_width) { |
n_spaces -= output_tab_width; |
while (n_spaces >= output_tab_width) { |
} |
putchar('\t'); |
} |
n_spaces -= output_tab_width; |
while (n_spaces-- > 0) putchar(' '); |
} |
|
} |
|
while (n_spaces-- > 0) |
|
putchar(' '); |
} |
} |
|
|
/* Output a single word, or add it to the buffer. |
/* Output a single word, or add it to the buffer. |
|
|
* lines of a paragraph. They'll often be the same, of course. |
* lines of a paragraph. They'll often be the same, of course. |
*/ |
*/ |
static void |
static void |
output_word(size_t indent0, size_t indent1, const char *word, size_t length, size_t spaces) { |
output_word(size_t indent0, size_t indent1, const char *word, size_t length, size_t spaces) |
size_t new_x = x+pending_spaces+length; |
{ |
size_t indent = output_in_paragraph ? indent1 : indent0; |
size_t new_x = x + pending_spaces + length; |
|
size_t indent = output_in_paragraph ? indent1 : indent0; |
|
|
/* If either |spaces==0| (at end of line) or |coalesce_spaces_P| |
/* If either |spaces==0| (at end of line) or |coalesce_spaces_P| |
* (squashing internal whitespace), then add just one space; |
* (squashing internal whitespace), then add just one space; |
* except that if the last character was a sentence-ender we |
* except that if the last character was a sentence-ender we |
* actually add two spaces. |
* actually add two spaces. |
*/ |
*/ |
if (coalesce_spaces_P || spaces==0) |
if (coalesce_spaces_P || spaces == 0) |
spaces = strchr(sentence_enders, word[length-1]) ? 2 : 1; |
spaces = strchr(sentence_enders, word[length-1]) ? 2 : 1; |
|
|
if (new_x<=goal_length) { |
if (new_x <= goal_length) { |
/* After adding the word we still aren't at the goal length, |
/* After adding the word we still aren't at the goal length, |
* so clearly we add it to the buffer rather than outputing it. |
* so clearly we add it to the buffer rather than outputing it. |
*/ |
*/ |
memset(output_buffer+x0, ' ', pending_spaces); |
memset(output_buffer+x0, ' ', pending_spaces); |
x0 += pending_spaces; x += pending_spaces; |
x0 += pending_spaces; |
memcpy(output_buffer+x0, word, length); |
x += pending_spaces; |
x0 += length; x += length; |
memcpy(output_buffer+x0, word, length); |
pending_spaces = spaces; |
x0 += length; |
} |
x += length; |
else { |
pending_spaces = spaces; |
/* Adding the word takes us past the goal. Print the line-so-far, |
} else { |
* and the word too iff either (1) the lsf is empty or (2) that |
/* Adding the word takes us past the goal. Print the line-so-far, |
* makes us nearer the goal but doesn't take us over the limit, |
* and the word too iff either (1) the lsf is empty or (2) that |
* or (3) the word on its own takes us over the limit. |
* makes us nearer the goal but doesn't take us over the limit, |
* In case (3) we put a newline in between. |
* or (3) the word on its own takes us over the limit. |
*/ |
* In case (3) we put a newline in between. |
if (indent>0) output_indent(indent); |
*/ |
fwrite(output_buffer, 1, x0, stdout); |
if (indent > 0) |
if (x0==0 || (new_x <= max_length && new_x-goal_length <= goal_length-x)) { |
output_indent(indent); |
printf("%*s", (int)pending_spaces, ""); |
fwrite(output_buffer, 1, x0, stdout); |
goto write_out_word; |
if (x0 == 0 || (new_x <= max_length && new_x-goal_length <= goal_length-x)) { |
} |
printf("%*s", (int)pending_spaces, ""); |
else { |
goto write_out_word; |
/* If the word takes us over the limit on its own, just |
} else { |
* spit it out and don't bother buffering it. |
/* 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+length > max_length) { |
if (indent>0) output_indent(indent); |
putchar('\n'); |
|
if (indent > 0) |
|
output_indent(indent); |
write_out_word: |
write_out_word: |
fwrite(word, 1, length, stdout); |
fwrite(word, 1, length, stdout); |
x0 = 0; x = indent1; pending_spaces = 0; |
x0 = 0; |
} |
x = indent1; |
else { |
pending_spaces = 0; |
memcpy(output_buffer, word, length); |
} else { |
x0 = length; x = length+indent1; pending_spaces = spaces; |
memcpy(output_buffer, word, length); |
} |
x0 = length; |
} |
x = length+indent1; |
putchar('\n'); |
pending_spaces = spaces; |
output_in_paragraph = 1; |
} |
} |
} |
|
|
|
putchar('\n'); |
|
output_in_paragraph = 1; |
|
} |
} |
} |
|
|
/* Process a stream, but just center its lines rather than trying to |
/* Process a stream, but just center its lines rather than trying to |
* format them neatly. |
* format them neatly. |
*/ |
*/ |
static void |
static void |
center_stream(FILE *stream, const char *name) { |
center_stream(FILE *stream, const char *name) |
char *line; |
{ |
size_t length; |
char *line; |
while ((line=get_line(stream, &length)) != 0) { |
size_t length; |
size_t l=length; |
size_t l; |
while (l>0 && isspace(*line)) { ++line; --l; } |
|
length=l; |
while ((line = get_line(stream, &length)) != 0) { |
while (l<goal_length) { putchar(' '); l+=2; } |
l = length; |
fwrite(line, 1, length, stdout); |
while (l > 0 && isspace(*line)) { |
putchar('\n'); |
++line; |
} |
--l; |
if (ferror(stream)) { perror(name); ++n_errors; } |
} |
|
|
|
length = l; |
|
|
|
while (l < goal_length) { |
|
putchar(' '); |
|
l += 2; |
|
} |
|
|
|
fwrite(line, 1, length, stdout); |
|
putchar('\n'); |
|
} |
|
|
|
if (ferror(stream)) { |
|
perror(name); |
|
++n_errors; |
|
} |
} |
} |
|
|
/* Get a single line from a stream. Expand tabs, strip control |
/* Get a single line from a stream. Expand tabs, strip control |
|
|
* |pending_spaces|. |
* |pending_spaces|. |
*/ |
*/ |
static char * |
static char * |
get_line(FILE *stream, size_t *lengthp) { |
get_line(FILE *stream, size_t *lengthp) |
static char *buf=NULL; |
{ |
static size_t length=0; |
int ch; |
size_t len=0; |
int troff = 0; |
int ch; |
static char *buf = NULL; |
size_t spaces_pending=0; |
static size_t length = 0; |
int troff=0; |
size_t len = 0; |
|
size_t spaces_pending = 0; |
|
|
if (buf==NULL) { length=100; buf=XMALLOC(length); } |
if (buf == NULL) { |
while ((ch=getc(stream)) != '\n' && ch != EOF) { |
length = 100; |
if (len+spaces_pending==0 && ch=='.' && !format_troff) troff=1; |
buf = XMALLOC(length); |
if (ch==' ') ++spaces_pending; |
} |
else if (troff || !iscntrl(ch)) { |
|
while (len+spaces_pending >= length) { |
while ((ch = getc(stream)) != '\n' && ch != EOF) { |
length*=2; buf=xrealloc(buf, length); |
if ((len + spaces_pending == 0) && (ch == '.' && !format_troff)) |
} |
troff = 1; |
while (spaces_pending > 0) { --spaces_pending; buf[len++]=' '; } |
if (ch == ' ') { |
buf[len++] = ch; |
++spaces_pending; |
} |
} else if (troff || !iscntrl(ch)) { |
else if (ch=='\t') |
while (len + spaces_pending >= length) { |
spaces_pending += tab_width - (len+spaces_pending)%tab_width; |
length *= 2; |
else if (ch=='\b') { if (len) --len; } |
buf = xrealloc(buf, length); |
} |
} |
*lengthp=len; |
|
return (len>0 || ch!=EOF) ? buf : 0; |
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. |
/* (Re)allocate some memory, exiting with an error if we can't. |
*/ |
*/ |
static void * |
static void * |
xrealloc(void *ptr, size_t nbytes) { |
xrealloc(void *ptr, size_t nbytes) |
void *p = realloc(ptr, nbytes); |
{ |
if (p == NULL) errx(EX_OSERR, "out of memory"); |
void *p; |
return p; |
|
|
p = realloc(ptr, nbytes); |
|
if (p == NULL) |
|
errx(EX_OSERR, "out of memory"); |
|
return p; |
} |
} |
|
|
|
void |
|
usage(void) |
|
{ |
|
|
|
fprintf(stderr, |
|
"Usage: fmt [-cmps] [-d chars] [-l num] [-t num]\n" |
|
" [-w width | -width | 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" |
|
" -n format lines beginning with a dot\n" |
|
" -p allow indented paragraphs\n" |
|
" -s coalesce whitespace inside lines\n" |
|
" -t <n> have tabs every <n> columns\n" |
|
" -w <n> set maximum width to <n>\n" |
|
" goal set target width to goal\n"); |
|
exit (0); |
|
} |
|
|