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
This commit is contained in:
Pádraig Brady 2017-02-26 14:40:19 -08:00
parent 9404382f6b
commit b1413b6011
4 changed files with 99 additions and 13 deletions

6
NEWS
View File

@ -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]

View 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

View 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;
}

View File

@ -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};