mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-25 12:23:09 +00:00
Add string 'repeat' subcommand
This feature add the ability to repeat a string a given number of times. For example: string repeat -n 3 foo
This commit is contained in:
parent
e0f62c178f
commit
98f4e49669
6 changed files with 208 additions and 1 deletions
|
@ -15,6 +15,8 @@ string match [(-a | --all)] [(-i | --ignore-case)] [(-r | --regex)]
|
|||
[(-n | --index)] [(-q | --quiet)] [(-v | --invert)] PATTERN [STRING...]
|
||||
string replace [(-a | --all)] [(-i | --ignore-case)] [(-r | --regex)]
|
||||
[(-q | --quiet)] PATTERN REPLACEMENT [STRING...]
|
||||
string repeat [(-n | --count)] [(-m | --max)] [(-N | --no-newline)]
|
||||
[(-q | --quiet)] [STRING...]
|
||||
\endfish
|
||||
|
||||
|
||||
|
@ -48,6 +50,8 @@ The following subcommands are available:
|
|||
|
||||
- `replace` is similar to `match` but replaces non-overlapping matching substrings with a replacement string and prints the result. By default, PATTERN is treated as a literal substring to be matched. If `-r` or `--regex` is given, PATTERN is interpreted as a Perl-compatible regular expression, and REPLACEMENT can contain C-style escape sequences like `\t` as well as references to capturing groups by number or name as `$n` or `${n}`. Exit status: 0 if at least one replacement was performed, or 1 otherwise.
|
||||
|
||||
- `repeat` repeats the STRING `-n` or `--count` times. The `-m` or `--max` option will limit the number of outputed char (excluding the newline). This option can be used by itself or in conjuction with `--count`. If both `--count` and `--max` are present, max char will be outputed unless the final repeated string size is less than max, in that case, the string will repeat until count has been reached. Both `--count` and `--max` will accept a number greater than or equal to zero, in the case of zero, nothing will be outputed. If `-N` or `--no-newline` is given, the output won't contain a newline character at the end. Exit status: 0 if yielded string is not empty, 1 otherwise.
|
||||
|
||||
\subsection regular-expressions Regular Expressions
|
||||
|
||||
Both the `match` and `replace` subcommand support regular expressions when used with the `-r` or `--regex` option. The dialect is that of PCRE2.
|
||||
|
@ -190,3 +194,19 @@ In general, special characters are special by default, so `a+` matches one or mo
|
|||
<outp>put a</outp>
|
||||
<outp>here</outp>
|
||||
\endfish
|
||||
|
||||
\subsection string-example-repeat Repeat Examples
|
||||
|
||||
\fish{cli-dark}
|
||||
>_ string repeat -n 2 'foo '
|
||||
<outp>foo foo</outp>
|
||||
|
||||
>_ echo foo | string repeat -n 2
|
||||
<outp>foofoo</outp>
|
||||
|
||||
>_ string repeat -n 2 -m 5 'foo'
|
||||
<outp>foofo</outp>
|
||||
|
||||
>_ string repeat -m 5 'foo'
|
||||
<outp>foofo</outp>
|
||||
\endfish
|
||||
|
|
|
@ -24,3 +24,7 @@ complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "replace"
|
|||
complete -f -c string -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] match replace" -s a -l all -d "Report all matches per line/string"
|
||||
complete -f -c string -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] match replace" -s i -l ignore-case -d "Case insensitive"
|
||||
complete -f -c string -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] match replace" -s r -l regex -d "Use regex instead of globs"
|
||||
complete -f -c string -n "test (count (commandline -opc)) -lt 2" -a "repeat"
|
||||
complete -f -c string -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] repeat" -s n -l count -a "(seq 1 10)" -d "Repetition count"
|
||||
complete -f -c string -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] repeat" -s m -l max -a "(seq 1 10)" -d "Maximum number of printed char"
|
||||
complete -f -c string -n "test (count (commandline -opc)) -ge 2; and contains -- (commandline -opc)[2] repeat" -s N -l no-newline -d "Remove newline"
|
||||
|
|
|
@ -949,6 +949,118 @@ static int string_split(parser_t &parser, io_streams_t &streams, int argc, wchar
|
|||
return splits.size() > arg_count ? BUILTIN_STRING_OK : BUILTIN_STRING_NONE;
|
||||
}
|
||||
|
||||
// Helper function to abstract the repeat logic from string_repeat
|
||||
// returns the to_repeat string, repeated count times.
|
||||
static wcstring wcsrepeat(const wcstring &to_repeat, size_t count) {
|
||||
wcstring repeated;
|
||||
repeated.reserve(to_repeat.length() * count);
|
||||
|
||||
for (size_t j = 0; j < count; j++) {
|
||||
repeated += to_repeat;
|
||||
}
|
||||
|
||||
return repeated;
|
||||
}
|
||||
|
||||
// Helper function to abstract the repeat until logic from string_repeat
|
||||
// returns the to_repeat string, repeated until max char has been reached.
|
||||
static wcstring wcsrepeat_until(const wcstring &to_repeat, size_t max) {
|
||||
size_t count = max / to_repeat.length();
|
||||
size_t mod = max % to_repeat.length();
|
||||
|
||||
return wcsrepeat(to_repeat, count) + to_repeat.substr(0, mod);
|
||||
}
|
||||
|
||||
static int string_repeat(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
||||
const wchar_t *short_options = L":n:m:Nq";
|
||||
const struct woption long_options[] = {{L"count", required_argument, 0, 'n'},
|
||||
{L"max", required_argument, 0, 'm'},
|
||||
{L"no-newline", no_argument, 0, 'N'},
|
||||
{L"quiet", no_argument, 0, 'q'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
size_t count = 0;
|
||||
size_t max = 0;
|
||||
bool newline = true;
|
||||
bool quiet = false;
|
||||
int opt;
|
||||
wgetopter_t w;
|
||||
|
||||
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'n': {
|
||||
long lcount = fish_wcstol(w.woptarg);
|
||||
if (lcount < 0 || errno == ERANGE) {
|
||||
string_error(streams, _(L"%ls: Invalid count value '%ls'\n"), argv[0], w.woptarg);
|
||||
return BUILTIN_STRING_ERROR;
|
||||
} else if (errno) {
|
||||
string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg);
|
||||
return BUILTIN_STRING_ERROR;
|
||||
}
|
||||
count = static_cast<size_t>(lcount);
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
long lmax = fish_wcstol(w.woptarg);
|
||||
if (lmax < 0 || errno == ERANGE) {
|
||||
string_error(streams, _(L"%ls: Invalid max value '%ls'\n"), argv[0], w.woptarg);
|
||||
return BUILTIN_STRING_ERROR;
|
||||
} else if (errno) {
|
||||
string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg);
|
||||
return BUILTIN_STRING_ERROR;
|
||||
}
|
||||
max = static_cast<size_t>(lmax);
|
||||
break;
|
||||
}
|
||||
case 'N': {
|
||||
newline = false;
|
||||
break;
|
||||
}
|
||||
case 'q': {
|
||||
quiet = true;
|
||||
break;
|
||||
}
|
||||
case ':': {
|
||||
string_error(streams, STRING_ERR_MISSING, argv[0]);
|
||||
return BUILTIN_STRING_ERROR;
|
||||
}
|
||||
case '?': {
|
||||
string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
||||
return BUILTIN_STRING_ERROR;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected opt");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int i = w.woptind;
|
||||
|
||||
if (string_args_from_stdin(streams) && argc > i) {
|
||||
string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]);
|
||||
return BUILTIN_STRING_ERROR;
|
||||
}
|
||||
|
||||
const wchar_t *to_repeat;
|
||||
wcstring storage;
|
||||
bool is_empty = true;
|
||||
|
||||
if ((to_repeat = string_get_arg(&i, argv, &storage, streams)) != NULL) {
|
||||
const wcstring word(to_repeat);
|
||||
const bool rep_until = (0 < max && word.length()*count > max) || !count;
|
||||
const wcstring repeated = rep_until ? wcsrepeat_until(word, max) : wcsrepeat(word, count);
|
||||
is_empty = repeated.empty();
|
||||
|
||||
if (!quiet && !is_empty) {
|
||||
streams.out.append(repeated);
|
||||
if (newline) streams.out.append(L"\n");
|
||||
}
|
||||
}
|
||||
|
||||
return !is_empty ? BUILTIN_STRING_OK : BUILTIN_STRING_NONE;
|
||||
}
|
||||
|
||||
static int string_sub(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
||||
const wchar_t *short_options = L":l:qs:";
|
||||
const struct woption long_options[] = {{L"length", required_argument, 0, 'l'},
|
||||
|
@ -1157,7 +1269,8 @@ static const struct string_subcommand {
|
|||
string_subcommands[] = {
|
||||
{L"escape", &string_escape}, {L"join", &string_join}, {L"length", &string_length},
|
||||
{L"match", &string_match}, {L"replace", &string_replace}, {L"split", &string_split},
|
||||
{L"sub", &string_sub}, {L"trim", &string_trim}, {0, 0}};
|
||||
{L"sub", &string_sub}, {L"trim", &string_trim}, {L"repeat", &string_repeat},
|
||||
{0, 0}};
|
||||
|
||||
/// The string builtin, for manipulating strings.
|
||||
int builtin_string(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
string repeat: Invalid count value '-1'
|
||||
string repeat: Invalid max value '-1'
|
||||
string repeat: Argument 'notanumber' is not a number
|
||||
string repeat: Argument 'notanumber' is not a number
|
||||
string repeat: Too many arguments
|
||||
string repeat: Expected argument
|
||||
string repeat: Unknown option '-l'
|
|
@ -90,3 +90,46 @@ string match -r -v "[dcantg].*" dog can cat diz; or echo "no regexp invert match
|
|||
string match -v "???" dog can cat diz; or echo "no glob invert match"
|
||||
|
||||
string match -rvn a bbb
|
||||
|
||||
# test repeat subcommand
|
||||
string repeat -n 2 'foo'
|
||||
|
||||
string repeat --count 2 'foo'
|
||||
|
||||
echo foo | string repeat -n 2
|
||||
|
||||
string repeat -n2 -q 'foo'; and echo "exit 0"
|
||||
|
||||
string repeat -n2 --quiet 'foo'; and echo "exit 0"
|
||||
|
||||
string repeat -n0 'foo'; or echo "exit 1"
|
||||
|
||||
string repeat -n0; or echo "exit 1"
|
||||
|
||||
string repeat -m0; or echo "exit 1"
|
||||
|
||||
string repeat -n1 -N 'there is '; echo "no newline"
|
||||
|
||||
string repeat -n1 --no-newline 'there is '; echo "no newline"
|
||||
|
||||
string repeat -n10 -m4 'foo'
|
||||
|
||||
string repeat -n10 --max 5 'foo'
|
||||
|
||||
string repeat -n3 -m20 'foo'
|
||||
|
||||
string repeat -m4 'foo'
|
||||
|
||||
string repeat -n-1 'foo'; or echo "exit 2"
|
||||
|
||||
string repeat -m-1 'foo'; or echo "exit 2"
|
||||
|
||||
string repeat -n notanumber 'foo'; or echo "exit 2"
|
||||
|
||||
string repeat -m notanumber 'foo'; or echo "exit 2"
|
||||
|
||||
echo 'stdin' | string repeat -n1 'and arg'; or echo "exit 2"
|
||||
|
||||
string repeat -n; or echo "exit 2"
|
||||
|
||||
string repeat -l fakearg 2>&1 | head -n1 1>&2
|
|
@ -63,3 +63,23 @@ missing argument returns 0
|
|||
no regexp invert match
|
||||
no glob invert match
|
||||
1 3
|
||||
foofoo
|
||||
foofoo
|
||||
foofoo
|
||||
exit 0
|
||||
exit 0
|
||||
exit 1
|
||||
exit 1
|
||||
exit 1
|
||||
there is no newline
|
||||
there is no newline
|
||||
foof
|
||||
foofo
|
||||
foofoofoo
|
||||
foof
|
||||
exit 2
|
||||
exit 2
|
||||
exit 2
|
||||
exit 2
|
||||
exit 2
|
||||
exit 2
|
||||
|
|
Loading…
Reference in a new issue