2016-04-20 10:49:15 +08:00
|
|
|
// Implementation of the string builtin.
|
2016-04-21 14:00:54 +08:00
|
|
|
#include "config.h"
|
2015-09-13 04:36:22 +08:00
|
|
|
|
2015-09-13 03:59:40 +08:00
|
|
|
#define PCRE2_CODE_UNIT_WIDTH WCHAR_T_BITS
|
|
|
|
#ifdef _WIN32
|
|
|
|
#define PCRE2_STATIC
|
|
|
|
#endif
|
2016-04-20 10:49:15 +08:00
|
|
|
#include <errno.h>
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <limits.h>
|
|
|
|
#include <stdarg.h>
|
2017-02-13 12:24:22 +08:00
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdint.h>
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <stdlib.h>
|
2019-03-13 05:06:01 +08:00
|
|
|
#include <cwchar>
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <wctype.h>
|
2017-02-13 12:24:22 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
#include <algorithm>
|
2017-06-12 02:49:59 +08:00
|
|
|
#include <cwctype>
|
2016-04-20 10:49:15 +08:00
|
|
|
#include <iterator>
|
2017-02-11 10:47:02 +08:00
|
|
|
#include <memory>
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <string>
|
2017-08-20 00:55:06 +08:00
|
|
|
#include <unordered_map>
|
2017-06-12 02:49:59 +08:00
|
|
|
#include <utility>
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <vector>
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2015-09-13 04:36:22 +08:00
|
|
|
#include "builtin.h"
|
|
|
|
#include "common.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
|
|
|
#include "io.h"
|
2015-09-13 04:36:22 +08:00
|
|
|
#include "parse_util.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "pcre2.h"
|
2017-07-27 21:05:35 +08:00
|
|
|
#include "wcstringutil.h"
|
2015-09-13 04:36:22 +08:00
|
|
|
#include "wgetopt.h"
|
2015-09-13 03:59:40 +08:00
|
|
|
#include "wildcard.h"
|
2016-04-21 14:00:54 +08:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
|
|
|
|
|
|
|
class parser_t;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
#define STRING_ERR_MISSING _(L"%ls: Expected argument\n")
|
2015-09-13 04:36:22 +08:00
|
|
|
|
2017-12-19 00:26:33 +08:00
|
|
|
// How many bytes we read() at once.
|
|
|
|
// Bash uses 128 here, so we do too (see READ_CHUNK_SIZE).
|
|
|
|
// This should be about the size of a line.
|
|
|
|
#define STRING_CHUNK_SIZE 128
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static void string_error(io_streams_t &streams, const wchar_t *fmt, ...) {
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append(L"string ");
|
2015-09-13 03:59:40 +08:00
|
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append_formatv(fmt, va);
|
2015-09-13 03:59:40 +08:00
|
|
|
va_end(va);
|
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static void string_unknown_option(parser_t &parser, io_streams_t &streams, const wchar_t *subcmd,
|
|
|
|
const wchar_t *opt) {
|
2015-09-22 02:24:49 +08:00
|
|
|
string_error(streams, BUILTIN_ERR_UNKNOWN, subcmd, opt);
|
|
|
|
builtin_print_help(parser, streams, L"string", streams.err);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
// We read from stdin if we are the second or later process in a pipeline.
|
|
|
|
static bool string_args_from_stdin(const io_streams_t &streams) {
|
2015-09-26 05:17:53 +08:00
|
|
|
return streams.stdin_is_directly_redirected;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-12-22 04:42:57 +08:00
|
|
|
static const wchar_t *string_get_arg_argv(int *argidx, const wchar_t *const *argv) {
|
|
|
|
return argv && argv[*argidx] ? argv[(*argidx)++] : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// A helper type for extracting arguments from either argv or stdin.
|
|
|
|
namespace {
|
|
|
|
class arg_iterator_t {
|
|
|
|
// The list of arguments passed to the string builtin.
|
|
|
|
const wchar_t *const *argv_;
|
|
|
|
// If using argv, index of the next argument to return.
|
|
|
|
int argidx_;
|
|
|
|
// If not using argv, a string to store bytes that have been read but not yet returned.
|
|
|
|
std::string buffer_;
|
2019-02-27 02:50:53 +08:00
|
|
|
// If set, when reading from a stream, split on newlines.
|
|
|
|
const bool split_;
|
2017-12-22 04:42:57 +08:00
|
|
|
// Backing storage for the next() string.
|
|
|
|
wcstring storage_;
|
|
|
|
const io_streams_t &streams_;
|
|
|
|
|
2018-05-30 12:11:50 +08:00
|
|
|
/// Reads the next argument from stdin, returning true if an argument was produced and false if
|
|
|
|
/// not. On true, the string is stored in storage_.
|
|
|
|
bool get_arg_stdin() {
|
2017-12-22 04:42:57 +08:00
|
|
|
assert(string_args_from_stdin(streams_) && "should not be reading from stdin");
|
2019-02-27 02:50:53 +08:00
|
|
|
// Read in chunks from fd until buffer has a line (or the end if split_ is unset).
|
2017-12-22 04:42:57 +08:00
|
|
|
size_t pos;
|
2019-02-27 02:50:53 +08:00
|
|
|
while (!split_ || (pos = buffer_.find('\n')) == std::string::npos) {
|
2017-12-22 04:42:57 +08:00
|
|
|
char buf[STRING_CHUNK_SIZE];
|
|
|
|
long n = read_blocked(streams_.stdin_fd, buf, STRING_CHUNK_SIZE);
|
|
|
|
if (n == 0) {
|
|
|
|
// If we still have buffer contents, flush them,
|
2018-05-30 12:11:50 +08:00
|
|
|
// in case there was no trailing sep.
|
|
|
|
if (buffer_.empty()) return false;
|
2017-12-22 04:42:57 +08:00
|
|
|
storage_ = str2wcstring(buffer_);
|
|
|
|
buffer_.clear();
|
2018-05-30 12:11:50 +08:00
|
|
|
return true;
|
2017-12-22 04:42:57 +08:00
|
|
|
}
|
|
|
|
if (n == -1) {
|
|
|
|
// Some error happened. We can't do anything about it,
|
|
|
|
// so ignore it.
|
|
|
|
// (read_blocked already retries for EAGAIN and EINTR)
|
|
|
|
storage_ = str2wcstring(buffer_);
|
|
|
|
buffer_.clear();
|
2018-05-30 12:11:50 +08:00
|
|
|
return false;
|
2017-12-22 04:42:57 +08:00
|
|
|
}
|
|
|
|
buffer_.append(buf, n);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2016-04-20 10:49:15 +08:00
|
|
|
|
2018-05-30 12:11:50 +08:00
|
|
|
// Split the buffer on the sep and return the first part.
|
2017-12-22 04:42:57 +08:00
|
|
|
storage_ = str2wcstring(buffer_, pos);
|
|
|
|
buffer_.erase(0, pos + 1);
|
2018-05-30 12:11:50 +08:00
|
|
|
return true;
|
2017-12-22 04:42:57 +08:00
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-12-22 04:42:57 +08:00
|
|
|
public:
|
2018-05-30 12:11:50 +08:00
|
|
|
arg_iterator_t(const wchar_t *const *argv, int argidx, const io_streams_t &streams,
|
2019-02-27 02:50:53 +08:00
|
|
|
bool split = true)
|
|
|
|
: argv_(argv), argidx_(argidx), split_(split), streams_(streams) {}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-01-02 23:00:38 +08:00
|
|
|
const wcstring *nextstr() {
|
|
|
|
if (string_args_from_stdin(streams_)) {
|
|
|
|
return get_arg_stdin() ? &storage_ : NULL;
|
|
|
|
}
|
|
|
|
if (auto arg = string_get_arg_argv(&argidx_, argv_)) {
|
|
|
|
storage_ = arg;
|
|
|
|
return &storage_;
|
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
2017-12-22 04:42:57 +08:00
|
|
|
};
|
2018-09-28 10:28:39 +08:00
|
|
|
} // namespace
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
// 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;
|
2017-06-21 12:55:16 +08:00
|
|
|
bool style_valid = false;
|
2018-03-29 21:12:08 +08:00
|
|
|
bool no_empty_valid = false;
|
2017-06-12 02:49:59 +08:00
|
|
|
|
|
|
|
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;
|
2018-03-29 21:12:08 +08:00
|
|
|
bool no_empty = false;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
long count = 0;
|
|
|
|
long length = 0;
|
|
|
|
long max = 0;
|
|
|
|
long start = 0;
|
2016-10-30 08:25:48 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
const wchar_t *chars_to_trim = L" \f\n\r\t";
|
|
|
|
const wchar_t *arg1 = NULL;
|
|
|
|
const wchar_t *arg2 = NULL;
|
2017-06-21 12:55:16 +08:00
|
|
|
|
|
|
|
escape_string_style_t escape_style = STRING_STYLE_SCRIPT;
|
2017-06-12 02:49:59 +08:00
|
|
|
} options_t;
|
|
|
|
|
2017-06-21 12:55:16 +08:00
|
|
|
/// This handles the `--style=xxx` flag.
|
|
|
|
static int handle_flag_1(wchar_t **argv, parser_t &parser, io_streams_t &streams, wgetopter_t &w,
|
|
|
|
options_t *opts) {
|
|
|
|
const wchar_t *cmd = argv[0];
|
|
|
|
|
|
|
|
if (opts->style_valid) {
|
2019-03-13 05:06:01 +08:00
|
|
|
if (std::wcscmp(w.woptarg, L"script") == 0) {
|
2017-06-21 12:55:16 +08:00
|
|
|
opts->escape_style = STRING_STYLE_SCRIPT;
|
2019-03-13 05:06:01 +08:00
|
|
|
} else if (std::wcscmp(w.woptarg, L"url") == 0) {
|
2017-06-21 12:55:16 +08:00
|
|
|
opts->escape_style = STRING_STYLE_URL;
|
2019-03-13 05:06:01 +08:00
|
|
|
} else if (std::wcscmp(w.woptarg, L"var") == 0) {
|
2017-06-21 12:55:16 +08:00
|
|
|
opts->escape_style = STRING_STYLE_VAR;
|
2019-03-13 05:06:01 +08:00
|
|
|
} else if (std::wcscmp(w.woptarg, L"regex") == 0) {
|
2018-11-17 10:21:05 +08:00
|
|
|
opts->escape_style = STRING_STYLE_REGEX;
|
2017-06-24 14:19:09 +08:00
|
|
|
} else {
|
2017-06-21 12:55:16 +08:00
|
|
|
string_error(streams, _(L"%ls: Invalid escape style '%ls'\n"), cmd, w.woptarg);
|
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
string_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
|
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2017-06-12 02:49:59 +08:00
|
|
|
return STATUS_CMD_OK;
|
|
|
|
} else if (opts->left_valid) {
|
|
|
|
opts->left = true;
|
|
|
|
return STATUS_CMD_OK;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2017-06-12 02:49:59 +08:00
|
|
|
string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2017-06-12 02:49:59 +08:00
|
|
|
string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2018-03-29 21:12:08 +08:00
|
|
|
} else if (opts->no_empty_valid) {
|
|
|
|
opts->no_empty = true;
|
|
|
|
return STATUS_CMD_OK;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2017-06-12 02:49:59 +08:00
|
|
|
string_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
|
|
|
}
|
2016-10-30 08:25:48 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
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:");
|
2018-03-29 21:12:08 +08:00
|
|
|
if (opts->no_empty_valid) short_opts.append(L"n");
|
2017-06-12 02:49:59 +08:00
|
|
|
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.
|
2019-02-07 19:45:17 +08:00
|
|
|
// Remember: adjust share/functions/string.fish when `string` options change
|
2018-09-28 10:28:39 +08:00
|
|
|
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-empty", 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"right", no_argument, NULL, 'r'}, {L"start", required_argument, NULL, 's'},
|
|
|
|
{L"style", required_argument, NULL, 1}, {NULL, 0, NULL, 0}};
|
2017-06-12 02:49:59 +08:00
|
|
|
|
2018-10-02 00:59:22 +08:00
|
|
|
static const std::unordered_map<char, decltype(*handle_flag_N)> flag_to_function = {
|
2017-06-12 02:49:59 +08:00
|
|
|
{'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},
|
2018-09-28 10:28:39 +08:00
|
|
|
{'v', handle_flag_v}, {1, handle_flag_1}};
|
2016-10-30 08:25:48 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
/// 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();
|
2017-06-09 11:56:24 +08:00
|
|
|
int opt;
|
|
|
|
wgetopter_t w;
|
2017-06-10 12:41:16 +08:00
|
|
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
2017-06-12 02:49:59 +08:00
|
|
|
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");
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
*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;
|
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
// 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);
|
2017-05-02 15:39:50 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|
|
|
|
|
2018-01-11 23:09:35 +08:00
|
|
|
static int string_escape(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
|
|
|
options_t opts;
|
|
|
|
opts.no_quoted_valid = true;
|
|
|
|
opts.style_valid = true;
|
|
|
|
int optind;
|
|
|
|
int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams);
|
|
|
|
if (retval != STATUS_CMD_OK) return retval;
|
2017-06-21 12:55:16 +08:00
|
|
|
|
2018-01-11 23:09:35 +08:00
|
|
|
// Currently, only the script style supports options.
|
|
|
|
// Ignore them for other styles for now.
|
2017-06-21 12:55:16 +08:00
|
|
|
escape_flags_t flags = 0;
|
2018-01-11 23:09:35 +08:00
|
|
|
if (opts.escape_style == STRING_STYLE_SCRIPT) {
|
|
|
|
flags = ESCAPE_ALL;
|
|
|
|
if (opts.no_quoted) flags |= ESCAPE_NO_QUOTED;
|
2017-06-21 12:55:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int nesc = 0;
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2018-01-11 23:09:35 +08:00
|
|
|
streams.out.append(escape_string(*arg, flags, opts.escape_style));
|
2017-06-12 02:49:59 +08:00
|
|
|
streams.out.append(L'\n');
|
|
|
|
nesc++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nesc > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2018-01-11 23:09:35 +08:00
|
|
|
DIE("should never reach this statement");
|
2017-06-12 02:49:59 +08:00
|
|
|
}
|
|
|
|
|
2018-01-11 23:09:35 +08:00
|
|
|
static int string_unescape(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
|
|
|
options_t opts;
|
|
|
|
opts.no_quoted_valid = true;
|
|
|
|
opts.style_valid = true;
|
|
|
|
int optind;
|
|
|
|
int retval = parse_opts(&opts, &optind, 0, argc, argv, parser, streams);
|
2017-06-23 11:47:54 +08:00
|
|
|
int nesc = 0;
|
|
|
|
unescape_flags_t flags = 0;
|
|
|
|
|
2018-01-11 23:09:35 +08:00
|
|
|
if (retval != STATUS_CMD_OK) return retval;
|
2017-06-23 11:47:54 +08:00
|
|
|
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2017-06-23 11:47:54 +08:00
|
|
|
wcstring result;
|
2018-01-11 23:09:35 +08:00
|
|
|
if (unescape_string(*arg, &result, flags, opts.escape_style)) {
|
2017-06-23 11:47:54 +08:00
|
|
|
streams.out.append(result);
|
|
|
|
streams.out.append(L'\n');
|
|
|
|
nesc++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nesc > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
|
|
|
DIE("should never reach this statement");
|
|
|
|
}
|
|
|
|
|
2018-06-25 05:03:13 +08:00
|
|
|
static int string_join_maybe0(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv,
|
|
|
|
bool is_join0) {
|
2017-06-12 02:49:59 +08:00
|
|
|
options_t opts;
|
|
|
|
opts.quiet_valid = true;
|
|
|
|
int optind;
|
2018-06-25 05:03:13 +08:00
|
|
|
int retval = parse_opts(&opts, &optind, is_join0 ? 0 : 1, argc, argv, parser, streams);
|
2017-06-12 02:49:59 +08:00
|
|
|
if (retval != STATUS_CMD_OK) return retval;
|
|
|
|
|
2018-06-25 05:03:13 +08:00
|
|
|
const wcstring sep = is_join0 ? wcstring(1, L'\0') : wcstring(opts.arg1);
|
2015-09-13 03:59:40 +08:00
|
|
|
int nargs = 0;
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.quiet) {
|
2016-04-20 10:49:15 +08:00
|
|
|
if (nargs > 0) {
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.out.append(sep);
|
|
|
|
}
|
2018-01-03 19:13:39 +08:00
|
|
|
streams.out.append(*arg);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
nargs++;
|
|
|
|
}
|
2017-06-12 02:49:59 +08:00
|
|
|
if (nargs > 0 && !opts.quiet) {
|
2018-06-25 05:03:13 +08:00
|
|
|
streams.out.push_back(is_join0 ? L'\0' : L'\n');
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-05-04 15:18:02 +08:00
|
|
|
return nargs > 1 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2018-06-25 05:03:13 +08:00
|
|
|
static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
|
|
|
return string_join_maybe0(parser, streams, argc, argv, false /* is_join0 */);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int string_join0(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
|
|
|
return string_join_maybe0(parser, streams, argc, argv, true /* is_join0 */);
|
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static int string_length(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
|
|
|
int nnonempty = 0;
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2018-01-03 19:13:55 +08:00
|
|
|
size_t n = arg->length();
|
2016-04-20 10:49:15 +08:00
|
|
|
if (n > 0) {
|
2015-09-13 03:59:40 +08:00
|
|
|
nnonempty++;
|
|
|
|
}
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.quiet) {
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.out.append(to_string(n));
|
|
|
|
streams.out.append(L'\n');
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-04 15:18:02 +08:00
|
|
|
return nnonempty > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
class string_matcher_t {
|
|
|
|
protected:
|
2017-06-12 02:49:59 +08:00
|
|
|
options_t opts;
|
2015-09-22 02:24:49 +08:00
|
|
|
io_streams_t &streams;
|
2015-09-13 03:59:40 +08:00
|
|
|
int total_matched;
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
public:
|
2018-02-19 10:39:03 +08:00
|
|
|
string_matcher_t(options_t opts_, io_streams_t &streams_)
|
|
|
|
: opts(std::move(opts_)), streams(streams_), total_matched(0) {}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-02-19 10:44:58 +08:00
|
|
|
virtual ~string_matcher_t() = default;
|
2018-01-24 02:53:24 +08:00
|
|
|
virtual bool report_matches(const wcstring &arg) = 0;
|
2018-08-10 07:46:11 +08:00
|
|
|
int match_count() const { return total_matched; }
|
2015-09-13 03:59:40 +08:00
|
|
|
};
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
class wildcard_matcher_t : public string_matcher_t {
|
|
|
|
private:
|
2015-09-13 03:59:40 +08:00
|
|
|
wcstring wcpattern;
|
2016-04-20 10:49:15 +08:00
|
|
|
|
|
|
|
public:
|
2018-01-24 02:53:24 +08:00
|
|
|
wildcard_matcher_t(const wchar_t * /*argv0*/, const wcstring &pattern, const options_t &opts,
|
2017-06-12 02:49:59 +08:00
|
|
|
io_streams_t &streams)
|
2016-04-20 10:49:15 +08:00
|
|
|
: string_matcher_t(opts, streams), wcpattern(parse_util_unescape_wildcards(pattern)) {
|
|
|
|
if (opts.ignore_case) {
|
|
|
|
for (size_t i = 0; i < wcpattern.length(); i++) {
|
2015-09-13 03:59:40 +08:00
|
|
|
wcpattern[i] = towlower(wcpattern[i]);
|
|
|
|
}
|
|
|
|
}
|
2018-09-16 19:03:15 +08:00
|
|
|
if (opts.entire) {
|
2019-01-04 15:45:53 +08:00
|
|
|
if (!wcpattern.empty()) {
|
|
|
|
if (wcpattern.front() != ANY_STRING) wcpattern.insert(0, 1, ANY_STRING);
|
|
|
|
if (wcpattern.back() != ANY_STRING) wcpattern.push_back(ANY_STRING);
|
|
|
|
} else {
|
|
|
|
// If the pattern is empty, this becomes one ANY_STRING that matches everything.
|
|
|
|
wcpattern.push_back(ANY_STRING);
|
|
|
|
}
|
2017-04-25 12:05:51 +08:00
|
|
|
}
|
2017-06-17 12:00:24 +08:00
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-02-19 10:50:35 +08:00
|
|
|
~wildcard_matcher_t() override = default;
|
2016-04-08 10:18:58 +08:00
|
|
|
|
2018-01-24 02:53:24 +08:00
|
|
|
bool report_matches(const wcstring &arg) override {
|
2017-06-17 12:00:24 +08:00
|
|
|
// Note: --all is a no-op for glob matching since the pattern is always matched
|
|
|
|
// against the entire argument.
|
|
|
|
bool match;
|
2016-04-08 10:18:58 +08:00
|
|
|
|
2017-06-17 12:00:24 +08:00
|
|
|
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) {
|
2018-01-24 02:53:24 +08:00
|
|
|
streams.out.append_format(L"1 %lu\n", arg.length());
|
2016-04-20 10:49:15 +08:00
|
|
|
} else {
|
2017-06-17 12:00:24 +08:00
|
|
|
streams.out.append(arg);
|
|
|
|
streams.out.append(L'\n');
|
2017-06-12 02:49:59 +08:00
|
|
|
}
|
2017-06-17 12:00:24 +08:00
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2017-06-17 12:00:24 +08:00
|
|
|
return true;
|
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
};
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static wcstring pcre2_strerror(int err_code) {
|
2015-09-13 07:52:38 +08:00
|
|
|
wchar_t buf[128];
|
2015-09-13 03:59:40 +08:00
|
|
|
pcre2_get_error_message(err_code, (PCRE2_UCHAR *)buf, sizeof(buf) / sizeof(wchar_t));
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
struct compiled_regex_t {
|
2015-09-13 03:59:40 +08:00
|
|
|
pcre2_code *code;
|
|
|
|
pcre2_match_data *match;
|
|
|
|
|
2018-01-24 02:53:24 +08:00
|
|
|
compiled_regex_t(const wchar_t *argv0, const wcstring &pattern, bool ignore_case,
|
2016-04-20 10:49:15 +08:00
|
|
|
io_streams_t &streams)
|
|
|
|
: code(0), match(0) {
|
|
|
|
// Disable some sequences that can lead to security problems.
|
2015-09-13 03:59:40 +08:00
|
|
|
uint32_t options = PCRE2_NEVER_UTF;
|
|
|
|
#if PCRE2_CODE_UNIT_WIDTH < 32
|
|
|
|
options |= PCRE2_NEVER_BACKSLASH_C;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int err_code = 0;
|
|
|
|
PCRE2_SIZE err_offset = 0;
|
|
|
|
|
2018-09-28 10:28:39 +08:00
|
|
|
code =
|
|
|
|
pcre2_compile(PCRE2_SPTR(pattern.c_str()), pattern.length(),
|
|
|
|
options | (ignore_case ? PCRE2_CASELESS : 0), &err_code, &err_offset, 0);
|
2016-04-20 10:49:15 +08:00
|
|
|
if (code == 0) {
|
|
|
|
string_error(streams, _(L"%ls: Regular expression compile error: %ls\n"), argv0,
|
|
|
|
pcre2_strerror(err_code).c_str());
|
2018-01-24 02:53:24 +08:00
|
|
|
string_error(streams, L"%ls: %ls\n", argv0, pattern.c_str());
|
2015-09-22 02:24:49 +08:00
|
|
|
string_error(streams, L"%ls: %*ls\n", argv0, err_offset, L"^");
|
2015-09-13 03:59:40 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
match = pcre2_match_data_create_from_pattern(code, 0);
|
2017-02-14 12:37:27 +08:00
|
|
|
assert(match);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
~compiled_regex_t() {
|
|
|
|
if (match != 0) {
|
2015-09-13 03:59:40 +08:00
|
|
|
pcre2_match_data_free(match);
|
|
|
|
}
|
2016-04-20 10:49:15 +08:00
|
|
|
if (code != 0) {
|
2015-09-13 03:59:40 +08:00
|
|
|
pcre2_code_free(code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
class pcre2_matcher_t : public string_matcher_t {
|
2015-09-13 03:59:40 +08:00
|
|
|
const wchar_t *argv0;
|
|
|
|
compiled_regex_t regex;
|
|
|
|
|
2018-01-24 02:53:24 +08:00
|
|
|
int report_match(const wcstring &arg, int pcre2_rc) {
|
2016-04-20 10:49:15 +08:00
|
|
|
// Return values: -1 = error, 0 = no match, 1 = match.
|
|
|
|
if (pcre2_rc == PCRE2_ERROR_NOMATCH) {
|
|
|
|
if (opts.invert_match && !opts.quiet) {
|
2016-06-01 05:14:03 +08:00
|
|
|
if (opts.index) {
|
2018-01-24 02:53:24 +08:00
|
|
|
streams.out.append_format(L"1 %lu\n", arg.length());
|
2016-06-01 05:14:03 +08:00
|
|
|
} else {
|
|
|
|
streams.out.append(arg);
|
|
|
|
streams.out.push_back(L'\n');
|
|
|
|
}
|
2016-04-08 10:18:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return opts.invert_match ? 1 : 0;
|
2016-04-20 10:49:15 +08:00
|
|
|
} else if (pcre2_rc < 0) {
|
|
|
|
string_error(streams, _(L"%ls: Regular expression match error: %ls\n"), argv0,
|
|
|
|
pcre2_strerror(pcre2_rc).c_str());
|
2015-09-13 03:59:40 +08:00
|
|
|
return -1;
|
2016-04-20 10:49:15 +08:00
|
|
|
} else if (pcre2_rc == 0) {
|
2015-09-13 03:59:40 +08:00
|
|
|
// The output vector wasn't big enough. Should not happen.
|
2015-09-22 02:24:49 +08:00
|
|
|
string_error(streams, _(L"%ls: Regular expression internal error\n"), argv0);
|
2015-09-13 03:59:40 +08:00
|
|
|
return -1;
|
2017-04-25 12:05:51 +08:00
|
|
|
} else if (opts.invert_match) {
|
|
|
|
return 0;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2016-04-08 10:18:58 +08:00
|
|
|
|
2017-05-02 13:19:58 +08:00
|
|
|
if (opts.entire) {
|
2017-04-25 12:05:51 +08:00
|
|
|
streams.out.append(arg);
|
|
|
|
streams.out.push_back(L'\n');
|
|
|
|
}
|
2016-04-08 10:18:58 +08:00
|
|
|
|
2015-09-13 03:59:40 +08:00
|
|
|
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(regex.match);
|
2017-05-02 13:19:58 +08:00
|
|
|
for (int j = (opts.entire ? 1 : 0); j < pcre2_rc; j++) {
|
2016-04-20 10:49:15 +08:00
|
|
|
PCRE2_SIZE begin = ovector[2 * j];
|
|
|
|
PCRE2_SIZE end = ovector[2 * j + 1];
|
2016-04-08 10:18:58 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
if (begin != PCRE2_UNSET && end != PCRE2_UNSET && !opts.quiet) {
|
|
|
|
if (opts.index) {
|
|
|
|
streams.out.append_format(L"%lu %lu", (unsigned long)(begin + 1),
|
|
|
|
(unsigned long)(end - begin));
|
2017-04-25 12:05:51 +08:00
|
|
|
} else if (end > begin) {
|
|
|
|
// May have end < begin if \K is used.
|
2016-04-08 10:18:58 +08:00
|
|
|
streams.out.append(wcstring(&arg[begin], end - begin));
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2016-04-08 10:18:58 +08:00
|
|
|
streams.out.push_back(L'\n');
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
}
|
2016-04-08 10:18:58 +08:00
|
|
|
|
|
|
|
return opts.invert_match ? 0 : 1;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
public:
|
2018-01-24 02:53:24 +08:00
|
|
|
pcre2_matcher_t(const wchar_t *argv0_, const wcstring &pattern, const options_t &opts,
|
2016-04-20 10:49:15 +08:00
|
|
|
io_streams_t &streams)
|
2015-09-22 02:24:49 +08:00
|
|
|
: string_matcher_t(opts, streams),
|
2015-09-13 03:59:40 +08:00
|
|
|
argv0(argv0_),
|
2016-04-20 10:49:15 +08:00
|
|
|
regex(argv0_, pattern, opts.ignore_case, streams) {}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-02-19 10:50:35 +08:00
|
|
|
~pcre2_matcher_t() override = default;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-01-24 02:53:24 +08:00
|
|
|
bool report_matches(const wcstring &arg) override {
|
2016-04-20 10:49:15 +08:00
|
|
|
// A return value of true means all is well (even if no matches were found), false indicates
|
|
|
|
// an unrecoverable error.
|
|
|
|
if (regex.code == 0) {
|
|
|
|
// pcre2_compile() failed.
|
2015-09-13 03:59:40 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int matched = 0;
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
// See pcre2demo.c for an explanation of this logic.
|
2018-01-24 02:53:24 +08:00
|
|
|
PCRE2_SIZE arglen = arg.length();
|
2016-04-20 10:49:15 +08:00
|
|
|
int rc = report_match(
|
2018-09-28 10:28:39 +08:00
|
|
|
arg, pcre2_match(regex.code, PCRE2_SPTR(arg.c_str()), arglen, 0, 0, regex.match, 0));
|
2016-04-20 10:49:15 +08:00
|
|
|
if (rc < 0) { // pcre2 match error.
|
2015-09-13 03:59:40 +08:00
|
|
|
return false;
|
2016-04-20 10:49:15 +08:00
|
|
|
} else if (rc == 0) { // no match
|
2015-09-13 03:59:40 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
matched++;
|
|
|
|
total_matched++;
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
if (opts.invert_match) {
|
2016-04-08 10:18:58 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
// Report any additional matches.
|
2015-09-13 03:59:40 +08:00
|
|
|
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(regex.match);
|
2016-04-20 10:49:15 +08:00
|
|
|
while (opts.all || matched == 0) {
|
2015-09-13 03:59:40 +08:00
|
|
|
uint32_t options = 0;
|
2016-04-20 10:49:15 +08:00
|
|
|
PCRE2_SIZE offset = ovector[1]; // start at end of previous match
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
if (ovector[0] == ovector[1]) {
|
|
|
|
if (ovector[0] == arglen) {
|
2015-09-13 03:59:40 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
options = PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED;
|
|
|
|
}
|
|
|
|
|
2018-09-28 10:28:39 +08:00
|
|
|
rc = report_match(arg, pcre2_match(regex.code, PCRE2_SPTR(arg.c_str()), arglen, offset,
|
|
|
|
options, regex.match, 0));
|
2016-04-20 10:49:15 +08:00
|
|
|
if (rc < 0) {
|
2015-09-13 03:59:40 +08:00
|
|
|
return false;
|
|
|
|
}
|
2016-04-20 10:49:15 +08:00
|
|
|
if (rc == 0) {
|
|
|
|
if (options == 0) { // all matches found
|
2015-09-13 03:59:40 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
ovector[1] = offset + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
matched++;
|
|
|
|
total_matched++;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static int string_match(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
2017-04-25 12:05:51 +08:00
|
|
|
wchar_t *cmd = argv[0];
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-05-02 13:19:58 +08:00
|
|
|
if (opts.entire && opts.index) {
|
2017-04-25 12:05:51 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_COMBO2, cmd,
|
2017-09-08 22:27:52 +08:00
|
|
|
_(L"--entire and --index are mutually exclusive"));
|
2017-05-02 15:39:50 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2017-04-25 12:05:51 +08:00
|
|
|
}
|
|
|
|
|
2017-01-22 06:53:29 +08:00
|
|
|
std::unique_ptr<string_matcher_t> matcher;
|
2017-06-12 02:49:59 +08:00
|
|
|
if (opts.regex) {
|
2017-04-25 12:05:51 +08:00
|
|
|
matcher = make_unique<pcre2_matcher_t>(cmd, pattern, opts, streams);
|
2016-04-20 10:49:15 +08:00
|
|
|
} else {
|
2017-04-25 12:05:51 +08:00
|
|
|
matcher = make_unique<wildcard_matcher_t>(cmd, pattern, opts, streams);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2018-01-24 02:53:24 +08:00
|
|
|
if (!matcher->report_matches(*arg)) {
|
2017-05-02 15:39:50 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-04 15:18:02 +08:00
|
|
|
return matcher->match_count() > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
class string_replacer_t {
|
|
|
|
protected:
|
2015-09-13 03:59:40 +08:00
|
|
|
const wchar_t *argv0;
|
2017-06-12 02:49:59 +08:00
|
|
|
options_t opts;
|
2015-09-13 03:59:40 +08:00
|
|
|
int total_replaced;
|
2015-09-22 02:24:49 +08:00
|
|
|
io_streams_t &streams;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
public:
|
2018-02-19 10:39:03 +08:00
|
|
|
string_replacer_t(const wchar_t *argv0_, options_t opts_, io_streams_t &streams_)
|
|
|
|
: argv0(argv0_), opts(std::move(opts_)), total_replaced(0), streams(streams_) {}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-02-19 10:44:58 +08:00
|
|
|
virtual ~string_replacer_t() = default;
|
2018-08-10 07:46:11 +08:00
|
|
|
int replace_count() const { return total_replaced; }
|
2018-01-24 02:53:24 +08:00
|
|
|
virtual bool replace_matches(const wcstring &arg) = 0;
|
2015-09-13 03:59:40 +08:00
|
|
|
};
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
class literal_replacer_t : public string_replacer_t {
|
2018-01-24 02:53:24 +08:00
|
|
|
const wcstring pattern;
|
|
|
|
const wcstring replacement;
|
2015-09-13 07:43:51 +08:00
|
|
|
size_t patlen;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
public:
|
2018-01-24 02:53:24 +08:00
|
|
|
literal_replacer_t(const wchar_t *argv0, const wcstring &pattern_, const wchar_t *replacement_,
|
2017-06-12 02:49:59 +08:00
|
|
|
const options_t &opts, io_streams_t &streams)
|
2015-09-22 02:24:49 +08:00
|
|
|
: string_replacer_t(argv0, opts, streams),
|
2016-04-20 10:49:15 +08:00
|
|
|
pattern(pattern_),
|
|
|
|
replacement(replacement_),
|
2018-01-24 02:53:24 +08:00
|
|
|
patlen(pattern.length()) {}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-02-19 10:50:35 +08:00
|
|
|
~literal_replacer_t() override = default;
|
2018-01-24 02:53:24 +08:00
|
|
|
bool replace_matches(const wcstring &arg) override;
|
2015-09-13 03:59:40 +08:00
|
|
|
};
|
|
|
|
|
2018-09-28 10:22:55 +08:00
|
|
|
static maybe_t<wcstring> interpret_escapes(const wcstring &arg) {
|
2017-06-09 11:56:24 +08:00
|
|
|
wcstring result;
|
2018-08-26 15:43:40 +08:00
|
|
|
result.reserve(arg.size());
|
|
|
|
const wchar_t *cursor = arg.c_str();
|
|
|
|
const wchar_t *end = cursor + arg.size();
|
|
|
|
while (cursor < end) {
|
|
|
|
if (*cursor == L'\\') {
|
2018-09-28 10:22:55 +08:00
|
|
|
if (auto escape_len = read_unquoted_escape(cursor, &result, true, false)) {
|
|
|
|
cursor += *escape_len;
|
|
|
|
} else {
|
|
|
|
// Invalid escape.
|
|
|
|
return none();
|
|
|
|
}
|
2017-06-09 11:56:24 +08:00
|
|
|
} else {
|
2018-08-26 15:43:40 +08:00
|
|
|
result.push_back(*cursor);
|
|
|
|
cursor++;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
}
|
2017-06-09 11:56:24 +08:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
class regex_replacer_t : public string_replacer_t {
|
|
|
|
compiled_regex_t regex;
|
2018-09-28 10:22:55 +08:00
|
|
|
maybe_t<wcstring> replacement;
|
2017-06-09 11:56:24 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
public:
|
2018-01-24 02:53:24 +08:00
|
|
|
regex_replacer_t(const wchar_t *argv0, const wcstring &pattern, const wcstring &replacement_,
|
2017-06-12 02:49:59 +08:00
|
|
|
const options_t &opts, io_streams_t &streams)
|
2015-09-22 02:24:49 +08:00
|
|
|
: string_replacer_t(argv0, opts, streams),
|
|
|
|
regex(argv0, pattern, opts.ignore_case, streams),
|
2016-04-20 10:49:15 +08:00
|
|
|
replacement(interpret_escapes(replacement_)) {}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-01-24 02:53:24 +08:00
|
|
|
bool replace_matches(const wcstring &arg) override;
|
2016-11-02 11:00:09 +08:00
|
|
|
};
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2016-11-02 11:00:09 +08:00
|
|
|
/// A return value of true means all is well (even if no replacements were performed), false
|
|
|
|
/// indicates an unrecoverable error.
|
2018-01-24 02:53:24 +08:00
|
|
|
bool literal_replacer_t::replace_matches(const wcstring &arg) {
|
2017-04-28 12:53:39 +08:00
|
|
|
wcstring result;
|
|
|
|
bool replacement_occurred = false;
|
|
|
|
|
|
|
|
if (patlen == 0) {
|
|
|
|
replacement_occurred = true;
|
|
|
|
result = arg;
|
|
|
|
} else {
|
2019-03-13 05:06:01 +08:00
|
|
|
auto &cmp_func = opts.ignore_case ? wcsncasecmp : std::wcsncmp;
|
2018-01-24 02:53:24 +08:00
|
|
|
const wchar_t *cur = arg.c_str();
|
2018-08-26 15:43:40 +08:00
|
|
|
const wchar_t *end = cur + arg.size();
|
|
|
|
while (cur < end) {
|
2018-09-28 10:28:39 +08:00
|
|
|
if ((opts.all || !replacement_occurred) &&
|
|
|
|
cmp_func(cur, pattern.c_str(), patlen) == 0) {
|
2017-04-28 12:53:39 +08:00
|
|
|
result += replacement;
|
|
|
|
cur += patlen;
|
|
|
|
replacement_occurred = true;
|
|
|
|
total_replaced++;
|
|
|
|
} else {
|
2018-08-26 15:43:40 +08:00
|
|
|
result.push_back(*cur);
|
2017-04-28 12:53:39 +08:00
|
|
|
cur++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!opts.quiet && (!opts.filter || replacement_occurred)) {
|
|
|
|
streams.out.append(result);
|
|
|
|
streams.out.append(L'\n');
|
2016-11-02 11:00:09 +08:00
|
|
|
}
|
|
|
|
|
2017-04-28 12:53:39 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A return value of true means all is well (even if no replacements were performed), false
|
|
|
|
/// indicates an unrecoverable error.
|
2018-01-24 02:53:24 +08:00
|
|
|
bool regex_replacer_t::replace_matches(const wcstring &arg) {
|
2018-09-28 10:28:39 +08:00
|
|
|
if (!regex.code) return false; // pcre2_compile() failed
|
2018-09-28 10:22:55 +08:00
|
|
|
if (!replacement) return false; // replacement was an invalid string
|
2017-04-28 12:53:39 +08:00
|
|
|
|
2016-11-02 11:00:09 +08:00
|
|
|
uint32_t options = PCRE2_SUBSTITUTE_OVERFLOW_LENGTH | PCRE2_SUBSTITUTE_EXTENDED |
|
|
|
|
(opts.all ? PCRE2_SUBSTITUTE_GLOBAL : 0);
|
2018-01-24 02:53:24 +08:00
|
|
|
size_t arglen = arg.length();
|
2016-11-02 11:00:09 +08:00
|
|
|
PCRE2_SIZE bufsize = (arglen == 0) ? 16 : 2 * arglen;
|
|
|
|
wchar_t *output = (wchar_t *)malloc(sizeof(wchar_t) * bufsize);
|
|
|
|
int pcre2_rc;
|
2018-01-24 02:53:24 +08:00
|
|
|
PCRE2_SIZE outlen = bufsize;
|
2016-11-02 11:00:09 +08:00
|
|
|
|
|
|
|
bool done = false;
|
|
|
|
while (!done) {
|
2017-02-14 12:37:27 +08:00
|
|
|
assert(output);
|
|
|
|
|
2018-01-24 02:53:24 +08:00
|
|
|
pcre2_rc = pcre2_substitute(regex.code, PCRE2_SPTR(arg.c_str()), arglen,
|
2016-11-02 11:00:09 +08:00
|
|
|
0, // start offset
|
|
|
|
options, regex.match,
|
|
|
|
0, // match context
|
2018-09-28 10:22:55 +08:00
|
|
|
PCRE2_SPTR(replacement->c_str()), replacement->length(),
|
2016-11-02 11:00:09 +08:00
|
|
|
(PCRE2_UCHAR *)output, &outlen);
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2016-11-02 11:00:09 +08:00
|
|
|
if (pcre2_rc != PCRE2_ERROR_NOMEMORY || bufsize >= outlen) {
|
|
|
|
done = true;
|
2016-04-20 10:49:15 +08:00
|
|
|
} else {
|
2016-11-02 11:00:09 +08:00
|
|
|
bufsize = outlen;
|
2016-11-15 13:31:51 +08:00
|
|
|
wchar_t *new_output = (wchar_t *)realloc(output, sizeof(wchar_t) * bufsize);
|
|
|
|
if (new_output) output = new_output;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2016-11-02 11:00:09 +08:00
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2018-01-24 02:53:24 +08:00
|
|
|
wcstring outstr(output, outlen);
|
2016-11-02 11:00:09 +08:00
|
|
|
bool rc = true;
|
|
|
|
if (pcre2_rc < 0) {
|
|
|
|
string_error(streams, _(L"%ls: Regular expression substitute error: %ls\n"), argv0,
|
|
|
|
pcre2_strerror(pcre2_rc).c_str());
|
|
|
|
rc = false;
|
|
|
|
} else {
|
2017-04-28 12:53:39 +08:00
|
|
|
bool replacement_occurred = pcre2_rc > 0;
|
|
|
|
if (!opts.quiet && (!opts.filter || replacement_occurred)) {
|
2018-01-24 02:53:24 +08:00
|
|
|
streams.out.append(outstr);
|
2016-11-02 11:00:09 +08:00
|
|
|
streams.out.append(L'\n');
|
|
|
|
}
|
|
|
|
total_replaced += pcre2_rc;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2016-11-02 11:00:09 +08:00
|
|
|
|
|
|
|
free(output);
|
|
|
|
return rc;
|
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static int string_replace(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
|
|
|
|
|
|
|
const wchar_t *pattern = opts.arg1;
|
|
|
|
const wchar_t *replacement = opts.arg2;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2017-01-22 07:47:12 +08:00
|
|
|
std::unique_ptr<string_replacer_t> replacer;
|
2017-06-12 02:49:59 +08:00
|
|
|
if (opts.regex) {
|
2017-01-22 07:47:12 +08:00
|
|
|
replacer = make_unique<regex_replacer_t>(argv[0], pattern, replacement, opts, streams);
|
2016-04-20 10:49:15 +08:00
|
|
|
} else {
|
2017-01-22 07:47:12 +08:00
|
|
|
replacer = make_unique<literal_replacer_t>(argv[0], pattern, replacement, opts, streams);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2018-01-24 02:53:24 +08:00
|
|
|
if (!replacer->replace_matches(*arg)) return STATUS_INVALID_ARGS;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-05-04 15:18:02 +08:00
|
|
|
return replacer->replace_count() > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2018-05-30 12:11:50 +08:00
|
|
|
static int string_split_maybe0(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv,
|
|
|
|
bool is_split0) {
|
2017-06-12 02:49:59 +08:00
|
|
|
options_t opts;
|
|
|
|
opts.quiet_valid = true;
|
|
|
|
opts.right_valid = true;
|
|
|
|
opts.max_valid = true;
|
|
|
|
opts.max = LONG_MAX;
|
2018-03-29 21:12:08 +08:00
|
|
|
opts.no_empty_valid = true;
|
2017-06-12 02:49:59 +08:00
|
|
|
int optind;
|
2018-05-30 12:11:50 +08:00
|
|
|
int retval = parse_opts(&opts, &optind, is_split0 ? 0 : 1, argc, argv, parser, streams);
|
2017-06-12 02:49:59 +08:00
|
|
|
if (retval != STATUS_CMD_OK) return retval;
|
|
|
|
|
2018-05-30 12:11:50 +08:00
|
|
|
const wcstring sep = is_split0 ? wcstring(1, L'\0') : wcstring(opts.arg1);
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2015-09-13 16:11:49 +08:00
|
|
|
wcstring_list_t splits;
|
|
|
|
size_t arg_count = 0;
|
2019-02-27 02:50:53 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams, !is_split0);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2017-06-12 02:49:59 +08:00
|
|
|
if (opts.right) {
|
2018-09-28 10:28:39 +08:00
|
|
|
split_about(arg->rbegin(), arg->rend(), sep.rbegin(), sep.rend(), &splits, opts.max,
|
|
|
|
opts.no_empty);
|
2016-04-20 10:49:15 +08:00
|
|
|
} else {
|
2018-09-28 10:28:39 +08:00
|
|
|
split_about(arg->begin(), arg->end(), sep.begin(), sep.end(), &splits, opts.max,
|
|
|
|
opts.no_empty);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2015-09-13 16:11:49 +08:00
|
|
|
arg_count++;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2016-04-20 10:49:15 +08:00
|
|
|
|
2015-09-13 16:11:49 +08:00
|
|
|
// If we are from the right, split_about gave us reversed strings, in reversed order!
|
2017-06-12 02:49:59 +08:00
|
|
|
if (opts.right) {
|
2016-04-20 10:49:15 +08:00
|
|
|
for (size_t j = 0; j < splits.size(); j++) {
|
2016-04-08 10:18:58 +08:00
|
|
|
std::reverse(splits[j].begin(), splits[j].end());
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2015-09-13 16:11:49 +08:00
|
|
|
std::reverse(splits.begin(), splits.end());
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2016-04-20 10:49:15 +08:00
|
|
|
|
2018-05-30 12:11:50 +08:00
|
|
|
const size_t split_count = splits.size();
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.quiet) {
|
2019-02-27 02:50:53 +08:00
|
|
|
if (is_split0 && splits.size()) {
|
|
|
|
// split0 ignores a trailing \0, so a\0b\0 is two elements.
|
|
|
|
// In contrast to split, where a\nb\n is three - "a", "b" and "".
|
|
|
|
//
|
|
|
|
// Remove the last element if it is empty.
|
|
|
|
if (splits.back().empty()) splits.pop_back();
|
|
|
|
}
|
2018-05-30 12:11:50 +08:00
|
|
|
auto &buff = streams.out.buffer();
|
|
|
|
for (const wcstring &split : splits) {
|
|
|
|
buff.append(split, separation_type_t::explicitly);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
// We split something if we have more split values than args.
|
2018-05-30 12:11:50 +08:00
|
|
|
return split_count > arg_count ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int string_split(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
|
|
|
return string_split_maybe0(parser, streams, argc, argv, false /* is_split0 */);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int string_split0(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
|
|
|
return string_split_maybe0(parser, streams, argc, argv, true /* is_split0 */);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-03-07 22:39:21 +08:00
|
|
|
// 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) {
|
2018-05-14 01:26:51 +08:00
|
|
|
if (to_repeat.length() == 0) return wcstring();
|
2017-03-07 22:39:21 +08:00
|
|
|
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) {
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2017-03-07 22:39:21 +08:00
|
|
|
|
|
|
|
bool is_empty = true;
|
|
|
|
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
if (const wcstring *word = aiter.nextstr()) {
|
2017-06-12 02:49:59 +08:00
|
|
|
const bool limit_repeat =
|
2018-05-14 01:26:51 +08:00
|
|
|
(opts.max > 0 && word->length() * opts.count > (size_t)opts.max) || !opts.count;
|
2017-06-12 02:49:59 +08:00
|
|
|
const wcstring repeated =
|
2018-05-14 01:26:51 +08:00
|
|
|
limit_repeat ? wcsrepeat_until(*word, opts.max) : wcsrepeat(*word, opts.count);
|
2017-03-07 22:39:21 +08:00
|
|
|
is_empty = repeated.empty();
|
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.quiet && !is_empty) {
|
2017-03-07 22:39:21 +08:00
|
|
|
streams.out.append(repeated);
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.no_newline) streams.out.append(L"\n");
|
2017-03-07 22:39:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-04 15:18:02 +08:00
|
|
|
return !is_empty ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2017-03-07 22:39:21 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static int string_sub(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2015-09-13 03:59:40 +08:00
|
|
|
|
|
|
|
int nsub = 0;
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *s = aiter.nextstr()) {
|
2015-09-13 17:15:37 +08:00
|
|
|
typedef wcstring::size_type size_type;
|
|
|
|
size_type pos = 0;
|
|
|
|
size_type count = wcstring::npos;
|
2017-06-12 02:49:59 +08:00
|
|
|
if (opts.start > 0) {
|
|
|
|
pos = static_cast<size_type>(opts.start - 1);
|
|
|
|
} else if (opts.start < 0) {
|
|
|
|
assert(opts.start != LONG_MIN); // checked above
|
|
|
|
size_type n = static_cast<size_type>(-opts.start);
|
2018-01-11 22:47:44 +08:00
|
|
|
pos = n > s->length() ? 0 : s->length() - n;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2018-01-11 22:47:44 +08:00
|
|
|
if (pos > s->length()) {
|
|
|
|
pos = s->length();
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
if (opts.length >= 0) {
|
|
|
|
count = static_cast<size_type>(opts.length);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
// Note that std::string permits count to extend past end of string.
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.quiet) {
|
2018-01-11 22:47:44 +08:00
|
|
|
streams.out.append(s->substr(pos, count));
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.out.append(L'\n');
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
nsub++;
|
|
|
|
}
|
|
|
|
|
2017-05-04 15:18:02 +08:00
|
|
|
return nsub > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static int string_trim(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2016-04-08 10:18:58 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
// If neither left or right is specified, we do both.
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.left && !opts.right) {
|
|
|
|
opts.left = opts.right = true;
|
2015-09-13 08:15:08 +08:00
|
|
|
}
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2015-09-13 08:15:08 +08:00
|
|
|
size_t ntrim = 0;
|
2016-04-20 10:49:15 +08:00
|
|
|
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2016-04-20 10:49:15 +08:00
|
|
|
// 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.
|
2018-01-11 23:03:52 +08:00
|
|
|
size_t begin = 0, end = arg->size();
|
2017-06-12 02:49:59 +08:00
|
|
|
if (opts.right) {
|
2018-01-11 23:03:52 +08:00
|
|
|
size_t last_to_keep = arg->find_last_not_of(opts.chars_to_trim);
|
2015-09-13 08:15:08 +08:00
|
|
|
end = (last_to_keep == wcstring::npos) ? 0 : last_to_keep + 1;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2017-06-12 02:49:59 +08:00
|
|
|
if (opts.left) {
|
2018-01-11 23:03:52 +08:00
|
|
|
size_t first_to_keep = arg->find_first_not_of(opts.chars_to_trim);
|
2015-09-13 08:15:08 +08:00
|
|
|
begin = (first_to_keep == wcstring::npos ? end : first_to_keep);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2018-01-11 23:03:52 +08:00
|
|
|
assert(begin <= end && end <= arg->size());
|
|
|
|
ntrim += arg->size() - (end - begin);
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.quiet) {
|
2018-01-11 23:03:52 +08:00
|
|
|
streams.out.append(wcstring(*arg, begin, end - begin));
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.out.append(L'\n');
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-04 15:18:02 +08:00
|
|
|
return ntrim > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2018-01-11 23:04:28 +08:00
|
|
|
// A helper function for lower and upper.
|
2018-09-28 10:28:39 +08:00
|
|
|
static int string_transform(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv,
|
|
|
|
std::wint_t (*func)(std::wint_t)) {
|
2017-06-12 02:49:59 +08:00
|
|
|
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;
|
2017-06-11 08:35:25 +08:00
|
|
|
|
|
|
|
int n_transformed = 0;
|
2017-12-22 04:42:57 +08:00
|
|
|
arg_iterator_t aiter(argv, optind, streams);
|
2018-05-28 18:05:47 +08:00
|
|
|
while (const wcstring *arg = aiter.nextstr()) {
|
2018-01-11 23:04:28 +08:00
|
|
|
wcstring transformed(*arg);
|
|
|
|
std::transform(transformed.begin(), transformed.end(), transformed.begin(), func);
|
|
|
|
if (transformed != *arg) n_transformed++;
|
2017-06-12 02:49:59 +08:00
|
|
|
if (!opts.quiet) {
|
2017-06-11 08:35:25 +08:00
|
|
|
streams.out.append(transformed);
|
|
|
|
streams.out.append(L'\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return n_transformed > 0 ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
|
|
|
}
|
|
|
|
|
2018-01-11 23:04:28 +08:00
|
|
|
/// Implementation of `string lower`.
|
|
|
|
static int string_lower(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
|
|
|
return string_transform(parser, streams, argc, argv, std::towlower);
|
|
|
|
}
|
|
|
|
|
2017-06-12 02:49:59 +08:00
|
|
|
/// Implementation of `string upper`.
|
2017-06-11 08:35:25 +08:00
|
|
|
static int string_upper(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) {
|
2018-01-11 23:04:28 +08:00
|
|
|
return string_transform(parser, streams, argc, argv, std::towupper);
|
2017-06-11 08:35:25 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
static const struct string_subcommand {
|
2015-09-13 03:59:40 +08:00
|
|
|
const wchar_t *name;
|
2016-11-02 10:12:14 +08:00
|
|
|
int (*handler)(parser_t &, io_streams_t &, int argc, //!OCLINT(unused param)
|
|
|
|
wchar_t **argv); //!OCLINT(unused param)
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
2016-04-08 10:18:58 +08:00
|
|
|
|
2018-06-25 05:03:13 +08:00
|
|
|
string_subcommands[] = {
|
|
|
|
{L"escape", &string_escape}, {L"join", &string_join}, {L"join0", &string_join0},
|
|
|
|
{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"trim", &string_trim}, {L"lower", &string_lower}, {L"upper", &string_upper},
|
|
|
|
{L"repeat", &string_repeat}, {L"unescape", &string_unescape}, {NULL, NULL}};
|
2015-09-13 03:59:40 +08:00
|
|
|
|
2016-04-20 10:49:15 +08:00
|
|
|
/// The string builtin, for manipulating strings.
|
|
|
|
int builtin_string(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2017-06-20 12:05:34 +08:00
|
|
|
wchar_t *cmd = argv[0];
|
2015-09-13 03:59:40 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
2016-04-20 10:49:15 +08:00
|
|
|
if (argc <= 1) {
|
2017-06-20 12:05:34 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_MISSING_SUBCMD, cmd);
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, L"string", streams.err);
|
2017-05-02 15:39:50 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
2019-03-13 05:06:01 +08:00
|
|
|
if (std::wcscmp(argv[1], L"-h") == 0 || std::wcscmp(argv[1], L"--help") == 0) {
|
2019-01-10 07:07:09 +08:00
|
|
|
builtin_print_help(parser, streams, L"string", streams.out);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const string_subcommand *subcmd = &string_subcommands[0];
|
2019-03-13 05:06:01 +08:00
|
|
|
while (subcmd->name != 0 && std::wcscmp(subcmd->name, argv[1]) != 0) {
|
2015-09-13 03:59:40 +08:00
|
|
|
subcmd++;
|
|
|
|
}
|
2017-06-20 12:05:34 +08:00
|
|
|
if (!subcmd->handler) {
|
|
|
|
streams.err.append_format(BUILTIN_ERR_INVALID_SUBCMD, cmd, argv[1]);
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, L"string", streams.err);
|
2017-05-02 15:39:50 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
argc--;
|
|
|
|
argv++;
|
2015-09-22 02:24:49 +08:00
|
|
|
return subcmd->handler(parser, streams, argc, argv);
|
2015-09-13 03:59:40 +08:00
|
|
|
}
|