2016-04-19 10:25:12 +08:00
|
|
|
// Functions for executing builtin functions.
|
|
|
|
//
|
|
|
|
// How to add a new builtin function:
|
|
|
|
//
|
|
|
|
// 1). Create a function in builtin.c with the following signature:
|
|
|
|
//
|
2020-11-22 21:39:48 +08:00
|
|
|
// <tt>static maybe_t<int> builtin_NAME(parser_t &parser, io_streams_t &streams, wchar_t
|
|
|
|
// **argv)</tt>
|
2016-04-19 10:25:12 +08:00
|
|
|
//
|
|
|
|
// where NAME is the name of the builtin, and args is a zero-terminated list of arguments.
|
|
|
|
//
|
|
|
|
// 2). Add a line like { L"NAME", &builtin_NAME, N_(L"Bla bla bla") }, to the builtin_data_t
|
|
|
|
// variable. The description is used by the completion system. Note that this array is sorted.
|
|
|
|
//
|
2020-02-20 09:00:35 +08:00
|
|
|
// 3). Create a file doc_src/NAME.rst, containing the manual for the builtin in
|
2019-02-25 11:41:51 +08:00
|
|
|
// reStructuredText-format. Check the other builtin manuals for proper syntax.
|
2016-04-19 10:25:12 +08:00
|
|
|
//
|
2020-02-20 09:00:35 +08:00
|
|
|
// 4). Use 'git add doc_src/NAME.txt' to start tracking changes to the documentation file.
|
2016-05-19 06:30:21 +08:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2019-10-14 06:50:48 +08:00
|
|
|
#include "builtin.h"
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
#include <unistd.h>
|
2017-02-14 12:37:27 +08:00
|
|
|
|
2019-11-19 09:11:16 +08:00
|
|
|
#include <cerrno>
|
2019-10-14 06:50:48 +08:00
|
|
|
#include <cstring>
|
|
|
|
#include <cwchar>
|
2022-08-21 14:14:48 +08:00
|
|
|
#include <deque>
|
2016-06-24 08:24:19 +08:00
|
|
|
#include <memory>
|
2017-04-14 14:13:55 +08:00
|
|
|
#include <string>
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2021-11-10 09:07:15 +08:00
|
|
|
#include "builtins/argparse.h"
|
|
|
|
#include "builtins/bind.h"
|
|
|
|
#include "builtins/builtin.h"
|
|
|
|
#include "builtins/cd.h"
|
|
|
|
#include "builtins/command.h"
|
|
|
|
#include "builtins/commandline.h"
|
|
|
|
#include "builtins/complete.h"
|
|
|
|
#include "builtins/disown.h"
|
|
|
|
#include "builtins/eval.h"
|
|
|
|
#include "builtins/fg.h"
|
|
|
|
#include "builtins/functions.h"
|
|
|
|
#include "builtins/history.h"
|
|
|
|
#include "builtins/jobs.h"
|
|
|
|
#include "builtins/math.h"
|
2021-11-18 03:22:21 +08:00
|
|
|
#include "builtins/path.h"
|
2021-11-10 09:07:15 +08:00
|
|
|
#include "builtins/printf.h"
|
|
|
|
#include "builtins/read.h"
|
|
|
|
#include "builtins/set.h"
|
|
|
|
#include "builtins/set_color.h"
|
2023-01-16 11:52:08 +08:00
|
|
|
#include "builtins/shared.rs.h"
|
2021-11-10 09:07:15 +08:00
|
|
|
#include "builtins/source.h"
|
|
|
|
#include "builtins/status.h"
|
|
|
|
#include "builtins/string.h"
|
|
|
|
#include "builtins/test.h"
|
|
|
|
#include "builtins/type.h"
|
|
|
|
#include "builtins/ulimit.h"
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "complete.h"
|
2023-01-16 11:52:08 +08:00
|
|
|
#include "cxx.h"
|
|
|
|
#include "cxxgen.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2019-05-28 06:56:53 +08:00
|
|
|
#include "flog.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "io.h"
|
2022-08-21 14:14:48 +08:00
|
|
|
#include "null_terminated_array.h"
|
2016-04-19 10:25:12 +08:00
|
|
|
#include "parse_constants.h"
|
2006-02-15 03:56:36 +08:00
|
|
|
#include "parse_util.h"
|
2016-04-19 10:25:12 +08:00
|
|
|
#include "parser.h"
|
|
|
|
#include "proc.h"
|
|
|
|
#include "reader.h"
|
|
|
|
#include "wgetopt.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2006-06-21 05:20:16 +08:00
|
|
|
|
2023-01-16 11:52:08 +08:00
|
|
|
static maybe_t<RustBuiltin> try_get_rust_builtin(const wcstring &cmd);
|
2023-02-06 06:52:25 +08:00
|
|
|
static maybe_t<int> builtin_run_rust(parser_t &parser, io_streams_t &streams,
|
|
|
|
const wcstring_list_t &argv, RustBuiltin builtin);
|
2023-01-16 11:52:08 +08:00
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Counts the number of arguments in the specified null-terminated array
|
2016-04-19 10:25:12 +08:00
|
|
|
int builtin_count_args(const wchar_t *const *argv) {
|
2016-08-17 06:30:49 +08:00
|
|
|
int argc;
|
2019-11-19 10:34:50 +08:00
|
|
|
for (argc = 1; argv[argc] != nullptr;) {
|
2016-09-11 05:55:27 +08:00
|
|
|
argc++;
|
|
|
|
}
|
2016-08-17 06:30:49 +08:00
|
|
|
|
2019-11-19 10:34:50 +08:00
|
|
|
assert(argv[argc] == nullptr);
|
2012-11-19 08:30:30 +08:00
|
|
|
return argc;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// This function works like wperror, but it prints its result into the streams.err string instead
|
|
|
|
/// to stderr. Used by the builtin commands.
|
2021-10-31 18:51:16 +08:00
|
|
|
void builtin_wperror(const wchar_t *program_name, io_streams_t &streams) {
|
2019-03-13 06:07:07 +08:00
|
|
|
char *err = std::strerror(errno);
|
2021-10-31 18:51:16 +08:00
|
|
|
if (program_name != nullptr) {
|
|
|
|
streams.err.append(program_name);
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append(L": ");
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2019-11-19 10:34:50 +08:00
|
|
|
if (err != nullptr) {
|
2012-12-20 05:31:06 +08:00
|
|
|
const wcstring werr = str2wcstring(err);
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append(werr);
|
|
|
|
streams.err.push_back(L'\n');
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:56 +08:00
|
|
|
static const wchar_t *const short_options = L"+:h";
|
2022-11-29 08:35:49 +08:00
|
|
|
static const struct woption long_options[] = {{L"help", no_argument, 'h'}, {}};
|
2017-06-15 09:25:51 +08:00
|
|
|
|
2017-06-17 11:42:33 +08:00
|
|
|
int parse_help_only_cmd_opts(struct help_only_cmd_opts_t &opts, int *optind, int argc,
|
2022-12-23 00:23:42 +08:00
|
|
|
const wchar_t **argv, parser_t &parser, io_streams_t &streams) {
|
2021-02-14 10:41:09 +08:00
|
|
|
const wchar_t *cmd = argv[0];
|
2017-06-15 09:25:51 +08:00
|
|
|
int opt;
|
|
|
|
wgetopter_t w;
|
2019-11-19 10:34:50 +08:00
|
|
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
|
2017-06-15 09:25:51 +08:00
|
|
|
switch (opt) { //!OCLINT(too few branches)
|
|
|
|
case 'h': {
|
2017-06-17 11:42:33 +08:00
|
|
|
opts.print_help = true;
|
2017-06-15 12:19:59 +08:00
|
|
|
break;
|
2017-06-15 09:25:51 +08:00
|
|
|
}
|
2017-06-30 12:49:57 +08:00
|
|
|
case ':': {
|
2022-12-23 00:23:42 +08:00
|
|
|
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
2017-06-30 12:49:57 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
2017-06-15 09:25:51 +08:00
|
|
|
case '?': {
|
2022-12-23 00:23:42 +08:00
|
|
|
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);
|
2017-06-15 09:25:51 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
DIE("unexpected retval from wgetopt_long");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*optind = w.woptind;
|
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|
|
|
|
|
2019-10-20 17:38:17 +08:00
|
|
|
/// Display help/usage information for the specified builtin or function from manpage
|
2016-08-17 06:30:49 +08:00
|
|
|
///
|
|
|
|
/// @param name
|
2019-10-20 17:38:17 +08:00
|
|
|
/// builtin or function name to get up help for
|
2016-08-17 06:30:49 +08:00
|
|
|
///
|
2019-10-20 17:38:17 +08:00
|
|
|
/// Process and print help for the specified builtin or function.
|
2020-03-14 04:59:10 +08:00
|
|
|
void builtin_print_help(parser_t &parser, const io_streams_t &streams, const wchar_t *name,
|
2023-01-01 02:13:03 +08:00
|
|
|
const wcstring &error_message) {
|
2016-04-19 10:25:12 +08:00
|
|
|
// This won't ever work if no_exec is set.
|
2019-10-20 17:38:17 +08:00
|
|
|
if (no_exec()) return;
|
2022-07-25 22:25:04 +08:00
|
|
|
const wcstring name_esc = escape_string(name);
|
2019-10-20 17:38:17 +08:00
|
|
|
wcstring cmd = format_string(L"__fish_print_help %ls ", name_esc.c_str());
|
|
|
|
io_chain_t ios;
|
2023-01-01 02:13:03 +08:00
|
|
|
if (!error_message.empty()) {
|
|
|
|
cmd.append(escape_string(error_message));
|
2019-10-20 17:38:17 +08:00
|
|
|
// If it's an error, redirect the output of __fish_print_help to stderr
|
2019-12-20 06:14:23 +08:00
|
|
|
ios.push_back(std::make_shared<io_fd_t>(STDOUT_FILENO, STDERR_FILENO));
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2021-03-16 05:59:37 +08:00
|
|
|
auto res = parser.eval(cmd, ios);
|
2021-09-22 08:45:18 +08:00
|
|
|
if (res.status.normal_exited() && res.status.exit_code() == 2) {
|
2021-03-20 00:20:30 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_MISSING_HELP, name_esc.c_str(), name_esc.c_str());
|
2021-03-16 05:59:37 +08:00
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
2006-06-21 05:20:16 +08:00
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Perform error reporting for encounter with unknown option.
|
2022-12-23 00:23:42 +08:00
|
|
|
void builtin_unknown_option(parser_t &parser, io_streams_t &streams, const wchar_t *cmd,
|
|
|
|
const wchar_t *opt, bool print_hints) {
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, opt);
|
2022-12-23 00:23:42 +08:00
|
|
|
if (print_hints) {
|
|
|
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
|
|
|
}
|
2007-01-21 22:55:27 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Perform error reporting for encounter with missing argument.
|
2022-12-23 00:23:42 +08:00
|
|
|
void builtin_missing_argument(parser_t &parser, io_streams_t &streams, const wchar_t *cmd,
|
|
|
|
const wchar_t *opt, bool print_hints) {
|
2020-01-09 00:33:36 +08:00
|
|
|
if (opt[0] == L'-' && opt[1] != L'-') {
|
2021-11-04 13:51:20 +08:00
|
|
|
// if c in -qc '-qc' is missing the argument, now opt is just 'c'
|
2020-01-09 00:33:36 +08:00
|
|
|
opt += std::wcslen(opt) - 1;
|
2021-11-04 13:51:20 +08:00
|
|
|
// now prepend - to output -c
|
|
|
|
streams.err.append_format(BUILTIN_ERR_MISSING, cmd, wcstring(L"-").append(opt).c_str());
|
2021-11-09 03:36:01 +08:00
|
|
|
} else
|
2021-11-04 13:51:20 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_MISSING, cmd, opt);
|
2022-12-23 00:23:42 +08:00
|
|
|
|
|
|
|
if (print_hints) {
|
|
|
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
|
|
|
}
|
2019-03-27 02:13:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Print the backtrace and call for help that we use at the end of error messages.
|
|
|
|
void builtin_print_error_trailer(parser_t &parser, output_stream_t &b, const wchar_t *cmd) {
|
|
|
|
b.append(L"\n");
|
2019-03-27 02:29:44 +08:00
|
|
|
const wcstring stacktrace = parser.current_line();
|
|
|
|
// Don't print two empty lines if we don't have a stacktrace.
|
|
|
|
if (!stacktrace.empty()) {
|
|
|
|
b.append(stacktrace);
|
|
|
|
b.append(L"\n");
|
|
|
|
}
|
2019-03-27 02:13:01 +08:00
|
|
|
b.append_format(_(L"(Type 'help %ls' for related documentation)\n"), cmd);
|
2007-08-02 03:44:50 +08:00
|
|
|
}
|
|
|
|
|
2022-10-29 11:07:36 +08:00
|
|
|
/// A generic builtin that only supports showing a help message. This is only a placeholder that
|
2016-04-20 09:17:39 +08:00
|
|
|
/// prints the help message. Useful for commands that live in the parser.
|
2021-02-14 10:41:09 +08:00
|
|
|
static maybe_t<int> builtin_generic(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
|
2017-06-15 13:46:12 +08:00
|
|
|
const wchar_t *cmd = argv[0];
|
2016-04-19 10:25:12 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
2017-06-17 11:42:33 +08:00
|
|
|
help_only_cmd_opts_t opts;
|
2017-06-15 13:46:12 +08:00
|
|
|
int optind;
|
2022-12-23 00:23:42 +08:00
|
|
|
int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams);
|
2017-06-15 13:46:12 +08:00
|
|
|
if (retval != STATUS_CMD_OK) return retval;
|
|
|
|
|
|
|
|
if (opts.print_help) {
|
2019-10-20 17:38:17 +08:00
|
|
|
builtin_print_help(parser, streams, cmd);
|
2017-06-15 13:46:12 +08:00
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
// Hackish - if we have no arguments other than the command, we are a "naked invocation" and we
|
|
|
|
// just print help.
|
2020-10-27 02:13:34 +08:00
|
|
|
if (argc == 1 || wcscmp(cmd, L"time") == 0) {
|
2019-10-20 17:38:17 +08:00
|
|
|
builtin_print_help(parser, streams, cmd);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2014-01-13 07:10:59 +08:00
|
|
|
}
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-10-17 17:56:03 +08:00
|
|
|
|
2023-01-16 11:52:08 +08:00
|
|
|
static maybe_t<int> implemented_in_rust(parser_t &, io_streams_t &, const wchar_t **) {
|
|
|
|
DIE("builtin is implemented in Rust, this should not be called");
|
|
|
|
}
|
|
|
|
|
2018-01-04 07:42:12 +08:00
|
|
|
// How many bytes we read() at once.
|
|
|
|
// Since this is just for counting, it can be massive.
|
2022-09-21 02:58:37 +08:00
|
|
|
#define COUNT_CHUNK_SIZE (512 * 256)
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Implementation of the builtin count command, used to count the number of arguments sent to it.
|
2021-02-14 10:41:09 +08:00
|
|
|
static maybe_t<int> builtin_count(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(parser);
|
2018-01-04 07:42:12 +08:00
|
|
|
int argc = 0;
|
|
|
|
|
|
|
|
// Count the newlines coming in via stdin like `wc -l`.
|
|
|
|
if (streams.stdin_is_directly_redirected) {
|
2021-02-11 09:19:08 +08:00
|
|
|
assert(streams.stdin_fd >= 0 &&
|
|
|
|
"Should have a valid fd since stdin is directly redirected");
|
2018-01-04 07:42:12 +08:00
|
|
|
char buf[COUNT_CHUNK_SIZE];
|
|
|
|
while (true) {
|
|
|
|
long n = read_blocked(streams.stdin_fd, buf, COUNT_CHUNK_SIZE);
|
2021-02-11 09:19:08 +08:00
|
|
|
if (n == 0) {
|
|
|
|
break;
|
|
|
|
} else if (n < 0) {
|
|
|
|
wperror(L"read");
|
|
|
|
return STATUS_CMD_ERROR;
|
|
|
|
}
|
2018-01-04 07:42:12 +08:00
|
|
|
for (int i = 0; i < n; i++) {
|
2021-10-26 00:40:43 +08:00
|
|
|
if (buf[i] == '\n') {
|
2018-01-04 07:42:12 +08:00
|
|
|
argc++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always add the size of argv.
|
|
|
|
// That means if you call `something | count a b c`, you'll get the count of something _plus 3_.
|
|
|
|
argc += builtin_count_args(argv) - 1;
|
|
|
|
streams.out.append_format(L"%d\n", argc);
|
|
|
|
return argc == 0 ? STATUS_CMD_ERROR : STATUS_CMD_OK;
|
2007-08-01 05:23:32 +08:00
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// This function handles both the 'continue' and the 'break' builtins that are used for loop
|
|
|
|
/// control.
|
2020-11-22 21:39:48 +08:00
|
|
|
static maybe_t<int> builtin_break_continue(parser_t &parser, io_streams_t &streams,
|
2021-02-14 10:41:09 +08:00
|
|
|
const wchar_t **argv) {
|
2019-03-13 05:06:01 +08:00
|
|
|
int is_break = (std::wcscmp(argv[0], L"break") == 0);
|
2012-11-19 08:30:30 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
if (argc != 1) {
|
2019-10-20 17:38:17 +08:00
|
|
|
wcstring error_message = format_string(BUILTIN_ERR_UNKNOWN, argv[0], argv[1]);
|
2023-01-01 02:13:03 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], error_message);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2021-10-26 03:22:51 +08:00
|
|
|
// Paranoia: ensure we have a real loop.
|
2021-10-26 03:43:30 +08:00
|
|
|
// This is checked in the AST but we may be invoked dynamically, e.g. just via "eval break".
|
2021-10-26 03:22:51 +08:00
|
|
|
bool has_loop = false;
|
|
|
|
for (const auto &b : parser.blocks()) {
|
|
|
|
if (b.type() == block_type_t::while_block || b.type() == block_type_t::for_block) {
|
|
|
|
has_loop = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (b.is_function_call()) break;
|
|
|
|
}
|
|
|
|
if (!has_loop) {
|
|
|
|
wcstring error_message = format_string(_(L"%ls: Not inside of loop\n"), argv[0]);
|
2023-01-01 02:13:03 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], error_message);
|
2021-10-26 03:22:51 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
|
|
|
}
|
|
|
|
|
2019-05-19 14:12:34 +08:00
|
|
|
// Mark the status in the libdata.
|
|
|
|
parser.libdata().loop_status = is_break ? loop_status_t::breaks : loop_status_t::continues;
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Implementation of the builtin breakpoint command, used to launch the interactive debugger.
|
2021-02-14 10:41:09 +08:00
|
|
|
static maybe_t<int> builtin_breakpoint(parser_t &parser, io_streams_t &streams,
|
|
|
|
const wchar_t **argv) {
|
|
|
|
const wchar_t *cmd = argv[0];
|
2019-11-19 10:34:50 +08:00
|
|
|
if (argv[1] != nullptr) {
|
2017-06-20 12:05:34 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 0, builtin_count_args(argv) - 1);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-10-10 05:38:26 +08:00
|
|
|
}
|
|
|
|
|
2017-06-20 12:05:34 +08:00
|
|
|
// If we're not interactive then we can't enter the debugger. So treat this command as a no-op.
|
2019-05-28 05:52:48 +08:00
|
|
|
if (!parser.is_interactive()) {
|
2017-06-20 12:05:34 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-06-20 12:05:34 +08:00
|
|
|
// Ensure we don't allow creating a breakpoint at an interactive prompt. There may be a simpler
|
|
|
|
// or clearer way to do this but this works.
|
|
|
|
const block_t *block1 = parser.block_at_index(1);
|
2019-12-23 07:37:14 +08:00
|
|
|
if (!block1 || block1->type() == block_type_t::breakpoint) {
|
2017-06-20 12:05:34 +08:00
|
|
|
streams.err.append_format(_(L"%ls: Command not valid at an interactive prompt\n"), cmd);
|
|
|
|
return STATUS_ILLEGAL_CMD;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2019-05-20 05:40:06 +08:00
|
|
|
const block_t *bpb = parser.push_block(block_t::breakpoint_block());
|
2019-05-27 09:51:26 +08:00
|
|
|
reader_read(parser, STDIN_FILENO, streams.io_chain ? *streams.io_chain : io_chain_t());
|
2017-01-22 07:35:35 +08:00
|
|
|
parser.pop_block(bpb);
|
2019-05-13 05:00:44 +08:00
|
|
|
return parser.get_last_status();
|
2006-11-11 18:54:00 +08:00
|
|
|
}
|
|
|
|
|
2021-10-31 18:51:16 +08:00
|
|
|
static maybe_t<int> builtin_true(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(parser);
|
|
|
|
UNUSED(streams);
|
2020-04-05 02:34:42 +08:00
|
|
|
UNUSED(argv);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2014-09-30 04:26:28 +08:00
|
|
|
}
|
|
|
|
|
2021-10-31 18:51:16 +08:00
|
|
|
static maybe_t<int> builtin_false(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(parser);
|
|
|
|
UNUSED(streams);
|
2020-04-05 02:34:42 +08:00
|
|
|
UNUSED(argv);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2014-09-30 04:26:28 +08:00
|
|
|
}
|
|
|
|
|
2021-10-31 18:51:16 +08:00
|
|
|
static maybe_t<int> builtin_gettext(parser_t &parser, io_streams_t &streams, const wchar_t **argv) {
|
2020-05-30 02:53:44 +08:00
|
|
|
UNUSED(parser);
|
|
|
|
for (int i = 1; i < builtin_count_args(argv); i++) {
|
|
|
|
streams.out.append(_(argv[i]));
|
|
|
|
}
|
|
|
|
return STATUS_CMD_OK;
|
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
// END OF BUILTIN COMMANDS
|
|
|
|
// Below are functions for handling the builtin commands.
|
2018-09-29 12:22:24 +08:00
|
|
|
// THESE MUST BE SORTED BY NAME! Completion lookup uses binary search.
|
2005-12-15 21:59:02 +08:00
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
// Data about all the builtin commands in fish.
|
|
|
|
// Functions that are bound to builtin_generic are handled directly by the parser.
|
2018-09-29 12:22:24 +08:00
|
|
|
// NOTE: These must be kept in sorted order!
|
2021-08-27 04:40:02 +08:00
|
|
|
static constexpr builtin_data_t builtin_datas[] = {
|
2020-03-28 16:39:12 +08:00
|
|
|
{L".", &builtin_source, N_(L"Evaluate contents of file")},
|
2020-03-28 06:42:44 +08:00
|
|
|
{L":", &builtin_true, N_(L"Return a successful result")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"[", &builtin_test, N_(L"Test a condition")},
|
2020-05-30 02:53:44 +08:00
|
|
|
{L"_", &builtin_gettext, N_(L"Translate a string")},
|
2023-02-25 00:00:05 +08:00
|
|
|
{L"abbr", &implemented_in_rust, N_(L"Manage abbreviations")},
|
2022-01-28 08:21:22 +08:00
|
|
|
{L"and", &builtin_generic, N_(L"Run command if last command succeeded")},
|
2017-07-08 05:32:41 +08:00
|
|
|
{L"argparse", &builtin_argparse, N_(L"Parse options in fish script")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"begin", &builtin_generic, N_(L"Create a block of code")},
|
2023-03-01 06:42:12 +08:00
|
|
|
{L"bg", &implemented_in_rust, N_(L"Send job to background")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"bind", &builtin_bind, N_(L"Handle fish key bindings")},
|
2023-02-25 03:21:27 +08:00
|
|
|
{L"block", &implemented_in_rust, N_(L"Temporarily block delivery of events")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"break", &builtin_break_continue, N_(L"Stop the innermost loop")},
|
2022-01-23 13:18:24 +08:00
|
|
|
{L"breakpoint", &builtin_breakpoint, N_(L"Halt execution and start debug prompt")},
|
2022-01-28 08:21:22 +08:00
|
|
|
{L"builtin", &builtin_builtin, N_(L"Run a builtin specifically")},
|
|
|
|
{L"case", &builtin_generic, N_(L"Block of code to run conditionally")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"cd", &builtin_cd, N_(L"Change working directory")},
|
2022-01-28 08:21:22 +08:00
|
|
|
{L"command", &builtin_command, N_(L"Run a command specifically")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"commandline", &builtin_commandline, N_(L"Set or get the commandline")},
|
|
|
|
{L"complete", &builtin_complete, N_(L"Edit command specific completions")},
|
2023-02-21 01:57:02 +08:00
|
|
|
{L"contains", &implemented_in_rust, N_(L"Search for a specified string in a list")},
|
2022-01-23 13:01:25 +08:00
|
|
|
{L"continue", &builtin_break_continue, N_(L"Skip over remaining innermost loop")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"count", &builtin_count, N_(L"Count the number of arguments")},
|
2017-03-23 08:50:57 +08:00
|
|
|
{L"disown", &builtin_disown, N_(L"Remove job from job list")},
|
2023-02-06 05:08:32 +08:00
|
|
|
{L"echo", &implemented_in_rust, N_(L"Print arguments")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"else", &builtin_generic, N_(L"Evaluate block if condition is false")},
|
2023-02-11 01:19:22 +08:00
|
|
|
{L"emit", &implemented_in_rust, N_(L"Emit an event")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"end", &builtin_generic, N_(L"End a block of commands")},
|
2019-04-12 19:53:08 +08:00
|
|
|
{L"eval", &builtin_eval, N_(L"Evaluate a string as a statement")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"exec", &builtin_generic, N_(L"Run command in current process")},
|
2023-02-19 00:13:58 +08:00
|
|
|
{L"exit", &implemented_in_rust, N_(L"Exit the shell")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"false", &builtin_false, N_(L"Return an unsuccessful result")},
|
|
|
|
{L"fg", &builtin_fg, N_(L"Send job to foreground")},
|
|
|
|
{L"for", &builtin_generic, N_(L"Perform a set of commands multiple times")},
|
|
|
|
{L"function", &builtin_generic, N_(L"Define a new function")},
|
|
|
|
{L"functions", &builtin_functions, N_(L"List or remove functions")},
|
|
|
|
{L"history", &builtin_history, N_(L"History of commands executed by user")},
|
|
|
|
{L"if", &builtin_generic, N_(L"Evaluate block if condition is true")},
|
|
|
|
{L"jobs", &builtin_jobs, N_(L"Print currently running jobs")},
|
2017-08-23 10:57:30 +08:00
|
|
|
{L"math", &builtin_math, N_(L"Evaluate math expressions")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"not", &builtin_generic, N_(L"Negate exit status of job")},
|
|
|
|
{L"or", &builtin_generic, N_(L"Execute command if previous command failed")},
|
Add "path" builtin
This adds a "path" builtin that can handle paths.
Implemented so far:
- "path filter PATHS", filters paths according to existence and optionally type and permissions
- "path base" and "path dir", run basename and dirname, respectively
- "path extension PATHS", prints the extension, if any
- "path strip-extension", prints the path without the extension
- "path normalize PATHS", normalizes paths - removing "/./" components
- and such.
- "path real", does realpath - i.e. normalizing *and* link resolution.
Some of these - base, dir, {strip-,}extension and normalize operate on the paths only as strings, so they handle nonexistent paths. filter and real ignore any nonexistent paths.
All output is split explicitly, so paths with newlines in them are
handled correctly. Alternatively, all subcommands have a "--null-input"/"-z" and "--null-output"/"-Z" option to handle null-terminated input and create null-terminated output. So
find . -print0 | path base -z
prints the basename of all files in the current directory,
recursively.
With "-Z" it also prints it null-separated.
(if stdout is going to a command substitution, we probably want to
skip this)
All subcommands also have a "-q"/"--quiet" flag that tells them to skip output. They return true "when something happened". For match/filter that's when a file passed, for "base"/"dir"/"extension"/"strip-extension" that's when something about the path *changed*.
Filtering
---------
`filter` supports all the file*types* `test` has - "dir", "file", "link", "block"..., as well as the permissions - "read", "write", "exec" and things like "suid".
It is missing the tty check and the check for the file being non-empty. The former is best done via `isatty`, the latter I don't think I've ever seen used.
There currently is no way to only get "real" files, i.e. ignore links pointing to files.
Examples
--------
> path real /bin///sh
/usr/bin/bash
> path extension foo.mp4
mp4
> path extension ~/.config
(nothing, because ".config" isn't an extension.)
2021-08-28 20:45:24 +08:00
|
|
|
{L"path", &builtin_path, N_(L"Handle paths")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"printf", &builtin_printf, N_(L"Prints formatted text")},
|
2023-03-01 13:05:27 +08:00
|
|
|
{L"pwd", &implemented_in_rust, N_(L"Print the working directory")},
|
2023-02-19 05:06:05 +08:00
|
|
|
{L"random", &implemented_in_rust, N_(L"Generate random number")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"read", &builtin_read, N_(L"Read a line of input into variables")},
|
2023-03-06 10:38:41 +08:00
|
|
|
{L"realpath", &implemented_in_rust, N_(L"Show absolute path sans symlinks")},
|
2023-02-19 00:13:58 +08:00
|
|
|
{L"return", &implemented_in_rust, N_(L"Stop the currently evaluated function")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"set", &builtin_set, N_(L"Handle environment variables")},
|
|
|
|
{L"set_color", &builtin_set_color, N_(L"Set the terminal color")},
|
|
|
|
{L"source", &builtin_source, N_(L"Evaluate contents of file")},
|
|
|
|
{L"status", &builtin_status, N_(L"Return status information about fish")},
|
|
|
|
{L"string", &builtin_string, N_(L"Manipulate strings")},
|
2022-01-28 08:21:22 +08:00
|
|
|
{L"switch", &builtin_generic, N_(L"Conditionally run blocks of code")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"test", &builtin_test, N_(L"Test a condition")},
|
2020-02-24 06:42:31 +08:00
|
|
|
{L"time", &builtin_generic, N_(L"Measure how long a command or block takes")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"true", &builtin_true, N_(L"Return a successful result")},
|
2020-09-21 23:45:35 +08:00
|
|
|
{L"type", &builtin_type, N_(L"Check if a thing is a thing")},
|
2022-01-23 13:21:21 +08:00
|
|
|
{L"ulimit", &builtin_ulimit, N_(L"Get/set resource usage limits")},
|
2023-01-16 11:52:08 +08:00
|
|
|
{L"wait", &implemented_in_rust, N_(L"Wait for background processes completed")},
|
2020-05-30 02:53:44 +08:00
|
|
|
{L"while", &builtin_generic, N_(L"Perform a command multiple times")},
|
|
|
|
};
|
2021-08-27 04:40:02 +08:00
|
|
|
ASSERT_SORTED_BY_NAME(builtin_datas);
|
2012-02-01 11:47:56 +08:00
|
|
|
|
2022-09-21 02:58:37 +08:00
|
|
|
#define BUILTIN_COUNT (sizeof builtin_datas / sizeof *builtin_datas)
|
2018-09-29 12:22:24 +08:00
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Look up a builtin_data_t for a specified builtin
|
|
|
|
///
|
|
|
|
/// @param name
|
|
|
|
/// Name of the builtin
|
|
|
|
///
|
|
|
|
/// @return
|
|
|
|
/// Pointer to a builtin_data_t
|
|
|
|
///
|
2016-04-19 10:25:12 +08:00
|
|
|
static const builtin_data_t *builtin_lookup(const wcstring &name) {
|
2021-08-27 04:40:02 +08:00
|
|
|
return get_by_sorted_name(name.c_str(), builtin_datas);
|
2006-02-05 21:08:40 +08:00
|
|
|
}
|
2006-01-31 03:53:10 +08:00
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Is there a builtin command with the given name?
|
2016-10-24 04:58:12 +08:00
|
|
|
bool builtin_exists(const wcstring &cmd) { return static_cast<bool>(builtin_lookup(cmd)); }
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2017-07-20 02:44:53 +08:00
|
|
|
/// Is the command a keyword we need to special-case the handling of `-h` and `--help`.
|
2018-10-01 07:57:05 +08:00
|
|
|
static const wchar_t *const help_builtins[] = {L"for", L"while", L"function", L"if",
|
|
|
|
L"end", L"switch", L"case"};
|
2021-02-15 05:15:29 +08:00
|
|
|
static bool cmd_needs_help(const wcstring &cmd) { return contains(help_builtins, cmd); }
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Execute a builtin command
|
2021-02-15 05:15:29 +08:00
|
|
|
proc_status_t builtin_run(parser_t &parser, const wcstring_list_t &argv, io_streams_t &streams) {
|
|
|
|
if (argv.empty()) return proc_status_t::from_exit_code(STATUS_INVALID_ARGS);
|
|
|
|
const wcstring &cmdname = argv.front();
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-06-19 13:07:48 +08:00
|
|
|
// We can be handed a keyword by the parser as if it was a command. This happens when the user
|
|
|
|
// follows the keyword by `-h` or `--help`. Since it isn't really a builtin command we need to
|
|
|
|
// handle displaying help for it here.
|
2021-02-15 05:15:29 +08:00
|
|
|
if (argv.size() == 2 && parse_util_argument_is_help(argv[1]) && cmd_needs_help(cmdname)) {
|
|
|
|
builtin_print_help(parser, streams, cmdname.c_str());
|
2019-02-26 02:05:42 +08:00
|
|
|
return proc_status_t::from_exit_code(STATUS_CMD_OK);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2023-02-06 06:52:25 +08:00
|
|
|
maybe_t<int> builtin_ret;
|
|
|
|
|
|
|
|
auto rust_builtin = try_get_rust_builtin(cmdname);
|
|
|
|
if (rust_builtin.has_value()) {
|
|
|
|
builtin_ret = builtin_run_rust(parser, streams, argv, *rust_builtin);
|
|
|
|
} else if (const builtin_data_t *data = builtin_lookup(cmdname)) {
|
2021-04-04 06:36:04 +08:00
|
|
|
// Construct the permutable argv array which the builtin expects, and execute the builtin.
|
2021-02-15 05:15:29 +08:00
|
|
|
null_terminated_array_t<wchar_t> argv_arr(argv);
|
2023-02-06 06:52:25 +08:00
|
|
|
builtin_ret = data->func(parser, streams, argv_arr.get());
|
|
|
|
} else {
|
|
|
|
FLOGF(error, UNKNOWN_BUILTIN_ERR_MSG, cmdname.c_str());
|
|
|
|
return proc_status_t::from_exit_code(STATUS_CMD_ERROR);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-05 06:19:47 +08:00
|
|
|
|
2023-02-06 06:52:25 +08:00
|
|
|
// Flush our out and error streams, and check for their errors.
|
|
|
|
int out_ret = streams.out.flush_and_check_error();
|
|
|
|
int err_ret = streams.err.flush_and_check_error();
|
|
|
|
|
|
|
|
// Resolve our status code.
|
|
|
|
// If the builtin itself produced an error, use that error.
|
|
|
|
// Otherwise use any errors from writing to out and writing to err, in that order.
|
|
|
|
int code = builtin_ret.has_value() ? *builtin_ret : 0;
|
|
|
|
if (code == 0) code = out_ret;
|
|
|
|
if (code == 0) code = err_ret;
|
|
|
|
|
|
|
|
// The exit code is cast to an 8-bit unsigned integer, so saturate to 255. Otherwise,
|
|
|
|
// multiples of 256 are reported as 0.
|
|
|
|
if (code > 255) code = 255;
|
|
|
|
|
|
|
|
// Handle the case of an empty status.
|
|
|
|
if (code == 0 && !builtin_ret.has_value()) {
|
|
|
|
return proc_status_t::empty();
|
|
|
|
}
|
|
|
|
if (code < 0) {
|
|
|
|
FLOGF(warning, "builtin %ls returned invalid exit code %d", cmdname.c_str(), code);
|
|
|
|
}
|
|
|
|
return proc_status_t::from_exit_code(code);
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Returns a list of all builtin names.
|
2018-02-19 10:33:04 +08:00
|
|
|
wcstring_list_t builtin_get_names() {
|
2012-02-01 11:47:56 +08:00
|
|
|
wcstring_list_t result;
|
2018-09-29 12:22:24 +08:00
|
|
|
result.reserve(BUILTIN_COUNT);
|
2019-11-20 05:46:47 +08:00
|
|
|
for (const auto &builtin_data : builtin_datas) {
|
|
|
|
result.push_back(builtin_data.name);
|
2012-02-01 11:47:56 +08:00
|
|
|
}
|
|
|
|
return result;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Insert all builtin names into list.
|
2020-01-16 08:13:41 +08:00
|
|
|
void builtin_get_names(completion_list_t *list) {
|
2019-11-19 10:34:50 +08:00
|
|
|
assert(list != nullptr);
|
2018-09-29 12:22:24 +08:00
|
|
|
list->reserve(list->size() + BUILTIN_COUNT);
|
2019-11-20 05:46:47 +08:00
|
|
|
for (const auto &builtin_data : builtin_datas) {
|
|
|
|
append_completion(list, builtin_data.name);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-01-17 00:56:47 +08:00
|
|
|
}
|
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Return a one-line description of the specified builtin.
|
2019-03-15 06:12:14 +08:00
|
|
|
const wchar_t *builtin_get_desc(const wcstring &name) {
|
2019-03-15 12:45:31 +08:00
|
|
|
const wchar_t *result = L"";
|
2012-11-19 08:30:30 +08:00
|
|
|
const builtin_data_t *builtin = builtin_lookup(name);
|
2016-04-19 10:25:12 +08:00
|
|
|
if (builtin) {
|
2018-09-29 12:22:24 +08:00
|
|
|
result = _(builtin->desc);
|
2012-05-18 10:37:46 +08:00
|
|
|
}
|
|
|
|
return result;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
2023-01-16 11:52:08 +08:00
|
|
|
|
|
|
|
static maybe_t<RustBuiltin> try_get_rust_builtin(const wcstring &cmd) {
|
2023-02-25 00:00:05 +08:00
|
|
|
if (cmd == L"abbr") {
|
|
|
|
return RustBuiltin::Abbr;
|
|
|
|
}
|
2023-03-01 06:42:12 +08:00
|
|
|
if (cmd == L"bg") {
|
|
|
|
return RustBuiltin::Bg;
|
|
|
|
}
|
2023-02-25 03:21:27 +08:00
|
|
|
if (cmd == L"block") {
|
|
|
|
return RustBuiltin::Block;
|
|
|
|
}
|
2023-02-21 01:57:02 +08:00
|
|
|
if (cmd == L"contains") {
|
|
|
|
return RustBuiltin::Contains;
|
|
|
|
}
|
2023-02-06 05:08:32 +08:00
|
|
|
if (cmd == L"echo") {
|
|
|
|
return RustBuiltin::Echo;
|
|
|
|
}
|
2023-02-11 01:19:22 +08:00
|
|
|
if (cmd == L"emit") {
|
|
|
|
return RustBuiltin::Emit;
|
|
|
|
}
|
2023-02-19 00:13:58 +08:00
|
|
|
if (cmd == L"exit") {
|
|
|
|
return RustBuiltin::Exit;
|
|
|
|
}
|
2023-03-01 13:05:27 +08:00
|
|
|
if (cmd == L"pwd") {
|
|
|
|
return RustBuiltin::Pwd;
|
|
|
|
}
|
2023-02-19 05:06:05 +08:00
|
|
|
if (cmd == L"random") {
|
|
|
|
return RustBuiltin::Random;
|
|
|
|
}
|
2023-03-06 10:38:41 +08:00
|
|
|
if (cmd == L"realpath") {
|
|
|
|
return RustBuiltin::Realpath;
|
|
|
|
}
|
2023-01-16 11:52:08 +08:00
|
|
|
if (cmd == L"wait") {
|
|
|
|
return RustBuiltin::Wait;
|
|
|
|
}
|
2023-02-19 00:13:58 +08:00
|
|
|
if (cmd == L"return") {
|
|
|
|
return RustBuiltin::Return;
|
|
|
|
}
|
2023-01-16 11:52:08 +08:00
|
|
|
return none();
|
|
|
|
}
|
|
|
|
|
2023-02-06 06:52:58 +08:00
|
|
|
static maybe_t<int> builtin_run_rust(parser_t &parser, io_streams_t &streams,
|
|
|
|
const wcstring_list_t &argv, RustBuiltin builtin) {
|
2023-01-16 11:52:08 +08:00
|
|
|
::rust::Vec<wcharz_t> rust_argv;
|
|
|
|
for (const wcstring &arg : argv) {
|
|
|
|
rust_argv.emplace_back(arg.c_str());
|
|
|
|
}
|
2023-02-06 06:52:58 +08:00
|
|
|
|
|
|
|
int status_code;
|
|
|
|
bool update_status = rust_run_builtin(parser, streams, rust_argv, builtin, status_code);
|
|
|
|
if (update_status) {
|
|
|
|
return status_code;
|
|
|
|
} else {
|
|
|
|
return none();
|
|
|
|
}
|
2023-01-16 11:52:08 +08:00
|
|
|
}
|