colcrt: reimplementation

This implementation aims to be easier to read, more robust dealing all sorts
of unexpected inputs, and possibly even more correct.  The correctness
refers to last line handling this implementation does differently than the
previous.  With the previous last line that ended to EOF without \n was not
printed.

Signed-off-by: Sami Kerola <kerolasa@iki.fi>
This commit is contained in:
Sami Kerola 2015-09-23 09:42:58 +01:00
parent 5abd5d8aec
commit ea4ccc9ea1
No known key found for this signature in database
GPG Key ID: A9553245FDE9B739
1 changed files with 180 additions and 247 deletions

View File

@ -40,26 +40,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> /* for close() */
#include <string.h>
#include <getopt.h>
#include "nls.h"
#include "nls.h"
#include "widechar.h"
#include "c.h"
#include "closestream.h"
int plus(wchar_t c, wchar_t d);
void move(int l, int m);
void pflush(int ol);
static void __attribute__ ((__noreturn__)) usage(FILE * out);
/*
* colcrt - replaces col for crts with new nroff esp. when using tbl.
* Bill Joy UCB July 14, 1977
*
* This filter uses a screen buffer, 267 half-lines by 132 columns.
* It interprets the up and down sequences generated by the new
* This filter uses the up and down sequences generated by the new
* nroff when used with tbl and by \u \d and \r.
* General overstriking doesn't work correctly.
* Underlining is split onto multiple lines, etc.
@ -68,266 +60,207 @@ static void __attribute__ ((__noreturn__)) usage(FILE * out);
* Option -2 forces printing of all half lines.
*/
#define FLUSH_SIZE 62
#define PAGE_ARRAY_ROWS 267
#define PAGE_ARRAY_COLS 132
wchar_t page[PAGE_ARRAY_ROWS + 1][PAGE_ARRAY_COLS + 1];
enum { OUTPUT_COLS = 132 };
int outline = 1;
int outcol;
char suppresul;
char printall;
void colcrt(FILE *f);
int main(int argc, char **argv) {
struct colcrt_control {
FILE *f;
int i, opt;
wchar_t line[OUTPUT_COLS + 1];
wchar_t line_under[OUTPUT_COLS + 1];
unsigned int
print_nl:1,
need_line_under:1,
no_underlining:1,
half_lines:1;
};
static void __attribute__((__noreturn__)) usage(FILE *out)
{
fputs(USAGE_HEADER, out);
fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name);
fputs(USAGE_SEPARATOR, out);
fputs(_("Filter nroff output for CRT previewing.\n"), out);
fputs(USAGE_OPTIONS, out);
fputs(_(" -, --no-underlining suppress all underlining\n"), out);
fputs(_(" -2, --half-lines print all half-lines\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(USAGE_HELP, out);
fputs(USAGE_VERSION, out);
fprintf(out, USAGE_MAN_TAIL("colcrt(1)"));
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}
static void trim_trailing_spaces(wchar_t *s)
{
size_t size;
wchar_t *end;
size = wcslen(s);
if (!size)
return;
end = s + size - 1;
while (s <= end && iswspace(*end))
end--;
*(end + 1) = L'\0';
}
static void output_lines(struct colcrt_control *ctl, int col)
{
/* first line */
trim_trailing_spaces(ctl->line);
fputws(ctl->line, stdout);
if (ctl->print_nl)
fputwc(L'\n', stdout);
if (!ctl->half_lines && !ctl->no_underlining)
ctl->print_nl = 0;
wmemset(ctl->line, L'\0', OUTPUT_COLS);
/* second line */
if (ctl->need_line_under) {
ctl->need_line_under = 0;
ctl->line_under[col] = L'\0';
trim_trailing_spaces(ctl->line_under);
fputws(ctl->line_under, stdout);
fputwc(L'\n', stdout);
wmemset(ctl->line_under, L' ', OUTPUT_COLS);
} else if (ctl->half_lines && 0 < col)
fputwc(L'\n', stdout);
}
static int rubchars(struct colcrt_control *ctl, int col, int n)
{
while (0 < n && 0 < col) {
ctl->line[col] = L'\0';
ctl->line_under[col] = L' ';
n--;
col--;
}
return col;
}
static void colcrt(struct colcrt_control *ctl)
{
int col;
wint_t c;
ctl->print_nl = 1;
if (ctl->half_lines)
fputwc(L'\n', stdout);
for (col = 0; /* nothing */; col++) {
if (OUTPUT_COLS - 1 < col) {
output_lines(ctl, col);
errno = 0;
while ((c = getwc(ctl->f)) != L'\n') {
if (errno == 0 && c == WEOF)
return;
else
errno = 0;
}
col = -1;
continue;
}
c = getwc(ctl->f);
switch (c) {
case 033: /* ESC */
c = getwc(ctl->f);
if (c == L'8') {
col = rubchars(ctl, col, 1);
continue;
}
if (c == L'7') {
col = rubchars(ctl, col, 2);
continue;
}
continue;
case WEOF:
ctl->print_nl = 0;
output_lines(ctl, col);
return;
case L'\n':
output_lines(ctl, col);
col = -1;
continue;
case L'\t':
for (/* nothing */; col % 8 && col < OUTPUT_COLS; col++) {
ctl->line[col] = L' ';
}
col--;
continue;
case L'_':
ctl->line[col] = L' ';
if (!ctl->no_underlining) {
ctl->need_line_under = 1;
ctl->line_under[col] = L'-';
}
continue;
default:
if (!iswprint(c)) {
col--;
continue;
}
ctl->print_nl = 1;
ctl->line[col] = c;
}
}
}
int main(int argc, char **argv)
{
struct colcrt_control ctl = { 0 };
int opt;
enum { NO_UL_OPTION = CHAR_MAX + 1 };
static const struct option longopts[] = {
{ "no-underlining", no_argument, 0, NO_UL_OPTION },
{ "half-lines", no_argument, 0, '2' },
{ "version", no_argument, 0, 'V' },
{ "help", no_argument, 0, 'h' },
{ NULL, 0, 0, 0}
{"no-underlining", no_argument, NULL, NO_UL_OPTION},
{"half-lines", no_argument, NULL, '2'},
{"version", no_argument, NULL, 'V'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
atexit(close_stdout);
/* Take care of lonely hyphen option. */
for (i = 0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '\0') {
suppresul = 1;
for (opt = 0; opt < argc; opt++)
if (argv[opt][0] == '-' && argv[opt][1] == '\0') {
ctl.no_underlining = 1;
argc--;
memmove(argv + i, argv + i + 1,
sizeof(char *) * (argc - i));
i--;
memmove(argv + opt, argv + opt + 1,
sizeof(char *) * (argc - opt));
opt--;
}
while ((opt = getopt_long(argc, argv, "2Vh", longopts, NULL)) != -1)
switch (opt) {
case NO_UL_OPTION:
suppresul = 1;
break;
case '2':
printall = 1;
break;
case 'V':
printf(UTIL_LINUX_VERSION);
return EXIT_SUCCESS;
case 'h':
usage(stdout);
default:
usage(stderr);
case NO_UL_OPTION:
ctl.no_underlining = 1;
break;
case '2':
ctl.half_lines = 1;
break;
case 'V':
printf(UTIL_LINUX_VERSION);
return EXIT_SUCCESS;
case 'h':
usage(stdout);
default:
usage(stderr);
}
argc -= optind;
argv += optind;
do {
wmemset(ctl.line, L'\0', OUTPUT_COLS);
wmemset(ctl.line_under, L' ', OUTPUT_COLS);
if (argc > 0) {
if (!(f = fopen(argv[0], "r"))) {
fflush(stdout);
err(EXIT_FAILURE, "%s", argv[0]);
}
if (!(ctl.f = fopen(argv[0], "r")))
err(EXIT_FAILURE, _("cannot open %s"), argv[0]);
argc--;
argv++;
} else {
f = stdin;
}
colcrt(f);
if (f != stdin)
fclose(f);
} else
ctl.f = stdin;
colcrt(&ctl);
if (ctl.f != stdin)
fclose(ctl.f);
} while (argc > 0);
fflush(stdout);
return EXIT_SUCCESS;
}
void colcrt(FILE *f) {
wint_t c;
wchar_t *cp, *dp;
int i, w;
for (;;) {
c = getwc(f);
if (c == WEOF) {
pflush(outline);
fflush(stdout);
break;
}
switch (c) {
case '\n':
if (outline >= (PAGE_ARRAY_ROWS - 2))
pflush(FLUSH_SIZE);
outline += 2;
outcol = 0;
continue;
case '\016':
case '\017':
continue;
case 033:
c = getwc(f);
switch (c) {
case '9':
if (outline >= (PAGE_ARRAY_ROWS - 1))
pflush(FLUSH_SIZE);
outline++;
continue;
case '8':
if (outline >= 1)
outline--;
continue;
case '7':
outline -= 2;
if (outline < 0)
outline = 0;
continue;
default:
continue;
}
case '\b':
if (outcol)
outcol--;
continue;
case '\t':
outcol += 8;
outcol &= ~7;
outcol--;
c = ' ';
/* fallthrough */
default:
w = wcwidth(c);
if (w < 0)
continue;
if (outcol + w > PAGE_ARRAY_COLS) {
outcol++;
continue;
}
cp = &page[outline][outcol];
outcol += w;
if (c == '_') {
if (suppresul)
continue;
cp += PAGE_ARRAY_COLS;
c = '-';
}
if (*cp == 0) {
/* trick! */
for (i = 0; i < w; i++)
cp[i] = c;
dp = cp - (outcol - w);
for (cp--; cp >= dp && *cp == 0; cp--)
*cp = ' ';
} else {
if (plus(c, *cp) || plus(*cp, c))
*cp = '+';
else if (*cp == ' ' || *cp == 0) {
for (i = 1; i < w; i++)
if (cp[i] != ' ' && cp[i] != 0)
continue;
for (i = 0; i < w; i++)
cp[i] = c;
}
}
continue;
}
}
}
int plus(wchar_t c, wchar_t d)
{
return (c == '|' && (d == '-' || d == '_'));
}
int first;
void pflush(int ol)
{
register int i;
register wchar_t *cp;
char lastomit;
int l, w;
l = ol;
lastomit = 0;
if (l > (PAGE_ARRAY_ROWS - 1))
l = PAGE_ARRAY_ROWS - 1;
else
l |= 1;
for (i = first | 1; i < l; i++) {
move(i, i - 1);
move(i, i + 1);
}
for (i = first; i < l; i++) {
cp = page[i];
if (printall == 0 && lastomit == 0 && *cp == 0) {
lastomit = 1;
continue;
}
lastomit = 0;
while (*cp) {
if ((w = wcwidth(*cp)) > 0) {
putwchar(*cp);
cp += w;
} else
cp++;
}
putwchar('\n');
}
memmove(page, page[ol], (PAGE_ARRAY_ROWS - ol) * PAGE_ARRAY_COLS * sizeof(wchar_t));
memset(page[PAGE_ARRAY_ROWS - ol], '\0', ol * PAGE_ARRAY_COLS * sizeof(wchar_t));
outline -= ol;
outcol = 0;
first = 1;
}
void move(int l, int m)
{
register wchar_t *cp, *dp;
for (cp = page[l], dp = page[m]; *cp; cp++, dp++) {
switch (*cp) {
case '|':
if (*dp != ' ' && *dp != '|' && *dp != 0)
return;
break;
case ' ':
break;
default:
return;
}
}
if (*cp == 0) {
for (cp = page[l], dp = page[m]; *cp; cp++, dp++)
if (*cp == '|')
*dp = '|';
else if (*dp == 0)
*dp = ' ';
page[l][0] = 0;
}
}
static void __attribute__ ((__noreturn__)) usage(FILE * out)
{
fputs(USAGE_HEADER, out);
fprintf(out, _(" %s [options] [<file>...]\n"), program_invocation_short_name);
fputs(USAGE_SEPARATOR, out);
fputs(_("Filter nroff output for CRT previewing.\n"), out);
fputs(USAGE_OPTIONS, out);
fputs(_(" -, --no-underlining suppress all underlining\n"), out);
fputs(_(" -2, --half-lines print all half-lines\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(USAGE_HELP, out);
fputs(USAGE_VERSION, out);
fprintf(out, USAGE_MAN_TAIL("colcrt(1)"));
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}