root/usr.bin/column/column.c
/*      $NetBSD: column.c,v 1.22 2020/08/27 01:52:04 simonb Exp $       */

/*
 * 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/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
 The Regents of the University of California.  All rights reserved.");
#endif /* not lint */

#ifndef lint
#if 0
static char sccsid[] = "@(#)column.c    8.4 (Berkeley) 5/4/95";
#endif
__RCSID("$NetBSD: column.c,v 1.22 2020/08/27 01:52:04 simonb Exp $");
#endif /* not lint */

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

#include <ctype.h>
#include <err.h>
#include <termios.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>

#define TAB     8
#define TABROUND(l)     (((l) + TAB) & ~(TAB - 1))

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) __dead;

static int termwidth = 80;              /* default terminal width */

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

int
main(int argc, char **argv)
{
        struct winsize win;
        FILE *fp;
        int ch, tflag, xflag;
        const char *p;

        setprogname(*argv);

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

        tflag = xflag = 0;
        while ((ch = getopt(argc, argv, "c:s:tx")) != -1)
                switch(ch) {
                case 'c':
                        termwidth = atoi(optarg);
                        break;
                case 's':
                        separator = optarg;
                        break;
                case 't':
                        tflag = 1;
                        break;
                case 'x':
                        xflag = 1;
                        break;
                case '?':
                default:
                        usage();
                }
        argc -= optind;
        argv += optind;

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

        if (!entries)
                return eval;

        maxlength = TABROUND(maxlength);
        if (tflag)
                maketbl();
        else if (maxlength >= termwidth)
                print();
        else if (xflag)
                c_columnate();
        else
                r_columnate();
        return eval;
}

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

        numcols = termwidth / maxlength;
        endcol = maxlength;
        for (chcnt = col = 0, lp = list;; ++lp) {
                chcnt += printf("%s", *lp);
                if (!--entries)
                        break;
                if (++col == numcols) {
                        chcnt = col = 0;
                        endcol = maxlength;
                        (void)putchar('\n');
                } else {
                        while ((cnt = TABROUND(chcnt)) <= endcol) {
                                (void)putchar('\t');
                                chcnt = cnt;
                        }
                        endcol += maxlength;
                }
        }
        if (chcnt)
                (void)putchar('\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) {
                        chcnt += printf("%s", list[base]);
                        if ((base += numrows) >= entries)
                                break;
                        while ((cnt = TABROUND(chcnt)) <= endcol) {
                                (void)putchar('\t');
                                chcnt = cnt;
                        }
                        endcol += maxlength;
                }
                (void)putchar('\n');
        }
}

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

        for (cnt = entries, lp = list; cnt--; ++lp)
                (void)printf("%s\n", *lp);
}

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

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

        t = tbl = ecalloc(entries, sizeof(*t));
        cols = ecalloc((maxcols = DEFCOLS), sizeof(*cols));
        lens = ecalloc(maxcols, sizeof(*lens));
        for (cnt = 0, lp = list; cnt < entries; ++cnt, ++lp, ++t) {
                for (coloff = 0, p = *lp;
                    (cols[coloff] = strtok(p, separator)) != NULL; p = NULL)
                        if (++coloff == maxcols) {
                                ncols = erealloc(cols, (maxcols +
                                    DEFCOLS) * sizeof(*ncols));
                                nlens = erealloc(lens, (maxcols +
                                    DEFCOLS) * sizeof(*nlens));
                                cols = ncols;
                                lens = nlens;
                                (void)memset(cols + maxcols, 0,
                                    DEFCOLS * sizeof(*cols));
                                (void)memset(lens + maxcols, 0,
                                    DEFCOLS * sizeof(*lens));
                                maxcols += DEFCOLS;
                        }
                t->list = ecalloc(coloff, sizeof(*(t->list)));
                t->len = ecalloc(coloff, sizeof(*(t->len)));
                for (t->cols = coloff; --coloff >= 0;) {
                        t->list[coloff] = cols[coloff];
                        t->len[coloff] = strlen(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)printf("%s%*s", t->list[coloff],
                            lens[coloff] - t->len[coloff] + 2, " ");
                (void)printf("%s\n", t->list[coloff]);
        }
        free(tbl);
        free(cols);
        free(lens);
}

#define DEFNUM          1000

static void
input(FILE *fp)
{
        static int maxentry;
        int len;
        size_t blen;
        char *p, *buf;
        char **n;

        if (!list)
                list = ecalloc((maxentry = DEFNUM), sizeof(*list));
        while ((buf = fgetln(fp, &blen)) != NULL) {
                buf = estrndup(buf, blen);
                for (p = buf; *p && isspace((unsigned char)*p); ++p);
                if (!*p) {
                        free(buf);
                        continue;
                }
                if (!(p = strchr(p, '\n'))) {
                        warnx("line too long");
                        eval = 1;
                        free(buf);
                        continue;
                }
                *p = '\0';
                len = p - buf;
                if (maxlength < len)
                        maxlength = len;
                if (entries == maxentry) {
                        n = erealloc(list, (maxentry + DEFNUM) * sizeof(*n));
                        (void)memset(n + maxentry, 0, sizeof(*n) * DEFNUM);
                        maxentry += DEFNUM;
                        list = n;
                }
                list[entries++] = buf;
        }
}

static void
usage(void)
{

        (void)fprintf(stderr,
            "Usage: %s [-tx] [-c columns] [-s sep] [file ...]\n",
            getprogname());
        exit(1);
}