diff options
author | Jim Meyering <jim@meyering.net> | 1992-11-08 02:50:43 +0000 |
---|---|---|
committer | Jim Meyering <jim@meyering.net> | 1992-11-08 02:50:43 +0000 |
commit | b25038ce9a234ea0906ddcbd8a0012e917e6c661 (patch) | |
tree | a4360f1b307910d9266f65fc851479c218219009 /src/pr.c | |
parent | f33e06711c51330972e2adf07d21a4e69c8f44f6 (diff) | |
download | coreutils-b25038ce9a234ea0906ddcbd8a0012e917e6c661.tar.xz |
Initial revision
Diffstat (limited to 'src/pr.c')
-rw-r--r-- | src/pr.c | 1844 |
1 files changed, 1844 insertions, 0 deletions
diff --git a/src/pr.c b/src/pr.c new file mode 100644 index 000000000..10595ad73 --- /dev/null +++ b/src/pr.c @@ -0,0 +1,1844 @@ +/* pr -- convert text files for printing. + Copyright (C) 1988, 1991 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* Author: Pete TerMaat. */ + +/* Things to watch: Sys V screws up on ... + pr -n -3 -s: /usr/dict/words + pr -m -o10 -n /usr/dict/words{,,,} + pr -6 -a -n -o5 /usr/dict/words + + Ideas: + + Keep a things_to_do list of functions to call when we know we have + something to print. Cleaner than current series of checks. + + Improve the printing of control prefixes. + + + Options: + + +PAGE Begin output at page PAGE of the output. + + -COLUMN Produce output that is COLUMN columns wide and print + columns down. + + -a Print columns across rather than down. The input + one + two + three + four + will be printed as + one two three + four + + -b Balance columns on the last page. + + -c Print unprintable characters as control prefixes. + Control-g is printed as ^G. + + -d Double space the output. + + -e[c[k]] Expand tabs to spaces on input. Optional argument C + is the input tab character. (Default is `\t'.) Optional + argument K is the input tab character's width. (Default is 8.) + + -F + -f Use formfeeds instead of newlines to separate pages. + + -h header Replace the filename in the header with the string HEADER. + + -i[c[k]] Replace spaces with tabs on output. Optional argument + C is the output tab character. (Default is `\t'.) Optional + argument K is the output tab character's width. (Default + is 8.) + + -l lines Set the page length to LINES. Default is 66. + + -m Print files in parallel. + + -n[c[k]] Precede each column with a line number. + (With parallel files, precede each line with a line + number.) Optional argument C is the character to print + after each number. (Default `\t'.) Optional argument + K is the number of digits per line number. (Default 5.) + + -o offset Offset each line with a margin OFFSET spaces wide. + Total page width is the size of this offset plus the + width set with `-w'. + + -r Ignore files that can't be opened. + + -s[c] Separate each line with a character. Optional argument C is + the character to be used. Default is `\t'. + + -t Do not print headers or footers. + + -v Print unprintable characters as escape sequences. + Control-G becomes \007. + + -w width Set the page width to WIDTH characters. */ + +#include <stdio.h> +#include <getopt.h> +#include <ctype.h> +#include <sys/types.h> +#include <time.h> +#include "system.h" + +#ifdef isascii +#define ISPRINT(c) (isascii (c) && isprint (c)) +#define ISDIGIT(c) (isascii (c) && isdigit (c)) +#else +#define ISPRINT(c) isprint (c) +#define ISDIGIT(c) isdigit (c) +#endif + +int char_to_clump (); +int read_line (); +int print_page (); +int print_stored (); +char *xmalloc (); +char *xrealloc (); +int open_file (); +int skip_to_page (); +void error (); +void getoptarg (); +void usage (); +void print_files (); +void init_header (); +void init_store_cols (); +void store_columns (); +void balance (); +void store_char (); +void pad_down (); +void read_rest_of_line (); +void print_char (); +void cleanup (); + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +/* Used with start_position in the struct COLUMN described below. + If start_position == ANYWHERE, we aren't truncating columns and + can begin printing a column anywhere. Otherwise we must pad to + the horizontal position start_position. */ +#define ANYWHERE 0 + +/* Each column has one of these structures allocated for it. + If we're only dealing with one file, fp is the same for all + columns. + + The general strategy is to spend time setting up these column + structures (storing columns if necessary), after which printing + is a matter of flitting from column to column and calling + print_func. + + Parallel files, single files printing across in multiple + columns, and single files printing down in multiple columns all + fit the same printing loop. + + print_func Function used to print lines in this column. + If we're storing this column it will be + print_stored(), Otherwise it will be read_line(). + + char_func Function used to process characters in this column. + If we're storing this column it will be store_char(), + otherwise it will be print_char(). + + current_line Index of the current entry in line_vector, which + contains the index of the first character of the + current line in buff[]. + + lines_stored Number of lines in this column which are stored in + buff. + + lines_to_print If we're storing this column, lines_to_print is + the number of stored_lines which remain to be + printed. Otherwise it is the number of lines + we can print without exceeding lines_per_body. + + start_position The horizontal position we want to be in before we + print the first character in this column. + + numbered True means precede this column with a line number. */ + +struct COLUMN +{ + FILE *fp; /* Input stream for this column. */ + char *name; /* File name. */ + enum + { + OPEN, + ON_HOLD, /* Hit a form feed. */ + CLOSED + } status; /* Status of the file pointer. */ + int (*print_func) (); /* Func to print lines in this col. */ + void (*char_func) (); /* Func to print/store chars in this col. */ + int current_line; /* Index of current place in line_vector. */ + int lines_stored; /* Number of lines stored in buff. */ + int lines_to_print; /* No. lines stored or space left on page. */ + int start_position; /* Horizontal position of first char. */ + int numbered; +}; + +typedef struct COLUMN COLUMN; + +#define NULLCOL (COLUMN *)0 + +/* All of the columns to print. */ +COLUMN *column_vector; + +/* When printing a single file in multiple downward columns, + we store the leftmost columns contiguously in buff. + To print a line from buff, get the index of the first char + from line_vector[i], and print up to line_vector[i + 1]. */ +char *buff; + +/* Index of the position in buff where the next character + will be stored. */ +int buff_current; + +/* The number of characters in buff. + Used for allocation of buff and to detect overflow of buff. */ +int buff_allocated; + +/* Array of indices into buff. + Each entry is an index of the first character of a line. + This is used when storing lines to facilitate shuffling when + we do column balancing on the last page. */ +int *line_vector; + +/* Array of horizonal positions. + For each line in line_vector, end_vector[line] is the horizontal + position we are in after printing that line. We keep track of this + so that we know how much we need to pad to prepare for the next + column. */ +int *end_vector; + +/* (-m) True means we're printing multiple files in parallel. */ +int parallel_files = FALSE; + +/* (-[0-9]+) True means we're given an option explicitly specifying + number of columns. Used to detect when this option is used with -m. */ +int explicit_columns = FALSE; + +/* (-t) True means we're printing headers and footers. */ +int extremities = TRUE; + +/* True means we need to print a header as soon as we know we've got input + to print after it. */ +int print_a_header; + +/* (-h) True means we're using the standard header rather than a + customized one specified by the -h flag. */ +int standard_header = TRUE; + +/* (-f) True means use formfeeds instead of newlines to separate pages. */ +int use_form_feed = FALSE; + +/* True means we haven't encountered any filenames in the argument list. */ +int input_is_stdin = TRUE; + +/* True means we have read the standard input. */ +int have_read_stdin = FALSE; + +/* True means the -a flag has been given. */ +int print_across_flag = FALSE; + +/* True means we're printing one file in multiple (>1) downward columns. */ +int storing_columns = TRUE; + +/* (-b) True means balance columns on the last page as Sys V does. */ +int balance_columns = FALSE; + +/* (-l) Number of lines on a page, including header and footer lines. */ +int lines_per_page = 66; + +/* Number of lines in the header and footer can be reset to 0 using + the -t flag. */ +int lines_per_header = 5; +int lines_per_body; +int lines_per_footer = 5; + +/* (-w) Width in characters of the page. Does not include the width of + the margin. */ +int chars_per_line = 72; + +/* Number of characters in a column. Based on the gutter and page widths. */ +int chars_per_column; + +/* (-e) True means convert tabs to spaces on input. */ +int untabify_input = FALSE; + +/* (-e) The input tab character. */ +char input_tab_char = '\t'; + +/* (-e) Tabstops are at chars_per_tab, 2*chars_per_tab, 3*chars_per_tab, ... + where the leftmost column is 1. */ +int chars_per_input_tab = 8; + +/* (-i) True means convert spaces to tabs on output. */ +int tabify_output = FALSE; + +/* (-i) The output tab character. */ +char output_tab_char = '\t'; + +/* (-i) The width of the output tab. */ +int chars_per_output_tab = 8; + +/* Keeps track of pending white space. When we hit a nonspace + character after some whitespace, we print whitespace, tabbing + if necessary to get to output_position + spaces_not_printed. */ +int spaces_not_printed; + +/* Number of spaces between columns (though tabs can be used when possible to + use up the equivalent amount of space). Not sure if this is worth making + a flag for. BSD uses 0, Sys V uses 1. Sys V looks better. */ +int chars_per_gutter = 1; + +/* (-o) Number of spaces in the left margin (tabs used when possible). */ +int chars_per_margin = 0; + +/* Position where the next character will fall. + Leftmost position is 0 + chars_per_margin. + Rightmost position is chars_per_margin + chars_per_line - 1. + This is important for converting spaces to tabs on output. */ +int output_position; + +/* Horizontal position relative to the current file. + (output_position depends on where we are on the page; + input_position depends on where we are in the file.) + Important for converting tabs to spaces on input. */ +int input_position; + +/* Count number of failed opens so we can exit with non-zero + status if there were any. */ +int failed_opens = 0; + +/* The horizontal position we'll be at after printing a tab character + of width c_ from the position h_. */ +#define pos_after_tab(c_, h_) h_ - h_ % c_ + c_ + +/* The number of spaces taken up if we print a tab character with width + c_ from position h_. */ +#define tab_width(c_, h_) - h_ % c_ + c_ + +/* (-NNN) Number of columns of text to print. */ +int columns = 1; + +/* (+NNN) Page number on which to begin printing. */ +int first_page_number = 1; + +/* Number of files open (not closed, not on hold). */ +int files_ready_to_read = 0; + +/* Number of columns with either an open file or stored lines. */ +int cols_ready_to_print = 0; + +/* Current page number. Displayed in header. */ +int page_number; + +/* Current line number. Displayed when -n flag is specified. + + When printing files in parallel (-m flag), line numbering is as follows: + 1 foo goo moo + 2 hoo too zoo + + When printing files across (-a flag), ... + 1 foo 2 moo 3 goo + 4 hoo 3 too 6 zoo + + Otherwise, line numbering is as follows: + 1 foo 3 goo 5 too + 2 moo 4 hoo 6 zoo */ +int line_number; + +/* (-n) True means lines should be preceded by numbers. */ +int numbered_lines = FALSE; + +/* True means print a number as soon as we know we'll be printing + from the current column. */ +int print_a_number; + +/* (-n) Character which follows each line number. */ +char number_separator = '\t'; + +/* (-n) Width in characters of a line number. */ +int chars_per_number = 5; + +/* Used when widening the first column to accommodate numbers -- only + needed when printing files in parallel. Includes width of both the + number and the number_separator. */ +int number_width; + +/* Buffer sprintf uses to format a line number. */ +char *number_buff; + +/* (-v) True means unprintable characters are printed as escape sequences. + control-g becomes \007. */ +int use_esc_sequence = FALSE; + +/* (-c) True means unprintable characters are printed as control prefixes. + control-g becomes ^G. */ +int use_cntrl_prefix = FALSE; + +/* (-d) True means output is double spaced. */ +int double_space = FALSE; + +/* Number of files opened initially in init_files. Should be 1 + unless we're printing multiple files in parallel. */ +int total_files = 0; + +/* (-r) True means don't complain if we can't open a file. */ +int ignore_failed_opens = FALSE; + +/* (-s) True means we separate columns with a specified character. */ +int use_column_separator = FALSE; + +/* Character used to separate columns if the the -s flag has been specified. */ +char column_separator = '\t'; + +/* Number of separator characters waiting to be printed as soon as we + know that we have any input remaining to be printed. */ +int separators_not_printed; + +/* Position we need to pad to, as soon as we know that we have input + remaining to be printed. */ +int padding_not_printed; + +/* True means we should pad the end of the page. Remains false until we + know we have a page to print. */ +int pad_vertically; + +/* (-h) String of characters used in place of the filename in the header. */ +char *custom_header; + +/* String containing the date, filename or custom header, and "Page ". */ +char *header; + +int *clump_buff; + +/* True means we truncate lines longer than chars_per_column. */ +int truncate_lines = FALSE; + +/* The name under which this program was invoked. */ +char *program_name; + +void +main (argc, argv) + int argc; + char **argv; +{ + int c; + char *s; + int files = 0; + char **file_names, **file_name_vector; + int accum = 0; + + program_name = argv[0]; + + file_name_vector = (char **) xmalloc (argc * sizeof (char **)); + file_names = file_name_vector; + + for (;;) + { + c = getopt (argc, argv, "-0123456789abcde::fFh:i::l:mn::o:rs::tvw:"); + + if (c == 1) /* Non-option argument. */ + { + s = optarg; + if (*s == '+') + { + if (!ISDIGIT (*++s)) + usage ("`+' requires a numeric argument"); + first_page_number = atoi (s); + } + else + { + *file_names++ = optarg; + ++files; + } + } + else if (files > 0) + { + if (parallel_files && explicit_columns) + error (1, 0, +"Cannot specify number of columns when printing in parallel."); + + if (parallel_files && print_across_flag) + error (1, 0, +"Cannot specify both printing across and printing in parallel."); + + if (parallel_files) + print_files (files, file_name_vector); + else + { + file_names = file_name_vector; + while (files--) + print_files (1, file_names++); + } + + input_is_stdin = FALSE; + file_names = file_name_vector; + files = 0; + cleanup (); + } + + if (ISDIGIT (c)) + { + accum = accum * 10 + c - '0'; + continue; + } + else + { + if (accum > 0) + { + columns = accum; + explicit_columns = TRUE; + } + accum = 0; + } + + switch (c) + { + case 'a': + print_across_flag = TRUE; + storing_columns = FALSE; + break; + case 'b': + balance_columns = TRUE; + break; + case 'c': + use_cntrl_prefix = TRUE; + break; + case 'd': + double_space = TRUE; + break; + case 'e': + if (optarg) + getoptarg (optarg, 'e', &input_tab_char, + &chars_per_input_tab); + /* Could check tab width > 0. */ + untabify_input = TRUE; + break; + case 'f': + case 'F': + use_form_feed = TRUE; + break; + case 'h': + custom_header = optarg; + standard_header = FALSE; + break; + case 'i': + if (optarg) + getoptarg (optarg, 'i', &output_tab_char, + &chars_per_output_tab); + /* Could check tab width > 0. */ + tabify_output = TRUE; + break; + case 'l': + lines_per_page = atoi (optarg); + break; + case 'm': + parallel_files = TRUE; + storing_columns = FALSE; + break; + case 'n': + numbered_lines = TRUE; + if (optarg) + getoptarg (optarg, 'n', &number_separator, + &chars_per_number); + break; + case 'o': + chars_per_margin = atoi (optarg); + break; + case 'r': + ignore_failed_opens = TRUE; + break; + case 's': + use_column_separator = TRUE; + if (optarg) + { + s = optarg; + column_separator = *s; + if (*++s) + { + fprintf (stderr, "\ +%s: extra characters in the argument to the `-s' option: `%s'\n", + program_name, s); + usage ((char *) 0); + } + } + break; + case 't': + extremities = FALSE; + break; + case 'v': + use_esc_sequence = TRUE; + break; + case 'w': + chars_per_line = atoi (optarg); + break; + case '?': + usage ((char *) 0); + break; + } + + if (c == EOF) + break; + } + + if (input_is_stdin) + print_files (0, (char **) 0); + + if (have_read_stdin && fclose (stdin) == EOF) + error (1, errno, "standard input"); + if (ferror (stdout) || fclose (stdout) == EOF) + error (1, errno, "write error"); + if (failed_opens > 0) + exit(1); + exit (0); +} + +/* Parse options of the form -scNNN. + + Example: -nck, where 'n' is the option, c is the optional number + separator, and k is the optional width of the field used when printing + a number. */ + +void +getoptarg (arg, switch_char, character, number) + char *arg, switch_char, *character; + int *number; +{ + if (!ISDIGIT (*arg)) + *character = *arg++; + if (*arg) + { + if (ISDIGIT (*arg)) + *number = atoi (arg); + else + { + fprintf (stderr, "\ +%s: extra characters in the argument to the `-%c' option: `%s'\n", + program_name, switch_char, arg); + usage ((char *) 0); + } + } +} + +/* Set parameters related to formatting. */ + +void +init_parameters (number_of_files) + int number_of_files; +{ + int chars_used_by_number = 0; + + lines_per_body = lines_per_page - lines_per_header - lines_per_footer; + if (lines_per_body <= 0) + extremities = FALSE; + if (extremities == FALSE) + lines_per_body = lines_per_page; + + if (double_space) + lines_per_body = lines_per_body / 2; + + /* If input is stdin, cannot print parallel files. BSD dumps core + on this. */ + if (number_of_files == 0) + parallel_files = FALSE; + + if (parallel_files) + columns = number_of_files; + + /* Tabification is assumed for multiple columns. */ + if (columns > 1) + { + if (!use_column_separator) + truncate_lines = TRUE; + + untabify_input = TRUE; + tabify_output = TRUE; + } + else + storing_columns = FALSE; + + if (numbered_lines) + { + if (number_separator == input_tab_char) + { + number_width = chars_per_number + + tab_width (chars_per_input_tab, + (chars_per_margin + chars_per_number)); + } + else + number_width = chars_per_number + 1; + /* The number is part of the column width unless we are + printing files in parallel. */ + if (parallel_files) + chars_used_by_number = number_width; + } + + chars_per_column = (chars_per_line - chars_used_by_number - + (columns - 1) * chars_per_gutter) / columns; + + if (chars_per_column < 1) + error (1, 0, "page width too narrow"); + + if (numbered_lines) + { + if (number_buff != (char *) 0) + free (number_buff); + number_buff = (char *) + xmalloc (2 * chars_per_number * sizeof (char)); + } + + /* Pick the maximum between the tab width and the width of an + escape sequence. */ + if (clump_buff != (int *) 0) + free (clump_buff); + clump_buff = (int *) xmalloc ((chars_per_input_tab > 4 + ? chars_per_input_tab : 4) * sizeof (int)); +} + +/* Open the necessary files, + maintaining a COLUMN structure for each column. + + With multiple files, each column p has a different p->fp. + With single files, each column p has the same p->fp. + Return 1 if (number_of_files > 0) and no files can be opened, + 0 otherwise. */ + +int +init_fps (number_of_files, av) + int number_of_files; + char **av; +{ + int i, files_left; + COLUMN *p; + FILE *firstfp; + char *firstname; + + total_files = 0; + + if (column_vector != NULLCOL) + free ((char *) column_vector); + column_vector = (COLUMN *) xmalloc (columns * sizeof (COLUMN)); + + if (parallel_files) + { + files_left = number_of_files; + for (p = column_vector; files_left--; ++p, ++av) + { + if (open_file (*av, p) == 0) + { + --p; + --columns; + } + } + if (columns == 0) + return 1; + init_header ("", -1); + } + else + { + p = column_vector; + if (number_of_files > 0) + { + if (open_file (*av, p) == 0) + return 1; + init_header (*av, fileno (p->fp)); + } + else + { + p->name = "standard input"; + p->fp = stdin; + have_read_stdin = TRUE; + p->status = OPEN; + ++total_files; + init_header ("", -1); + } + + firstname = p->name; + firstfp = p->fp; + for (i = columns - 1, ++p; i; --i, ++p) + { + p->name = firstname; + p->fp = firstfp; + p->status = OPEN; + } + } + files_ready_to_read = total_files; + return 0; +} + +/* Determine print_func and char_func, the functions + used by each column for printing and/or storing. + + Determine the horizontal position desired when we begin + printing a column (p->start_position). */ + +void +init_funcs () +{ + int i, h, h_next; + COLUMN *p; + + h = chars_per_margin; + + if (use_column_separator) + h_next = ANYWHERE; + else + { + /* When numbering lines of parallel files, we enlarge the + first column to accomodate the number. Looks better than + the Sys V approach. */ + if (parallel_files && numbered_lines) + h_next = h + chars_per_column + number_width; + else + h_next = h + chars_per_column; + } + + /* This loop takes care of all but the rightmost column. */ + + for (p = column_vector, i = 1; i < columns; ++p, ++i) + { + if (storing_columns) /* One file, multi columns down. */ + { + p->char_func = store_char; + p->print_func = print_stored; + } + else + /* One file, multi columns across; or parallel files. */ + { + p->char_func = print_char; + p->print_func = read_line; + } + + /* Number only the first column when printing files in + parallel. */ + p->numbered = numbered_lines && (!parallel_files || i == 1); + p->start_position = h; + + /* If we're using separators, all start_positions are + ANYWHERE, except the first column's start_position when + using a margin. */ + + if (use_column_separator) + { + h = ANYWHERE; + h_next = ANYWHERE; + } + else + { + h = h_next + chars_per_gutter; + h_next = h + chars_per_column; + } + } + + /* The rightmost column. + + Doesn't need to be stored unless we intend to balance + columns on the last page. */ + if (storing_columns && balance_columns) + { + p->char_func = store_char; + p->print_func = print_stored; + } + else + { + p->char_func = print_char; + p->print_func = read_line; + } + + p->numbered = numbered_lines && (!parallel_files || i == 1); + p->start_position = h; +} + +/* Open a file. Return nonzero if successful, zero if failed. */ + +int +open_file (name, p) + char *name; + COLUMN *p; +{ + if (!strcmp (name, "-")) + { + p->name = "standard input"; + p->fp = stdin; + have_read_stdin = 1; + } + else + { + p->name = name; + p->fp = fopen (name, "r"); + } + if (p->fp == NULL) + { + ++failed_opens; + if (!ignore_failed_opens) + error (0, errno, "%s", name); + return 0; + } + p->status = OPEN; + ++total_files; + return 1; +} + +/* Close the file in P. + + If we aren't dealing with multiple files in parallel, we change + the status of all columns in the column list to reflect the close. */ + +void +close_file (p) + COLUMN *p; +{ + COLUMN *q; + int i; + + if (p->status == CLOSED) + return; + if (ferror (p->fp)) + error (1, errno, "%s", p->name); + if (p->fp != stdin && fclose (p->fp) == EOF) + error (1, errno, "%s", p->name); + + if (!parallel_files) + { + for (q = column_vector, i = columns; i; ++q, --i) + { + q->status = CLOSED; + if (q->lines_stored == 0) + { +#if 0 + if (cols_ready_to_print > 0) + --cols_ready_to_print; +#endif + q->lines_to_print = 0; + } + } + } + else + { + p->status = CLOSED; + p->lines_to_print = 0; + } + + --files_ready_to_read; +} + +/* Put a file on hold until we start a new page, + since we've hit a form feed. + + If we aren't dealing with parallel files, we must change the + status of all columns in the column list. */ + +void +hold_file (p) + COLUMN *p; +{ + COLUMN *q; + int i; + + if (!parallel_files) + for (q = column_vector, i = columns; i; ++q, --i) + q->status = ON_HOLD; + else + p->status = ON_HOLD; + p->lines_to_print = 0; + --files_ready_to_read; +} + +/* Undo hold_file -- go through the column list and change any + ON_HOLD columns to OPEN. Used at the end of each page. */ + +void +reset_status () +{ + int i = columns; + COLUMN *p; + + for (p = column_vector; i; --i, ++p) + if (p->status == ON_HOLD) + { + p->status = OPEN; + files_ready_to_read++; + } +} + +/* Print a single file, or multiple files in parallel. + + Set up the list of columns, opening the necessary files. + Allocate space for storing columns, if necessary. + Skip to first_page_number, if user has asked to skip leading pages. + Determine which functions are appropriate to store/print lines + in each column. + Print the file(s). */ + +void +print_files (number_of_files, av) + int number_of_files; + char **av; +{ + init_parameters (number_of_files); + if (init_fps (number_of_files, av)) + return; + if (storing_columns) + init_store_cols (); + + if (first_page_number > 1) + { + if (!skip_to_page (first_page_number)) + return; + else + page_number = first_page_number; + } + else + page_number = 1; + + init_funcs (); + + line_number = 1; + while (print_page ()) + ; +} + +/* Generous estimate of number of characters taken up by "Jun 7 00:08 " and + "Page NNNNN". */ +#define CHARS_FOR_DATE_AND_PAGE 50 + +/* Initialize header information. + If DESC is non-negative, it is a file descriptor open to + FILENAME for reading. + + Allocate space for a header string, + Determine the time, insert file name or user-specified string. + + It might be nice to have a "blank headers" option, since + pr -h "" still prints the date and page number. */ + +void +init_header (filename, desc) + char *filename; + int desc; +{ + int chars_per_header; + char *f = filename; + char *t, *middle; + struct stat st; + + if (filename == 0) + f = ""; + + /* If parallel files or standard input, use current time. */ + if (desc < 0 || !strcmp (filename, "-") || fstat (desc, &st)) + st.st_mtime = time ((time_t *) 0); + t = ctime (&st.st_mtime); + + t[16] = '\0'; /* Mark end of month and time string. */ + t[24] = '\0'; /* Mark end of year string. */ + + middle = standard_header ? f : custom_header; + + chars_per_header = strlen (middle) + CHARS_FOR_DATE_AND_PAGE + 1; + if (header != (char *) 0) + free (header); + header = (char *) xmalloc (chars_per_header * sizeof (char)); + + sprintf (header, "%s %s %s Page", &t[4], &t[20], middle); +} + +/* Set things up for printing a page + + Scan through the columns ... + Determine which are ready to print + (i.e., which have lines stored or open files) + Set p->lines_to_print appropriately + (to p->lines_stored if we're storing, or lines_per_body + if we're reading straight from the file) + Keep track of this total so we know when to stop printing */ + +void +init_page () +{ + int j; + COLUMN *p; + + cols_ready_to_print = 0; + + if (storing_columns) + { + store_columns (); + for (j = columns - 1, p = column_vector; j; --j, ++p) + { + p->lines_to_print = p->lines_stored; + if (p->lines_to_print != 0) + ++cols_ready_to_print; + } + + /* Last column. */ + if (balance_columns) + { + p->lines_to_print = p->lines_stored; + if (p->lines_to_print != 0) + ++cols_ready_to_print; + } + /* Since we're not balancing columns, we don't need to store + the rightmost column. Read it straight from the file. */ + else + { + if (p->status == OPEN) + { + p->lines_to_print = lines_per_body; + ++cols_ready_to_print; + } + else + p->lines_to_print = 0; + } + } + else + for (j = columns, p = column_vector; j; --j, ++p) + if (p->status == OPEN) + { + p->lines_to_print = lines_per_body; + ++cols_ready_to_print; + } + else + p->lines_to_print = 0; +} + +/* Print one page. + + As long as there are lines left on the page and columns ready to print, + Scan across the column list + if the column has stored lines or the file is open + pad to the appropriate spot + print the column + pad the remainder of the page with \n or \f as requested + reset the status of all files -- any files which where on hold because + of formfeeds are now put back into the lineup. */ + +int +print_page () +{ + int j; + int lines_left_on_page; + COLUMN *p; + + /* Used as an accumulator (with | operator) of successive values of + pad_vertically. The trick is to set pad_vertically + to zero before each run through the inner loop, then after that + loop, it tells us whether a line was actually printed (whether a + newline needs to be output -- or two for double spacing). But those + values have to be accumulated (in pv) so we can invoke pad_down + properly after the outer loop completes. */ + int pv; + + init_page (); + + if (cols_ready_to_print == 0) + return FALSE; + + if (extremities) + print_a_header = TRUE; + + /* Don't pad unless we know a page was printed. */ + pad_vertically = FALSE; + pv = FALSE; + + lines_left_on_page = lines_per_body; + if (double_space) + lines_left_on_page *= 2; + + while (lines_left_on_page > 0 && cols_ready_to_print > 0) + { + output_position = 0; + spaces_not_printed = 0; + separators_not_printed = 0; + pad_vertically = FALSE; + + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + { + input_position = 0; + if (p->lines_to_print > 0) + { + padding_not_printed = p->start_position; + + if (!(p->print_func) (p)) + read_rest_of_line (p); + pv |= pad_vertically; + + if (use_column_separator) + ++separators_not_printed; + + if (--p->lines_to_print <= 0 && --cols_ready_to_print <= 0) + break; + } + } + + if (pad_vertically) + { + putchar ('\n'); + --lines_left_on_page; + } + + if (double_space && pv && extremities) + { + putchar ('\n'); + --lines_left_on_page; + } + } + + pad_vertically = pv; + + if (pad_vertically && extremities) + pad_down (lines_left_on_page + lines_per_footer); + + reset_status (); /* Change ON_HOLD to OPEN. */ + + return TRUE; /* More pages to go. */ +} + +/* Allocate space for storing columns. + + This is necessary when printing multiple columns from a single file. + Lines are stored consecutively in buff, separated by '\0'. + (We can't use a fixed offset since with the '-s' flag lines aren't + truncated.) + + We maintain a list (line_vector) of pointers to the beginnings + of lines in buff. We allocate one more than the number of lines + because the last entry tells us the index of the last character, + which we need to know in order to print the last line in buff. */ + +void +init_store_cols () +{ + int total_lines = lines_per_body * columns; + int chars_if_truncate = total_lines * (chars_per_column + 1); + + if (line_vector != (int *) 0) + free ((int *) line_vector); + line_vector = (int *) xmalloc ((total_lines + 1) * sizeof (int *)); + + if (end_vector != (int *) 0) + free ((int *) end_vector); + end_vector = (int *) xmalloc (total_lines * sizeof (int *)); + + if (buff != (char *) 0) + free (buff); + buff_allocated = use_column_separator ? 2 * chars_if_truncate + : chars_if_truncate; /* Tune this. */ + buff = (char *) xmalloc (buff_allocated * sizeof (char)); +} + +/* Store all but the rightmost column. + (Used when printing a single file in multiple downward columns) + + For each column + set p->current_line to be the index in line_vector of the + first line in the column + For each line in the column + store the line in buff + add to line_vector the index of the line's first char + buff_start is the index in buff of the first character in the + current line. */ + +void +store_columns () +{ + int i, j; + int line = 0; + int buff_start; + int last_col; /* The rightmost column which will be saved in buff */ + COLUMN *p; + + buff_current = 0; + buff_start = 0; + + if (balance_columns) + last_col = columns; + else + last_col = columns - 1; + + for (i = 1, p = column_vector; i <= last_col; ++i, ++p) + p->lines_stored = 0; + + for (i = 1, p = column_vector; i <= last_col && files_ready_to_read; + ++i, ++p) + { + p->current_line = line; + for (j = lines_per_body; j && files_ready_to_read; --j) + + if (p->status == OPEN) /* Redundant. Clean up. */ + { + input_position = 0; + + if (!read_line (p, i)) + read_rest_of_line (p); + + if (p->status == OPEN + || buff_start != buff_current) + { + ++p->lines_stored; + line_vector[line] = buff_start; + end_vector[line++] = input_position; + buff_start = buff_current; + } + } + } + + /* Keep track of the location of the last char in buff. */ + line_vector[line] = buff_start; + + if (balance_columns && p->lines_stored != lines_per_body) + balance (line); +} + +void +balance (total_stored) + int total_stored; +{ + COLUMN *p; + int i, lines; + int first_line = 0; + + for (i = 1, p = column_vector; i <= columns; ++i, ++p) + { + lines = total_stored / columns; + if (i <= total_stored % columns) + ++lines; + + p->lines_stored = lines; + p->current_line = first_line; + + first_line += lines; + } +} + +/* Store a character in the buffer. */ + +void +store_char (c) + int c; +{ + if (buff_current >= buff_allocated) + { + /* May be too generous. */ + buff_allocated = 2 * buff_allocated; + buff = (char *) xrealloc (buff, buff_allocated * sizeof (char)); + } + buff[buff_current++] = (char) c; +} + +void +number (p) + COLUMN *p; +{ + int i; + char *s; + + sprintf (number_buff, "%*d", chars_per_number, line_number++); + s = number_buff; + for (i = chars_per_number; i > 0; i--) + (p->char_func) ((int) *s++); + + if (number_separator == input_tab_char) + { + i = number_width - chars_per_number; + while (i-- > 0) + (p->char_func) ((int) ' '); + } + else + (p->char_func) ((int) number_separator); + + if (truncate_lines && !parallel_files) + input_position += number_width; +} + +/* Print (or store) padding until the current horizontal position + is position. */ + +void +pad_across_to (position) + int position; +{ + register int h = output_position; + + if (tabify_output) + spaces_not_printed = position - output_position; + else + { + while (++h <= position) + putchar (' '); + output_position = position; + } +} + +/* Pad to the bottom of the page. + + If the user has requested a formfeed, use one. + Otherwise, use newlines. */ + +void +pad_down (lines) + int lines; +{ + register int i; + + if (use_form_feed) + putchar ('\f'); + else + for (i = lines; i; --i) + putchar ('\n'); +} + +/* Read the rest of the line. + + Read from the current column's file until an end of line is + hit. Used when we've truncated a line and we no longer need + to print or store its characters. */ + +void +read_rest_of_line (p) + COLUMN *p; +{ + register int c; + FILE *f = p->fp; + + while ((c = getc (f)) != '\n') + { + if (c == '\f') + { + hold_file (p); + break; + } + else if (c == EOF) + { + close_file (p); + break; + } + } +} + +/* If we're tabifying output, + + When print_char encounters white space it keeps track + of our desired horizontal position and delays printing + until this function is called. */ + +void +print_white_space () +{ + register int h_new; + register int h_old = output_position; + register int goal = h_old + spaces_not_printed; + + while (goal - h_old > 1 + && (h_new = pos_after_tab (chars_per_output_tab, h_old)) <= goal) + { + putchar (output_tab_char); + h_old = h_new; + } + while (++h_old <= goal) + putchar (' '); + + output_position = goal; + spaces_not_printed = 0; +} + +/* Print column separators. + + We keep a count until we know that we'll be printing a line, + then print_separators() is called. */ + +void +print_separators () +{ + for (; separators_not_printed > 0; --separators_not_printed) + print_char (column_separator); +} + +/* Print (or store, depending on p->char_func) a clump of N + characters. */ + +void +print_clump (p, n, clump) + COLUMN *p; + int n; + int *clump; +{ + while (n--) + (p->char_func) (*clump++); +} + +/* Print a character. + + If we're tabifying, all tabs have been converted to spaces by + process_char(). Keep a count of consecutive spaces, and when + a nonspace is encountered, call print_white_space() to print the + required number of tabs and spaces. */ + +void +print_char (c) + int c; +{ + if (tabify_output) + { + if (c == ' ') + { + ++spaces_not_printed; + return; + } + else if (spaces_not_printed > 0) + print_white_space (); + + /* Nonprintables are assumed to have width 0, except '\b'. */ + if (!ISPRINT (c)) + { + if (c == '\b') + --output_position; + } + else + ++output_position; + } + putchar (c); +} + +/* Skip to page PAGE before printing. */ + +int +skip_to_page (page) + int page; +{ + int n, i, j; + COLUMN *p; + + for (n = 1; n < page; ++n) + { + for (i = 1; i <= lines_per_body; ++i) + { + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + read_rest_of_line (p); + } + reset_status (); + } + return files_ready_to_read > 0; +} + +/* Print a header. + + Formfeeds are assumed to use up two lines at the beginning of + the page. */ + +void +print_header () +{ + if (!use_form_feed) + fprintf (stdout, "\n\n"); + + output_position = 0; + pad_across_to (chars_per_margin); + print_white_space (); + + fprintf (stdout, "%s %d\n\n\n", header, page_number++); + + print_a_header = FALSE; + output_position = 0; +} + +/* Print (or store, if p->char_func is store_char()) a line. + + Read a character to determine whether we have a line or not. + (We may hit EOF, \n, or \f) + + Once we know we have a line, + set pad_vertically = TRUE, meaning it's safe + to pad down at the end of the page, since we do have a page. + print a header if needed. + pad across to padding_not_printed if needed. + print any separators which need to be printed. + print a line number if it needs to be printed. + + Print the clump which corresponds to the first character. + + Enter a loop and keep printing until an end of line condition + exists, or until we exceed chars_per_column. + + Return FALSE if we exceed chars_per_column before reading + an end of line character, TRUE otherwise. */ + +int +read_line (p) + COLUMN *p; +{ + register int c, chars; + int last_input_position; + + c = getc (p->fp); + + last_input_position = input_position; + switch (c) + { + case '\f': + hold_file (p); + return TRUE; + case EOF: + close_file (p); + return TRUE; + case '\n': + break; + default: + chars = char_to_clump (c); + } + + if (truncate_lines && input_position > chars_per_column) + { + input_position = last_input_position; + return FALSE; + } + + if (p->char_func != store_char) + { + pad_vertically = TRUE; + + if (print_a_header) + print_header (); + + if (padding_not_printed != ANYWHERE) + { + pad_across_to (padding_not_printed); + padding_not_printed = ANYWHERE; + } + + if (use_column_separator) + print_separators (); + } + + if (p->numbered) + number (p); + + if (c == '\n') + return TRUE; + + print_clump (p, chars, clump_buff); + + for (;;) + { + c = getc (p->fp); + + switch (c) + { + case '\n': + return TRUE; + case '\f': + hold_file (p); + return TRUE; + case EOF: + close_file (p); + return TRUE; + } + + last_input_position = input_position; + chars = char_to_clump (c); + if (truncate_lines && input_position > chars_per_column) + { + input_position = last_input_position; + return FALSE; + } + + print_clump (p, chars, clump_buff); + } +} + +/* Print a line from buff. + + If this function has been called, we know we have something to + print. Therefore we set pad_vertically to TRUE, print + a header if necessary, pad across if necessary, and print + separators if necessary. + + Return TRUE, meaning there is no need to call read_rest_of_line. */ + +int +print_stored (p) + COLUMN *p; +{ + int line = p->current_line++; + register char *first = &buff[line_vector[line]]; + register char *last = &buff[line_vector[line + 1]]; + + pad_vertically = TRUE; + + if (print_a_header) + print_header (); + + if (padding_not_printed != ANYWHERE) + { + pad_across_to (padding_not_printed); + padding_not_printed = ANYWHERE; + } + + if (use_column_separator) + print_separators (); + + while (first != last) + print_char (*first++); + + if (spaces_not_printed == 0) + output_position = p->start_position + end_vector[line]; + + return TRUE; +} + +/* Convert a character to the proper format and return the number of + characters in the resulting clump. Increment input_position by + the width of the clump. + + Tabs are converted to clumps of spaces. + Nonprintable characters may be converted to clumps of escape + sequences or control prefixes. + + Note: the width of a clump is not necessarily equal to the number of + characters in clump_buff. (e.g, the width of '\b' is -1, while the + number of characters is 1.) */ + +int +char_to_clump (c) + int c; +{ + register int *s = clump_buff; + register int i; + char esc_buff[4]; + int width; + int chars; + + if (c == input_tab_char) + { + width = tab_width (chars_per_input_tab, input_position); + + if (untabify_input) + { + for (i = width; i; --i) + *s++ = ' '; + chars = width; + } + else + { + *s = c; + chars = 1; + } + + } + else if (!ISPRINT (c)) + { + if (use_esc_sequence) + { + width = 4; + chars = 4; + *s++ = '\\'; + sprintf (esc_buff, "%03o", c); + for (i = 0; i <= 2; ++i) + *s++ = (int) esc_buff[i]; + } + else if (use_cntrl_prefix) + { + if (c < 0200) + { + width = 2; + chars = 2; + *s++ = '^'; + *s++ = c ^ 0100; + } + else + { + width = 4; + chars = 4; + *s++ = '\\'; + sprintf (esc_buff, "%03o", c); + for (i = 0; i <= 2; ++i) + *s++ = (int) esc_buff[i]; + } + } + else if (c == '\b') + { + width = -1; + chars = 1; + *s = c; + } + else + { + width = 0; + chars = 1; + *s = c; + } + } + else + { + width = 1; + chars = 1; + *s = c; + } + + input_position += width; + return chars; +} + +/* We've just printed some files and need to clean up things before + looking for more options and printing the next batch of files. + + Free everything we've xmalloc'ed, except `header'. */ + +void +cleanup () +{ + if (number_buff) + free (number_buff); + if (clump_buff) + free (clump_buff); + if (column_vector) + free (column_vector); + if (line_vector) + free (line_vector); + if (end_vector) + free (end_vector); + if (buff) + free (buff); +} + +/* Complain, print a usage message, and die. */ + +void +usage (reason) + char *reason; +{ + if (reason) + fprintf (stderr, "%s: %s\n", program_name, reason); + + fprintf (stderr, "\ +Usage: %s [+PAGE] [-COLUMN] [-abcdfFmrtv] [-e[in-tab-char[in-tab-width]]]\n\ + [-h header] [-i[out-tab-char[out-tab-width]]] [-l page-length]\n\ + [-n[number-separator[digits]]] [-o left-margin]\n\ + [-s[column-separator]] [-w page-width] [file...]\n", + program_name); + exit (2); +} |