From 9daef34543cc3267ec8e2cc22488bf202448ed84 Mon Sep 17 00:00:00 2001 From: Jacob Keller 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?= 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