]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
expand,unexpand: support specifying a trailing tab size
authorPádraig Brady <P@draigBrady.com>
Sun, 26 Feb 2017 22:40:19 +0000 (14:40 -0800)
committerPádraig Brady <P@draigBrady.com>
Wed, 1 Mar 2017 03:26:01 +0000 (19:26 -0800)
* 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
doc/coreutils.texi
src/expand-common.c
tests/misc/expand.pl

diff --git a/NEWS b/NEWS
index 11931a48b51ced2ae2578fb37e0b1b0adb37af89..1fc92c84e6947c7fd78984ae6fda99372ddb8c6b 100644 (file)
--- 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]
 
index d72760bff97be92143378b1af9cf71388b8856b2..f9ab490411f9f49e3f7d7ec04d3428ca78838c77 100644 (file)
@@ -6695,8 +6695,16 @@ The program accepts the following options.  Also see @ref{Common options}.
 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.
+last tab stop given with single spaces.
+@macro gnuExpandTabs
+Tab stops can be separated by 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 example, @option{--tabs=2,4,/8} will set tab stops at position 2 and 4,
+and every multiple of 8 after that.
+@end macro
+@gnuExpandTabs
+
 
 For compatibility, GNU @command{expand} also accepts the obsolete
 option syntax, @option{-@var{t1}[,@var{t2}]@dots{}}.  New scripts
@@ -6748,8 +6756,10 @@ The program accepts the following options.  Also see @ref{Common options}.
 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.
+beyond the tab stops given unchanged.
+@gnuExpandTabs
+
+This option implies the @option{-a} option.
 
 For compatibility, GNU @command{unexpand} supports the obsolete option syntax,
 @option{-@var{tab1}[,@var{tab2}]@dots{}}, where tab stops must be
index ae2b979cf16acd9c9c74ee4545ab94b9f1768478..9b6cdb9dc5ace8b53a71d24d9aa0a6adcc32b377 100644 (file)
@@ -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;
 }
index aa8341cd36c84563c59b195a47192f4ccad9916e..b04d2e72db6bc2f0c047a0a918c45d1b4e4e0c87 100755 (executable)
@@ -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};