tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

PR/11227 - tabs not provided



Hi List!

I was perusing the list of PRs for 5.0 and came across PR/11227 which reported that the tabs utility [1] was not in NetBSD.

This looked fairly trivial, so I've spent a few hours this weekend coding one up and writing a man page for it. They're attached to this mail along with a Makefile. Although they're copyright me, I would like to donate them to NetBSD as other OS's have their own implementation. You'll be pleased to know that mine provides full compliance with POSIX [2] whilst also being the smallest implementation I know of. tabs weighs in at 14k on amd64 and 9k on i386 compiled on -current.

Thanks

Roy

[1] http://www.netbsd.org/cgi-bin/query-pr-single.pl?number=11227
[2] http://www.opengroup.org/onlinepubs/009695399/utilities/tabs.html
#       $NetBSD: $

PROG=   tabs
DPADD=  ${LIBTERMCAP}
LDADD=  -ltermcap

.include <bsd.prog.mk>
/* 
 * tabs - set terminal tabs
 * Copyright 2008 Roy Marples <roy%marples.name@localhost>
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 */

const char copyright[] = "Copyright (c) 2008 Roy Marples";

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

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termcap.h>
#include <unistd.h>

#define NSTOPS 20

struct tabspec {
        const char *opt;
        const char *spec;
};
static const struct tabspec tabspecs[] = {
        {"a",   "1,10,16,36,72"},
        {"a2",  "1,10,16,40,72"},
        {"c",   "1,8,12,16,20,55"},
        {"c2",  "1,6,10,14,49"},
        {"c3",  "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67"},
        {"f",   "1,7,11,15,19,23"},
        {"p",   "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61"},
        {"s",   "1,10,55"},
        {"u",   "1,12,20,44"}
};
static const size_t ntabspecs = sizeof(tabspecs) / sizeof(tabspecs[0]);

static char tbuf[2048];

static void
usage(void)
{
        fprintf(stderr,
                "usage: %s [-n|-a|-a2|-c|-c2|-c3|-f|-p|-s|-u] [+m[n]]"
                " [-T type]\n"
                "       %s [-T type] [+[n]] n1[,n2,...]\n",
                getprogname(), getprogname());
        exit(EXIT_FAILURE);
        /* NOTREACHED */
}

int
main(int argc, char **argv)
{
        char *term, *arg, *tbp, *token, *end, *tabs = NULL;
        const char *cr, *ct, *st, *ML;
        int i, j, n, inc = 8, stops[NSTOPS], nstops, last, cols, margin = 0;
        struct winsize ws;

        term = getenv("TERM");
        for (i = 1; i < argc; i++) {
                if (argv[i][0] == '+') {
                        arg = argv[i] + 1;
                        if (*arg == 'm')
                                arg++;
                        if (*arg == '\0')
                                margin = 10;
                        else {
                                errno = 0;
                                margin = strtol(arg, &end, 10);
                                if (errno || *end || margin < 0)
                                        errx(EXIT_FAILURE,
                                             "%s: invalid margin",
                                             argv[i] + 2);
                        }
                        continue;
                }
                if (argv[i][0] != '-') {
                        tabs = argv[i];
                        break;
                }
                arg = argv[i] + 1;
                if (*arg == '\0') 
                        usage();
                if (*arg == '-') {
                        if (argv[i + 1])
                                tabs = argv[i + 1];
                        break;
                }
                if (arg[0] == 'T' && arg[1] == '\0') {
                        term = argv[++i];
                        if (term == NULL)
                                usage();
                        continue;
                }
                if (isdigit((int)arg[0])) {
                        if (arg[1] != '\0')
                                errx(EXIT_FAILURE,
                                     "%s: invalid increament", arg);
                        inc = arg[0] - '0';
                        continue;
                }
                for (j = 0; j < ntabspecs; j++) {
                        if (arg[0] == tabspecs[j].opt[0] &&
                            arg[1] == tabspecs[j].opt[1])
                        {
                                tabs = strdup(tabspecs[j].spec);
                                break;
                        }
                }
                if (j == ntabspecs)
                        usage();
        }

        if (tabs)
            last = nstops = 0;
        else
            nstops = -1;
        while ((token = strsep(&tabs, ", "))) {
                if (*token == '\0')
                        continue;
                if (nstops >= NSTOPS)
                        errx(EXIT_FAILURE,
                             "too many tab stops (max %d)", NSTOPS);
                errno = 0;
                n = strtol(token, &end, 10);
                if (errno || *end || n <= 0)
                        errx(EXIT_FAILURE, "%s: invalid tab stop", token);
                if (*token == '+') {
                        if (nstops == 0)
                                errx(EXIT_FAILURE,
                                     "first tab stop may not be relative");
                        n += last;
                }
                if (last > n)
                        errx(EXIT_FAILURE, "tab stops may not go backwards");
                last = stops[nstops++] = n;
        }

        if (term == NULL)
                errx(EXIT_FAILURE, "no value for $TERM and -T not given");
        switch (tgetent(tbuf, term)) {
        case -1:
                errx(EXIT_FAILURE, "termcap database could not be opened");
        case 0:
                errx(EXIT_FAILURE, "%s: no termcap entry", term);
        }
        tbp = tbuf;
        cr = tgetstr("cr", &tbp);
        if (cr == NULL)
                cr = "\r";
        ct = tgetstr("ct", &tbp);
        if (ct == NULL)
                errx(EXIT_FAILURE, "terminal cannot clear tabs");
        st = tgetstr("st", &tbp);
        if (st == NULL)
                errx(EXIT_FAILURE, "terminal cannot set tabs");
        ML = tgetstr("ML", &tbp);

        /* Clear existing tabs */
        tputs(cr, 1, putchar);
        tputs(ct, 1, putchar);
        tputs(cr, 1, putchar);

        if (ML) {
                printf("%*s", margin, "");
                tputs(ML, 1, putchar);
        } else if (margin)
                warnx("terminal cannot set left margin");

        if (nstops >= 0) {
                printf("%*s", stops[0] - 1, "");
                tputs(st, 1, putchar);
                for (i = 1; i < nstops; i++) {
                        printf("%*s", stops[i] - stops[i - 1], "");
                        tputs(st, 1, putchar);
                }
        } else if (inc > 0) {
                cols = 0;
                term = getenv("COLUMNS");
                if (term) {
                        errno = 0;
                        cols = strtol(term, &end, 10);
                        if (errno || *end || cols < 0)
                                cols = 0;
                }
                if (cols == 0) {
                        if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
                                cols = ws.ws_col;
                        else {
                                cols = tgetnum("co");
                                if (cols == 0) {
                                        cols = 80;
                                        warnx("terminal does not specify number"
                                              "columns; defaulting to %d",
                                              cols);
                                }
                        }
                }
                for (i = 0; i < cols / inc; i++) {
                        printf("%*s", inc, "");
                        tputs(st, 1, putchar);
                }
        }
        tputs(cr, 1, putchar);

        exit(EXIT_SUCCESS);
}
.\" Copyright 2008 Roy Marples
.\" 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.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
.\"
.Dd November 01, 2008
.Dt TABS 1
.Os
.Sh NAME
.Nm tabs
.Nd set terminal tabs
.Sh SYNOPSIS
.Nm
.Op Fl n Ns | Ns Fl a Ns | Ns Fl a2 Ns | Ns Fl c Ns | Ns Fl c2 \
Ns | Ns Fl c3 Ns | Ns Fl f Ns | Ns Fl p Ns | Ns Fl s Ns | Ns Fl u
.Op +m Ns Op n
.Op Fl T Ar type
.Nm
.Op Fl T Ar type
.Op + Ns Op n
.Ar n1 Ns Op , Ns Ar n2 Ns , Ns Ar ...
.Sh DESCRIPTION
The
.Nm
utility displays a series of characters that first clears the hardware terminal
tab settings and then initializes the tab stops at the specified positions
and optionally adjusts the margin.
.Pp
The phrase "tab-stop position N" means that, from the start of a line of
output, tabbing to position N shall cause the next character output to be in
the (N+1)th column on that line.
.Pp
The following options are supported:
.Bl -tag -width Fl
.It Fl Ar n
Specifies repetitive tab stops separated by a uniform number of columns,
.Ar n ,
where
.Ar n
is a single digit decimal number.
The default usage of
.Nm
with no arguments is equivalent to
.Nm
.Fl 8 .
When
.Fl 0
is used, the tab stops are cleared and no new ones set.
.It Fl a
Assembler, applicable to some mainframes.
Equivalent to
.Nm
1,10,16,36,72 .
.It Fl a2
Assembler, applicable to some mainframes.
Equivalent to
.Nm
1,10,16,40,72
.It Fl c
.Tn COBOL ,
normal format.
Equivalent to
.Nm
1,8,12,16,20,55
.It Fl c2
.Tn COBOL ,
compact format (columns 1 to 6 omitted).
Equivalent to
.Nm
1,6,10,14,49
.It Fl c3
.Tn COBOL ,
compact format (columns 1 to 6 omitted), with more tabs than
.Fl c2 .
Equivalent to
.Nm
1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67
.It Fl f
.Tn FORTRAN .
Equivalent to
.Nm
1,7,11,15,19,23
.It Fl p
.Tn PL/1 .
Equivalent to
.Nm
1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61
.It Fl s
.Tn SNOBOL .
Equivalent to
.Nm
1,10,55
.It Fl u
Assembler, applicable to some mainframes.
Equivalent to
.Nm
1,12,20,44
.It Fl T Ar type
Indicates the type of terminal.
.El
.Sh ENVIRONMENT
The
.Ev COLUMNS
and
.Ev TERM
environment variables affect the execution of
.Nm
as described in
.Xr environ 7 .
.Pp
The
.Fl T
option overrides
.Ev TERM .
If neither
.Ev TERM
nor the
.Fl T
option are present,
.Nm
will fail.
.Sh SEE ALSO
.Xr expand 1 ,
.Xr stty 1 ,
.Xr tput 1 ,
.Xr unexpand 1 ,
.Xr termcap 5
.Sh EXIT STATUS
.Nm
exits 0 on success and >0 if an error occurs.
.Sh STANDARDS
The
.Nm
utility conforms to
.St -p1003.1 .
.Sh BUGS
The current
.Xr termcap 5
database does not define the set left soft margin
.Ql ML
capability for any terminals.
.Sh AUTHORS
.An Roy Marples <roy%marples.name@localhost>


Home | Main Index | Thread Index | Old Index