Replace col with the FreeBSD version that adds wide char support

This commit is contained in:
christos 2020-11-01 22:27:15 +00:00
parent d5a2f60bf8
commit 0076513c11
3 changed files with 254 additions and 158 deletions

View File

@ -1,6 +1,7 @@
# $NetBSD: Makefile,v 1.5 2009/04/14 22:15:18 lukem Exp $
# $NetBSD: Makefile,v 1.6 2020/11/01 22:27:15 christos Exp $
# @(#)Makefile 8.1 (Berkeley) 6/6/93
WARNS=6
PROG= col
.include <bsd.prog.mk>

View File

@ -1,5 +1,4 @@
.\" $NetBSD: col.1,v 1.10 2012/03/22 07:58:18 wiz Exp $
.\"
.\" $NetBSD: col.1,v 1.11 2020/11/01 22:27:15 christos Exp $
.\" Copyright (c) 1990, 1993
.\" The Regents of the University of California. All rights reserved.
.\"
@ -31,8 +30,9 @@
.\" SUCH DAMAGE.
.\"
.\" @(#)col.1 8.1 (Berkeley) 6/29/93
.\" $FreeBSD: head/usr.bin/col/col.1 366913 2020-10-21 16:30:34Z fernape $
.\"
.Dd February 22, 1999
.Dd November 1, 2020
.Dt COL 1
.Os
.Sh NAME
@ -40,23 +40,21 @@
.Nd filter reverse line feeds from input
.Sh SYNOPSIS
.Nm
.Op Fl bfpx
.Op Fl bfhpx
.Op Fl l Ar num
.Sh DESCRIPTION
The
.Nm
filters out reverse (and half reverse) line feeds so that the output is
utility filters out reverse (and half reverse) line feeds so that the output is
in the correct order with only forward and half forward line
feeds, and replaces white-space characters with tabs where possible.
This can be useful in processing the output of
.Xr nroff 1
and
.Xr tbl 1 .
.Pp
The
.Nm
reads from the standard input and writes to the standard output.
utility reads from the standard input and writes to the standard output.
.Pp
The options are as follows:
.Bl -tag -width "-l num "
.Bl -tag -width indent
.It Fl b
Do not output any backspaces, printing only the last character
written to each column position.
@ -64,6 +62,13 @@ written to each column position.
Forward half line feeds are permitted (``fine'' mode).
Normally characters printed on a half line boundary are printed
on the following line.
.It Fl h
Do not output multiple spaces instead of tabs (default).
.It Fl l Ar num
Buffer at least
.Ar num
lines in memory.
By default, 128 lines are buffered.
.It Fl p
Force unknown control sequences to be passed through unchanged.
Normally,
@ -72,25 +77,35 @@ will filter out any control sequences from the input other than those
recognized and interpreted by itself, which are listed below.
.It Fl x
Output multiple spaces instead of tabs.
.It Fl l Ar num
Buffer at least
.Ar num
lines in memory.
By default, 128 lines are buffered.
.El
.Pp
The control sequences for carriage motion that
In the input stream,
.Nm
understands and their decimal values are listed in the following
table:
understands both the escape sequences of the form escape-digit
mandated by
.St -susv2
and the traditional
.Bx
format escape-control-character.
The control sequences for carriage motion and their ASCII values
are as follows:
.Pp
.Bl -tag -width "carriage return" -compact
.It ESC\-BELL
reverse line feed (escape then bell).
.It ESC\-7
reverse line feed (escape then 7)
reverse line feed (escape then 7).
.It ESC\-BACKSPACE
half reverse line feed (escape then backspace).
.It ESC\-8
half reverse line feed (escape then 8)
half reverse line feed (escape then 8).
.It ESC\-TAB
half forward line feed (escape than tab).
.It ESC\-9
half forward line feed (escape then 9)
half forward line feed (escape then 9).
In
.Fl f
mode, this sequence may also occur in the output stream.
.It backspace
moves back one column (8); ignored in the first column
.It carriage return
@ -100,7 +115,7 @@ forward line feed (10); also does carriage return
.It shift in
shift to normal character set (15)
.It shift out
shift to alternative character set (14)
shift to alternate character set (14)
.It space
moves forward one column (32)
.It tab
@ -112,27 +127,46 @@ reverse line feed (11)
All unrecognized control characters and escape sequences are
discarded.
.Pp
The
.Nm
keeps track of the character set as characters are read and makes
utility keeps track of the character set as characters are read and makes
sure the character set is correct when they are output.
.Pp
If the input attempts to back up to the last flushed line,
.Nm
will display a warning message.
.Sh ENVIRONMENT
The
.Ev LANG , LC_ALL
and
.Ev LC_CTYPE
environment variables affect the execution of
.Nm
as described in
.Xr environ 7 .
.Sh EXIT STATUS
.Ex -std
.Sh EXAMPLES
We can use
.Nm
to filter the output of
.Xr man 1
and remove the backspace characters (
.Em ^H
) before searching for some text:
.Bd -literal -offset indent
man ls | col -b | grep HISTORY
.Ed
.Sh SEE ALSO
.Xr expand 1 ,
.Xr nroff 1 ,
.Xr tbl 1
.Xr expand 1
.Sh STANDARDS
The
.Nm
utility conforms to
.St -xpg4.2 .
The
.Fl l
option is an extension to the standard.
.St -susv2 .
.Sh HISTORY
A
.Nm
command appeared in
command
appeared in
.At v6 .

View File

@ -1,6 +1,8 @@
/* $NetBSD: col.c,v 1.18 2014/10/18 14:56:14 christos Exp $ */
/* $NetBSD: col.c,v 1.19 2020/11/01 22:27:15 christos Exp $ */
/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1990, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
@ -41,16 +43,25 @@ __COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\
#ifndef lint
#if 0
static char sccsid[] = "@(#)col.c 8.5 (Berkeley) 5/4/95";
__FBSDID("$FreeBSD: head/usr.bin/col/col.c 366577 2020-10-09 15:27:37Z markj $")
;
#endif
__RCSID("$NetBSD: col.c,v 1.18 2014/10/18 14:56:14 christos Exp $");
__RCSID("$NetBSD: col.c,v 1.19 2020/11/01 22:27:15 christos Exp $");
#endif /* not lint */
#include <ctype.h>
#include <err.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>
#define BS '\b' /* backspace */
#define TAB '\t' /* tab */
@ -61,9 +72,9 @@ __RCSID("$NetBSD: col.c,v 1.18 2014/10/18 14:56:14 christos Exp $");
#define SI '\017' /* shift in to normal character set */
#define SO '\016' /* shift out to alternate character set */
#define VT '\013' /* vertical tab (aka reverse line feed) */
#define RLF '\007' /* ESC-07 reverse line feed */
#define RHLF '\010' /* ESC-010 reverse half-line feed */
#define FHLF '\011' /* ESC-011 forward half-line feed */
#define RLF '7' /* ESC-7 reverse line feed */
#define RHLF '8' /* ESC-8 reverse half-line feed */
#define FHLF '9' /* ESC-9 forward half-line feed */
/* build up at least this many lines before flushing them out */
#define BUFFER_MARGIN 32
@ -73,9 +84,10 @@ typedef char CSET;
typedef struct char_str {
#define CS_NORMAL 1
#define CS_ALTERNATE 2
short c_column; /* column character is in */
int c_column; /* column character is in */
CSET c_set; /* character set (currently only 2) */
char c_char; /* character in question */
wchar_t c_char; /* character in question */
int c_width; /* character width */
} CHAR;
typedef struct line_str LINE;
@ -89,6 +101,7 @@ struct line_str {
int l_max_col; /* max column in the line */
};
static void addto_lineno(int *, int);
static LINE *alloc_line(void);
static void dowarn(int);
static void flush_line(LINE *);
@ -96,26 +109,26 @@ static void flush_lines(int);
static void flush_blanks(void);
static void free_line(LINE *);
__dead static void usage(void);
__dead static void wrerr(void);
static void *xmalloc(void *, size_t);
static CSET last_set; /* char_set of last char printed */
static LINE *lines;
static int compress_spaces; /* if doing space -> tab conversion */
static int fine; /* if `fine' resolution (half lines) */
static int max_bufd_lines; /* max # lines to keep in memory */
static int max_bufd_lines; /* max # of half lines to keep in memory */
static int nblank_lines; /* # blanks after last flushed line */
static int no_backspaces; /* if not to output any backspaces */
static int pass_unknown_seqs; /* whether to pass unknown control sequences */
static int pass_unknown_seqs; /* pass unknown control sequences */
#define PUTC(ch) \
if (putchar(ch) == EOF) \
wrerr();
do { \
if (putwchar(ch) == WEOF) \
errx(EXIT_FAILURE, "write error"); \
} while (/*CONSTCOND*/0)
int
main(int argc, char **argv)
{
int ch;
wint_t ch;
CHAR *c;
CSET cur_set; /* current character set */
LINE *l; /* current line */
@ -125,11 +138,13 @@ main(int argc, char **argv)
int max_line; /* max value of cur_line */
int this_line; /* line l points to */
int nflushd_lines; /* number of lines that were flushed */
int adjust, opt, warned;
int adjust, opt, warned, width;
int e;
max_bufd_lines = 128;
(void)setlocale(LC_CTYPE, "");
max_bufd_lines = 256;
compress_spaces = 1; /* compress spaces into tabs */
pass_unknown_seqs = 0; /* remove unknown escape sequences */
while ((opt = getopt(argc, argv, "bfhl:px")) != -1)
switch (opt) {
case 'b': /* do not output backspaces */
@ -142,11 +157,11 @@ main(int argc, char **argv)
compress_spaces = 1;
break;
case 'l': /* buffered line count */
if ((max_bufd_lines = atoi(optarg)) <= 0) {
(void)fprintf(stderr,
"col: bad -l argument %s.\n", optarg);
exit(EXIT_FAILURE);
}
max_bufd_lines = (int)strtoi(optarg, NULL, 0, 1,
(INT_MAX - BUFFER_MARGIN) / 2, &e) * 2;
if (e)
errc(EXIT_FAILURE, e, "bad -l argument `%s'",
optarg);
break;
case 'p': /* pass unknown control sequences */
pass_unknown_seqs = 1;
@ -162,16 +177,13 @@ main(int argc, char **argv)
if (optind != argc)
usage();
/* this value is in half lines */
max_bufd_lines *= 2;
adjust = cur_col = extra_lines = warned = 0;
cur_line = max_line = nflushd_lines = this_line = 0;
cur_set = last_set = CS_NORMAL;
lines = l = alloc_line();
while ((ch = getchar()) != EOF) {
if (!isgraph(ch)) {
while ((ch = getwchar()) != WEOF) {
if (!iswgraph(ch)) {
switch (ch) {
case BS: /* can't go back further */
if (cur_col == 0)
@ -182,21 +194,32 @@ main(int argc, char **argv)
cur_col = 0;
continue;
case ESC: /* just ignore EOF */
switch(getchar()) {
switch(getwchar()) {
/*
* In the input stream, accept both the
* XPG5 sequences ESC-digit and the
* traditional BSD sequences ESC-ctrl.
*/
case '\007':
/* FALLTHROUGH */
case RLF:
cur_line -= 2;
addto_lineno(&cur_line, -2);
break;
case '\010':
/* FALLTHROUGH */
case RHLF:
cur_line--;
addto_lineno(&cur_line, -1);
break;
case '\011':
/* FALLTHROUGH */
case FHLF:
cur_line++;
addto_lineno(&cur_line, 1);
if (cur_line > max_line)
max_line = cur_line;
}
continue;
case NL:
cur_line += 2;
addto_lineno(&cur_line, 2);
if (cur_line > max_line)
max_line = cur_line;
cur_col = 0;
@ -215,7 +238,12 @@ main(int argc, char **argv)
++cur_col;
continue;
case VT:
cur_line -= 2;
addto_lineno(&cur_line, -2);
continue;
}
if (iswspace(ch)) {
if ((width = wcwidth(ch)) > 0)
cur_col += width;
continue;
}
if (!pass_unknown_seqs)
@ -223,58 +251,61 @@ main(int argc, char **argv)
}
/* Must stuff ch in a line - are we at the right one? */
if (cur_line != this_line - adjust) {
if (cur_line + adjust != this_line) {
LINE *lnew;
int nmove;
adjust = 0;
nmove = cur_line - this_line;
if (!fine) {
/* round up to next line */
if (cur_line & 1) {
adjust = 1;
nmove++;
}
}
if (nmove < 0) {
for (; nmove < 0 && l->l_prev; nmove++)
adjust = !fine && (cur_line & 1);
if (cur_line + adjust < this_line) {
while (cur_line + adjust < this_line &&
l->l_prev != NULL) {
l = l->l_prev;
if (nmove) {
this_line--;
}
if (cur_line + adjust < this_line) {
if (nflushd_lines == 0) {
/*
* Allow backup past first
* line if nothing has been
* flushed yet.
*/
for (; nmove < 0; nmove++) {
while (cur_line + adjust
< this_line) {
lnew = alloc_line();
l->l_prev = lnew;
lnew->l_next = l;
l = lines = lnew;
extra_lines++;
this_line--;
}
} else {
if (!warned++)
dowarn(cur_line);
cur_line -= nmove;
cur_line = this_line - adjust;
}
}
} else {
/* may need to allocate here */
for (; nmove > 0 && l->l_next; nmove--)
while (cur_line + adjust > this_line) {
if (l->l_next == NULL) {
l->l_next = alloc_line();
l->l_next->l_prev = l;
}
l = l->l_next;
for (; nmove > 0; nmove--) {
lnew = alloc_line();
lnew->l_prev = l;
l->l_next = lnew;
l = lnew;
this_line++;
}
}
this_line = cur_line + adjust;
nmove = this_line - nflushd_lines;
if (nmove >= max_bufd_lines + BUFFER_MARGIN) {
nflushd_lines += nmove - max_bufd_lines;
flush_lines(nmove - max_bufd_lines);
if (this_line > nflushd_lines &&
this_line - nflushd_lines >=
max_bufd_lines + BUFFER_MARGIN) {
if (extra_lines) {
flush_lines(extra_lines);
extra_lines = 0;
}
flush_lines(this_line - nflushd_lines -
max_bufd_lines);
nflushd_lines = this_line - max_bufd_lines;
}
}
/* grow line's buffer? */
@ -282,14 +313,16 @@ main(int argc, char **argv)
int need;
need = l->l_lsize ? l->l_lsize * 2 : 90;
l->l_line = (CHAR *)xmalloc((void *) l->l_line,
(unsigned) need * sizeof(CHAR));
if ((l->l_line = realloc(l->l_line,
(unsigned)need * sizeof(CHAR))) == NULL)
err(EXIT_FAILURE, NULL);
l->l_lsize = need;
}
c = &l->l_line[l->l_line_len++];
c->c_char = ch;
c->c_set = cur_set;
c->c_column = cur_col;
c->c_width = wcwidth(ch);
/*
* If things are put in out of order, they will need sorting
* when it is flushed.
@ -298,32 +331,48 @@ main(int argc, char **argv)
l->l_needs_sort = 1;
else
l->l_max_col = cur_col;
cur_col++;
if (c->c_width > 0)
cur_col += c->c_width;
}
if (ferror(stdin))
err(EXIT_FAILURE, NULL);
if (extra_lines) {
/*
* Extra lines only exist if no lines have been flushed
* yet. This means that 'lines' must point to line zero
* after we flush the extra lines.
*/
flush_lines(extra_lines);
l = lines;
this_line = 0;
}
if (max_line == 0)
exit(EXIT_SUCCESS); /* no lines, so just exit */
/* goto the last line that had a character on it */
for (; l->l_next; l = l->l_next)
this_line++;
flush_lines(this_line - nflushd_lines + extra_lines + 1);
flush_lines(this_line - nflushd_lines + 1);
/* make sure we leave things in a sane state */
if (last_set != CS_NORMAL)
PUTC('\017');
PUTC(SI);
/* flush out the last few blank lines */
nblank_lines = max_line - this_line;
if (max_line & 1)
nblank_lines++;
else if (!nblank_lines)
/* missing a \n on the last line? */
if (max_line >= this_line)
nblank_lines = max_line - this_line + (max_line & 1);
if (nblank_lines == 0)
/* end with a newline even if the source doesn't */
nblank_lines = 2;
flush_blanks();
exit(EXIT_SUCCESS);
/* NOTREACHED */
}
/*
* Prints the first 'nflush' lines. Printed lines are freed.
* After this function returns, 'lines' points to the first
* of the remaining lines, and 'nblank_lines' will have the
* number of half line feeds between the final flushed line
* and the first remaining line.
*/
static void
flush_lines(int nflush)
{
@ -335,10 +384,10 @@ flush_lines(int nflush)
if (l->l_line) {
flush_blanks();
flush_line(l);
free(l->l_line);
}
if (l->l_next)
nblank_lines++;
if (l->l_line)
(void)free((void *)l->l_line);
free_line(l);
}
if (lines)
@ -346,9 +395,8 @@ flush_lines(int nflush)
}
/*
* Print a number of newline/half newlines. If fine flag is set, nblank_lines
* is the number of half line feeds, otherwise it is the number of whole line
* feeds.
* Print a number of newline/half newlines.
* nblank_lines is the number of half line feeds.
*/
static void
flush_blanks(void)
@ -367,8 +415,8 @@ flush_blanks(void)
for (i = nb; --i >= 0;)
PUTC('\n');
if (half) {
PUTC('\033');
PUTC('\011');
PUTC(ESC);
PUTC(FHLF);
if (!nb)
PUTC('\r');
}
@ -383,14 +431,14 @@ static void
flush_line(LINE *l)
{
CHAR *c, *endc;
int nchars, last_col, this_col;
int i, j, nchars, last_col, save, this_col, tot;
last_col = 0;
nchars = l->l_line_len;
if (l->l_needs_sort) {
static CHAR *sorted;
static int count_size, *count, i, save, sorted_size, tot;
static int count_size, *count, sorted_size;
/*
* Do an O(n) sort on l->l_line by column being careful to
@ -398,15 +446,17 @@ flush_line(LINE *l)
*/
if (l->l_lsize > sorted_size) {
sorted_size = l->l_lsize;
sorted = (CHAR *)xmalloc((void *)sorted,
(unsigned)sizeof(CHAR) * sorted_size);
if ((sorted = realloc(sorted,
sizeof(CHAR) * (size_t)sorted_size)) == NULL)
err(EXIT_FAILURE, NULL);
}
if (l->l_max_col >= count_size) {
count_size = l->l_max_col + 1;
count = (int *)xmalloc((void *)count,
(unsigned)sizeof(int) * count_size);
if ((count = realloc(count,
sizeof(int) * (size_t)count_size)) == NULL)
err(EXIT_FAILURE, NULL);
}
(void)memset(count, 0, sizeof(int) * l->l_max_col + 1);
memset(count, 0, sizeof(int) * (size_t)l->l_max_col + 1);
for (i = nchars, c = l->l_line; --i >= 0; c++)
count[c->c_column]++;
@ -433,45 +483,74 @@ flush_line(LINE *l)
} while (--nchars > 0 && this_col == endc->c_column);
/* if -b only print last character */
if (no_backspaces)
if (no_backspaces) {
c = endc - 1;
if (nchars > 0 &&
this_col + c->c_width > endc->c_column)
continue;
}
if (this_col > last_col) {
int nspace = this_col - last_col;
if (compress_spaces && nspace > 1) {
int ntabs;
while (1) {
int tab_col, tab_size;
ntabs = ((last_col % 8) + nspace) / 8;
if (ntabs) {
nspace -= (ntabs * 8) - (last_col % 8);
while (--ntabs >= 0)
tab_col = (last_col + 8) & ~7;
if (tab_col > this_col)
break;
tab_size = tab_col - last_col;
if (tab_size == 1)
PUTC(' ');
else
PUTC('\t');
nspace -= tab_size;
last_col = tab_col;
}
}
while (--nspace >= 0)
PUTC(' ');
last_col = this_col;
}
last_col++;
for (;;) {
if (c->c_set != last_set) {
switch (c->c_set) {
case CS_NORMAL:
PUTC('\017');
PUTC(SI);
break;
case CS_ALTERNATE:
PUTC('\016');
PUTC(SO);
}
last_set = c->c_set;
}
PUTC(c->c_char);
if ((c + 1) < endc)
for (j = 0; j < c->c_width; j++)
PUTC('\b');
if (++c >= endc)
break;
PUTC('\b');
}
last_col += (c - 1)->c_width;
}
}
/*
* Increment or decrement a line number, checking for overflow.
* Stop one below INT_MAX such that the adjust variable is safe.
*/
void
addto_lineno(int *lno, int offset)
{
if (offset > 0) {
if (*lno >= INT_MAX - offset)
errx(EXIT_FAILURE, "too many lines");
} else {
if (*lno < INT_MIN - offset)
errx(EXIT_FAILURE, "too many reverse line feeds");
}
*lno += offset;
}
#define NALLOC 64
@ -485,7 +564,8 @@ alloc_line(void)
int i;
if (!line_freelist) {
l = (LINE *)xmalloc(NULL, sizeof(LINE) * NALLOC);
if ((l = realloc(NULL, sizeof(LINE) * NALLOC)) == NULL)
err(EXIT_FAILURE, NULL);
line_freelist = l;
for (i = 1; i < NALLOC; i++, l++)
l->l_next = l + 1;
@ -494,7 +574,7 @@ alloc_line(void)
l = line_freelist;
line_freelist = l->l_next;
(void)memset(l, 0, sizeof(LINE));
memset(l, 0, sizeof(LINE));
return (l);
}
@ -506,30 +586,11 @@ free_line(LINE *l)
line_freelist = l;
}
static void *
xmalloc(void *p, size_t size)
{
void *q;
if (!(q = (void *)realloc(p, size)))
err(EXIT_FAILURE, "realloc");
p = q;
return (p);
}
static void
usage(void)
{
(void)fprintf(stderr, "usage: col [-bfpx] [-l nline]\n");
exit(EXIT_FAILURE);
}
static void
wrerr(void)
{
(void)fprintf(stderr, "col: write error.\n");
(void)fprintf(stderr, "Usage: %s [-bfhpx] [-l nline]\n", getprogname());
exit(EXIT_FAILURE);
}