Add string split --fields

This commit is contained in:
Jason Nader 2020-03-21 01:31:23 +09:00 committed by Fabian Homborg
parent a29bc127ce
commit 7cb1d3a646
4 changed files with 49 additions and 5 deletions

View File

@ -8,8 +8,8 @@ Synopsis
::
string split [(-m | --max) MAX] [(-n | --no-empty)] [(-q | --quiet)] [(-r | --right)] SEP [STRING...]
string split0 [(-m | --max) MAX] [(-n | --no-empty)] [(-q | --quiet)] [(-r | --right)] [STRING...]
string split [(-f | --fields) FIELDS] [(-m | --max) MAX] [(-n | --no-empty)] [(-q | --quiet)] [(-r | --right)] SEP [STRING...]
string split0 [(-f | --fields) FIELDS] [(-m | --max) MAX] [(-n | --no-empty)] [(-q | --quiet)] [(-r | --right)] [STRING...]
.. END SYNOPSIS
@ -18,7 +18,7 @@ Description
.. BEGIN DESCRIPTION
``string split`` splits each STRING on the separator SEP, which can be an empty string. If ``-m`` or ``--max`` is specified, at most MAX splits are done on each STRING. If ``-r`` or ``--right`` is given, splitting is performed right-to-left. This is useful in combination with ``-m`` or ``--max``. With ``-n`` or ``--no-empty``, empty results are excluded from consideration (e.g. ``hello\n\nworld`` would expand to two strings and not three). Exit status: 0 if at least one split was performed, or 1 otherwise.
``string split`` splits each STRING on the separator SEP, which can be an empty string. If ``-m`` or ``--max`` is specified, at most MAX splits are done on each STRING. If ``-r`` or ``--right`` is given, splitting is performed right-to-left. This is useful in combination with ``-m`` or ``--max``. With ``-n`` or ``--no-empty``, empty results are excluded from consideration (e.g. ``hello\n\nworld`` would expand to two strings and not three). Use ``-f`` or ``--fields`` to print out specific fields. Exit status: 0 if at least one split was performed, or 1 otherwise.
See also the ``--delimiter`` option of the :ref:`read <cmd-read>` command.
@ -49,6 +49,11 @@ Examples
b
c
>_ string split -f1,3 '' abc
a
c
NUL Delimited Examples
^^^^^^^^^^^^^^^^^^^^^^

View File

@ -13,6 +13,7 @@ complete -x -c string -n "test (count (commandline -opc)) -ge 2; and contains --
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a split
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a split0
complete -x -c string -n 'test (count (commandline -opc)) -ge 2; and string match -qr split0\?\$ -- (commandline -opc)[2]' -s m -l max -a "(seq 1 10)" -d "Specify maximum number of splits"
complete -x -c string -n 'test (count (commandline -opc)) -ge 2; and string match -qr split0\?\$ -- (commandline -opc)[2]' -s f -l fields -a "(seq 1 10)" -d "Specify fields"
complete -f -c string -n 'test (count (commandline -opc)) -ge 2; and string match -qr split0\?\$ -- (commandline -opc)[2]' -s r -l right -d "Split right-to-left"
complete -f -c string -n 'test (count (commandline -opc)) -ge 2; and string match -qr split0\?\$ -- (commandline -opc)[2]' -s n -l no-empty -d "Empty results excluded"
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a collect

View File

@ -155,6 +155,7 @@ typedef struct { //!OCLINT(too many fields)
bool style_valid = false;
bool no_empty_valid = false;
bool no_trim_newlines_valid = false;
bool fields_valid = false;
bool all = false;
bool entire = false;
@ -177,6 +178,8 @@ typedef struct { //!OCLINT(too many fields)
long start = 0;
long end = 0;
std::vector<int> fields;
const wchar_t *chars_to_trim = L" \f\n\r\t\v";
const wchar_t *arg1 = nullptr;
const wchar_t *arg2 = nullptr;
@ -267,6 +270,19 @@ static int handle_flag_f(wchar_t **argv, parser_t &parser, io_streams_t &streams
if (opts->filter_valid) {
opts->filter = true;
return STATUS_CMD_OK;
} else if (opts->fields_valid) {
for (const wcstring &s : split_string(w.woptarg, L',')) {
int field = fish_wcstoi(wcsdup(s.c_str()));
if (field <= 0 || field == INT_MIN || errno == ERANGE) {
string_error(streams, _(L"%ls: Invalid fields value '%ls'\n"), argv[0], w.woptarg);
return STATUS_INVALID_ARGS;
} else if (errno) {
string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg);
return STATUS_INVALID_ARGS;
}
opts->fields.push_back(field);
}
return STATUS_CMD_OK;
}
string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
return STATUS_INVALID_ARGS;
@ -423,6 +439,7 @@ static wcstring construct_short_opts(options_t *opts) { //!OCLINT(high npath co
if (opts->end_valid) short_opts.append(L"e:");
if (opts->no_empty_valid) short_opts.append(L"n");
if (opts->no_trim_newlines_valid) short_opts.append(L"N");
if (opts->fields_valid) short_opts.append(L"f:");
return short_opts;
}
@ -450,6 +467,7 @@ static const struct woption long_options[] = {{L"all", no_argument, nullptr, 'a'
{L"start", required_argument, nullptr, 's'},
{L"style", required_argument, nullptr, 1},
{L"no-trim-newlines", no_argument, nullptr, 'N'},
{L"fields", required_argument, nullptr, 'f'},
{nullptr, 0, nullptr, 0}};
static const std::unordered_map<char, decltype(*handle_flag_N)> flag_to_function = {
@ -1083,6 +1101,7 @@ static int string_split_maybe0(parser_t &parser, io_streams_t &streams, int argc
opts.max_valid = true;
opts.max = LONG_MAX;
opts.no_empty_valid = true;
opts.fields_valid = true;
int optind;
int retval = parse_opts(&opts, &optind, is_split0 ? 0 : 1, argc, argv, parser, streams);
if (retval != STATUS_CMD_OK) return retval;
@ -1121,8 +1140,17 @@ static int string_split_maybe0(parser_t &parser, io_streams_t &streams, int argc
if (splits.back().empty()) splits.pop_back();
}
auto &buff = streams.out.buffer();
for (const wcstring &split : splits) {
buff.append(split, separation_type_t::explicitly);
if (opts.fields.size() > 0) {
for (const auto &field : opts.fields) {
// field indexing starts from 1
if (field - 1 < (long)split_count) {
buff.append(splits.at(field - 1), separation_type_t::explicitly);
}
}
} else {
for (const wcstring &split : splits) {
buff.append(split, separation_type_t::explicitly);
}
}
}

View File

@ -88,6 +88,16 @@ string split "" abc
# CHECK: b
# CHECK: c
string split --fields=2 "" abc
# CHECK: b
string split --fields=2,3 "" abc
# CHECK: b
# CHECK: c
string split --fields=2,9 "" abc
# CHECK: b
seq 3 | string join ...
# CHECK: 1...2...3