root/usr.bin/column/column.c
/*-
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 1989, 1993, 1994
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/param.h>

#include <err.h>
#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>

#define TAB     8

static void     c_columnate(void);
static void     input(FILE *);
static void     maketbl(void);
static void     print(void);
static void     r_columnate(void);
static void     usage(void);
static int      width(const wchar_t *);

static int      termwidth = 80;         /* default terminal width */
static int      tblcols;                /* number of table columns for -t */

static int      entries;                /* number of records */
static int      eval;                   /* exit value */
static int      maxlength;              /* longest record */
static wchar_t  **list;                 /* array of pointers to records */
static const wchar_t *separator = L"\t "; /* field separator for table option */

int
main(int argc, char **argv)
{
        struct winsize win;
        FILE *fp;
        int ch, tflag, xflag;
        char *p;
        const char *errstr, *src;
        wchar_t *newsep;
        size_t seplen;

        setlocale(LC_ALL, "");

        if (ioctl(1, TIOCGWINSZ, &win) == -1 || !win.ws_col) {
                if ((p = getenv("COLUMNS")))
                        termwidth = atoi(p);
        } else
                termwidth = win.ws_col;

        tflag = xflag = 0;
        while ((ch = getopt(argc, argv, "c:l:s:tx")) != -1)
                switch(ch) {
                case 'c':
                        termwidth = strtonum(optarg, 0, INT_MAX, &errstr);
                        if (errstr != NULL)
                                errx(1, "invalid terminal width \"%s\": %s",
                                    optarg, errstr);
                        break;
                case 'l':
                        tblcols = strtonum(optarg, 0, INT_MAX, &errstr);
                        if (errstr != NULL)
                                errx(1, "invalid max width \"%s\": %s",
                                    optarg, errstr);
                        break;
                case 's':
                        src = optarg;
                        seplen = mbsrtowcs(NULL, &src, 0, NULL);
                        if (seplen == (size_t)-1)
                                err(1, "bad separator");
                        newsep = calloc(seplen + 1, sizeof(wchar_t));
                        if (newsep == NULL)
                                err(1, NULL);
                        mbsrtowcs(newsep, &src, seplen + 1, NULL);
                        separator = newsep;
                        break;
                case 't':
                        tflag = 1;
                        break;
                case 'x':
                        xflag = 1;
                        break;
                case '?':
                default:
                        usage();
                }
        argc -= optind;
        argv += optind;

        if (tblcols && !tflag)
                errx(1, "the -l flag cannot be used without the -t flag");

        if (!*argv)
                input(stdin);
        else for (; *argv; ++argv)
                if ((fp = fopen(*argv, "r"))) {
                        input(fp);
                        (void)fclose(fp);
                } else {
                        warn("%s", *argv);
                        eval = 1;
                }

        if (!entries)
                exit(eval);

        maxlength = roundup(maxlength + 1, TAB);
        if (tflag)
                maketbl();
        else if (maxlength >= termwidth)
                print();
        else if (xflag)
                c_columnate();
        else
                r_columnate();
        exit(eval);
}

static void
c_columnate(void)
{
        int chcnt, col, cnt, endcol, numcols;
        wchar_t **lp;

        numcols = termwidth / maxlength;
        endcol = maxlength;
        for (chcnt = col = 0, lp = list;; ++lp) {
                wprintf(L"%ls", *lp);
                chcnt += width(*lp);
                if (!--entries)
                        break;
                if (++col == numcols) {
                        chcnt = col = 0;
                        endcol = maxlength;
                        putwchar('\n');
                } else {
                        while ((cnt = roundup(chcnt + 1, TAB)) <= endcol) {
                                (void)putwchar('\t');
                                chcnt = cnt;
                        }
                        endcol += maxlength;
                }
        }
        if (chcnt)
                putwchar('\n');
}

static void
r_columnate(void)
{
        int base, chcnt, cnt, col, endcol, numcols, numrows, row;

        numcols = termwidth / maxlength;
        numrows = entries / numcols;
        if (entries % numcols)
                ++numrows;

        for (row = 0; row < numrows; ++row) {
                endcol = maxlength;
                for (base = row, chcnt = col = 0; col < numcols; ++col) {
                        wprintf(L"%ls", list[base]);
                        chcnt += width(list[base]);
                        if ((base += numrows) >= entries)
                                break;
                        while ((cnt = roundup(chcnt + 1, TAB)) <= endcol) {
                                (void)putwchar('\t');
                                chcnt = cnt;
                        }
                        endcol += maxlength;
                }
                putwchar('\n');
        }
}

static void
print(void)
{
        int cnt;
        wchar_t **lp;

        for (cnt = entries, lp = list; cnt--; ++lp)
                (void)wprintf(L"%ls\n", *lp);
}

typedef struct _tbl {
        wchar_t **list;
        int cols, *len;
} TBL;
#define DEFCOLS 25

static void
maketbl(void)
{
        TBL *t;
        int coloff, cnt;
        wchar_t *p, **lp;
        int *lens, maxcols;
        TBL *tbl;
        wchar_t **cols;
        wchar_t *s;

        if ((t = tbl = calloc(entries, sizeof(TBL))) == NULL)
                err(1, NULL);
        if ((cols = calloc((maxcols = DEFCOLS), sizeof(*cols))) == NULL)
                err(1, NULL);
        if ((lens = calloc(maxcols, sizeof(int))) == NULL)
                err(1, NULL);
        for (cnt = 0, lp = list; cnt < entries; ++cnt, ++lp, ++t) {
                for (p = *lp; wcschr(separator, *p); ++p)
                        /* nothing */ ;
                for (coloff = 0; *p;) {
                        cols[coloff] = p;

                        if (++coloff == maxcols) {
                                if (!(cols = realloc(cols, ((u_int)maxcols +
                                    DEFCOLS) * sizeof(wchar_t *))) ||
                                    !(lens = realloc(lens,
                                    ((u_int)maxcols + DEFCOLS) * sizeof(int))))
                                        err(1, NULL);
                                memset((char *)lens + maxcols * sizeof(int),
                                    0, DEFCOLS * sizeof(int));
                                maxcols += DEFCOLS;
                        }

                        if ((!tblcols || coloff < tblcols) &&
                            (s = wcspbrk(p, separator))) {
                                *s++ = L'\0';
                                while (*s && wcschr(separator, *s))
                                        ++s;
                                p = s;
                        } else
                                break;
                }
                if ((t->list = calloc(coloff, sizeof(*t->list))) == NULL)
                        err(1, NULL);
                if ((t->len = calloc(coloff, sizeof(int))) == NULL)
                        err(1, NULL);
                for (t->cols = coloff; --coloff >= 0;) {
                        t->list[coloff] = cols[coloff];
                        t->len[coloff] = width(cols[coloff]);
                        if (t->len[coloff] > lens[coloff])
                                lens[coloff] = t->len[coloff];
                }
        }
        for (cnt = 0, t = tbl; cnt < entries; ++cnt, ++t) {
                for (coloff = 0; coloff < t->cols  - 1; ++coloff)
                        (void)wprintf(L"%ls%*ls", t->list[coloff],
                            lens[coloff] - t->len[coloff] + 2, L" ");
                (void)wprintf(L"%ls\n", t->list[coloff]);
                free(t->list);
                free(t->len);
        }
        free(lens);
        free(cols);
        free(tbl);
}

#define DEFNUM          1000
#define MAXLINELEN      (LINE_MAX + 1)

static void
input(FILE *fp)
{
        static int maxentry;
        int len;
        wchar_t *p, buf[MAXLINELEN];

        if (!list)
                if ((list = calloc((maxentry = DEFNUM), sizeof(*list))) ==
                    NULL)
                        err(1, NULL);
        while (fgetws(buf, MAXLINELEN, fp)) {
                for (p = buf; *p && iswspace(*p); ++p);
                if (!*p)
                        continue;
                if (!(p = wcschr(p, L'\n'))) {
                        warnx("line too long");
                        eval = 1;
                        continue;
                }
                *p = L'\0';
                len = width(buf);
                if (maxlength < len)
                        maxlength = len;
                if (entries == maxentry) {
                        maxentry += DEFNUM;
                        if (!(list = realloc(list,
                            (u_int)maxentry * sizeof(*list))))
                                err(1, NULL);
                }
                list[entries] = malloc((wcslen(buf) + 1) * sizeof(wchar_t));
                if (list[entries] == NULL)
                        err(1, NULL);
                wcscpy(list[entries], buf);
                entries++;
        }
}

/* Like wcswidth(), but ignores non-printing characters. */
static int
width(const wchar_t *wcs)
{
        int w, cw;

        for (w = 0; *wcs != L'\0'; wcs++)
                if ((cw = wcwidth(*wcs)) > 0)
                        w += cw;
        return (w);
}

static void
usage(void)
{
        (void)fprintf(stderr,
            "usage: column [-tx] [-c columns] [-l tblcols]"
            " [-s sep] [file ...]\n");
        exit(1);
}