root/usr/src/cmd/fold/fold.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */

#include <stdio.h>
#include <locale.h>
#include <wchar.h>
#include <euc.h>
#include <stdlib.h>             /* XCU4 */
#include <limits.h>
#include <libintl.h>
#include <langinfo.h>
#include <utime.h>
#include <widec.h>
#include <wctype.h>
#include <errno.h>


/*
 * fold - fold long lines for finite output devices
 */

static int fold =  80;
static int bflg = 0;
static int sflg = 0;
static int wflg = 0;
static int lastc = 0;
static int col = 0;
static int ncol = 0;
static int spcol = 0;
static wchar_t line[LINE_MAX];
static wchar_t *lastout = line;
static wchar_t *curc = line;
static wchar_t *lastsp = NULL;
#define MAXARG _POSIX_ARG_MAX

/*
 * Fix lint errors
 */
void exit();
static void Usage();
static void putch();
static void newline_init();
static int chr_width();
extern int errno;
static int get_foldw();


int
main(int argc, char *argv[])
{
        int c, narg;
        int ch;
        char *cmdline[MAXARG];
        int     new_argc;
        int w;
        extern int optind;
        extern char *optarg;

        (void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
        (void) textdomain(TEXT_DOMAIN);

        /*
         * Parse -width separately and build
         * the new command line without -width.
         * Next, use getopt() to parse this
         * new command line.
         */

        for (narg = new_argc = 0; narg < argc; narg ++) {
                if (argv[narg][0] == '-' &&
                        isdigit(argv[narg][1])) {

                        if (get_foldw((char *)&argv[narg][1], &w) < 0)
                                exit(1);

                        fold = w;       /* Update with new width */
                } else {
                        /* Build the new command line */
                        cmdline[new_argc++] = argv[narg];

                        /*
                         * Check to make sure the option with
                         * required arg should have arg.
                         * This would check errors introduced in
                         * mixing non-getopt() options and that of
                         * getopt()'s due to stripping non-getopt
                         * options.
                         */
                        if ((argv[narg][0] == '-') && (argv[narg][1] == 'w')) {
                                if (((narg+1) < argc) &&
                                        (argv[narg+1][0] == '-')) {
                                        (void) fprintf(stderr, "fold");
                                        (void) fprintf(stderr, gettext(
                                ": option requires an argument -- w\n"));
                                        Usage();
                                        exit(1);
                                }
                        }
                }
        }

        while ((ch = getopt(new_argc, cmdline, "w:bs")) != EOF) {
                switch (ch) {
                        case 'b':
                                bflg++;
                                break;
                        case 's':
                                sflg++;
                                break;
                        case 'w':
                                wflg++;
                                /* No required arg ? */
                                if ((optarg == (char *)NULL) ||
                                        ((optarg != (char *)NULL) &&
                                        (*optarg == '-'))) {
                                                (void) fprintf(stderr, "fold");
                                                (void) fprintf(stderr, gettext(
                                ": option requires an argument -- w\n"));
                                                Usage();
                                                exit(1);
                                }
                                /* Bad number ? */
                                if (get_foldw(optarg, &w) < 0)
                                        exit(1);

                                fold = w;
                                break;
                        default:
                                /*
                                 * Errors should be filtered in previous
                                 * pass.
                                 */
                                Usage();
                                exit(1);
                } /* switch */
        } /* while */

        do {
                if (new_argc > optind) {
                        if (freopen(cmdline[optind], "r", stdin) == NULL) {
                                perror(cmdline[optind]);
                                Usage();
                                exit(1);
                        }
                        optind++;
                }

                for (;;) {
                        c = getwc(stdin);
                        if (c == EOF)
                                break;
                        (void) putch(c);
                        lastc = c;
                }
                if (col != 0) newline_init();
        } while (new_argc > optind);

        return (0);
}

static void
putch(int c)
{
        wchar_t tline[LINE_MAX];

        switch (c) {
                case '\n':
                        ncol = 0;
                        break;
                case '\t':
                        if (bflg)
                                ncol = col + chr_width(c);
                        else
                                ncol = (col + 8) &~ 7;
                        break;
                case '\b':
                        if (bflg)
                                ncol = col + chr_width(c);
                        else
                                ncol = col ? col - 1 : 0;
                        break;
                case '\r':
                        if (bflg)
                                ncol = col + chr_width(c);
                        else
                                ncol = 0;
                        break;

                default:
                        if (bflg)
                                ncol = col + chr_width(c);
                        else
                                ncol = col + wcwidth(c);

        }

        /*
         * Special processing when -b is not specified
         * for backspace, and carriage return.
         * No newline is inseted before or after the
         * special character: backspace, or cr.
         * See man page for the handling of backspace
         * and CR when there is no -b.
         */
        if ((ncol > fold) && (bflg ||
                (!bflg && (lastc != '\b') && (c != '\b') &&
                (lastc != '\n') && (c != '\n'))))  {
                /*
                 * Need to check the last position for blank
                 */
                if (sflg && lastsp) {
                        /*
                         * Save the output buffer
                         * as NULL has to be insert into the last
                         * sp position.
                         */
                        (void) wscpy(tline, line);
                        *lastsp = (wchar_t)NULL;
                        (void) fputws(lastout, stdout);
                        (void) putwchar('\n');
                        /*
                         * Restore the output buffer to stuff
                         * NULL into the last sp position
                         * for the new line.
                         */
                        (void) wscpy(line, tline);
                        lastout = lastsp;
                        lastsp = NULL;  /* Reset the last sp */
                        ncol -= spcol;  /* Reset column positions */
                        col -= spcol;
                } else {
                        (void) newline_init();
                        (void) putwchar('\n');
                        lastout = curc;
                }
        }
        /* Output buffer is full ? */
        if ((curc + 1) >= (line + LINE_MAX)) {
                /* Reach buffer limit */
                if (col > 0) {
                        *curc = (wchar_t)NULL;
                        (void) fputws(lastout, stdout);
                        lastsp = NULL;
                }
                curc = lastout = line;

        }

        /* Store in output buffer */
        *curc++ = (wchar_t)c;

        switch (c) {
                case '\n':
                        (void)  newline_init();
                        curc = lastout = line;
                        break;
                case '\t':
                        if (bflg)
                                col = col + chr_width(c);
                        else
                                col = (col + 8) &~ 7;
                        if (sflg && iswspace(c)) {
                                lastsp = curc;
                                spcol = ncol;
                        }

                        break;
                case '\b':
                        if (bflg)
                                col = ncol;
                        else {
                                if (col)
                                        col--;
                        }
                        break;
                case '\r':
                        col = 0;
                        break;
                default:
                        if (sflg && iswspace(c)) {
                                lastsp = curc;
                                spcol = ncol;
                        }

                        if (bflg)
                                col += chr_width(c);
                        else
                                col += wcwidth(c);

                        break;
        }
}
static
void
Usage()
{
        (void) fprintf(stderr, gettext(
            "Usage: fold [-bs] [-w width | -width ] [file...]\n"));
}

static
void
newline_init()
{
        *curc = (wchar_t)NULL;
        (void) fputws(lastout, stdout);
        ncol = col = spcol = 0;
        lastsp = NULL;
}

static int
chr_width(c)
register int c;
{
        char chr[MB_LEN_MAX+1];
        register int    n;

        n = wctomb(chr, (wchar_t)c);

        return (n > 0 ? n : 0);
}

static int
get_foldw(toptarg, width)
char    *toptarg;
int             *width;
{
        char    *p;

        if (!toptarg)
                goto badno;

        *width = 0;
        errno = 0;
        *width = strtoul(toptarg, &p, 10);
        if (*width == -1)
                goto badno;

        if (*p)
                goto badno;

        if (!*width)
                goto badno;

        return (0);

badno:
        /* fold error message */
        (void) fprintf(stderr, gettext(
                "Bad number for fold\n"));
        Usage();
        return (-1);
}