123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 | /* expand-common - common functionality for expand/unexpand Copyright (C) 1989-2025 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */#include <config.h>#include <ctype.h>#include <stdio.h>#include <sys/types.h>#include"system.h"#include"c-ctype.h"#include"fadvise.h"#include"quote.h"#include"expand-common.h"/* If true, convert blanks even after nonblank characters have been read on the line. */bool convert_entire_line =false;/* If nonzero, the size of all tab stops. If zero, use 'tab_list' instead. */static colno tab_size =0;/* If nonzero, the size of all tab stops after the last specified. */static colno extend_size =0;/* If nonzero, an increment for additional tab stops after the last specified.*/static colno increment_size =0;/* The maximum distance between tab stops. */ idx_t max_column_width;/* Array of the explicit column numbers of the tab stops; after 'tab_list' is exhausted, each additional tab is replaced by a space. The first column is column 0. */static colno *tab_list =nullptr;/* The number of allocated entries in 'tab_list'. */static idx_t n_tabs_allocated =0;/* The index of the first invalid element of 'tab_list', where the next element can be added. */static idx_t first_free_tab =0;/* Null-terminated array of input filenames. */static char**file_list =nullptr;/* Default for 'file_list' if no files are given on the command line. */static char*stdin_argv[] ={(char*)"-",nullptr};/* True if we have ever read standard input. */static bool have_read_stdin =false;/* The desired exit status. */int exit_status = EXIT_SUCCESS;/* Add tab stop TABVAL to the end of 'tab_list'. */externvoidadd_tab_stop(colno tabval){ colno prev_column = first_free_tab ? tab_list[first_free_tab -1] :0; colno column_width = prev_column <= tabval ? tabval - prev_column :0;if(first_free_tab == n_tabs_allocated) tab_list =xpalloc(tab_list, &n_tabs_allocated,1, -1,sizeof*tab_list); tab_list[first_free_tab++] = tabval;if(max_column_width < column_width){if(ckd_add(&max_column_width, column_width,0))error(EXIT_FAILURE,0,_("tabs are too far apart"));}}static boolset_extend_size(colno tabval){bool ok =true;if(extend_size){error(0,0,_("'/' specifier only allowed"" with the last value")); ok =false;} extend_size = tabval;return ok;}static boolset_increment_size(colno tabval){bool ok =true;if(increment_size){error(0,0,_("'+' specifier only allowed"" with the last value")); ok =false;} increment_size = tabval;return ok;}/* Add the comma or blank separated list of tab stops STOPS to the list of tab stops. */externvoidparse_tab_stops(char const*stops){bool have_tabval =false; colno tabval =0;bool extend_tabval =false;bool increment_tabval =false;char const*num_start =nullptr;bool ok =true;for(; *stops; stops++){if(*stops ==','||isblank(to_uchar(*stops))){if(have_tabval){if(extend_tabval){if(!set_extend_size(tabval)){ ok =false;break;}}else if(increment_tabval){if(!set_increment_size(tabval)){ ok =false;break;}}elseadd_tab_stop(tabval);} have_tabval =false;}else if(*stops =='/'){if(have_tabval){error(0,0,_("'/' specifier not at start of number: %s"),quote(stops)); ok =false;} extend_tabval =true; increment_tabval =false;}else if(*stops =='+'){if(have_tabval){error(0,0,_("'+' specifier not at start of number: %s"),quote(stops)); ok =false;} increment_tabval =true; extend_tabval =false;}else if(c_isdigit(*stops)){if(!have_tabval){ tabval =0; have_tabval =true; num_start = stops;}/* Detect overflow. */if(!DECIMAL_DIGIT_ACCUMULATE(tabval, *stops -'0')){ idx_t len =strspn(num_start,"0123456789");char*bad_num =ximemdup0(num_start, len);error(0,0,_("tab stop is too large %s"),quote(bad_num));free(bad_num); ok =false; stops = num_start + len -1;}}else{error(0,0,_("tab size contains invalid character(s): %s"),quote(stops)); ok =false;break;}}if(ok && have_tabval){if(extend_tabval) ok &=set_extend_size(tabval);else if(increment_tabval) ok &=set_increment_size(tabval);elseadd_tab_stop(tabval);}if(! ok)exit(EXIT_FAILURE);}/* Check that the list of tab stops TABS, with ENTRIES entries, contains only nonzero, ascending values. */static voidvalidate_tab_stops(colno const*tabs, idx_t entries){ colno prev_tab =0;for(idx_t i =0; i < entries; i++){if(tabs[i] ==0)error(EXIT_FAILURE,0,_("tab size cannot be 0"));if(tabs[i] <= prev_tab)error(EXIT_FAILURE,0,_("tab sizes must be ascending")); prev_tab = tabs[i];}if(increment_size && extend_size)error(EXIT_FAILURE,0,_("'/' specifier is mutually exclusive with '+'"));}/* Called after all command-line options have been parsed, and add_tab_stop/parse_tab_stops have been called. Will validate the tab-stop values, and set the final values to: tab-stops = 8 (if no tab-stops given on command line) tab-stops = N (if value N specified as the only value). tab-stops = distinct values given on command line (if multiple values given).*/externvoidfinalize_tab_stops(void){validate_tab_stops(tab_list, first_free_tab);if(first_free_tab ==0) tab_size = max_column_width = extend_size ? extend_size : increment_size ? increment_size :8;else if(first_free_tab ==1&& ! extend_size && ! increment_size) tab_size = tab_list[0];else tab_size =0;}/* Return number of first tab stop after COLUMN. TAB_INDEX specifies many multiple tab-sizes. Set *LAST_TAB depending on whether we are returning COLUMN + 1 merely because we're past the last tab. If the number would overflow, diagnose this and exit. */extern colno get_next_tab_column(colno column, idx_t *tab_index,bool*last_tab){*last_tab =false; colno tab_distance;/* single tab-size - return multiples of it */if(tab_size) tab_distance = tab_size - column % tab_size;else{/* multiple tab-sizes - iterate them until the tab position is beyond the current input column. */for( ; *tab_index < first_free_tab ; (*tab_index)++ ){ colno tab = tab_list[*tab_index];if(column < tab)return tab;}/* relative last tab - return multiples of it */if(extend_size) tab_distance = extend_size - column % extend_size;else if(increment_size){/* incremental last tab - add increment_size to the previous tab stop */ colno end_tab = tab_list[first_free_tab -1]; tab_distance = increment_size - ((column - end_tab) % increment_size);}else{*last_tab =true; tab_distance =1;}} colno tab_stop;if(ckd_add(&tab_stop, column, tab_distance))error(EXIT_FAILURE,0,_("input line is too long"));return tab_stop;}/* Sets new file-list */externvoidset_file_list(char**list){ have_read_stdin =false;if(!list) file_list = stdin_argv;else file_list = list;}/* Close the old stream pointer FP if it is non-null, and return a new one opened to read the next input file. Open a filename of '-' as the standard input. Return nullptr if there are no more input files. */externFILE*next_file(FILE*fp){static char*prev_file;char*file;if(fp){int err = errno;if(!ferror(fp)) err =0;if(STREQ(prev_file,"-"))clearerr(fp);/* Also clear EOF. */else if(fclose(fp) !=0) err = errno;if(err){error(0, err,"%s",quotef(prev_file)); exit_status = EXIT_FAILURE;}}while((file = *file_list++) !=nullptr){if(STREQ(file,"-")){ have_read_stdin =true; fp = stdin;}else fp =fopen(file,"r");if(fp){ prev_file = file;fadvise(fp, FADVISE_SEQUENTIAL);return fp;}error(0, errno,"%s",quotef(file)); exit_status = EXIT_FAILURE;}returnnullptr;}/* */externvoidcleanup_file_list_stdin(void){if(have_read_stdin &&fclose(stdin) !=0)error(EXIT_FAILURE, errno,"-");}externvoidemit_tab_list_info(void){/* suppress syntax check for emit_mandatory_arg_note() */fputs(_("\ -t, --tabs=LIST use comma separated list of tab positions.\n\"), stdout);fputs(_("\ The last specified position can be prefixed with '/'\n\ to specify a tab size to use after the last\n\ explicitly specified tab stop. Also a prefix of '+'\n\ can be used to align remaining tab stops relative to\n\ the last specified tab stop instead of the first column\n\"), stdout);}
|