diff --git a/src/builtin_string.cpp b/src/builtin_string.cpp index 3b450d208..b525ee8e8 100644 --- a/src/builtin_string.cpp +++ b/src/builtin_string.cpp @@ -15,9 +15,12 @@ #include #include +#include #include +#include #include #include +#include #include #include "builtin.h" @@ -93,42 +96,333 @@ static const wchar_t *string_get_arg(int *argidx, wchar_t **argv, wcstring *stor return string_get_arg_argv(argidx, argv); } -static int string_escape(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - escape_flags_t flags = ESCAPE_ALL; +// This is used by the string subcommands to communicate with the option parser which flags are +// valid and get the result of parsing the command for flags. +typedef struct { //!OCLINT(too many fields) + bool all_valid = false; + bool chars_valid = false; + bool count_valid = false; + bool entire_valid = false; + bool filter_valid = false; + bool ignore_case_valid = false; + bool index_valid = false; + bool invert_valid = false; + bool left_valid = false; + bool length_valid = false; + bool max_valid = false; + bool no_newline_valid = false; + bool no_quoted_valid = false; + bool quiet_valid = false; + bool regex_valid = false; + bool right_valid = false; + bool start_valid = false; - static const wchar_t *short_options = L"n"; - static const struct woption long_options[] = {{L"no-quoted", no_argument, NULL, 'n'}, - {NULL, 0, NULL, 0}}; + bool all = false; + bool entire = false; + bool filter = false; + bool ignore_case = false; + bool index = false; + bool invert_match = false; + bool left = false; + bool no_newline = false; + bool no_quoted = false; + bool quiet = false; + bool regex = false; + bool right = false; + long count = 0; + long length = 0; + long max = 0; + long start = 0; + + const wchar_t *chars_to_trim = L" \f\n\r\t"; + const wchar_t *arg1 = NULL; + const wchar_t *arg2 = NULL; +} options_t; + +static int handle_flag_N(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->no_newline_valid) { + opts->no_newline = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_a(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->all_valid) { + opts->all = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_c(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->chars_valid) { + opts->chars_to_trim = w.woptarg; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_e(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->entire_valid) { + opts->entire = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_f(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->filter_valid) { + opts->filter = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_i(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->ignore_case_valid) { + opts->ignore_case = true; + return STATUS_CMD_OK; + } else if (opts->index_valid) { + opts->index = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_l(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->length_valid) { + opts->length = fish_wcstol(w.woptarg); + if (opts->length < 0 || opts->length == LONG_MIN || errno == ERANGE) { + string_error(streams, _(L"%ls: Invalid length 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; + } + return STATUS_CMD_OK; + } else if (opts->left_valid) { + opts->left = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_m(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->max_valid) { + opts->max = fish_wcstol(w.woptarg); + if (opts->max < 0 || errno == ERANGE) { + string_error(streams, _(L"%ls: Invalid max 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; + } + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_n(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->count_valid) { + opts->count = fish_wcstol(w.woptarg); + if (opts->count < 0 || errno == ERANGE) { + string_error(streams, _(L"%ls: Invalid count 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; + } + return STATUS_CMD_OK; + } else if (opts->index_valid) { + opts->index = true; + return STATUS_CMD_OK; + } else if (opts->no_quoted_valid) { + opts->no_quoted = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_q(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->quiet_valid) { + opts->quiet = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_r(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->regex_valid) { + opts->regex = true; + return STATUS_CMD_OK; + } else if (opts->right_valid) { + opts->right = true; + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_s(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->start_valid) { + opts->start = fish_wcstol(w.woptarg); + if (opts->start == 0 || opts->start == LONG_MIN || errno == ERANGE) { + string_error(streams, _(L"%ls: Invalid start 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; + } + return STATUS_CMD_OK; + } + string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; +} + +static int handle_flag_v(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w, + options_t *opts) { + if (opts->invert_valid) { + opts->invert_match = true; + 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 +/// 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. +static wcstring construct_short_opts(options_t *opts) { //!OCLINT(high npath complexity) + wcstring short_opts(L":"); + if (opts->all_valid) short_opts.append(L"a"); + if (opts->chars_valid) short_opts.append(L"c:"); + if (opts->count_valid) short_opts.append(L"n:"); + if (opts->entire_valid) short_opts.append(L"e"); + if (opts->filter_valid) short_opts.append(L"f"); + if (opts->ignore_case_valid) short_opts.append(L"i"); + if (opts->index_valid) short_opts.append(L"n"); + if (opts->invert_valid) short_opts.append(L"v"); + if (opts->left_valid) short_opts.append(L"l"); + if (opts->length_valid) short_opts.append(L"l:"); + if (opts->max_valid) short_opts.append(L"m:"); + if (opts->no_newline_valid) short_opts.append(L"N"); + if (opts->no_quoted_valid) short_opts.append(L"n"); + if (opts->quiet_valid) short_opts.append(L"q"); + if (opts->regex_valid) short_opts.append(L"r"); + if (opts->right_valid) short_opts.append(L"r"); + if (opts->start_valid) short_opts.append(L"s:"); + return short_opts; +} + +// 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. +static const struct woption long_options[] = { + {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"filter", no_argument, NULL, 'f'}, {L"ignore-case", no_argument, NULL, 'i'}, + {L"index", no_argument, NULL, 'n'}, {L"invert", no_argument, NULL, 'v'}, + {L"left", no_argument, NULL, 'l'}, {L"length", required_argument, NULL, 'l'}, + {L"max", required_argument, NULL, 'm'}, {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"right", no_argument, NULL, 'r'}, + {L"start", required_argument, NULL, 's'}, {NULL, 0, NULL, 0}}; + +static std::map flag_to_function = { + {'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}, + {'n', handle_flag_n}, {'q', handle_flag_q}, {'r', handle_flag_r}, {'s', handle_flag_s}, + {'v', handle_flag_v} +}; + +/// 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, + parser_t &parser, io_streams_t &streams) { + const wchar_t *cmd = argv[0]; + wcstring short_opts = construct_short_opts(opts); + const wchar_t *short_options = short_opts.c_str(); int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { //!OCLINT(too few branches) - case 'n': { - flags |= ESCAPE_NO_QUOTED; - break; - } - case '?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } + auto fn = flag_to_function.find(opt); + if (fn != flag_to_function.end()) { + int retval = fn->second(argv, parser, streams, w, opts); + if (retval != STATUS_CMD_OK) return retval; + } else if (opt == ':') { + string_error(streams, STRING_ERR_MISSING, cmd); + return STATUS_INVALID_ARGS; + } else if (opt == '?') { + string_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); + return STATUS_INVALID_ARGS; + } else { + DIE("unexpected retval from wgetopt_long"); } } - int i = w.woptind; - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); + *optind = w.woptind; + + // If the caller requires one or two mandatory args deal with that here. + if (n_req_args) { + opts->arg1 = string_get_arg_argv(optind, argv); + if (!opts->arg1) { + string_error(streams, STRING_ERR_MISSING, cmd); + return STATUS_INVALID_ARGS; + } + } + if (n_req_args > 1) { + opts->arg2 = string_get_arg_argv(optind, argv); + if (!opts->arg2) { + string_error(streams, STRING_ERR_MISSING, cmd); + return STATUS_INVALID_ARGS; + } + } + + // At this point we should not have optional args and be reading args from stdin. + if (string_args_from_stdin(streams) && argc > *optind) { + string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd); return STATUS_INVALID_ARGS; } + return STATUS_CMD_OK; +} + +static int string_escape(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { + options_t opts; + opts.no_quoted_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; + + escape_flags_t flags = ESCAPE_ALL; + if (opts.no_quoted) flags |= ESCAPE_NO_QUOTED; + int nesc = 0; wcstring storage; const wchar_t *arg; - while ((arg = string_get_arg(&i, argv, &storage, streams)) != 0) { + while ((arg = string_get_arg(&optind, argv, &storage, streams)) != 0) { streams.out.append(escape_string(arg, flags)); streams.out.append(L'\n'); nesc++; @@ -138,48 +432,18 @@ static int string_escape(parser_t &parser, io_streams_t &streams, int argc, wcha } static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - bool quiet = false; - - static const wchar_t *short_options = L"q"; - static const struct woption long_options[] = {{L"quiet", no_argument, NULL, 'q'}, - {NULL, 0, NULL, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { //!OCLINT(too few branches) - case L'q': { - quiet = true; - break; - } - case L'?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - const wchar_t *sep; - if ((sep = string_get_arg_argv(&i, argv)) == 0) { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } - - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } + options_t opts; + opts.quiet_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 1, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; + const wchar_t *sep = opts.arg1; int nargs = 0; const wchar_t *arg; wcstring storage; - while ((arg = string_get_arg(&i, argv, &storage, streams)) != 0) { - if (!quiet) { + while ((arg = string_get_arg(&optind, argv, &storage, streams)) != 0) { + if (!opts.quiet) { if (nargs > 0) { streams.out.append(sep); } @@ -187,7 +451,7 @@ static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_ } nargs++; } - if (nargs > 0 && !quiet) { + if (nargs > 0 && !opts.quiet) { streams.out.push_back(L'\n'); } @@ -195,46 +459,21 @@ static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_ } static int string_length(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - bool quiet = false; - - static const wchar_t *short_options = L"q"; - static const struct woption long_options[] = {{L"quiet", no_argument, NULL, 'q'}, - {NULL, 0, NULL, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { //!OCLINT(too few branches) - case L'q': { - quiet = true; - break; - } - case L'?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } + options_t opts; + opts.quiet_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; const wchar_t *arg; int nnonempty = 0; wcstring storage; - while ((arg = string_get_arg(&i, argv, &storage, streams)) != 0) { + while ((arg = string_get_arg(&optind, argv, &storage, streams)) != 0) { size_t n = wcslen(arg); if (n > 0) { nnonempty++; } - if (!quiet) { + if (!opts.quiet) { streams.out.append(to_string(n)); streams.out.append(L'\n'); } @@ -243,31 +482,14 @@ static int string_length(parser_t &parser, io_streams_t &streams, int argc, wcha return nnonempty > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR; } -struct match_options_t { - bool all; - bool entire; - bool ignore_case; - bool index; - bool invert_match; - bool quiet; - - match_options_t() - : all(false), - entire(false), - ignore_case(false), - index(false), - invert_match(false), - quiet(false) {} -}; - class string_matcher_t { protected: - match_options_t opts; + options_t opts; io_streams_t &streams; int total_matched; public: - string_matcher_t(const match_options_t &opts_, io_streams_t &streams_) + string_matcher_t(const options_t &opts_, io_streams_t &streams_) : opts(opts_), streams(streams_), total_matched(0) {} virtual ~string_matcher_t() {} @@ -280,8 +502,8 @@ class wildcard_matcher_t : public string_matcher_t { wcstring wcpattern; public: - wildcard_matcher_t(const wchar_t * /*argv0*/, const wchar_t *pattern, - const match_options_t &opts, io_streams_t &streams) + wildcard_matcher_t(const wchar_t * /*argv0*/, const wchar_t *pattern, const options_t &opts, + io_streams_t &streams) : string_matcher_t(opts, streams), wcpattern(parse_util_unescape_wildcards(pattern)) { if (opts.ignore_case) { for (size_t i = 0; i < wcpattern.length(); i++) { @@ -292,38 +514,38 @@ class wildcard_matcher_t : public string_matcher_t { if (wcpattern.front() != ANY_STRING) wcpattern.insert(0, 1, ANY_STRING); if (wcpattern.back() != ANY_STRING) wcpattern.push_back(ANY_STRING); } - } - - virtual ~wildcard_matcher_t() {} - - bool report_matches(const wchar_t *arg) { - // Note: --all is a no-op for glob matching since the pattern is always matched against the - // entire argument. - bool match; - - if (opts.ignore_case) { - wcstring s = arg; - for (size_t i = 0; i < s.length(); i++) { - s[i] = towlower(s[i]); } - match = wildcard_match(s, wcpattern, false); - } else { - match = wildcard_match(arg, wcpattern, false); - } - if (match ^ opts.invert_match) { - total_matched++; - if (!opts.quiet) { - if (opts.index) { - streams.out.append_format(L"1 %lu\n", wcslen(arg)); + virtual ~wildcard_matcher_t() {} + + bool report_matches(const wchar_t *arg) { + // Note: --all is a no-op for glob matching since the pattern is always matched + // against the entire argument. + bool match; + + if (opts.ignore_case) { + wcstring s = arg; + for (size_t i = 0; i < s.length(); i++) { + s[i] = towlower(s[i]); + } + match = wildcard_match(s, wcpattern, false); } else { - streams.out.append(arg); - streams.out.append(L'\n'); + match = wildcard_match(arg, wcpattern, false); } - } + if (match ^ opts.invert_match) { + total_matched++; + + if (!opts.quiet) { + if (opts.index) { + streams.out.append_format(L"1 %lu\n", wcslen(arg)); + } else { + streams.out.append(arg); + streams.out.append(L'\n'); + } + } + } + return true; } - return true; - } }; static wcstring pcre2_strerror(int err_code) { @@ -428,7 +650,7 @@ class pcre2_matcher_t : public string_matcher_t { } public: - pcre2_matcher_t(const wchar_t *argv0_, const wchar_t *pattern, const match_options_t &opts, + pcre2_matcher_t(const wchar_t *argv0_, const wchar_t *pattern, const options_t &opts, io_streams_t &streams) : string_matcher_t(opts, streams), argv0(argv0_), @@ -496,58 +718,19 @@ class pcre2_matcher_t : public string_matcher_t { static int string_match(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { wchar_t *cmd = argv[0]; - bool regex = false; - match_options_t opts; - static const wchar_t *short_options = L"aeinqrv"; - static const struct woption long_options[] = { - {L"all", no_argument, NULL, 'a'}, {L"entire", no_argument, NULL, 'e'}, - {L"ignore-case", no_argument, NULL, 'i'}, {L"index", no_argument, NULL, 'n'}, - {L"invert", no_argument, NULL, 'v'}, {L"quiet", no_argument, NULL, 'q'}, - {L"regex", no_argument, NULL, 'r'}, {NULL, 0, NULL, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { - case 'a': { - opts.all = true; - break; - } - case 'e': { - opts.entire = true; - break; - } - case 'i': { - opts.ignore_case = true; - break; - } - case 'n': { - opts.index = true; - break; - } - case 'v': { - opts.invert_match = true; - break; - } - case 'q': { - opts.quiet = true; - break; - } - case 'r': { - regex = true; - break; - } - case '?': { - string_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } + options_t opts; + opts.all_valid = true; + opts.entire_valid = true; + opts.ignore_case_valid = true; + opts.invert_valid = true; + opts.quiet_valid = true; + opts.regex_valid = true; + opts.index_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 1, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; + const wchar_t *pattern = opts.arg1; if (opts.entire && opts.index) { streams.err.append_format(BUILTIN_ERR_COMBO2, cmd, @@ -555,20 +738,8 @@ static int string_match(parser_t &parser, io_streams_t &streams, int argc, wchar return STATUS_INVALID_ARGS; } - int i = w.woptind; - const wchar_t *pattern; - if ((pattern = string_get_arg_argv(&i, argv)) == 0) { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } - - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } - std::unique_ptr matcher; - if (regex) { + if (opts.regex) { matcher = make_unique(cmd, pattern, opts, streams); } else { matcher = make_unique(cmd, pattern, opts, streams); @@ -576,7 +747,7 @@ static int string_match(parser_t &parser, io_streams_t &streams, int argc, wchar const wchar_t *arg; wcstring storage; - while ((arg = string_get_arg(&i, argv, &storage, streams)) != 0) { + while ((arg = string_get_arg(&optind, argv, &storage, streams)) != 0) { if (!matcher->report_matches(arg)) { return STATUS_INVALID_ARGS; } @@ -585,24 +756,15 @@ 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; } -struct replace_options_t { - bool all; - bool filter; - bool ignore_case; - bool quiet; - - replace_options_t() : all(false), filter(false), ignore_case(false), quiet(false) {} -}; - class string_replacer_t { protected: const wchar_t *argv0; - replace_options_t opts; + options_t opts; int total_replaced; io_streams_t &streams; public: - string_replacer_t(const wchar_t *argv0_, const replace_options_t &opts_, io_streams_t &streams_) + string_replacer_t(const wchar_t *argv0_, const options_t &opts_, io_streams_t &streams_) : argv0(argv0_), opts(opts_), total_replaced(0), streams(streams_) {} virtual ~string_replacer_t() {} @@ -617,7 +779,7 @@ class literal_replacer_t : public string_replacer_t { public: literal_replacer_t(const wchar_t *argv0, const wchar_t *pattern_, const wchar_t *replacement_, - const replace_options_t &opts, io_streams_t &streams) + const options_t &opts, io_streams_t &streams) : string_replacer_t(argv0, opts, streams), pattern(pattern_), replacement(replacement_), @@ -648,7 +810,7 @@ class regex_replacer_t : public string_replacer_t { public: regex_replacer_t(const wchar_t *argv0, const wchar_t *pattern, const wchar_t *replacement_, - const replace_options_t &opts, io_streams_t &streams) + const options_t &opts, io_streams_t &streams) : string_replacer_t(argv0, opts, streams), regex(argv0, pattern, opts.ignore_case, streams), replacement(interpret_escapes(replacement_)) {} @@ -741,79 +903,29 @@ bool regex_replacer_t::replace_matches(const wchar_t *arg) { } static int string_replace(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - bool regex = false; - replace_options_t opts; + options_t opts; + opts.all_valid = true; + opts.filter_valid = true; + opts.ignore_case_valid = true; + opts.quiet_valid = true; + opts.regex_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 2, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; - static const wchar_t *short_options = L"afiqr"; - static const struct woption long_options[] = { - {L"all", no_argument, NULL, 'a'}, {L"filter", no_argument, NULL, 'f'}, - {L"ignore-case", no_argument, NULL, 'i'}, {L"quiet", no_argument, NULL, 'q'}, - {L"regex", no_argument, 0, 'r'}, {NULL, 0, NULL, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { - case 'a': { - opts.all = true; - break; - } - case 'f': { - opts.filter = true; - break; - } - case 'i': { - opts.ignore_case = true; - break; - } - case 'q': { - opts.quiet = true; - break; - } - case 'r': { - regex = true; - break; - } - case '?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - const wchar_t *pattern, *replacement; - if ((pattern = string_get_arg_argv(&i, argv)) == 0) { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } - if ((replacement = string_get_arg_argv(&i, argv)) == 0) { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } - - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } + const wchar_t *pattern = opts.arg1; + const wchar_t *replacement = opts.arg2; std::unique_ptr replacer; - if (regex) { + if (opts.regex) { replacer = make_unique(argv[0], pattern, replacement, opts, streams); } else { replacer = make_unique(argv[0], pattern, replacement, opts, streams); } - const wchar_t *arg; wcstring storage; - while ((arg = string_get_arg(&i, argv, &storage, streams)) != 0) { - if (!replacer->replace_matches(arg)) { - return STATUS_INVALID_ARGS; - } + while (const wchar_t *arg = string_get_arg(&optind, argv, &storage, streams)) { + if (!replacer->replace_matches(arg)) return STATUS_INVALID_ARGS; } return replacer->replace_count() > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR; @@ -849,89 +961,43 @@ void split_about(ITER haystack_start, ITER haystack_end, ITER needle_start, ITER } static int string_split(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - long max = LONG_MAX; - bool quiet = false; - bool right = false; + options_t opts; + opts.quiet_valid = true; + opts.right_valid = true; + opts.max_valid = true; + opts.max = LONG_MAX; + int optind; + int retval = parse_opts(&opts, &optind, 1, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; - static const wchar_t *short_options = L":m:qr"; - static const struct woption long_options[] = {{L"max", required_argument, 0, 'm'}, - {L"quiet", no_argument, 0, 'q'}, - {L"right", no_argument, 0, 'r'}, - {0, 0, 0, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { - case 'm': { - max = fish_wcstol(w.woptarg); - if (errno) { - string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg); - return STATUS_INVALID_ARGS; - } - break; - } - case 'q': { - quiet = true; - break; - } - case 'r': { - right = true; - break; - } - case ':': { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } - case '?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - const wchar_t *sep; - if ((sep = string_get_arg_argv(&i, argv)) == NULL) { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } + const wchar_t *sep = opts.arg1; const wchar_t *sep_end = sep + wcslen(sep); - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } - wcstring_list_t splits; size_t arg_count = 0; wcstring storage; const wchar_t *arg; - while ((arg = string_get_arg(&i, argv, &storage, streams)) != 0) { + while ((arg = string_get_arg(&optind, argv, &storage, streams)) != 0) { const wchar_t *arg_end = arg + wcslen(arg); - if (right) { + if (opts.right) { typedef std::reverse_iterator reverser; split_about(reverser(arg_end), reverser(arg), reverser(sep_end), reverser(sep), &splits, - max); + opts.max); } else { - split_about(arg, arg_end, sep, sep_end, &splits, max); + split_about(arg, arg_end, sep, sep_end, &splits, opts.max); } arg_count++; } // If we are from the right, split_about gave us reversed strings, in reversed order! - if (right) { + if (opts.right) { for (size_t j = 0; j < splits.size(); j++) { std::reverse(splits[j].begin(), splits[j].end()); } std::reverse(splits.begin(), splits.end()); } - if (!quiet) { + if (!opts.quiet) { for (wcstring_list_t::const_iterator si = splits.begin(); si != splits.end(); ++si) { streams.out.append(*si); streams.out.append(L'\n'); @@ -965,90 +1031,30 @@ static wcstring wcsrepeat_until(const wcstring &to_repeat, size_t max) { } static int string_repeat(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - size_t count = 0; - size_t max = 0; - bool newline = true; - bool quiet = false; - - static const wchar_t *short_options = L":n:m:Nq"; - static const struct woption long_options[] = {{L"count", required_argument, NULL, 'n'}, - {L"max", required_argument, NULL, 'm'}, - {L"no-newline", no_argument, NULL, 'N'}, - {L"quiet", no_argument, NULL, 'q'}, - {NULL, 0, NULL, 0}}; - - 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 STATUS_INVALID_ARGS; - } else if (errno) { - string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg); - return STATUS_INVALID_ARGS; - } - count = static_cast(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 STATUS_INVALID_ARGS; - } else if (errno) { - string_error(streams, BUILTIN_ERR_NOT_NUMBER, argv[0], w.woptarg); - return STATUS_INVALID_ARGS; - } - max = static_cast(lmax); - break; - } - case 'N': { - newline = false; - break; - } - case 'q': { - quiet = true; - break; - } - case ':': { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } - case '?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } + options_t opts; + opts.count_valid = true; + opts.max_valid = true; + opts.quiet_valid = true; + opts.no_newline_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; const wchar_t *to_repeat; wcstring storage; bool is_empty = true; - if ((to_repeat = string_get_arg(&i, argv, &storage, streams)) != NULL && *to_repeat) { + if ((to_repeat = string_get_arg(&optind, argv, &storage, streams)) != NULL && *to_repeat) { 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); + const bool limit_repeat = + (opts.max > 0 && word.length() * opts.count > (size_t)opts.max) || !opts.count; + const wcstring repeated = + limit_repeat ? wcsrepeat_until(word, opts.max) : wcsrepeat(word, opts.count); is_empty = repeated.empty(); - if (!quiet && !is_empty) { + if (!opts.quiet && !is_empty) { streams.out.append(repeated); - if (newline) streams.out.append(L"\n"); + if (!opts.no_newline) streams.out.append(L"\n"); } } @@ -1056,94 +1062,40 @@ static int string_repeat(parser_t &parser, io_streams_t &streams, int argc, wcha } static int string_sub(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - long start = 0; - long length = -1; - bool quiet = false; - - static const wchar_t *short_options = L":l:qs:"; - static const struct woption long_options[] = {{L"length", required_argument, NULL, 'l'}, - {L"quiet", no_argument, NULL, 'q'}, - {L"start", required_argument, NULL, 's'}, - {NULL, 0, NULL, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { - case 'l': { - length = fish_wcstol(w.woptarg); - if (length < 0 || errno == ERANGE) { - string_error(streams, _(L"%ls: Invalid length 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; - } - break; - } - case 'q': { - quiet = true; - break; - } - case 's': { - start = fish_wcstol(w.woptarg); - if (start == 0 || start == LONG_MIN || errno == ERANGE) { - string_error(streams, _(L"%ls: Invalid start 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; - } - break; - } - case ':': { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } - case '?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } + options_t opts; + opts.length_valid = true; + opts.quiet_valid = true; + opts.start_valid = true; + opts.length = -1; + int optind; + int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; int nsub = 0; const wchar_t *arg; wcstring storage; - while ((arg = string_get_arg(&i, argv, &storage, streams)) != NULL) { + while ((arg = string_get_arg(&optind, argv, &storage, streams)) != NULL) { typedef wcstring::size_type size_type; size_type pos = 0; size_type count = wcstring::npos; wcstring s(arg); - if (start > 0) { - pos = static_cast(start - 1); - } else if (start < 0) { - assert(start != LONG_MIN); // checked above - size_type n = static_cast(-start); + if (opts.start > 0) { + pos = static_cast(opts.start - 1); + } else if (opts.start < 0) { + assert(opts.start != LONG_MIN); // checked above + size_type n = static_cast(-opts.start); pos = n > s.length() ? 0 : s.length() - n; } if (pos > s.length()) { pos = s.length(); } - if (length >= 0) { - count = static_cast(length); + if (opts.length >= 0) { + count = static_cast(opts.length); } // Note that std::string permits count to extend past end of string. - if (!quiet) { + if (!opts.quiet) { streams.out.append(s.substr(pos, count)); streams.out.append(L'\n'); } @@ -1154,62 +1106,18 @@ 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) { - bool do_left = 0, do_right = 0; - bool quiet = false; - wcstring chars_to_trim = L" \f\n\r\t"; - - static const wchar_t *short_options = L":c:lqr"; - static const struct woption long_options[] = {{L"chars", required_argument, NULL, 'c'}, - {L"left", no_argument, NULL, 'l'}, - {L"quiet", no_argument, NULL, 'q'}, - {L"right", no_argument, NULL, 'r'}, - {NULL, 0, NULL, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { - case 'c': { - chars_to_trim = w.woptarg; - break; - } - case 'l': { - do_left = true; - break; - } - case 'q': { - quiet = true; - break; - } - case 'r': { - do_right = true; - break; - } - case ':': { - string_error(streams, STRING_ERR_MISSING, argv[0]); - return STATUS_INVALID_ARGS; - } - case '?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } + options_t opts; + opts.chars_valid = true; + opts.left_valid = true; + opts.right_valid = true; + opts.quiet_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; // If neither left or right is specified, we do both. - if (!do_left && !do_right) { - do_left = true; - do_right = true; + if (!opts.left && !opts.right) { + opts.left = opts.right = true; } const wchar_t *arg; @@ -1217,22 +1125,22 @@ static int string_trim(parser_t &parser, io_streams_t &streams, int argc, wchar_ wcstring argstr; wcstring storage; - while ((arg = string_get_arg(&i, argv, &storage, streams)) != 0) { + while ((arg = string_get_arg(&optind, argv, &storage, streams)) != 0) { argstr = arg; // Begin and end are respectively the first character to keep on the left, and first // character to trim on the right. The length is thus end - start. size_t begin = 0, end = argstr.size(); - if (do_right) { - size_t last_to_keep = argstr.find_last_not_of(chars_to_trim); + if (opts.right) { + size_t last_to_keep = argstr.find_last_not_of(opts.chars_to_trim); end = (last_to_keep == wcstring::npos) ? 0 : last_to_keep + 1; } - if (do_left) { - size_t first_to_keep = argstr.find_first_not_of(chars_to_trim); + if (opts.left) { + size_t first_to_keep = argstr.find_first_not_of(opts.chars_to_trim); begin = (first_to_keep == wcstring::npos ? end : first_to_keep); } assert(begin <= end && end <= argstr.size()); ntrim += argstr.size() - (end - begin); - if (!quiet) { + if (!opts.quiet) { streams.out.append(wcstring(argstr, begin, end - begin)); streams.out.append(L'\n'); } @@ -1241,45 +1149,21 @@ static int string_trim(parser_t &parser, io_streams_t &streams, int argc, wchar_ return ntrim > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR; } +/// Implementation of `string lower`. static int string_lower(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - bool quiet = false; - - static const wchar_t *short_options = L"q"; - static const struct woption long_options[] = {{L"quiet", no_argument, NULL, 'q'}, - {NULL, 0, NULL, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { //!OCLINT(too few branches) - case L'q': { - quiet = true; - break; - } - case L'?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } + options_t opts; + opts.quiet_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; int n_transformed = 0; wcstring storage; - while (const wchar_t *arg = string_get_arg(&i, argv, &storage, streams)) { + while (const wchar_t *arg = string_get_arg(&optind, argv, &storage, streams)) { wcstring transformed(arg); std::transform(transformed.begin(), transformed.end(), transformed.begin(), std::towlower); if (wcscmp(transformed.c_str(), arg)) n_transformed++; - if (!quiet) { + if (!opts.quiet) { streams.out.append(transformed); streams.out.append(L'\n'); } @@ -1288,45 +1172,21 @@ static int string_lower(parser_t &parser, io_streams_t &streams, int argc, wchar return n_transformed > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR; } +/// Implementation of `string upper`. static int string_upper(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { - bool quiet = false; - - static const wchar_t *short_options = L"q"; - static const struct woption long_options[] = {{L"quiet", no_argument, NULL, 'q'}, - {NULL, 0, NULL, 0}}; - - int opt; - wgetopter_t w; - while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { - switch (opt) { //!OCLINT(too few branches) - case L'q': { - quiet = true; - break; - } - case L'?': { - string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]); - return STATUS_INVALID_ARGS; - } - default: { - DIE("unexpected retval from wgetopt_long"); - break; - } - } - } - - int i = w.woptind; - if (string_args_from_stdin(streams) && argc > i) { - string_error(streams, BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]); - return STATUS_INVALID_ARGS; - } + options_t opts; + opts.quiet_valid = true; + int optind; + int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams); + if (retval != STATUS_CMD_OK) return retval; int n_transformed = 0; wcstring storage; - while (const wchar_t *arg = string_get_arg(&i, argv, &storage, streams)) { + while (const wchar_t *arg = string_get_arg(&optind, argv, &storage, streams)) { wcstring transformed(arg); std::transform(transformed.begin(), transformed.end(), transformed.begin(), std::towupper); if (wcscmp(transformed.c_str(), arg)) n_transformed++; - if (!quiet) { + if (!opts.quiet) { streams.out.append(transformed); streams.out.append(L'\n'); }