LCOV - code coverage report
Current view: top level - src - expand.c (source / functions) Hit Total Coverage
Test: coreutils.info Lines: 141 154 91.6 %
Date: 2018-01-30 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /* expand - convert tabs to spaces
       2             :    Copyright (C) 89, 91, 1995-2006 Free Software Foundation, Inc.
       3             : 
       4             :    This program is free software: you can redistribute it and/or modify
       5             :    it under the terms of the GNU General Public License as published by
       6             :    the Free Software Foundation, either version 3 of the License, or
       7             :    (at your option) any later version.
       8             : 
       9             :    This program is distributed in the hope that it will be useful,
      10             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :    GNU General Public License for more details.
      13             : 
      14             :    You should have received a copy of the GNU General Public License
      15             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      16             : 
      17             : /* By default, convert all tabs to spaces.
      18             :    Preserves backspace characters in the output; they decrement the
      19             :    column count for tab calculations.
      20             :    The default action is equivalent to -8.
      21             : 
      22             :    Options:
      23             :    --tabs=tab1[,tab2[,...]]
      24             :    -t tab1[,tab2[,...]]
      25             :    -tab1[,tab2[,...]]   If only one tab stop is given, set the tabs tab1
      26             :                         columns apart instead of the default 8.  Otherwise,
      27             :                         set the tabs at columns tab1, tab2, etc. (numbered from
      28             :                         0); replace any tabs beyond the tab stops given with
      29             :                         single spaces.
      30             :    --initial
      31             :    -i                   Only convert initial tabs on each line to spaces.
      32             : 
      33             :    David MacKenzie <djm@gnu.ai.mit.edu> */
      34             : 
      35             : #include <config.h>
      36             : 
      37             : #include <stdio.h>
      38             : #include <getopt.h>
      39             : #include <sys/types.h>
      40             : #include "system.h"
      41             : #include "error.h"
      42             : #include "quote.h"
      43             : #include "xstrndup.h"
      44             : 
      45             : /* The official name of this program (e.g., no `g' prefix).  */
      46             : #define PROGRAM_NAME "expand"
      47             : 
      48             : #define AUTHORS "David MacKenzie"
      49             : 
      50             : /* The number of bytes added at a time to the amount of memory
      51             :    allocated for the output line.  */
      52             : #define OUTPUT_BLOCK 256
      53             : 
      54             : /* The name this program was run with.  */
      55             : char *program_name;
      56             : 
      57             : /* If true, convert blanks even after nonblank characters have been
      58             :    read on the line.  */
      59             : static bool convert_entire_line;
      60             : 
      61             : /* If nonzero, the size of all tab stops.  If zero, use `tab_list' instead.  */
      62             : static uintmax_t tab_size;
      63             : 
      64             : /* Array of the explicit column numbers of the tab stops;
      65             :    after `tab_list' is exhausted, each additional tab is replaced
      66             :    by a space.  The first column is column 0.  */
      67             : static uintmax_t *tab_list;
      68             : 
      69             : /* The number of allocated entries in `tab_list'.  */
      70             : static size_t n_tabs_allocated;
      71             : 
      72             : /* The index of the first invalid element of `tab_list',
      73             :    where the next element can be added.  */
      74             : static size_t first_free_tab;
      75             : 
      76             : /* Null-terminated array of input filenames.  */
      77             : static char **file_list;
      78             : 
      79             : /* Default for `file_list' if no files are given on the command line.  */
      80             : static char *stdin_argv[] =
      81             : {
      82             :   "-", NULL
      83             : };
      84             : 
      85             : /* True if we have ever read standard input.  */
      86             : static bool have_read_stdin;
      87             : 
      88             : /* The desired exit status.  */
      89             : static int exit_status;
      90             : 
      91             : static char const shortopts[] = "it:0::1::2::3::4::5::6::7::8::9::";
      92             : 
      93             : static struct option const longopts[] =
      94             : {
      95             :   {"tabs", required_argument, NULL, 't'},
      96             :   {"initial", no_argument, NULL, 'i'},
      97             :   {GETOPT_HELP_OPTION_DECL},
      98             :   {GETOPT_VERSION_OPTION_DECL},
      99             :   {NULL, 0, NULL, 0}
     100             : };
     101             : 
     102             : void
     103           9 : usage (int status)
     104             : {
     105           9 :   if (status != EXIT_SUCCESS)
     106           8 :     fprintf (stderr, _("Try `%s --help' for more information.\n"),
     107             :              program_name);
     108             :   else
     109             :     {
     110           1 :       printf (_("\
     111             : Usage: %s [OPTION]... [FILE]...\n\
     112             : "),
     113             :               program_name);
     114           1 :       fputs (_("\
     115             : Convert tabs in each FILE to spaces, writing to standard output.\n\
     116             : With no FILE, or when FILE is -, read standard input.\n\
     117             : \n\
     118             : "), stdout);
     119           1 :       fputs (_("\
     120             : Mandatory arguments to long options are mandatory for short options too.\n\
     121             : "), stdout);
     122           1 :       fputs (_("\
     123             :   -i, --initial       do not convert tabs after non blanks\n\
     124             :   -t, --tabs=NUMBER   have tabs NUMBER characters apart, not 8\n\
     125             : "), stdout);
     126           1 :       fputs (_("\
     127             :   -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
     128             : "), stdout);
     129           1 :       fputs (HELP_OPTION_DESCRIPTION, stdout);
     130           1 :       fputs (VERSION_OPTION_DESCRIPTION, stdout);
     131           1 :       emit_bug_reporting_address ();
     132             :     }
     133           9 :   exit (status);
     134             : }
     135             : 
     136             : /* Add tab stop TABVAL to the end of `tab_list'.  */
     137             : 
     138             : static void
     139          16 : add_tab_stop (uintmax_t tabval)
     140             : {
     141          16 :   if (first_free_tab == n_tabs_allocated)
     142          11 :     tab_list = X2NREALLOC (tab_list, &n_tabs_allocated);
     143          16 :   tab_list[first_free_tab++] = tabval;
     144          16 : }
     145             : 
     146             : /* Add the comma or blank separated list of tab stops STOPS
     147             :    to the list of tab stops.  */
     148             : 
     149             : static void
     150          34 : parse_tab_stops (char const *stops)
     151             : {
     152          34 :   bool have_tabval = false;
     153             :   uintmax_t tabval IF_LINT (= 0);
     154             :   char const *num_start IF_LINT (= NULL);
     155          34 :   bool ok = true;
     156             : 
     157          73 :   for (; *stops; stops++)
     158             :     {
     159          54 :       if (*stops == ',' || isblank (to_uchar (*stops)))
     160             :         {
     161          15 :           if (have_tabval)
     162           6 :             add_tab_stop (tabval);
     163          15 :           have_tabval = false;
     164             :         }
     165          39 :       else if (ISDIGIT (*stops))
     166             :         {
     167          24 :           if (!have_tabval)
     168             :             {
     169          23 :               tabval = 0;
     170          23 :               have_tabval = true;
     171          23 :               num_start = stops;
     172             :             }
     173             : 
     174             :           /* Detect overflow.  */
     175          24 :           if (!DECIMAL_DIGIT_ACCUMULATE (tabval, *stops - '0', uintmax_t))
     176             :             {
     177           0 :               size_t len = strspn (num_start, "0123456789");
     178           0 :               char *bad_num = xstrndup (num_start, len);
     179           0 :               error (0, 0, _("tab stop is too large %s"), quote (bad_num));
     180           0 :               free (bad_num);
     181           0 :               ok = false;
     182           0 :               stops = num_start + len - 1;
     183             :             }
     184             :         }
     185             :       else
     186             :         {
     187          15 :           error (0, 0, _("tab size contains invalid character(s): %s"),
     188             :                  quote (stops));
     189          15 :           ok = false;
     190          15 :           break;
     191             :         }
     192             :     }
     193             : 
     194          34 :   if (!ok)
     195          15 :     exit (EXIT_FAILURE);
     196             : 
     197          19 :   if (have_tabval)
     198          10 :     add_tab_stop (tabval);
     199          19 : }
     200             : 
     201             : /* Check that the list of tab stops TABS, with ENTRIES entries,
     202             :    contains only nonzero, ascending values.  */
     203             : 
     204             : static void
     205          47 : validate_tab_stops (uintmax_t const *tabs, size_t entries)
     206             : {
     207          47 :   uintmax_t prev_tab = 0;
     208             :   size_t i;
     209             : 
     210          54 :   for (i = 0; i < entries; i++)
     211             :     {
     212          14 :       if (tabs[i] == 0)
     213           6 :         error (EXIT_FAILURE, 0, _("tab size cannot be 0"));
     214           8 :       if (tabs[i] <= prev_tab)
     215           1 :         error (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
     216           7 :       prev_tab = tabs[i];
     217             :     }
     218          40 : }
     219             : 
     220             : /* Close the old stream pointer FP if it is non-NULL,
     221             :    and return a new one opened to read the next input file.
     222             :    Open a filename of `-' as the standard input.
     223             :    Return NULL if there are no more input files.  */
     224             : 
     225             : static FILE *
     226          90 : next_file (FILE *fp)
     227             : {
     228             :   static char *prev_file;
     229             :   char *file;
     230             : 
     231          90 :   if (fp)
     232             :     {
     233          50 :       if (ferror (fp))
     234             :         {
     235           6 :           error (0, errno, "%s", prev_file);
     236           6 :           exit_status = EXIT_FAILURE;
     237             :         }
     238          50 :       if (STREQ (prev_file, "-"))
     239          43 :         clearerr (fp);          /* Also clear EOF.  */
     240           7 :       else if (fclose (fp) != 0)
     241             :         {
     242           0 :           error (0, errno, "%s", prev_file);
     243           0 :           exit_status = EXIT_FAILURE;
     244             :         }
     245             :     }
     246             : 
     247         189 :   while ((file = *file_list++) != NULL)
     248             :     {
     249          59 :       if (STREQ (file, "-"))
     250             :         {
     251          43 :           have_read_stdin = true;
     252          43 :           prev_file = file;
     253          43 :           return stdin;
     254             :         }
     255          16 :       fp = fopen (file, "r");
     256          16 :       if (fp)
     257             :         {
     258           7 :           prev_file = file;
     259           7 :           return fp;
     260             :         }
     261           9 :       error (0, errno, "%s", file);
     262           9 :       exit_status = EXIT_FAILURE;
     263             :     }
     264          40 :   return NULL;
     265             : }
     266             : 
     267             : /* Change tabs to spaces, writing to stdout.
     268             :    Read each file in `file_list', in order.  */
     269             : 
     270             : static void
     271          40 : expand (void)
     272             : {
     273             :   /* Input stream.  */
     274          40 :   FILE *fp = next_file (NULL);
     275             : 
     276          40 :   if (!fp)
     277           4 :     return;
     278             : 
     279             :   for (;;)
     280           3 :     {
     281             :       /* Input character, or EOF.  */
     282             :       int c;
     283             : 
     284             :       /* If true, perform translations.  */
     285          39 :       bool convert = true;
     286             : 
     287             : 
     288             :       /* The following variables have valid values only when CONVERT
     289             :          is true:  */
     290             : 
     291             :       /* Column of next input character.  */
     292          39 :       uintmax_t column = 0;
     293             : 
     294             :       /* Index in TAB_LIST of next tab stop to examine.  */
     295          39 :       size_t tab_index = 0;
     296             : 
     297             : 
     298             :       /* Convert a line of text.  */
     299             : 
     300             :       do
     301             :         {
     302         662 :           while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
     303          14 :             continue;
     304             : 
     305         324 :           if (convert)
     306             :             {
     307         323 :               if (c == '\t')
     308             :                 {
     309             :                   /* Column the next input tab stop is on.  */
     310             :                   uintmax_t next_tab_column;
     311             : 
     312         279 :                   if (tab_size)
     313         264 :                     next_tab_column = column + (tab_size - column % tab_size);
     314             :                   else
     315             :                     for (;;)
     316          17 :                       if (tab_index == first_free_tab)
     317             :                         {
     318          11 :                           next_tab_column = column + 1;
     319          11 :                           break;
     320             :                         }
     321             :                       else
     322             :                         {
     323           5 :                           uintmax_t tab = tab_list[tab_index++];
     324           5 :                           if (column < tab)
     325             :                             {
     326           4 :                               next_tab_column = tab;
     327           4 :                               break;
     328             :                             }
     329             :                         }
     330             : 
     331         279 :                   if (next_tab_column < column)
     332           0 :                     error (EXIT_FAILURE, 0, _("input line is too long"));
     333             : 
     334        2340 :                   while (++column < next_tab_column)
     335        1782 :                     if (putchar (' ') < 0)
     336           0 :                       error (EXIT_FAILURE, errno, _("write error"));
     337             : 
     338         279 :                   c = ' ';
     339             :                 }
     340          44 :               else if (c == '\b')
     341             :                 {
     342             :                   /* Go back one column, and force recalculation of the
     343             :                      next tab stop.  */
     344           5 :                   column -= !!column;
     345           5 :                   tab_index -= !!tab_index;
     346             :                 }
     347             :               else
     348             :                 {
     349          39 :                   column++;
     350          39 :                   if (!column)
     351           0 :                     error (EXIT_FAILURE, 0, _("input line is too long"));
     352             :                 }
     353             : 
     354         323 :               convert &= convert_entire_line | !! isblank (c);
     355             :             }
     356             : 
     357         324 :           if (c < 0)
     358          36 :             return;
     359             : 
     360         288 :           if (putchar (c) < 0)
     361           0 :             error (EXIT_FAILURE, errno, _("write error"));
     362             :         }
     363         288 :       while (c != '\n');
     364             :     }
     365             : }
     366             : 
     367             : int
     368          72 : main (int argc, char **argv)
     369             : {
     370             :   int c;
     371             : 
     372             :   initialize_main (&argc, &argv);
     373          72 :   program_name = argv[0];
     374          72 :   setlocale (LC_ALL, "");
     375             :   bindtextdomain (PACKAGE, LOCALEDIR);
     376             :   textdomain (PACKAGE);
     377             : 
     378          72 :   atexit (close_stdout);
     379             : 
     380          72 :   have_read_stdin = false;
     381          72 :   exit_status = EXIT_SUCCESS;
     382          72 :   convert_entire_line = true;
     383          72 :   tab_list = NULL;
     384          72 :   first_free_tab = 0;
     385             : 
     386         173 :   while ((c = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
     387             :     {
     388          54 :       switch (c)
     389             :         {
     390          10 :         case 'i':
     391          10 :           convert_entire_line = false;
     392          10 :           break;
     393             : 
     394          20 :         case 't':
     395          20 :           parse_tab_stops (optarg);
     396          12 :           break;
     397             : 
     398          14 :         case '0': case '1': case '2': case '3': case '4':
     399             :         case '5': case '6': case '7': case '8': case '9':
     400          14 :           if (optarg)
     401          11 :             parse_tab_stops (optarg - 1);
     402             :           else
     403             :             {
     404             :               char tab_stop[2];
     405           3 :               tab_stop[0] = c;
     406           3 :               tab_stop[1] = '\0';
     407           3 :               parse_tab_stops (tab_stop);
     408             :             }
     409           7 :           break;
     410             : 
     411           1 :         case_GETOPT_HELP_CHAR;
     412             : 
     413           1 :         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
     414             : 
     415           8 :         default:
     416           8 :           usage (EXIT_FAILURE);
     417             :         }
     418             :     }
     419             : 
     420          47 :   validate_tab_stops (tab_list, first_free_tab);
     421             : 
     422          40 :   if (first_free_tab == 0)
     423          37 :     tab_size = 8;
     424           3 :   else if (first_free_tab == 1)
     425           1 :     tab_size = tab_list[0];
     426             :   else
     427           2 :     tab_size = 0;
     428             : 
     429          40 :   file_list = (optind < argc ? &argv[optind] : stdin_argv);
     430             : 
     431          40 :   expand ();
     432             : 
     433          40 :   if (have_read_stdin && fclose (stdin) != 0)
     434           0 :     error (EXIT_FAILURE, errno, "-");
     435             : 
     436          40 :   exit (exit_status);
     437             : }

Generated by: LCOV version 1.10