mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-29 21:43:55 +08:00
Add string collect
The `string collect` subcommand behaves quite similarly in practice to `string split0 -m 0` in that it doesn't split its output, but it also takes an optional `--trim-newline` flag to trim a single trailing newline off of the output. See issue #159.
This commit is contained in:
parent
5fd3bf79f5
commit
b41e5cbbb7
|
@ -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 newlines, such as `set contents (cat filename | string collect)`. It also supports a `--trim-newline` flag to trim a single trailing newline from the output (#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).
|
||||||
|
|
|
@ -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 trim-newline -d "Remove trailing newline"
|
||||||
|
|
||||||
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"
|
||||||
|
|
|
@ -6,6 +6,8 @@ string - manipulate strings
|
||||||
Synopsis
|
Synopsis
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
``string collect [(-n | --trim-newline)] [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 | --trim-newline)] [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``.
|
||||||
|
|
||||||
|
``--trim-newline`` trims a single trailing newline off of each output argument. This is useful when collecting the output from another command as the trailing newline is frequently not desired.
|
||||||
|
|
||||||
"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 \"(ech one\ntwo\nthree | string collect -n)\"
|
||||||
|
"one
|
||||||
|
two
|
||||||
|
three"
|
||||||
|
|
||||||
|
|
||||||
Match Glob Examples
|
Match Glob Examples
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
|
@ -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 trim_newline_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 trim_newline = false;
|
||||||
|
|
||||||
long count = 0;
|
long count = 0;
|
||||||
long length = 0;
|
long length = 0;
|
||||||
|
@ -330,6 +332,9 @@ static int handle_flag_n(wchar_t **argv, parser_t &parser, io_streams_t &streams
|
||||||
} else if (opts->no_empty_valid) {
|
} else if (opts->no_empty_valid) {
|
||||||
opts->no_empty = true;
|
opts->no_empty = true;
|
||||||
return STATUS_CMD_OK;
|
return STATUS_CMD_OK;
|
||||||
|
} else if (opts->trim_newline_valid) {
|
||||||
|
opts->trim_newline = 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->trim_newline_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"trim-newline", 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,27 @@ 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.trim_newline_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 end = arg->cend();
|
||||||
|
if (opts.trim_newline && !arg->empty() && arg->back() == L'\n') {
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
|
||||||
|
buff.append(arg->cbegin(), 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 +1333,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) {
|
||||||
|
|
|
@ -309,3 +309,9 @@ string repeat: Unknown option '-l'
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# string split0 in functions
|
# string split0 in functions
|
||||||
|
|
||||||
|
####################
|
||||||
|
# string collect
|
||||||
|
|
||||||
|
####################
|
||||||
|
# string collect in functions
|
||||||
|
|
|
@ -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 --trim-newline 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 >/dev/null; and echo expected success; or echo unexpected failure
|
||||||
|
echo | string collect -n >/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 '' >/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
|
||||||
|
|
|
@ -483,3 +483,37 @@ 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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user