diff options
author | Jim Meyering <jim@meyering.net> | 1996-12-05 04:33:33 +0000 |
---|---|---|
committer | Jim Meyering <jim@meyering.net> | 1996-12-05 04:33:33 +0000 |
commit | ed0923a11c1cde29b423c83b3f22be3d2b73f464 (patch) | |
tree | 6c14553d8fb852e3e8ae57f2327c21052be8cc62 /src/pr.c | |
parent | 42096600e32f6e06a015233988825493a3840cf7 (diff) | |
download | coreutils-ed0923a11c1cde29b423c83b3f22be3d2b73f464.tar.xz |
Apply big patch (patch-20) from Roland Huebner.
Diffstat (limited to 'src/pr.c')
-rw-r--r-- | src/pr.c | 964 |
1 files changed, 763 insertions, 201 deletions
@@ -32,10 +32,12 @@ Options: - +PAGE Begin output at page PAGE of the output. + +FIRST_PAGE[:LAST_PAGE] + begin [stop] printing with page FIRST_[LAST_]PAGE -COLUMN Produce output that is COLUMN columns wide and print columns down. + Balance columns on the last page is automatically set. -a Print columns across rather than down. The input one @@ -47,6 +49,21 @@ four -b Balance columns on the last page. + -b is no longer an independent option. It's always used + together with -COLUMN (unless -a is used) to get a + consistent formulation with "FF set by hand" in input + files. Each formfeed found terminates the number of lines + to be read with the actual page. The situation for + printing columns down is equivalent to that on the last + page. So we need a balancing. + + We do not yet eliminate source text dealing with -COLUMN + only. Tune this if it proved to be a satisfactory + procedure. + + Keeping -b as an underground option guarantees some + downward compatibility. Utilities using pr with -b + (a most frequently used form) still work as usual. -c Print unprintable characters as control prefixes. Control-g is printed as ^G. @@ -59,23 +76,54 @@ -F -f Use formfeeds instead of newlines to separate pages. + A three line HEADER is used, no TRAILER (without -f + both HEADER and TRAILER are made of five lines). - -h header Replace the filename in the header with the string HEADER. + -h HEADER Replace the filename in the header with the string HEADER. + Checking and left-hand-side truncation of the length of the + standard and custom header string. A centered header is used. + The format of date and time has been shortened + to yy-mm-dd HH:MM to give place to a maximal filename + information. + -h "" now prints a blank line header. -h"" shows an error. -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.) + -j Merge full lines, turns off -w line truncation, no column + alignment, -s[STRING] sets separators, works with all + column options (-COLUMN | -a -COLUMN | -m). + -l lines Set the page length to LINES. Default is 66. - -m Print files in parallel. + -m Print files in parallel; pad_across_to align columns; + truncate lines and print separator strings; + Do it also with empty columns to get a continuous line + numbering and column marking by separators throughout + the whole merged file. + + Empty pages in some input files produce empty columns + [marked by separators] in the common pages. Completely + empty common pages show no column separators at all. + + The layout of a merged page is ruled by the largest form + feed distance of the single pages at that page. Shorter + columns will be filled up with empty lines. + + Together with -j option join lines of full length and + in addition set separators when -s option is used. -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.) + k is the number of digits per line number. (Default 5.) + Default counting starts with 1st line of input file. + + -N number Start counting with number at 1st line of first page + printed. -o offset Offset each line with a margin OFFSET spaces wide. Total page width is the size of this offset plus the @@ -83,15 +131,41 @@ -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'. + -s[STRING] Separate columns by any string STRING. + Don't use -s "STRING". + without -s: default separator 'space' s used, + same as -s" ", + with -s only: no separator is used, same as -s"". + Quotes should be used with blanks and some shell active + characters. + + -t Do not print headers or footers but retain form feeds + set in input files (some page layout is not changed). - -t Do not print headers or footers. + -T Do not print headers or footers, eliminate form feeds + in input files. -v Print unprintable characters as escape sequences. Control-G becomes \007. - -w width Set the page width to WIDTH characters. */ + -w width Set the page width to WIDTH characters. + (In pr versions newer than 1.14 -s option does no longer + affect -w option.) + + With/without -w width the header line is truncated. + Default is 72 characters. + With -w width text lines will be truncated, unless -j is + used. Together with one of the column options + (-COLUMN| -a -COLUMN| -m) column alignment is used. + Without -w PAGE_WIDTH + - but with one of the column options default truncation of + 72 characters is used (to keep downward compatibility + and to simplify most frequently met column tasks). + Column alignment and column separators are used. + - and without any of the column options no line truncation + is used (to keep downward compatibility and to meet most + frequent tasks). That's equivalent to -w 72 -j . +*/ #include <config.h> @@ -102,10 +176,23 @@ #include <time.h> #include "system.h" #include "error.h" +#include "xstrtol.h" + +#if HAVE_LIMITS_H +#include <limits.h> +#endif char *xmalloc (); char *xrealloc (); +#ifndef UINT_MAX +#define UINT_MAX ((unsigned int) ~(unsigned int) 0) +#endif + +#ifndef INT_MAX +#define INT_MAX ((int) (UINT_MAX >> 1)) +#endif + #ifndef TRUE #define TRUE 1 #define FALSE 0 @@ -156,46 +243,63 @@ char *xrealloc (); 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; -}; + FILE *fp; /* Input stream for this column. */ + char *name; /* File name. */ + enum + { + OPEN, + FF_FOUND, /* used with -b option, set with \f, changed + to ON_HOLD after print_header */ + 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; + int full_page_printed; /* True means printed without a FF found. */ + + /* p->full_page_printed controls a special case of "FF set by hand": + True means a full page has been printed without FF found. To avoid an + additional empty page we have to ignore a FF immediately following in + the next line. */ + }; typedef struct COLUMN COLUMN; #define NULLCOL (COLUMN *)0 static int char_to_clump __P ((int c)); -static int read_line __P ((COLUMN *p)); +static int read_line __P ((COLUMN * p)); static int print_page __P ((void)); -static int print_stored __P ((COLUMN *p)); -static int open_file __P ((char *name, COLUMN *p)); +static int print_stored __P ((COLUMN * p)); +static int open_file __P ((char *name, COLUMN * p)); static int skip_to_page __P ((int page)); +static void print_header __P ((void)); +static void pad_across_to __P ((int position)); +static void number __P ((COLUMN * p)); static void getoptarg __P ((char *arg, char switch_char, char *character, int *number)); static void usage __P ((int status)); static void print_files __P ((int number_of_files, char **av)); +static void init_parameters __P ((int number_of_files)); static void init_header __P ((char *filename, int desc)); static void init_store_cols __P ((void)); static void store_columns __P ((void)); static void balance __P ((int total_stored)); static void store_char __P ((int c)); static void pad_down __P ((int lines)); -static void read_rest_of_line __P ((COLUMN *p)); +static void read_rest_of_line __P ((COLUMN * p)); +static void skip_read __P ((COLUMN * p, int column_number)); static void print_char __P ((int c)); static void cleanup __P ((void)); +static void first_last_page __P ((char *pages)); +static void print_sep_string __P ((void)); +static void separator_string (char *optarg_S); /* The name under which this program was invoked. */ char *program_name; @@ -205,7 +309,7 @@ static 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 + To print a line from buff, get the index of the first character from line_vector[i], and print up to line_vector[i + 1]. */ static char *buff; @@ -233,13 +337,33 @@ static int *end_vector; /* (-m) True means we're printing multiple files in parallel. */ static int parallel_files = FALSE; +/* (-m) True means a line starts with some empty columns (some files + already CLOSED or ON_HOLD) which we have to align. */ +static int align_empty_cols; + +/* (-m) True means we have not yet found any printable column in a line. + align_empty_cols = TRUE has to be maintained. */ +static int empty_line; + +/* (-m) False means printable column output precedes a form feed found. + Column align is done only once. No additional action with that form + feed. + True means we found only a form feed in a column. Maybe we have to do + some column align with that form feed. */ +static int FF_only; + /* (-[0-9]+) True means we're given an option explicitly specifying number of columns. Used to detect when this option is used with -m. */ static int explicit_columns = FALSE; -/* (-t) True means we're printing headers and footers. */ +/* (-t|-T) False means we aren't printing headers and footers. */ static int extremities = TRUE; +/* (-t) True means we retain all FF set by hand in input files. + False is set with -T option. */ +static int keep_FF = FALSE; +static int print_a_FF = FALSE; + /* True means we need to print a header as soon as we know we've got input to print after it. */ static int print_a_header; @@ -261,6 +385,9 @@ static int print_across_flag = FALSE; static int storing_columns = TRUE; /* (-b) True means balance columns on the last page as Sys V does. */ +/* That's no longer an independent option. With storing_columns = TRUE + balance_columns = TRUE is used too (s. function init_parameters). + We get a consistent formulation with "FF set by hand" in input files. */ static int balance_columns = FALSE; /* (-l) Number of lines on a page, including header and footer lines. */ @@ -276,7 +403,15 @@ static int lines_per_footer = 5; the margin. */ static int chars_per_line = 72; -/* Number of characters in a column. Based on the gutter and page widths. */ +/* (-w) True means we truncate lines longer than chars_per_column. */ +static int truncate_lines = FALSE; + +/* (-j) True means we join lines without any line truncation. -j + dominates -w option. */ +static int join_lines = FALSE; + +/* Number of characters in a column. Based on col_sep_length and + page width. */ static int chars_per_column; /* (-e) True means convert tabs to spaces on input. */ @@ -303,11 +438,6 @@ static int chars_per_output_tab = 8; if necessary to get to output_position + spaces_not_printed. */ static 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. */ -static int chars_per_gutter = 1; - /* (-o) Number of spaces in the left margin (tabs used when possible). */ static int chars_per_margin = 0; @@ -338,8 +468,9 @@ static int failed_opens = 0; /* (-NNN) Number of columns of text to print. */ static int columns = 1; -/* (+NNN) Page number on which to begin printing. */ +/* (+NNN:MMM) Page numbers on which to begin and stop printing. */ static int first_page_number = 1; +static int last_page_number = 0; /* Number of files open (not closed, not on hold). */ static int files_ready_to_read = 0; @@ -350,16 +481,16 @@ static 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 + 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 + 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 */ + 1 foo 3 goo 5 too + 2 moo 4 hoo 6 zoo */ static int line_number; /* (-n) True means lines should be preceded by numbers. */ @@ -368,6 +499,19 @@ static int numbered_lines = FALSE; /* (-n) Character which follows each line number. */ static char number_separator = '\t'; +/* (-n) line counting starts with 1st line of input file (not with 1st + line of 1st page printed). */ +static int line_count = 1; + +/* (-n) True means counting of skipped lines starts with 1st line of + input file. False means -N option is used in addition, counting of + skipped lines not required. */ +static int skip_count = TRUE; + +/* (-N) Counting starts with start_line_number = NUMBER at 1st line of + first page printed, usually not 1st page of input file. */ +static int start_line_num = 1; + /* (-n) Width in characters of a line number. */ static int chars_per_number = 5; @@ -397,11 +541,15 @@ static int total_files = 0; /* (-r) True means don't complain if we can't open a file. */ static int ignore_failed_opens = FALSE; -/* (-s) True means we separate columns with a specified character. */ -static int use_column_separator = FALSE; +/* (-s) True means we separate columns with a specified string. + -s option does not affect line truncation nor column alignment. */ +static int use_col_separator = FALSE; -/* Character used to separate columns if the the -s flag has been specified. */ -static char column_separator = '\t'; +/* String used to separate columns if the -s option has been specified. + Default value with -s is a space. */ +static char *col_sep_string; +static int col_sep_length = 0; +static char *column_separator = " "; /* Number of separator characters waiting to be printed as soon as we know that we have any input remaining to be printed. */ @@ -423,19 +571,27 @@ static char *header; static int *clump_buff; -/* True means we truncate lines longer than chars_per_column. */ -static int truncate_lines = FALSE; - /* If nonzero, display usage information and exit. */ static int show_help; /* If nonzero, print the version on standard output then exit. */ static int show_version; +/* True means we read the line no. lines_per_body in skip_read + called by skip_to_page. That variable controls the coincidence of a + "FF set by hand" and "full_page_printed", see above the definition of + structure COLUMN. */ +static int last_line = FALSE; + +/* If nonzero, print a non-variable date and time with the header + -h HEADER using pr test-suite */ +static int test_suite; + static struct option const long_options[] = { {"help", no_argument, &show_help, 1}, {"version", no_argument, &show_version, 1}, + {"test", no_argument, &test_suite, 1}, {0, 0, 0, 0} }; @@ -452,11 +608,66 @@ cols_ready_to_print (void) n = 0; for (q = column_vector, i = 0; i < columns; ++q, ++i) if (q->status == OPEN || + q->status == FF_FOUND || /* With -b: To print a header only */ (storing_columns && q->lines_stored > 0 && q->lines_to_print > 0)) ++n; return n; } +/* Estimate first_ / last_page_number + using option +FIRST_PAGE:LAST_PAGE */ + +static void +first_last_page (char *pages) +{ + char *str1; + + if (*pages == ':') + { + error (0, 0, _("invalid range of page numbers: `%s'"), pages); + usage (2); + } + + str1 = strchr (pages, ':'); + if (str1 != NULL) + *str1 = '\0'; + + { + long int tmp_long; + if (xstrtol (pages, NULL, 10, &tmp_long, NULL) != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, _("invalid starting page number: `%s'"), + pages); + first_page_number = (int) tmp_long; + } + + if (str1 == NULL) + return; + + { + long int tmp_long; + if (xstrtol (str1 + 1, NULL, 10, &tmp_long, NULL) != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, _("invalid ending page number: `%s'"), + str1 + 1); + last_page_number = (int) tmp_long; + } + + if (first_page_number > last_page_number) + error (EXIT_FAILURE, 0, + _("starting page number is larger than ending page number")); +} + +/* Estimate length of col_sep_string with option -s[STRING] */ + +static void +separator_string (char *optarg_S) +{ + col_sep_length = (int) strlen (optarg_S); + col_sep_string = (char *) xmalloc (col_sep_length + 1); + strcpy (col_sep_string, optarg_S); +} + int main (int argc, char **argv) { @@ -478,22 +689,16 @@ main (int argc, char **argv) while (1) { c = getopt_long (argc, argv, - "-0123456789abcde::fFh:i::l:mn::o:rs::tvw:", + "-0123456789abcde::fFh:i::jl:mn::N:o:rs::tTvw:", long_options, (int *) 0); - if (c == 1) /* Non-option argument. */ + if (c == 1) /* Non-option argument. */ { char *s; s = optarg; if (*s == '+') { ++s; - if (!ISDIGIT (*s)) - { - error (0, 0, _("`+' requires a numeric argument")); - usage (2); - } - /* FIXME: use strtol */ - first_page_number = atoi (s); + first_last_page (s); } else { @@ -526,7 +731,7 @@ main (int argc, char **argv) switch (c) { - case 0: /* getopt long option */ + case 0: /* getopt long option */ break; case 'a': @@ -564,9 +769,19 @@ main (int argc, char **argv) /* Could check tab width > 0. */ tabify_output = TRUE; break; - case 'l': - lines_per_page = atoi (optarg); + case 'j': + join_lines = TRUE; break; + case 'l': + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, NULL) != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, + _("`-l PAGE_LENGTH' invalid number of lines: `%s'"), optarg); + lines_per_page = (int) tmp_long; + break; + } case 'm': parallel_files = TRUE; storing_columns = FALSE; @@ -577,37 +792,57 @@ main (int argc, char **argv) getoptarg (optarg, 'n', &number_separator, &chars_per_number); break; + case 'N': + skip_count = FALSE; + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, NULL) != LONGINT_OK + || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, + _("`-N NUMBER' invalid starting line number: `%s'"), optarg); + start_line_num = (int) tmp_long; + break; + } case 'o': - chars_per_margin = atoi (optarg); - break; + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, NULL) != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, + _("`-o MARGIN' invalid line offset: `%s'"), optarg); + chars_per_margin = (int) tmp_long; + break; + } case 'r': ignore_failed_opens = TRUE; break; case 's': - use_column_separator = TRUE; + use_col_separator = TRUE; if (optarg) - { - char *s; - 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 (2); - } - } + separator_string (optarg); break; case 't': extremities = FALSE; + keep_FF = TRUE; + break; + case 'T': + extremities = FALSE; + keep_FF = FALSE; break; case 'v': use_esc_sequence = TRUE; break; case 'w': - chars_per_line = atoi (optarg); - break; + truncate_lines = TRUE; + { + long int tmp_long; + if (xstrtol (optarg, NULL, 10, &tmp_long, NULL) != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) + error (EXIT_FAILURE, 0, + _("`-w PAGE_WIDTH' invalid column number: `%s'"), optarg); + chars_per_line = (int) tmp_long; + break; + } default: usage (2); break; @@ -625,13 +860,13 @@ main (int argc, char **argv) if (parallel_files && explicit_columns) error (EXIT_FAILURE, 0, - _("Cannot specify number of columns when printing in parallel.")); + _("Cannot specify number of columns when printing in parallel.")); if (parallel_files && print_across_flag) error (EXIT_FAILURE, 0, - _("Cannot specify both printing across and printing in parallel.")); + _("Cannot specify both printing across and printing in parallel.")); - for ( ; optind < argc; optind++) + for (; optind < argc; optind++) { file_names[n_files++] = argv[optind]; } @@ -648,7 +883,7 @@ main (int argc, char **argv) else { int i; - for (i=0; i<n_files; i++) + for (i = 0; i < n_files; i++) print_files (1, &file_names[i]); } } @@ -660,7 +895,7 @@ main (int argc, char **argv) if (ferror (stdout) || fclose (stdout) == EOF) error (EXIT_FAILURE, errno, _("write error")); if (failed_opens > 0) - exit(1); + exit (EXIT_FAILURE); exit (EXIT_SUCCESS); } @@ -677,15 +912,16 @@ getoptarg (char *arg, char switch_char, char *character, int *number) *character = *arg++; if (*arg) { - if (ISDIGIT (*arg)) - *number = atoi (arg); - else + long int tmp_long; + if (xstrtol (arg, NULL, 10, &tmp_long, NULL) != LONGINT_OK + || tmp_long <= 0 || tmp_long > INT_MAX) { - fprintf (stderr, _("\ -%s: extra characters in the argument to the `-%c' option: `%s'\n"), - program_name, switch_char, arg); + error (0, 0, + _("`-%c' extra characters or invalid number in the argument: `%s'"), + switch_char, arg); usage (2); } + *number = (int) tmp_long; } } @@ -696,9 +932,18 @@ init_parameters (int number_of_files) { int chars_used_by_number = 0; + if (use_form_feed) + { + lines_per_header = 3; + lines_per_footer = 0; + } + lines_per_body = lines_per_page - lines_per_header - lines_per_footer; if (lines_per_body <= 0) - extremities = FALSE; + { + extremities = FALSE; + keep_FF = TRUE; + } if (extremities == FALSE) lines_per_body = lines_per_page; @@ -713,20 +958,35 @@ init_parameters (int number_of_files) if (parallel_files) columns = number_of_files; + /* One file, multi columns down: -b option is set to get a consistent + formulation with "FF set by hand" in input files. */ + if (storing_columns) + balance_columns = TRUE; + /* Tabification is assumed for multiple columns. */ if (columns > 1) { - if (!use_column_separator) - truncate_lines = TRUE; + if (!use_col_separator) + { + col_sep_string = column_separator; + col_sep_length = 1; + use_col_separator = TRUE; + } + truncate_lines = TRUE; untabify_input = TRUE; tabify_output = TRUE; } else storing_columns = FALSE; + /* -j dominates -w in any case */ + if (join_lines) + truncate_lines = FALSE; + if (numbered_lines) { + line_count = start_line_num; if (number_separator == input_tab_char) { number_width = chars_per_number + @@ -742,7 +1002,7 @@ init_parameters (int number_of_files) } chars_per_column = (chars_per_line - chars_used_by_number - - (columns - 1) * chars_per_gutter) / columns; + (columns - 1) * col_sep_length) / columns; if (chars_per_column < 1) error (EXIT_FAILURE, 0, _("page width too narrow")); @@ -751,8 +1011,7 @@ init_parameters (int number_of_files) { if (number_buff != (char *) 0) free (number_buff); - number_buff = (char *) - xmalloc (2 * chars_per_number * sizeof (char)); + number_buff = (char *) xmalloc (2 * chars_per_number); } /* Pick the maximum between the tab width and the width of an @@ -769,7 +1028,10 @@ init_parameters (int number_of_files) 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. */ + 0 otherwise. + + With each column/file p, p->full_page_printed is initialized, + see also open_file. */ static int init_fps (int number_of_files, char **av) @@ -815,6 +1077,7 @@ init_fps (int number_of_files, char **av) p->fp = stdin; have_read_stdin = TRUE; p->status = OPEN; + p->full_page_printed = FALSE; ++total_files; init_header ("", -1); } @@ -826,6 +1089,7 @@ init_fps (int number_of_files, char **av) p->name = firstname; p->fp = firstfp; p->status = OPEN; + p->full_page_printed = FALSE; } } files_ready_to_read = total_files; @@ -846,7 +1110,7 @@ init_funcs (void) h = chars_per_margin; - if (use_column_separator) + if (!truncate_lines) h_next = ANYWHERE; else { @@ -859,6 +1123,10 @@ init_funcs (void) h_next = h + chars_per_column; } + /* Enlarge p->start_position of first column to use the same form of + padding_not_printed with all columns. */ + h = h + col_sep_length; + /* This loop takes care of all but the rightmost column. */ for (p = column_vector, i = 1; i < columns; ++p, ++i) @@ -880,18 +1148,18 @@ init_funcs (void) p->numbered = numbered_lines && (!parallel_files || i == 1); p->start_position = h; - /* If we're using separators, all start_positions are + /* If we don't truncate lines, all start_positions are ANYWHERE, except the first column's start_position when using a margin. */ - if (use_column_separator) + if (!truncate_lines) { h = ANYWHERE; h_next = ANYWHERE; } else { - h = h_next + chars_per_gutter; + h = h_next + col_sep_length; h_next = h + chars_per_column; } } @@ -915,10 +1183,13 @@ init_funcs (void) p->start_position = h; } -/* Open a file. Return nonzero if successful, zero if failed. */ +/* Open a file. Return nonzero if successful, zero if failed. + + With each file p, p->full_page_printed is initialized, + see also init_fps. */ static int -open_file (char *name, COLUMN *p) +open_file (char *name, COLUMN * p) { if (!strcmp (name, "-")) { @@ -939,6 +1210,7 @@ open_file (char *name, COLUMN *p) return 0; } p->status = OPEN; + p->full_page_printed = FALSE; ++total_files; return 1; } @@ -949,7 +1221,7 @@ open_file (char *name, COLUMN *p) the status of all columns in the column list to reflect the close. */ static void -close_file (COLUMN *p) +close_file (COLUMN * p) { COLUMN *q; int i; @@ -988,16 +1260,22 @@ close_file (COLUMN *p) status of all columns in the column list. */ static void -hold_file (COLUMN *p) +hold_file (COLUMN * p) { COLUMN *q; int i; if (!parallel_files) for (q = column_vector, i = columns; i; ++q, --i) - q->status = ON_HOLD; + { + if (storing_columns) + q->status = FF_FOUND; + else + q->status = ON_HOLD; + } else p->status = ON_HOLD; + p->lines_to_print = 0; --files_ready_to_read; } @@ -1017,6 +1295,9 @@ reset_status (void) p->status = OPEN; files_ready_to_read++; } + + if (storing_columns) + files_ready_to_read = 1; } /* Print a single file, or multiple files in parallel. @@ -1049,14 +1330,14 @@ print_files (int number_of_files, char **av) init_funcs (); - line_number = 1; + line_number = line_count; 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 +/* Estimate the number of characters taken up by a short format date and + time: "yy-mm-dd HH:MM" and: "Page NNNN". */ +#define CHARS_FOR_DATE_AND_PAGE 23 /* Initialize header information. If DESC is non-negative, it is a file descriptor open to @@ -1064,48 +1345,83 @@ print_files (int number_of_files, char **av) 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. */ + Make use of a centered header with left-hand-side truncation marked by + a '*` in front, if necessary. */ static void init_header (char *filename, int desc) { - int chars_per_header; + int chars_per_middle, chars_free, lhs_blanks, rhs_blanks; char *f = filename; - char *t, *middle; + char *no_middle = ""; + char *header_text, *fmt, *t_buf; + struct tm *tmptr; struct stat st; + char *datim = "- Date/Time --"; + + fmt = "%y-%m-%d %H:%M"; /* date/time short format */ 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); + if (header != (char *) 0) + free (header); + header = (char *) xmalloc (chars_per_line + 1); - t[16] = '\0'; /* Mark end of month and time string. */ - t[24] = '\0'; /* Mark end of year string. */ + if (!standard_header && *custom_header == '\0') + sprintf (header, "%s", " "); /* blank line header */ + else + { + /* If parallel files or standard input, use current time. */ + if (desc < 0 || !strcmp (filename, "-") || fstat (desc, &st)) + st.st_mtime = time ((time_t *) 0); - middle = standard_header ? f : custom_header; + { + size_t t_buf_size = 15; + t_buf = (char *) xmalloc (t_buf_size); + tmptr = localtime (&st.st_mtime); + strftime (t_buf, t_buf_size, fmt, tmptr); + } - 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)); + chars_per_middle = chars_per_line - CHARS_FOR_DATE_AND_PAGE; + if (chars_per_middle < 3) + { + header_text = no_middle; /* Nothing free for a heading */ + lhs_blanks = 1; + rhs_blanks = 1; + } + else + { + header_text = standard_header ? f : custom_header; + chars_free = chars_per_middle - (int) strlen (header_text); + if (chars_free > 1) + { + lhs_blanks = chars_free / 2; /* text not truncated */ + rhs_blanks = chars_free - lhs_blanks; + } + else + { /* lhs truncation */ + header_text = header_text - chars_free + 2; + *header_text = '*'; + lhs_blanks = 1; + rhs_blanks = 1; + } + } - sprintf (header, _("%s %s %s Page"), &t[4], &t[20], middle); + sprintf (header, _("%s%*s%s%*sPage"), (test_suite ? datim : t_buf), + lhs_blanks, " ", header_text, rhs_blanks, " "); + } } /* 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 */ + 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 */ static void init_page (void) @@ -1148,16 +1464,37 @@ init_page (void) p->lines_to_print = 0; } +/* Align empty columns and print separators. + Empty columns will be formed by files with status ON_HOLD or CLOSED + when printing multiple files in parallel. */ + +static void +align_column (COLUMN * p) +{ + padding_not_printed = p->start_position; + if (padding_not_printed - col_sep_length > 0) + { + pad_across_to (padding_not_printed - col_sep_length); + padding_not_printed = ANYWHERE; + } + + if (use_col_separator) + print_sep_string (); + + if (p->numbered) + number (p); +} + /* 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 + 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. */ + of formfeeds are now put back into the lineup. */ static int print_page (void) @@ -1197,28 +1534,49 @@ print_page (void) spaces_not_printed = 0; separators_not_printed = 0; pad_vertically = FALSE; + align_empty_cols = FALSE; + empty_line = TRUE; for (j = 1, p = column_vector; j <= columns; ++j, ++p) { input_position = 0; - if (p->lines_to_print > 0) + if (p->lines_to_print > 0 || p->status == FF_FOUND) { + FF_only = FALSE; 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; - --p->lines_to_print; if (p->lines_to_print <= 0) { if (cols_ready_to_print () <= 0) break; } + + /* File p changed its status to ON_HOLD or CLOSED */ + if (parallel_files && p->status != OPEN) + { + if (empty_line) + align_empty_cols = TRUE; + else if (p->status == CLOSED || + (p->status == ON_HOLD && FF_only)) + align_column (p); + } + } + else if (parallel_files) + { + /* File status ON_HOLD or CLOSED */ + if (empty_line) + align_empty_cols = TRUE; + else + align_column (p); } + + /* We need it also with an empty column */ + if (use_col_separator) + ++separators_not_printed; } if (pad_vertically) @@ -1234,10 +1592,23 @@ print_page (void) } } + if (lines_left_on_page == 0) + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status == OPEN) + p->full_page_printed = TRUE; + pad_vertically = pv; if (pad_vertically && extremities) pad_down (lines_left_on_page + lines_per_footer); + else if (keep_FF && print_a_FF) + { + putchar ('\f'); + print_a_FF = FALSE; + } + + if (last_page_number && page_number > last_page_number) + return FALSE; /* Stop printing with LAST_PAGE */ reset_status (); /* Change ON_HOLD to OPEN. */ @@ -1248,6 +1619,8 @@ print_page (void) This is necessary when printing multiple columns from a single file. Lines are stored consecutively in buff, separated by '\0'. + + The following doesn't apply any longer - any tuning possible? (We can't use a fixed offset since with the '-s' flag lines aren't truncated.) @@ -1272,22 +1645,22 @@ init_store_cols (void) if (buff != (char *) 0) free (buff); - buff_allocated = use_column_separator ? 2 * chars_if_truncate + buff_allocated = use_col_separator ? 2 * chars_if_truncate : chars_if_truncate; /* Tune this. */ - buff = (char *) xmalloc (buff_allocated * sizeof (char)); + buff = (char *) xmalloc (buff_allocated); } /* 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. */ + 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. */ static void store_columns (void) @@ -1375,7 +1748,7 @@ store_char (int c) } static void -number (COLUMN *p) +number (COLUMN * p) { int i; char *s; @@ -1440,7 +1813,7 @@ pad_down (int lines) to print or store its characters. */ static void -read_rest_of_line (COLUMN *p) +read_rest_of_line (COLUMN * p) { register int c; FILE *f = p->fp; @@ -1449,6 +1822,74 @@ read_rest_of_line (COLUMN *p) { if (c == '\f') { + if ((c = getc (f)) != '\n') + ungetc (c, f); + if (keep_FF) + print_a_FF = TRUE; + hold_file (p); + break; + } + else if (c == EOF) + { + close_file (p); + break; + } + } +} + +/* Read a line with skip_to_page. + + Read from the current column's file until an end of line is + hit. Used when we read full lines to skip pages. + With skip_to_page we have to check for FF-coincidence which is done + in function read_line otherwise. + Count lines of skipped pages to find the line number of 1st page + printed relative to 1st line of input file (start_line_num). */ + +static void +skip_read (COLUMN * p, int column_number) +{ + register int c; + FILE *f = p->fp; + int i, single_ff = FALSE; + COLUMN *q; + + /* Read 1st character in a line or any character succeeding a FF */ + if ((c = getc (f)) == '\f' && p->full_page_printed) + /* A FF-coincidence with a previous full_page_printed. + To avoid an additional empty page, eliminate the FF */ + if ((c = getc (f)) == '\n') + c = getc (f); + + p->full_page_printed = FALSE; + + /* 1st character a FF means a single FF without any printable + characters. Don't count it as a line with -n option. */ + if (c == '\f') + single_ff = TRUE; + + /* Preparing for a FF-coincidence: Maybe we finish that page + without a FF found */ + if (last_line) + p->full_page_printed = TRUE; + + while (c != '\n') + { + if (c == '\f') + { + /* No FF-coincidence possible, + no catching up of a FF-coincidence with next page */ + if (last_line) + { + if (!parallel_files) + for (q = column_vector, i = columns; i; ++q, --i) + q->full_page_printed = FALSE; + else + p->full_page_printed = FALSE; + } + + if ((c = getc (f)) != '\n') + ungetc (c, f); hold_file (p); break; } @@ -1457,7 +1898,12 @@ read_rest_of_line (COLUMN *p) close_file (p); break; } + c = getc (f); } + + if (skip_count) + if ((!parallel_files || column_number == 1) && !single_ff) + ++line_count; } /* If we're tabifying output, @@ -1474,7 +1920,7 @@ print_white_space (void) 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) + && (h_new = pos_after_tab (chars_per_output_tab, h_old)) <= goal) { putchar (output_tab_char); h_old = h_new; @@ -1489,20 +1935,32 @@ print_white_space (void) /* Print column separators. We keep a count until we know that we'll be printing a line, - then print_separators() is called. */ + then print_sep_string() is called. */ static void -print_separators (void) +print_sep_string () { + char *s; + int l = col_sep_length; + + s = col_sep_string; + + if (spaces_not_printed > 0) + print_white_space (); + for (; separators_not_printed > 0; --separators_not_printed) - print_char (column_separator); + { + while (l-- > 0) + putchar (*s++); + output_position += col_sep_length; + } } /* Print (or store, depending on p->char_func) a clump of N characters. */ static void -print_clump (COLUMN *p, int n, int *clump) +print_clump (COLUMN * p, int n, int *clump) { while (n--) (p->char_func) (*clump++); @@ -1510,6 +1968,8 @@ print_clump (COLUMN *p, int n, int *clump) /* Print a character. + Update the following comment: process-char hasn't been used any + longer. 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 @@ -1550,12 +2010,23 @@ skip_to_page (int page) for (n = 1; n < page; ++n) { - for (i = 1; i <= lines_per_body; ++i) + for (i = 1; i < lines_per_body; ++i) { for (j = 1, p = column_vector; j <= columns; ++j, ++p) - read_rest_of_line (p); + if (p->status == OPEN) + skip_read (p, j); } + last_line = TRUE; + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + if (p->status == OPEN) + skip_read (p, j); + + if (storing_columns) /* change FF_FOUND to ON_HOLD */ + for (j = 1, p = column_vector; j <= columns; ++j, ++p) + p->status = ON_HOLD; + reset_status (); + last_line = FALSE; } return files_ready_to_read > 0; } @@ -1575,7 +2046,10 @@ print_header (void) pad_across_to (chars_per_margin); print_white_space (); - fprintf (stdout, "%s %d\n\n\n", header, page_number++); + if (!standard_header && *custom_header == '\0') + fprintf (stdout, "%s\n\n\n", header); + else + fprintf (stdout, "%s%5d\n\n\n", header, page_number++); print_a_header = FALSE; output_position = 0; @@ -1587,37 +2061,57 @@ print_header (void) (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. + 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. + 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. */ + an end of line character, TRUE otherwise. */ static int -read_line (COLUMN *p) +read_line (COLUMN * p) { register int c, chars; int last_input_position; + int j, k; + COLUMN *q; -#ifdef lint /* Suppress `used before initialized' warning. */ +/* Suppress `used before initialized' warning. */ +#ifdef lint chars = 0; #endif + /* read 1st character in each line or any character succeeding a FF: */ c = getc (p->fp); last_input_position = input_position; + + if (c == '\f' && p->full_page_printed) + if ((c = getc (p->fp)) == '\n') + c = getc (p->fp); + p->full_page_printed = FALSE; + switch (c) { case '\f': + if ((c = getc (p->fp)) != '\n') + ungetc (c, p->fp); + FF_only = TRUE; + if (print_a_header && !storing_columns) + { + pad_vertically = TRUE; + print_header (); + } + else if (keep_FF) + print_a_FF = TRUE; hold_file (p); return TRUE; case EOF: @@ -1639,22 +2133,41 @@ read_line (COLUMN *p) { pad_vertically = TRUE; - if (print_a_header) + if (print_a_header && !storing_columns) print_header (); - if (padding_not_printed != ANYWHERE) + if (parallel_files && align_empty_cols) { - pad_across_to (padding_not_printed); + /* We have to align empty columns at the beginning of a line. */ + k = separators_not_printed; + separators_not_printed = 0; + for (j = 1, q = column_vector; j <= k; ++j, ++q) + { + align_column (q); + separators_not_printed += 1; + } + padding_not_printed = p->start_position; + if (truncate_lines) + spaces_not_printed = chars_per_column; + else + spaces_not_printed = 0; + align_empty_cols = FALSE; + } + + if (padding_not_printed - col_sep_length > 0) + { + pad_across_to (padding_not_printed - col_sep_length); padding_not_printed = ANYWHERE; } - if (use_column_separator) - print_separators (); + if (use_col_separator) + print_sep_string (); } if (p->numbered) number (p); + empty_line = FALSE; if (c == '\n') return TRUE; @@ -1669,6 +2182,10 @@ read_line (COLUMN *p) case '\n': return TRUE; case '\f': + if ((c = getc (p->fp)) != '\n') + ungetc (c, p->fp); + if (keep_FF) + print_a_FF = TRUE; hold_file (p); return TRUE; case EOF: @@ -1690,16 +2207,24 @@ read_line (COLUMN *p) /* 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. + If this function has been called, we know we have "something to + print". But it remains to be seen whether we have a real text page + or an empty page (a single form feed) with/without a header only. + Therefore first we set pad_vertically to TRUE and print a header + if necessary. + If FF_FOUND and we are using -t|-T option we omit any newline by + setting pad_vertically to FALSE (see print_page). + Otherwise we pad across if necessary, print separators if necessary + and text of COLUMN *p. Return TRUE, meaning there is no need to call read_rest_of_line. */ static int -print_stored (COLUMN *p) +print_stored (COLUMN * p) { + COLUMN *q; + int i; + int line = p->current_line++; register char *first = &buff[line_vector[line]]; register char *last = &buff[line_vector[line + 1]]; @@ -1709,20 +2234,36 @@ print_stored (COLUMN *p) if (print_a_header) print_header (); - if (padding_not_printed != ANYWHERE) + if (p->status == FF_FOUND) + { + for (i = 1, q = column_vector; i <= columns; ++i, ++q) + q->status = ON_HOLD; + if (column_vector->lines_to_print <= 0) + { + if (!extremities) + pad_vertically = FALSE; + return TRUE; /* print a header only */ + } + } + + if (padding_not_printed - col_sep_length > 0) { - pad_across_to (padding_not_printed); + pad_across_to (padding_not_printed - col_sep_length); padding_not_printed = ANYWHERE; } - if (use_column_separator) - print_separators (); + if (use_col_separator) + print_sep_string (); while (first != last) print_char (*first++); if (spaces_not_printed == 0) - output_position = p->start_position + end_vector[line]; + { + output_position = p->start_position + end_vector[line]; + if (p->start_position - col_sep_length == chars_per_margin) + output_position -= col_sep_length; + } return TRUE; } @@ -1855,33 +2396,54 @@ usage (int status) Usage: %s [OPTION]... [FILE]...\n\ "), program_name); + printf (_("\ Paginate or columnate FILE(s) for printing.\n\ \n\ - +PAGE begin printing with page PAGE\n\ - -COLUMN produce COLUMN-column output and print columns down\n\ - -a print columns across rather than down\n\ - -b balance columns on the last page\n\ + +FIRST_PAGE[:LAST_PAGE] + begin [stop] printing with page FIRST_[LAST_]PAGE\n\ + -COLUMN produce COLUMN-column output and print columns down,\n\ + unless -a is used. Balance number of lines in the\n\ + columns on each page.\n\ + -a print columns across rather than down, used together\n\ + with -COLUMN\n\ -c use hat notation (^G) and octal backslash notation\n\ -d double space the output\n\ -e[CHAR[WIDTH]] expand input CHARs (TABs) to tab WIDTH (8)\n\ - -f, -F simulate formfeed with newlines on output\n\ - -h HEADER use HEADER instead of filename in page headers\n\ + -F, -f use form feeds instead of newlines to separate pages\n\ + (by a 3-line page header with -f or a 5-line header\n\ + and trailer without -f)\n\ + -h HEADER use a centered HEADER instead of filename in page headers\n\ + with long headers left-hand-side truncation may occur\n\ + -h \"\" prints a blank line. Don't use -h\"\"\n\ -i[CHAR[WIDTH]] replace spaces with CHARs (TABs) to tab WIDTH (8)\n\ + -j merge full lines, turns off -w line truncation, no column\n\ + alignment, -s[STRING] sets separators\n\ -l PAGE_LENGTH set the page length to PAGE_LENGTH (66) lines\n\ - -m print all files in parallel, one in each column\n\ + (default number of lines of text 56, with -f 63)\n\ + -m print all files in parallel, one in each column,\n\ + truncate lines, but join lines of full length with -j\n\ -n[SEP[DIGITS]] number lines, use DIGITS (5) digits, then SEP (TAB)\n\ + default counting starts with 1st line of input file\n\ + -N NUMBER start counting with NUMBER at 1st line of first\n\ + page printed (see +FIRST_PAGE)\n\ -o MARGIN offset each line with MARGIN spaces (do not affect -w)\n\ -r inhibit warning when a file cannot be opened\n\ - -s[SEP] separate columns by character SEP (TAB)\n\ - -t inhibit 5-line page headers and trailers\n\ + -s[STRING] separate columns by an optional STRING\n\ + don't use -s \"STRING\" \n\ + without -s: default sep. \'space\' used, same as -s\" \"\n\ + -s only: no separator used, same as -s\"\" \n\ + -t inhibit page headers and trailers\n\ + -T inhibit page headers and trailers, eliminate any page\n\ + layout by form feeds set in input files\n\ -v use octal backslash notation\n\ - -w PAGE_WIDTH set page width to PAGE_WIDTH (72) columns\n\ - --help display this help and exit\n\ - --version output version information and exit\n\ + -w PAGE_WIDTH set page width to PAGE_WIDTH (72) columns, truncate\n\ + lines (see also -j option)\n\ + --help display this help and exit\n\ + --version output version information and exit\n\ \n\ --t implied by -l N when N < 10. Without -s, columns are separated by\n\ -spaces. With no FILE, or when FILE is -, read standard input.\n\ +-T implied by -l nn when nn <= 10 or <= 3 with -f. With no FILE, or when\n\ +FILE is -, read standard input.\n\ ")); puts (_("\nReport bugs to textutils-bugs@gnu.ai.mit.edu")); } |