From 7cb1d3a646d16cad30a2e8ac0f51d5e14ff8c409 Mon Sep 17 00:00:00 2001 From: Jason Nader Date: Sat, 21 Mar 2020 01:31:23 +0900 Subject: [PATCH] Add `string split --fields` --- doc_src/cmds/string-split.rst | 11 ++++++++--- share/completions/string.fish | 1 + src/builtin_string.cpp | 32 ++++++++++++++++++++++++++++++-- tests/checks/string.fish | 10 ++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/doc_src/cmds/string-split.rst b/doc_src/cmds/string-split.rst index 17a979d0a..10631eaa2 100644 --- a/doc_src/cmds/string-split.rst +++ b/doc_src/cmds/string-split.rst @@ -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 ` command. @@ -49,6 +49,11 @@ Examples b c + >_ string split -f1,3 '' abc + a + c + + NUL Delimited Examples ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/share/completions/string.fish b/share/completions/string.fish index 6c4de96f1..c60ccc1c5 100644 --- a/share/completions/string.fish +++ b/share/completions/string.fish @@ -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 diff --git a/src/builtin_string.cpp b/src/builtin_string.cpp index a570c81be..9bfa00089 100644 --- a/src/builtin_string.cpp +++ b/src/builtin_string.cpp @@ -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 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 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); + } } } diff --git a/tests/checks/string.fish b/tests/checks/string.fish index b3d598a49..d450621cb 100644 --- a/tests/checks/string.fish +++ b/tests/checks/string.fish @@ -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