>From cb158f2a7458ddc793cb81fec429b571e33f302f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draig=20Brady?= Date: Sun, 26 Feb 2017 14:40:19 -0800 Subject: [PATCH] expand,unexpand: support specifying a trailing tab size * doc/coreutils.texi (expand invocation): Document the feature. (unexpand invocation): Likewise. * src/expand-common.c (extend_size): A new global to use when the last tab stop is prefixed by '/'. (set_extend_size): A new function to validate and set the new extend_size global. (parse_tab_stops): Call set_extend_size() for '/' prefixes. (finalize_tab_stops): Ensure a single specified '/' is treated like a standard tabsize, but also ensure that when '/' is specified with a single other entry that we process as a list rather than a tab size. (get_next_tab_stop): Use the tab size if set, for items after the user specified tab position list. * tests/misc/expand.pl: Add test cases * NEWS: Mention the new feature. Fixes http://bugs.gnu.org/25540 --- NEWS | 6 +++++ doc/coreutils.texi | 8 +++++-- src/expand-common.c | 68 +++++++++++++++++++++++++++++++++++++++++++++------- tests/misc/expand.pl | 20 +++++++++++++++- 4 files changed, 91 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index 11931a4..1fc92c8 100644 --- a/NEWS +++ b/NEWS @@ -58,6 +58,12 @@ GNU coreutils NEWS -*- outline -*- 'cp -fl A B' no longer remove B before creating the new link. That is, there is no longer a brief moment when B does not exist. +** New features + + expand and unexpand now support specifying a tab size to use + after explicitly specified tab stops, by prefixing the last + specified number like --tabs=2,4,/8. + * Noteworthy changes in release 8.26 (2016-11-30) [stable] diff --git a/doc/coreutils.texi b/doc/coreutils.texi index d72760b..ed644c7 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -6696,7 +6696,9 @@ If only one tab stop is given, set the tabs @var{tab1} spaces apart (default is 8). Otherwise, set the tabs at columns @var{tab1}, @var{tab2}, @dots{} (numbered from 0), and replace any tabs beyond the last tab stop given with single spaces. Tab stops can be separated by -blanks as well as by commas. +blanks as well as by commas. As a GNU extension the last @var{tab} specified +can be prefixed with a @samp{/} to indicate a tab size to use for +remaining positions. For compatibility, GNU @command{expand} also accepts the obsolete option syntax, @address@hidden,@address@hidden New scripts @@ -6749,7 +6751,9 @@ If only one tab stop is given, set the tabs @var{tab1} columns apart instead of the default 8. Otherwise, set the tabs at columns @var{tab1}, @var{tab2}, @dots{} (numbered from 0), and leave blanks beyond the tab stops given unchanged. Tab stops can be separated by -blanks as well as by commas. This option implies the @option{-a} option. +blanks as well as by commas. As a GNU extension the last @var{tab} specified +can be prefixed with a @samp{/} to indicate a tab size to use for +remaining positions. This option implies the @option{-a} option. For compatibility, GNU @command{unexpand} supports the obsolete option syntax, @address@hidden,@address@hidden, where tab stops must be diff --git a/src/expand-common.c b/src/expand-common.c index ae2b979..9b6cdb9 100644 --- a/src/expand-common.c +++ b/src/expand-common.c @@ -34,6 +34,9 @@ bool convert_entire_line = false; /* If nonzero, the size of all tab stops. If zero, use 'tab_list' instead. */ static uintmax_t tab_size = 0; +/* If nonzero, the size of all tab stops after the last specifed. */ +static uintmax_t extend_size = 0; + /* The maximum distance between tab stops. */ size_t max_column_width; @@ -85,14 +88,32 @@ add_tab_stop (uintmax_t tabval) } } +static bool +set_extend_size (uintmax_t tabval) +{ + bool ok = true; + + if (extend_size) + { + error (0, 0, + _("'/' specifier only allowed" + " with the last value")); + ok = false; + } + extend_size = tabval; + + return ok; +} + /* Add the comma or blank separated list of tab stops STOPS to the list of tab stops. */ extern void parse_tab_stops (char const *stops) { bool have_tabval = false; - uintmax_t tabval IF_LINT ( = 0); - char const *num_start IF_LINT ( = NULL); + uintmax_t tabval = 0; + bool extend_tabval = false; + char const *num_start = NULL; bool ok = true; for (; *stops; stops++) @@ -100,9 +121,31 @@ parse_tab_stops (char const *stops) if (*stops == ',' || isblank (to_uchar (*stops))) { if (have_tabval) - add_tab_stop (tabval); + { + if (extend_tabval) + { + if (! set_extend_size (tabval)) + { + extend_tabval = false; + ok = false; + break; + } + } + else + add_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; + } else if (ISDIGIT (*stops)) { if (!have_tabval) @@ -132,11 +175,16 @@ parse_tab_stops (char const *stops) } } + if (have_tabval) + { + if (extend_tabval) + ok &= set_extend_size (tabval); + else + add_tab_stop (tabval); + } + if (!ok) exit (EXIT_FAILURE); - - if (have_tabval) - add_tab_stop (tabval); } /* Check that the list of tab stops TABS, with ENTRIES entries, @@ -172,8 +220,8 @@ finalize_tab_stops (void) validate_tab_stops (tab_list, first_free_tab); if (first_free_tab == 0) - tab_size = max_column_width = 8; - else if (first_free_tab == 1) + tab_size = max_column_width = extend_size ? extend_size : 8; + else if (first_free_tab == 1 && ! extend_size) tab_size = tab_list[0]; else tab_size = 0; @@ -199,6 +247,10 @@ get_next_tab_column (const uintmax_t column, size_t* tab_index, return tab; } + /* relative last tab - return multiples of it */ + if (extend_size) + return column + (extend_size - column % extend_size); + *last_tab = true; return 0; } diff --git a/tests/misc/expand.pl b/tests/misc/expand.pl index aa8341c..b04d2e7 100755 --- a/tests/misc/expand.pl +++ b/tests/misc/expand.pl @@ -39,9 +39,10 @@ my @Tests = ['t5', '--tabs=""', {IN=>"a\tb\tc"}, {OUT=>"a b c"}], ['t6', '--tabs=","', {IN=>"a\tb\tc"}, {OUT=>"a b c"}], ['t7', '--tabs=" "', {IN=>"a\tb\tc"}, {OUT=>"a b c"}], + ['t8', '--tabs="/"', {IN=>"a\tb\tc"}, {OUT=>"a b c"}], # Input field wider than the specified tab list - ['t8', '--tabs=6,9', {IN=>"a\tbbbbbbbbbbbbb\tc"}, + ['if', '--tabs=6,9', {IN=>"a\tbbbbbbbbbbbbb\tc"}, {OUT=>"a bbbbbbbbbbbbb c"}], ['i1', '--tabs=3 -i', {IN=>"\ta\tb"}, {OUT=>" a\tb"}], @@ -138,6 +139,17 @@ my @Tests = {OUT=>"1 2 3 4 5\n" . "a bHELLO\b\b\b c d e\n"}], + # Test the trailing '/' feature which specifies the + # tab size to use after the last specified stop + ['trail1', '--tabs=1,/5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}], + ['trail2', '--tabs=2,/5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}], + ['trail3', '--tabs=1,2,/5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}], + ['trail4', '--tabs=/5', {IN=>"\ta\tb"}, {OUT=>" a b"}], + ['trail5', '--tabs=//5', {IN=>"\ta\tb"}, {OUT=>" a b"}], + ['trail6', '--tabs=/,/5', {IN=>"\ta\tb"}, {OUT=>" a b"}], + ['trail7', '--tabs=,/5', {IN=>"\ta\tb"}, {OUT=>" a b"}], + ['trail8', '--tabs=1 -t/5', {IN=>"\ta\tb\tc"}, {OUT=>" a b c"}], + ['trail9', '--tab=1,2 -t/5',{IN=>"\ta\tb\tc"}, {OUT=>" a b c"}], # Test errors ['e1', '--tabs="a"', {IN=>''}, {OUT=>''}, {EXIT=>1}, @@ -148,6 +160,12 @@ my @Tests = {ERR => "$prog: tab size cannot be 0\n"}], ['e4', '--tabs=3,3', {IN=>''}, {OUT=>''}, {EXIT=>1}, {ERR => "$prog: tab sizes must be ascending\n"}], + ['e5', '--tabs=/3,6,8', {IN=>''}, {OUT=>''}, {EXIT=>1}, + {ERR => "$prog: '/' specifier only allowed with the last value\n"}], + ['e6', '-t/3 -t/6', {IN=>''}, {OUT=>''}, {EXIT=>1}, + {ERR => "$prog: '/' specifier only allowed with the last value\n"}], + ['e7', '--tabs=3/', {IN=>''}, {OUT=>''}, {EXIT=>1}, + {ERR => "$prog: '/' specifier not at start of number: '/'\n"}], ); my $save_temps = $ENV{DEBUG}; -- 2.5.5