Hi Jacob,

I adjusted your patch as follows:

 - Added NEWS, texinfo
 - Fixed/adjusted the test
 - Fixed/adjusted finalize_tab_stops() to fix bug caught by the test
 - Adjusted error handling to give more consistent errors

I also adjusted the --help for both expand and unexpand,
which I did as a separate patch (attached)
I'll apply both of these later.

thanks,
Pádraig.
From 9daef34543cc3267ec8e2cc22488bf202448ed84 Mon Sep 17 00:00:00 2001
From: Jacob Keller <[email protected]>
Date: Tue, 28 Mar 2017 15:49:50 -0700
Subject: [PATCH 1/2] expand,unexpand: add support for incremental tab stops

Support --tabs="1,+8" which is equivalent to --tabs="1,9,17,..."
useful for viewing unified diff output with its 1 character
gutter for example.

* doc/coreutils.texi ({expand,unexpand} invocation): Document,
using diff processing as the example.
* src/expand-common.c (set_increment_size): Update the new
increment_size global.
(parse_tab_stops): Handle the new '+' prefix.
(finalize_tab_stops): Verify both '+' and '/' prefixes
are not used together.
* tests/misc/expand.pl: Add test cases.
* NEWS: Mention the new feature.
---
 NEWS                 |  4 ++++
 doc/coreutils.texi   |  8 +++++++
 src/expand-common.c  | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 tests/misc/expand.pl | 16 ++++++++++++++
 4 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 47d237b..7f2d895 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,10 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** New features
 
+  expand and unexpand now support specifying an offset for tab stops
+  by prefixing the last specified number like --tabs=1,+8 which is
+  useful for visualizing diff output for example.
+
   split supports a new --hex-suffixes[=from] option to create files with
   lower case hexadecimal suffixes, similar to the --numeric-suffixes option.
 
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index b8e24aa..c22e076 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -7056,10 +7056,18 @@ If only one tab stop is given, set the tabs @var{tab1} spaces apart
 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.
+
+Also the last @var{tab} specified can be prefixed with a @samp{+} to indicate
+a tab size to use for remaining positions, offset from the final explicitly
+specified tab stop.
+For example, to ignore the 1 character gutter present in diff output,
+one can specify a 1 character offset using @option{--tabs=1,+8},
+which will set tab stops at positions 1,9,17,@dots{}
 @end macro
 @gnuExpandTabs
 
diff --git a/src/expand-common.c b/src/expand-common.c
index e1b549f..05e5bec 100644
--- a/src/expand-common.c
+++ b/src/expand-common.c
@@ -38,6 +38,9 @@ static uintmax_t tab_size = 0;
 /* If nonzero, the size of all tab stops after the last specifed.  */
 static uintmax_t extend_size = 0;
 
+/* If nonzero, an increment for additional tab stops after the last specified.*/
+static uintmax_t increment_size = 0;
+
 /* The maximum distance between tab stops.  */
 size_t max_column_width;
 
@@ -106,6 +109,23 @@ set_extend_size (uintmax_t tabval)
   return ok;
 }
 
+static bool
+set_increment_size (uintmax_t 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.  */
 extern void
@@ -114,6 +134,7 @@ parse_tab_stops (char const *stops)
   bool have_tabval = false;
   uintmax_t tabval = 0;
   bool extend_tabval = false;
+  bool increment_tabval = false;
   char const *num_start = NULL;
   bool ok = true;
 
@@ -131,6 +152,14 @@ parse_tab_stops (char const *stops)
                       break;
                     }
                 }
+              else if (increment_tabval)
+                {
+                  if (! set_increment_size (tabval))
+                    {
+                      ok = false;
+                      break;
+                    }
+                }
               else
                 add_tab_stop (tabval);
             }
@@ -145,6 +174,18 @@ parse_tab_stops (char const *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 (ISDIGIT (*stops))
         {
@@ -179,6 +220,8 @@ parse_tab_stops (char const *stops)
     {
       if (extend_tabval)
         ok &= set_extend_size (tabval);
+      else if (increment_tabval)
+        ok &= set_increment_size (tabval);
       else
         add_tab_stop (tabval);
     }
@@ -204,6 +247,9 @@ validate_tab_stops (uintmax_t const *tabs, size_t entries)
         die (EXIT_FAILURE, 0, _("tab sizes must be ascending"));
       prev_tab = tabs[i];
     }
+
+  if (increment_size && extend_size)
+    die (EXIT_FAILURE, 0, _("'/' specifier is mutually exclusive with '+'"));
 }
 
 /* Called after all command-line options have been parsed,
@@ -220,8 +266,10 @@ finalize_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 : 8;
-  else if (first_free_tab == 1 && ! extend_size)
+    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;
@@ -251,6 +299,14 @@ get_next_tab_column (const uintmax_t column, size_t* tab_index,
   if (extend_size)
     return column + (extend_size - column % extend_size);
 
+  /* incremental last tab - add increment_size to the previous tab stop */
+  if (increment_size)
+    {
+      uintmax_t end_tab = tab_list[first_free_tab - 1];
+
+      return column + (increment_size - ((column - end_tab) % increment_size));
+    }
+
   *last_tab = true;
   return 0;
 }
diff --git a/tests/misc/expand.pl b/tests/misc/expand.pl
index b04d2e7..7fe7830 100755
--- a/tests/misc/expand.pl
+++ b/tests/misc/expand.pl
@@ -146,11 +146,27 @@ my @Tests =
    ['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"}],
+   ['trail5a','--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 incremental trailing '+' feature which specifies that
+   # tab stops should continue every increment
+   ['incre0', '--tab=1,+5',    {IN=>"+\t\ta\tb"}, {OUT=>"+          a    b"}],
+   ['incre1', '--tabs=1,+5',   {IN=>"\ta\tb\tc"}, {OUT=>" a    b    c"}],
+   ['incre2', '--tabs=2,+5',   {IN=>"\ta\tb\tc"}, {OUT=>"  a    b    c"}],
+   ['incre3', '--tabs=1,2,+5', {IN=>"\ta\tb\tc"}, {OUT=>" a     b    c"}],
+   ['incre4', '--tabs=+5',     {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
+   ['incre5', '--tabs=++5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
+   ['incre5a','--tabs=/+5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
+   ['incre6', '--tabs=+,+5',   {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
+   ['incre7', '--tabs=,+5',    {IN=>"\ta\tb"},    {OUT=>"     a    b"}],
+   ['incre8', '--tabs=1 -t+5', {IN=>"\ta\tb\tc"}, {OUT=>" a    b    c"}],
+   ['incre9', '--tab=1,2 -t+5',{IN=>"\ta\tb\tc"}, {OUT=>" a     b    c"}],
+
+
    # Test errors
    ['e1', '--tabs="a"', {IN=>''}, {OUT=>''}, {EXIT=>1},
     {ERR => "$prog: tab size contains invalid character(s): 'a'\n"}],
-- 
2.9.3


From 4c85c757e55c1b75635672624632e3e6c92c5dd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email protected]>
Date: Sun, 2 Apr 2017 16:52:34 -0700
Subject: [PATCH 2/2] doc: refactor and update expand and unexpand --help

* src/expand-common.c (emit_tab_list_info): A new function to
output the extended info on --tab=LIST, including the new
'+' and '/' prefixes.
* src/expand-common.h: Declare the above.
* src/expand.c (usage:): Call emit_tab_list_info and
match alignment with that used in unexpand --help.
* src/unexpand.c (usage): Likewise.
---
 src/expand-common.c | 17 +++++++++++++++++
 src/expand-common.h |  4 ++++
 src/expand.c        |  8 +++-----
 src/unexpand.c      |  2 +-
 4 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/src/expand-common.c b/src/expand-common.c
index 05e5bec..8c83fb3 100644
--- a/src/expand-common.c
+++ b/src/expand-common.c
@@ -382,3 +382,20 @@ cleanup_file_list_stdin (void)
     if (have_read_stdin && fclose (stdin) != 0)
       die (EXIT_FAILURE, errno, "-");
 }
+
+
+extern void
+emit_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);
+}
diff --git a/src/expand-common.h b/src/expand-common.h
index 5eec99d..8cc6ebe 100644
--- a/src/expand-common.h
+++ b/src/expand-common.h
@@ -70,3 +70,7 @@ next_file (FILE *fp);
 /* */
 extern void
 cleanup_file_list_stdin (void);
+
+
+extern void
+emit_tab_list_info (void);
diff --git a/src/expand.c b/src/expand.c
index 2b7781c..1e78316 100644
--- a/src/expand.c
+++ b/src/expand.c
@@ -78,12 +78,10 @@ Convert tabs in each FILE to spaces, writing to standard output.\n\
       emit_mandatory_arg_note ();
 
       fputs (_("\
-  -i, --initial       do not convert tabs after non blanks\n\
-  -t, --tabs=NUMBER   have tabs NUMBER characters apart, not 8\n\
-"), stdout);
-      fputs (_("\
-  -t, --tabs=LIST     use comma separated list of explicit tab positions\n\
+  -i, --initial    do not convert tabs after non blanks\n\
+  -t, --tabs=N     have tabs N characters apart, not 8\n\
 "), stdout);
+      emit_tab_list_info ();
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       emit_ancillary_info (PROGRAM_NAME);
diff --git a/src/unexpand.c b/src/unexpand.c
index fd334dd..60c9422 100644
--- a/src/unexpand.c
+++ b/src/unexpand.c
@@ -90,8 +90,8 @@ Convert blanks in each FILE to tabs, writing to standard output.\n\
   -a, --all        convert all blanks, instead of just initial blanks\n\
       --first-only  convert only leading sequences of blanks (overrides -a)\n\
   -t, --tabs=N     have tabs N characters apart instead of 8 (enables -a)\n\
-  -t, --tabs=LIST  use comma separated LIST of tab positions (enables -a)\n\
 "), stdout);
+      emit_tab_list_info ();
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       emit_ancillary_info (PROGRAM_NAME);
-- 
2.9.3

Reply via email to