Merge pull request #5943 from lilyball/string_collect

Add new `string` subcommand `string collect`
This commit is contained in:
Lily Ballard
2019-06-22 00:30:20 -07:00
committed by GitHub
8 changed files with 136 additions and 5 deletions

View File

@@ -12,7 +12,7 @@ max_line_length = 100
[{Makefile,*.in}] [{Makefile,*.in}]
indent_style = tab indent_style = tab
[*.md] [*.{md,rst}]
trim_trailing_whitespace = false trim_trailing_whitespace = false
[*.{sh,ac}] [*.{sh,ac}]

View File

@@ -15,6 +15,7 @@
- The `--debug` option has been extended to allow specifying categories. Categories may be listed via `fish --print-debug-categories`. - The `--debug` option has been extended to allow specifying categories. Categories may be listed via `fish --print-debug-categories`.
- `string replace` had an additional round of escaping in the replacement (not the match!), so escaping backslashes would require `string replace -ra '([ab])' '\\\\\\\$1' a`. A new feature flag `string-replace-fewer-backslashes` can be used to disable this, so that it becomes `string replace -ra '([ab])' '\\\\$1' a` (#5556). - `string replace` had an additional round of escaping in the replacement (not the match!), so escaping backslashes would require `string replace -ra '([ab])' '\\\\\\\$1' a`. A new feature flag `string-replace-fewer-backslashes` can be used to disable this, so that it becomes `string replace -ra '([ab])' '\\\\$1' a` (#5556).
- Some parser errors did not set `$status` to non-zero. This has been corrected (b2a1da602f79878f4b0adc4881216c928a542608). - Some parser errors did not set `$status` to non-zero. This has been corrected (b2a1da602f79878f4b0adc4881216c928a542608).
- `string` has a new `collect` subcommand that disables newline-splitting on its input. This is meant to be used as the end of a command substitution pipeline to produce a single output argument potentially containing internal newlines, such as `set output (some-cmd | string collect)`. Any trailing newlines are trimmed, just like `"$(cmd)"` substitution in sh. It also supports a `--no-trim-newlines` flag to disable trailing newline trimming, which may be useful when doing something like `set contents (cat filename | string collect -N)` (#159).
### Syntax changes and new commands ### Syntax changes and new commands
- Brace expansion now only takes place if the braces include a "," or a variable expansion, so things like `git reset HEAD@{0}` now work (#5869). - Brace expansion now only takes place if the braces include a "," or a variable expansion, so things like `git reset HEAD@{0}` now work (#5869).

View File

@@ -1,7 +1,7 @@
# Completion for builtin string # Completion for builtin string
# This follows a strict command-then-options approach, so we can just test the number of tokens # This follows a strict command-then-options approach, so we can just test the number of tokens
complete -f -c string complete -f -c string
complete -f -c string -n "test (count (commandline -opc)) -ge 2; and not contains -- (commandline -opc)[2] escape" -s q -l quiet -d "Do not print output" complete -f -c string -n "test (count (commandline -opc)) -ge 2; and not contains -- (commandline -opc)[2] escape collect" -s q -l quiet -d "Do not print output"
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "lower" complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "lower"
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "upper" complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "upper"
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "length" complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "length"
@@ -13,6 +13,8 @@ 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 m -l max -a "(seq 1 10)" -d "Specify maximum number of splits"
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 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)) -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"
complete -f -c string -n 'test (count (commandline -opc)) -ge 2; and string match -qr collect\$ -- (commandline -opc)[2]' -s N -l no-trim-newlines -d "Don't trim trailing newlines"
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "join" complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "join"
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "join0" complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "join0"

View File

@@ -6,6 +6,8 @@ string - manipulate strings
Synopsis Synopsis
-------- --------
``string collect [(-N | --no-trim-newlines)] [STRING...]``
``string escape [(-n | --no-quoted)] [--style=xxx] [STRING...]`` ``string escape [(-n | --no-quoted)] [--style=xxx] [STRING...]``
``string join [(-q | --quiet)] SEP [STRING...]`` ``string join [(-q | --quiet)] SEP [STRING...]``
@@ -48,6 +50,17 @@ Most subcommands accept a ``-q`` or ``--quiet`` switch, which suppresses the usu
The following subcommands are available. The following subcommands are available.
"collect" subcommand
--------------------
``string collect [(-N | --no-trim-newlines)] [STRING...]``
``string collect`` collects its input into a single output argument, without splitting the output when used in a command substitution. This is useful when trying to collect multiline output from another command into a variable. Exit status: 0 if any output argument is non-empty, or 1 otherwise.
If invoked with multiple arguments instead of input, ``string collect`` preserves each argument separately, where the number of output arguments is equal to the number of arguments given to ``string collect``.
Any trailing newlines on the input are trimmed, just as with ``"$(cmd)"`` substitution in sh. ``--no-trim-newlines`` can be used to disable this behavior, which may be useful when running a command such as ``set contents (cat filename | string collect -N)``.
"escape" and "unescape" subcommands "escape" and "unescape" subcommands
----------------------------------- -----------------------------------
@@ -329,6 +342,22 @@ Examples
a1_20b2__c_E6_85_A1 a1_20b2__c_E6_85_A1
::
>_ echo \"(echo one\ntwo\nthree | string collect)\"
"one
two
three
"
>_ echo \"(echo one\ntwo\nthree | string collect -N)\"
"one
two
three"
Match Glob Examples Match Glob Examples
------------------- -------------------

View File

@@ -157,6 +157,7 @@ typedef struct { //!OCLINT(too many fields)
bool start_valid = false; bool start_valid = false;
bool style_valid = false; bool style_valid = false;
bool no_empty_valid = false; bool no_empty_valid = false;
bool no_trim_newlines_valid = false;
bool all = false; bool all = false;
bool entire = false; bool entire = false;
@@ -171,6 +172,7 @@ typedef struct { //!OCLINT(too many fields)
bool regex = false; bool regex = false;
bool right = false; bool right = false;
bool no_empty = false; bool no_empty = false;
bool no_trim_newlines = false;
long count = 0; long count = 0;
long length = 0; long length = 0;
@@ -214,6 +216,9 @@ static int handle_flag_N(wchar_t **argv, parser_t &parser, io_streams_t &streams
if (opts->no_newline_valid) { if (opts->no_newline_valid) {
opts->no_newline = true; opts->no_newline = true;
return STATUS_CMD_OK; return STATUS_CMD_OK;
} else if (opts->no_trim_newlines_valid) {
opts->no_trim_newlines = true;
return STATUS_CMD_OK;
} }
string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
return STATUS_INVALID_ARGS; return STATUS_INVALID_ARGS;
@@ -408,12 +413,13 @@ static wcstring construct_short_opts(options_t *opts) { //!OCLINT(high npath co
if (opts->right_valid) short_opts.append(L"r"); if (opts->right_valid) short_opts.append(L"r");
if (opts->start_valid) short_opts.append(L"s:"); if (opts->start_valid) short_opts.append(L"s:");
if (opts->no_empty_valid) short_opts.append(L"n"); if (opts->no_empty_valid) short_opts.append(L"n");
if (opts->no_trim_newlines_valid) short_opts.append(L"N");
return short_opts; return short_opts;
} }
// Note that several long flags share the same short flag. That is okay. The caller is expected // Note that several long flags share the same short flag. That is okay. The caller is expected
// to indicate that a max of one of the long flags sharing a short flag is valid. // to indicate that a max of one of the long flags sharing a short flag is valid.
// Remember: adjust share/functions/string.fish when `string` options change // Remember: adjust share/completions/string.fish when `string` options change
static const struct woption long_options[] = { static const struct woption long_options[] = {
{L"all", no_argument, NULL, 'a'}, {L"chars", required_argument, NULL, 'c'}, {L"all", no_argument, NULL, 'a'}, {L"chars", required_argument, NULL, 'c'},
{L"count", required_argument, NULL, 'n'}, {L"entire", no_argument, NULL, 'e'}, {L"count", required_argument, NULL, 'n'}, {L"entire", no_argument, NULL, 'e'},
@@ -424,7 +430,8 @@ static const struct woption long_options[] = {
{L"no-newline", no_argument, NULL, 'N'}, {L"no-quoted", no_argument, NULL, 'n'}, {L"no-newline", no_argument, NULL, 'N'}, {L"no-quoted", no_argument, NULL, 'n'},
{L"quiet", no_argument, NULL, 'q'}, {L"regex", no_argument, NULL, 'r'}, {L"quiet", no_argument, NULL, 'q'}, {L"regex", no_argument, NULL, 'r'},
{L"right", no_argument, NULL, 'r'}, {L"start", required_argument, NULL, 's'}, {L"right", no_argument, NULL, 'r'}, {L"start", required_argument, NULL, 's'},
{L"style", required_argument, NULL, 1}, {NULL, 0, NULL, 0}}; {L"style", required_argument, NULL, 1}, {L"no-trim-newlines", no_argument, NULL, 'N'},
{NULL, 0, NULL, 0}};
static const std::unordered_map<char, decltype(*handle_flag_N)> flag_to_function = { static const std::unordered_map<char, decltype(*handle_flag_N)> flag_to_function = {
{'N', handle_flag_N}, {'a', handle_flag_a}, {'c', handle_flag_c}, {'e', handle_flag_e}, {'N', handle_flag_N}, {'a', handle_flag_a}, {'c', handle_flag_c}, {'e', handle_flag_e},
@@ -1125,6 +1132,29 @@ static int string_split0(parser_t &parser, io_streams_t &streams, int argc, wcha
return string_split_maybe0(parser, streams, argc, argv, true /* is_split0 */); return string_split_maybe0(parser, streams, argc, argv, true /* is_split0 */);
} }
static int string_collect(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
options_t opts;
opts.no_trim_newlines_valid = true;
int optind;
int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams);
if (retval != STATUS_CMD_OK) return retval;
auto &buff = streams.out.buffer();
arg_iterator_t aiter(argv, optind, streams, /* don't split */ false);
while (const wcstring *arg = aiter.nextstr()) {
auto begin = arg->cbegin(), end = arg->cend();
if (!opts.no_trim_newlines) {
while (end > begin && *(end-1) == L'\n') {
--end;
}
}
buff.append(begin, end, separation_type_t::explicitly);
}
return buff.size() > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
}
// Helper function to abstract the repeat logic from string_repeat // Helper function to abstract the repeat logic from string_repeat
// returns the to_repeat string, repeated count times. // returns the to_repeat string, repeated count times.
static wcstring wcsrepeat(const wcstring &to_repeat, size_t count) { static wcstring wcsrepeat(const wcstring &to_repeat, size_t count) {
@@ -1305,7 +1335,8 @@ string_subcommands[] = {
{L"length", &string_length}, {L"match", &string_match}, {L"replace", &string_replace}, {L"length", &string_length}, {L"match", &string_match}, {L"replace", &string_replace},
{L"split", &string_split}, {L"split0", &string_split0}, {L"sub", &string_sub}, {L"split", &string_split}, {L"split0", &string_split0}, {L"sub", &string_sub},
{L"trim", &string_trim}, {L"lower", &string_lower}, {L"upper", &string_upper}, {L"trim", &string_trim}, {L"lower", &string_lower}, {L"upper", &string_upper},
{L"repeat", &string_repeat}, {L"unescape", &string_unescape}, {NULL, NULL}}; {L"repeat", &string_repeat}, {L"unescape", &string_unescape}, {L"collect", &string_collect},
{NULL, NULL}};
/// The string builtin, for manipulating strings. /// The string builtin, for manipulating strings.
int builtin_string(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int builtin_string(parser_t &parser, io_streams_t &streams, wchar_t **argv) {

View File

@@ -309,3 +309,9 @@ string repeat: Unknown option '-l'
#################### ####################
# string split0 in functions # string split0 in functions
####################
# string collect
####################
# string collect in functions

View File

@@ -392,4 +392,31 @@ function dualsplit
end end
count (dualsplit) count (dualsplit)
logmsg string collect
count (echo one\ntwo\nthree\nfour | string collect)
count (echo one | string collect)
echo [(echo one\ntwo\nthree | string collect)]
echo [(echo one\ntwo\nthree | string collect -N)]
printf '[%s]\n' (string collect one\n\n two\n)
printf '[%s]\n' (string collect -N one\n\n two\n)
printf '[%s]\n' (string collect --no-trim-newlines one\n\n two\n)
# string collect returns 0 when it has any output, otherwise 1
string collect >/dev/null; and echo unexpected success; or echo expected failure
echo -n | string collect >/dev/null; and echo unexpected success; or echo expected failure
echo | string collect -N >/dev/null; and echo expected success; or echo unexpected failure
echo | string collect >/dev/null; and echo unexpected success; or echo expected failure
string collect a >/dev/null; and echo expected success; or echo unexpected failure
string collect -N '' >/dev/null; and echo unexpected success; or echo expected failure
string collect \n\n >/dev/null; and echo unexpected success; or echo expected failure
logmsg string collect in functions
# This function outputs some newline-separated content, and some
# explicitly un-separated content.
function dualcollect
echo alpha
echo beta
echo gamma\ndelta\nomega | string collect
end
count (dualcollect)
exit 0 exit 0

View File

@@ -483,3 +483,38 @@ Split something
#################### ####################
# string split0 in functions # string split0 in functions
4 4
####################
# string collect
1
1
[one
two
three]
[one
two
three
]
[one]
[two]
[one
]
[two
]
[one
]
[two
]
expected failure
expected failure
expected success
expected failure
expected success
expected failure
expected failure
####################
# string collect in functions
3