mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-15 22:44:01 +00:00
New command "string pad" to pad text to a given width (#7340)
Pads text to a given width, or the maximum width of all inputs.
This commit is contained in:
parent
46746b4b26
commit
92511b09c4
4 changed files with 180 additions and 10 deletions
50
doc_src/cmds/string-pad.rst
Normal file
50
doc_src/cmds/string-pad.rst
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
string-pad - pad characters before and after string
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. BEGIN SYNOPSIS
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
string pad [(-r | --right)] [(-c | --char) CHAR] [(-w | --width) INTEGER] [STRING...]
|
||||||
|
|
||||||
|
.. END SYNOPSIS
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. BEGIN DESCRIPTION
|
||||||
|
|
||||||
|
``string pad`` pads each STRING with CHAR to the given width.
|
||||||
|
|
||||||
|
The default behavior is left padding with spaces and default width is the length of string (hence, no padding).
|
||||||
|
|
||||||
|
If ``-r`` or ``--right`` is given, only pad after string.
|
||||||
|
|
||||||
|
The ``-c`` or ``--char`` switch causes padding with the character CHAR instead of default whitespace character.
|
||||||
|
|
||||||
|
If ``-w`` or ``--width`` is given, pad the string to given width. Width less than the string width will result in an unchanged string.
|
||||||
|
|
||||||
|
.. END DESCRIPTION
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. BEGIN EXAMPLES
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>_ string pad -w 10 -c ' ' 'abc'
|
||||||
|
abc
|
||||||
|
|
||||||
|
>_ string pad --right --width 12 --char=z foo barbaz
|
||||||
|
foozzzzzzzzz
|
||||||
|
barbazzzzzzz
|
||||||
|
|
||||||
|
>_ string pad -w 6 --char=- foo | string pad --right -w 9 --char=-
|
||||||
|
---foo---
|
||||||
|
|
||||||
|
|
||||||
|
.. END EXAMPLES
|
|
@ -15,6 +15,7 @@ Synopsis
|
||||||
string length [(-q | --quiet)] [STRING...]
|
string length [(-q | --quiet)] [STRING...]
|
||||||
string lower [(-q | --quiet)] [STRING...]
|
string lower [(-q | --quiet)] [STRING...]
|
||||||
string match [(-a | --all)] [(-e | --entire)] [(-i | --ignore-case)] [(-r | --regex)] [(-n | --index)] [(-q | --quiet)] [(-v | --invert)] PATTERN [STRING...]
|
string match [(-a | --all)] [(-e | --entire)] [(-i | --ignore-case)] [(-r | --regex)] [(-n | --index)] [(-q | --quiet)] [(-v | --invert)] PATTERN [STRING...]
|
||||||
|
string pad [(-r | --right)] [(-c | --char) CHAR] [(-w | --width) INTEGER] [STRING...]
|
||||||
string repeat [(-n | --count) COUNT] [(-m | --max) MAX] [(-N | --no-newline)] [(-q | --quiet)] [STRING...]
|
string repeat [(-n | --count) COUNT] [(-m | --max) MAX] [(-N | --no-newline)] [(-q | --quiet)] [STRING...]
|
||||||
string replace [(-a | --all)] [(-f | --filter)] [(-i | --ignore-case)] [(-r | --regex)] [(-q | --quiet)] PATTERN REPLACEMENT [STRING...]
|
string replace [(-a | --all)] [(-f | --filter)] [(-i | --ignore-case)] [(-r | --regex)] [(-q | --quiet)] PATTERN REPLACEMENT [STRING...]
|
||||||
string split [(-m | --max) MAX] [(-n | --no-empty)] [(-q | --quiet)] [(-r | --right)] SEP [STRING...]
|
string split [(-m | --max) MAX] [(-n | --no-empty)] [(-q | --quiet)] [(-r | --right)] SEP [STRING...]
|
||||||
|
@ -141,6 +142,21 @@ Examples
|
||||||
:start-after: BEGIN EXAMPLES
|
:start-after: BEGIN EXAMPLES
|
||||||
:end-before: END EXAMPLES
|
:end-before: END EXAMPLES
|
||||||
|
|
||||||
|
"pad" subcommand
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. include:: string-pad.rst
|
||||||
|
:start-after: BEGIN SYNOPSIS
|
||||||
|
:end-before: END SYNOPSIS
|
||||||
|
|
||||||
|
.. include:: string-pad.rst
|
||||||
|
:start-after: BEGIN DESCRIPTION
|
||||||
|
:end-before: END DESCRIPTION
|
||||||
|
|
||||||
|
.. include:: string-pad.rst
|
||||||
|
:start-after: BEGIN EXAMPLES
|
||||||
|
:end-before: END EXAMPLES
|
||||||
|
|
||||||
"repeat" subcommand
|
"repeat" subcommand
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,8 @@ class arg_iterator_t {
|
||||||
// valid and get the result of parsing the command for flags.
|
// valid and get the result of parsing the command for flags.
|
||||||
using options_t = struct options_t { //!OCLINT(too many fields)
|
using options_t = struct options_t { //!OCLINT(too many fields)
|
||||||
bool all_valid = false;
|
bool all_valid = false;
|
||||||
bool chars_valid = false;
|
bool char_to_pad_valid = false;
|
||||||
|
bool chars_to_trim_valid = false;
|
||||||
bool count_valid = false;
|
bool count_valid = false;
|
||||||
bool entire_valid = false;
|
bool entire_valid = false;
|
||||||
bool filter_valid = false;
|
bool filter_valid = false;
|
||||||
|
@ -157,6 +158,7 @@ using options_t = struct options_t { //!OCLINT(too many fields)
|
||||||
bool no_trim_newlines_valid = false;
|
bool no_trim_newlines_valid = false;
|
||||||
bool fields_valid = false;
|
bool fields_valid = false;
|
||||||
bool allow_empty_valid = false;
|
bool allow_empty_valid = false;
|
||||||
|
bool width_valid = false;
|
||||||
|
|
||||||
bool all = false;
|
bool all = false;
|
||||||
bool entire = false;
|
bool entire = false;
|
||||||
|
@ -179,6 +181,9 @@ using options_t = struct options_t { //!OCLINT(too many fields)
|
||||||
long max = 0;
|
long max = 0;
|
||||||
long start = 0;
|
long start = 0;
|
||||||
long end = 0;
|
long end = 0;
|
||||||
|
size_t width = 0;
|
||||||
|
|
||||||
|
wchar_t char_to_pad = ' ';
|
||||||
|
|
||||||
std::vector<int> fields;
|
std::vector<int> fields;
|
||||||
|
|
||||||
|
@ -242,9 +247,16 @@ static int handle_flag_a(wchar_t **argv, parser_t &parser, io_streams_t &streams
|
||||||
|
|
||||||
static int handle_flag_c(wchar_t **argv, parser_t &parser, io_streams_t &streams,
|
static int handle_flag_c(wchar_t **argv, parser_t &parser, io_streams_t &streams,
|
||||||
const wgetopter_t &w, options_t *opts) {
|
const wgetopter_t &w, options_t *opts) {
|
||||||
if (opts->chars_valid) {
|
if (opts->chars_to_trim_valid) {
|
||||||
opts->chars_to_trim = w.woptarg;
|
opts->chars_to_trim = w.woptarg;
|
||||||
return STATUS_CMD_OK;
|
return STATUS_CMD_OK;
|
||||||
|
} else if (opts->char_to_pad_valid) {
|
||||||
|
if (wcslen(w.woptarg) != 1) {
|
||||||
|
string_error(streams, _(L"%ls: Padding should be a character '%ls'\n"), argv[0], w.woptarg);
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
opts->char_to_pad = w.woptarg[0];
|
||||||
|
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;
|
||||||
|
@ -451,13 +463,33 @@ static int handle_flag_v(wchar_t **argv, parser_t &parser, io_streams_t &streams
|
||||||
return STATUS_INVALID_ARGS;
|
return STATUS_INVALID_ARGS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int handle_flag_w(wchar_t **argv, parser_t &parser, io_streams_t &streams,
|
||||||
|
const wgetopter_t &w, options_t *opts) {
|
||||||
|
long width = 0;
|
||||||
|
if (opts->width_valid) {
|
||||||
|
width = fish_wcstol(w.woptarg);
|
||||||
|
if (width < 0) {
|
||||||
|
string_error(streams, _(L"%ls: Invalid width 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->width = static_cast<size_t>(width);
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
}
|
||||||
|
string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
||||||
|
return STATUS_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
/// This constructs the wgetopt() short options string based on which arguments are valid for the
|
/// This constructs the wgetopt() short options string based on which arguments are valid for the
|
||||||
/// subcommand. We have to do this because many short flags have multiple meanings and may or may
|
/// subcommand. We have to do this because many short flags have multiple meanings and may or may
|
||||||
/// not require an argument depending on the meaning.
|
/// not require an argument depending on the meaning.
|
||||||
static wcstring construct_short_opts(options_t *opts) { //!OCLINT(high npath complexity)
|
static wcstring construct_short_opts(options_t *opts) { //!OCLINT(high npath complexity)
|
||||||
wcstring short_opts(L":");
|
wcstring short_opts(L":");
|
||||||
if (opts->all_valid) short_opts.append(L"a");
|
if (opts->all_valid) short_opts.append(L"a");
|
||||||
if (opts->chars_valid) short_opts.append(L"c:");
|
if (opts->char_to_pad_valid) short_opts.append(L"c:");
|
||||||
|
if (opts->chars_to_trim_valid) short_opts.append(L"c:");
|
||||||
if (opts->count_valid) short_opts.append(L"n:");
|
if (opts->count_valid) short_opts.append(L"n:");
|
||||||
if (opts->entire_valid) short_opts.append(L"e");
|
if (opts->entire_valid) short_opts.append(L"e");
|
||||||
if (opts->filter_valid) short_opts.append(L"f");
|
if (opts->filter_valid) short_opts.append(L"f");
|
||||||
|
@ -478,6 +510,7 @@ static wcstring construct_short_opts(options_t *opts) { //!OCLINT(high npath co
|
||||||
if (opts->no_trim_newlines_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:");
|
if (opts->fields_valid) short_opts.append(L"f:");
|
||||||
if (opts->allow_empty_valid) short_opts.append(L"a");
|
if (opts->allow_empty_valid) short_opts.append(L"a");
|
||||||
|
if (opts->width_valid) short_opts.append(L"w:");
|
||||||
return short_opts;
|
return short_opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,13 +540,14 @@ static const struct woption long_options[] = {{L"all", no_argument, nullptr, 'a'
|
||||||
{L"no-trim-newlines", no_argument, nullptr, 'N'},
|
{L"no-trim-newlines", no_argument, nullptr, 'N'},
|
||||||
{L"fields", required_argument, nullptr, 'f'},
|
{L"fields", required_argument, nullptr, 'f'},
|
||||||
{L"allow-empty", no_argument, nullptr, 'a'},
|
{L"allow-empty", no_argument, nullptr, 'a'},
|
||||||
|
{L"width", required_argument, nullptr, 'w'},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 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},
|
||||||
{'f', handle_flag_f}, {'i', handle_flag_i}, {'l', handle_flag_l}, {'m', handle_flag_m},
|
{'f', handle_flag_f}, {'i', handle_flag_i}, {'l', handle_flag_l}, {'m', handle_flag_m},
|
||||||
{'n', handle_flag_n}, {'q', handle_flag_q}, {'r', handle_flag_r}, {'s', handle_flag_s},
|
{'n', handle_flag_n}, {'q', handle_flag_q}, {'r', handle_flag_r}, {'s', handle_flag_s},
|
||||||
{'v', handle_flag_v}, {1, handle_flag_1}};
|
{'v', handle_flag_v}, {'w', handle_flag_w}, {1, handle_flag_1}};
|
||||||
|
|
||||||
/// Parse the arguments for flags recognized by a specific string subcommand.
|
/// Parse the arguments for flags recognized by a specific string subcommand.
|
||||||
static int parse_opts(options_t *opts, int *optind, int n_req_args, int argc, wchar_t **argv,
|
static int parse_opts(options_t *opts, int *optind, int n_req_args, int argc, wchar_t **argv,
|
||||||
|
@ -937,6 +971,52 @@ static int string_match(parser_t &parser, io_streams_t &streams, int argc, wchar
|
||||||
return matcher->match_count() > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
return matcher->match_count() > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int string_pad(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
||||||
|
options_t opts;
|
||||||
|
opts.char_to_pad_valid = true;
|
||||||
|
opts.right_valid = true;
|
||||||
|
opts.width_valid = true;
|
||||||
|
int optind;
|
||||||
|
int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams);
|
||||||
|
if (retval != STATUS_CMD_OK) return retval;
|
||||||
|
|
||||||
|
// Pad left by default
|
||||||
|
if (!opts.right) {
|
||||||
|
opts.left = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find max width of strings and keep the inputs
|
||||||
|
size_t max_width = 0;
|
||||||
|
std::vector<wcstring> all_inputs;
|
||||||
|
|
||||||
|
arg_iterator_t aiter_width(argv, optind, streams);
|
||||||
|
while (const wcstring *arg = aiter_width.nextstr()) {
|
||||||
|
wcstring input_string = *arg;
|
||||||
|
size_t width = fish_wcswidth(input_string);
|
||||||
|
if (width > max_width) max_width = width;
|
||||||
|
all_inputs.push_back(input_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pad_width = max_width > opts.width ? max_width : opts.width;
|
||||||
|
for (auto &input : all_inputs) {
|
||||||
|
wcstring padded = input;
|
||||||
|
size_t padded_width = fish_wcswidth(padded);
|
||||||
|
if (pad_width >= padded_width) {
|
||||||
|
size_t pad = pad_width - padded_width;
|
||||||
|
if (opts.left) {
|
||||||
|
padded.insert(0, pad, opts.char_to_pad);
|
||||||
|
}
|
||||||
|
if (opts.right) {
|
||||||
|
padded.append(pad, opts.char_to_pad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
streams.out.append(padded);
|
||||||
|
streams.out.append(L'\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return STATUS_CMD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
class string_replacer_t {
|
class string_replacer_t {
|
||||||
protected:
|
protected:
|
||||||
const wchar_t *argv0;
|
const wchar_t *argv0;
|
||||||
|
@ -1368,7 +1448,7 @@ static int string_sub(parser_t &parser, io_streams_t &streams, int argc, wchar_t
|
||||||
|
|
||||||
static int string_trim(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
static int string_trim(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
||||||
options_t opts;
|
options_t opts;
|
||||||
opts.chars_valid = true;
|
opts.chars_to_trim_valid = true;
|
||||||
opts.left_valid = true;
|
opts.left_valid = true;
|
||||||
opts.right_valid = true;
|
opts.right_valid = true;
|
||||||
opts.quiet_valid = true;
|
opts.quiet_valid = true;
|
||||||
|
@ -1453,6 +1533,7 @@ string_subcommands[] = {
|
||||||
{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}, {L"collect", &string_collect},
|
{L"repeat", &string_repeat}, {L"unescape", &string_unescape}, {L"collect", &string_collect},
|
||||||
|
{L"pad", &string_pad},
|
||||||
{nullptr, nullptr}};
|
{nullptr, nullptr}};
|
||||||
|
|
||||||
/// The string builtin, for manipulating strings.
|
/// The string builtin, for manipulating strings.
|
||||||
|
|
|
@ -42,6 +42,29 @@ string length "hello, world"
|
||||||
string length -q ""; and echo not zero length; or echo zero length
|
string length -q ""; and echo not zero length; or echo zero length
|
||||||
# CHECK: zero length
|
# CHECK: zero length
|
||||||
|
|
||||||
|
string pad foo
|
||||||
|
# CHECK: foo
|
||||||
|
|
||||||
|
string pad -r -w 4 foo
|
||||||
|
# CHECK: foo
|
||||||
|
|
||||||
|
string pad -r -w 7 -c '-' foo
|
||||||
|
# CHECK: foo----
|
||||||
|
|
||||||
|
string pad --width 7 -c '=' foo
|
||||||
|
# CHECK: ====foo
|
||||||
|
|
||||||
|
echo \|(string pad --width 10 --right foo)\|
|
||||||
|
# CHECK: |foo |
|
||||||
|
|
||||||
|
string pad -w 4 -c . 🐟
|
||||||
|
# CHECK: ..🐟
|
||||||
|
|
||||||
|
string pad -c . long longer longest
|
||||||
|
# CHECK: ...long
|
||||||
|
# CHECK: .longer
|
||||||
|
# CHECK: longest
|
||||||
|
|
||||||
string sub --length 2 abcde
|
string sub --length 2 abcde
|
||||||
# CHECK: ab
|
# CHECK: ab
|
||||||
|
|
||||||
|
@ -192,7 +215,7 @@ string unescape --style=url (string escape --style=url 'a b#c"\'d')
|
||||||
# CHECK: a b#c"'d
|
# CHECK: a b#c"'d
|
||||||
|
|
||||||
string unescape --style=url (string escape --style=url \na\nb%c~d\n)
|
string unescape --style=url (string escape --style=url \na\nb%c~d\n)
|
||||||
# CHECK:
|
# CHECK:
|
||||||
# CHECK: a
|
# CHECK: a
|
||||||
# CHECK: b%c~d
|
# CHECK: b%c~d
|
||||||
|
|
||||||
|
@ -260,7 +283,7 @@ string replace -a " " _ "spaces to underscores"
|
||||||
# CHECK: spaces_to_underscores
|
# CHECK: spaces_to_underscores
|
||||||
|
|
||||||
string replace -r -a "[^\d.]+" " " "0 one two 3.14 four 5x"
|
string replace -r -a "[^\d.]+" " " "0 one two 3.14 four 5x"
|
||||||
# CHECK: 0 3.14 5
|
# CHECK: 0 3.14 5
|
||||||
|
|
||||||
string replace -r "(\w+)\s+(\w+)" "\$2 \$1 \$\$" "left right"
|
string replace -r "(\w+)\s+(\w+)" "\$2 \$1 \$\$" "left right"
|
||||||
# CHECK: right left $
|
# CHECK: right left $
|
||||||
|
@ -295,7 +318,7 @@ and echo Unexpected exit status at line (status --current-line-number)
|
||||||
# 'string match -r with empty capture groups'
|
# 'string match -r with empty capture groups'
|
||||||
string match -r '^([ugoa]*)([=+-]?)([rwx]*)$' '=r'
|
string match -r '^([ugoa]*)([=+-]?)([rwx]*)$' '=r'
|
||||||
#CHECK: =r
|
#CHECK: =r
|
||||||
#CHECK:
|
#CHECK:
|
||||||
#CHECK: =
|
#CHECK: =
|
||||||
#CHECK: r
|
#CHECK: r
|
||||||
|
|
||||||
|
@ -575,13 +598,13 @@ printf '[%s]\n' (string collect one\n\n two\n)
|
||||||
# CHECK: [two]
|
# CHECK: [two]
|
||||||
printf '[%s]\n' (string collect -N one\n\n two\n)
|
printf '[%s]\n' (string collect -N one\n\n two\n)
|
||||||
# CHECK: [one
|
# CHECK: [one
|
||||||
# CHECK:
|
# CHECK:
|
||||||
# CHECK: ]
|
# CHECK: ]
|
||||||
# CHECK: [two
|
# CHECK: [two
|
||||||
# CHECK: ]
|
# CHECK: ]
|
||||||
printf '[%s]\n' (string collect --no-trim-newlines one\n\n two\n)
|
printf '[%s]\n' (string collect --no-trim-newlines one\n\n two\n)
|
||||||
# CHECK: [one
|
# CHECK: [one
|
||||||
# CHECK:
|
# CHECK:
|
||||||
# CHECK: ]
|
# CHECK: ]
|
||||||
# CHECK: [two
|
# CHECK: [two
|
||||||
# CHECK: ]
|
# CHECK: ]
|
||||||
|
|
Loading…
Reference in a new issue