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:
|
|
|
|
//
|
2017-03-22 19:43:13 +08:00
|
|
|
// <tt>static 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.
|
|
|
|
//
|
|
|
|
// 3). Create a file doc_src/NAME.txt, containing the manual for the builtin in Doxygen-format.
|
|
|
|
// Check the other builtin manuals for proper syntax.
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
|
|
|
#include <errno.h>
|
2017-02-14 12:37:27 +08:00
|
|
|
#include <stdio.h>
|
2016-04-19 10:25:12 +08:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <wchar.h>
|
2017-02-14 12:37:27 +08:00
|
|
|
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <algorithm>
|
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
|
|
|
|
|
|
|
#include "builtin.h"
|
2017-06-13 08:19:13 +08:00
|
|
|
#include "builtin_bind.h"
|
2017-06-13 09:22:57 +08:00
|
|
|
#include "builtin_block.h"
|
2017-06-14 12:40:53 +08:00
|
|
|
#include "builtin_cd.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "builtin_commandline.h"
|
|
|
|
#include "builtin_complete.h"
|
2017-06-14 09:27:03 +08:00
|
|
|
#include "builtin_disown.h"
|
2017-06-14 08:48:47 +08:00
|
|
|
#include "builtin_echo.h"
|
2017-06-13 09:40:58 +08:00
|
|
|
#include "builtin_emit.h"
|
2017-06-13 10:39:16 +08:00
|
|
|
#include "builtin_functions.h"
|
2017-06-13 12:11:42 +08:00
|
|
|
#include "builtin_history.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "builtin_jobs.h"
|
|
|
|
#include "builtin_printf.h"
|
2017-06-13 13:26:24 +08:00
|
|
|
#include "builtin_random.h"
|
2017-06-13 12:34:24 +08:00
|
|
|
#include "builtin_read.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "builtin_set.h"
|
|
|
|
#include "builtin_set_color.h"
|
2017-06-14 12:20:21 +08:00
|
|
|
#include "builtin_source.h"
|
2017-06-13 12:34:24 +08:00
|
|
|
#include "builtin_status.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "builtin_string.h"
|
|
|
|
#include "builtin_test.h"
|
|
|
|
#include "builtin_ulimit.h"
|
|
|
|
#include "common.h"
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "complete.h"
|
|
|
|
#include "env.h"
|
2006-11-18 00:24:38 +08:00
|
|
|
#include "exec.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2016-04-19 10:25:12 +08:00
|
|
|
#include "intern.h"
|
2016-04-20 10:49:15 +08:00
|
|
|
#include "io.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"
|
2006-10-19 19:50:23 +08:00
|
|
|
#include "path.h"
|
2016-04-19 10:25:12 +08:00
|
|
|
#include "proc.h"
|
|
|
|
#include "reader.h"
|
|
|
|
#include "tokenizer.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
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
bool builtin_data_t::operator<(const wcstring &other) const {
|
2012-02-01 12:22:25 +08:00
|
|
|
return wcscmp(this->name, other.c_str()) < 0;
|
2012-02-01 11:47:56 +08:00
|
|
|
}
|
2006-02-05 21:08:40 +08:00
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
bool builtin_data_t::operator<(const builtin_data_t *other) const {
|
2012-02-01 11:47:56 +08:00
|
|
|
return wcscmp(this->name, other->name) < 0;
|
|
|
|
}
|
2005-09-20 21:26:39 +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;
|
2016-10-05 11:06:14 +08:00
|
|
|
for (argc = 1; argv[argc] != NULL;) {
|
2016-09-11 05:55:27 +08:00
|
|
|
argc++;
|
|
|
|
}
|
2016-08-17 06:30:49 +08:00
|
|
|
|
|
|
|
assert(argv[argc] == NULL);
|
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.
|
2016-04-21 14:00:54 +08:00
|
|
|
void builtin_wperror(const wchar_t *s, io_streams_t &streams) {
|
2015-09-22 02:24:49 +08:00
|
|
|
char *err = strerror(errno);
|
2016-04-19 10:25:12 +08:00
|
|
|
if (s != NULL) {
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append(s);
|
|
|
|
streams.err.append(L": ");
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
if (err != NULL) {
|
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
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Count the number of times the specified character occurs in the specified string.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int count_char(const wchar_t *str, wchar_t c) {
|
2012-11-19 08:30:30 +08:00
|
|
|
int res = 0;
|
2016-04-19 10:25:12 +08:00
|
|
|
for (; *str; str++) {
|
|
|
|
res += (*str == c);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
return res;
|
2006-05-26 19:24:02 +08:00
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Obtain help/usage information for the specified builtin from manpage in subshell
|
|
|
|
///
|
|
|
|
/// @param name
|
|
|
|
/// builtin name to get up help for
|
|
|
|
///
|
|
|
|
/// @return
|
|
|
|
/// A wcstring with a formatted manpage.
|
|
|
|
///
|
2016-04-19 10:25:12 +08:00
|
|
|
wcstring builtin_help_get(parser_t &parser, io_streams_t &streams, const wchar_t *name) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(parser);
|
2016-04-19 10:25:12 +08:00
|
|
|
// This won't ever work if no_exec is set.
|
|
|
|
if (no_exec) return wcstring();
|
2013-05-05 17:33:17 +08:00
|
|
|
|
2012-01-01 07:57:30 +08:00
|
|
|
wcstring_list_t lst;
|
2012-02-01 12:22:25 +08:00
|
|
|
wcstring out;
|
|
|
|
const wcstring name_esc = escape_string(name, 1);
|
2014-10-10 15:11:23 +08:00
|
|
|
wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str());
|
2016-04-19 10:25:12 +08:00
|
|
|
if (!streams.out_is_redirected && isatty(STDOUT_FILENO)) {
|
2014-10-10 15:11:23 +08:00
|
|
|
// since we're using a subshell, __fish_print_help can't tell we're in
|
|
|
|
// a terminal. Tell it ourselves.
|
|
|
|
int cols = common_get_width();
|
|
|
|
cmd = format_string(L"__fish_print_help --tty-width %d %ls", cols, name_esc.c_str());
|
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
if (exec_subshell(cmd, lst, false /* don't apply exit status */) >= 0) {
|
|
|
|
for (size_t i = 0; i < lst.size(); i++) {
|
2012-02-01 12:22:25 +08:00
|
|
|
out.append(lst.at(i));
|
|
|
|
out.push_back(L'\n');
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return out;
|
2006-11-18 00:24:38 +08:00
|
|
|
}
|
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Process and print for the specified builtin. If @c b is `sb_err`, also print the line
|
|
|
|
/// information.
|
2016-04-20 09:17:39 +08:00
|
|
|
///
|
2016-08-17 06:30:49 +08:00
|
|
|
/// If @c b is the buffer representing standard error, and the help message is about to be printed
|
2016-04-20 09:17:39 +08:00
|
|
|
/// to an interactive screen, it may be shortened to fit the screen.
|
2016-08-17 06:30:49 +08:00
|
|
|
///
|
2016-04-19 10:25:12 +08:00
|
|
|
void builtin_print_help(parser_t &parser, io_streams_t &streams, const wchar_t *cmd,
|
|
|
|
output_stream_t &b) {
|
2016-05-04 12:31:32 +08:00
|
|
|
bool is_stderr = &b == &streams.err;
|
2016-04-19 10:25:12 +08:00
|
|
|
if (is_stderr) {
|
2015-09-22 02:24:49 +08:00
|
|
|
b.append(parser.current_line());
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
|
2015-09-22 02:24:49 +08:00
|
|
|
const wcstring h = builtin_help_get(parser, streams, cmd);
|
2016-04-19 10:25:12 +08:00
|
|
|
|
|
|
|
if (!h.size()) return;
|
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
wchar_t *str = wcsdup(h.c_str());
|
2016-04-19 10:25:12 +08:00
|
|
|
if (str) {
|
2014-01-13 05:33:35 +08:00
|
|
|
bool is_short = false;
|
2016-04-19 10:25:12 +08:00
|
|
|
if (is_stderr) {
|
|
|
|
// Interactive mode help to screen - only print synopsis if the rest won't fit.
|
2012-11-19 08:30:30 +08:00
|
|
|
int screen_height, lines;
|
2016-04-19 10:25:12 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
screen_height = common_get_height();
|
|
|
|
lines = count_char(str, L'\n');
|
2016-05-15 11:35:54 +08:00
|
|
|
if (!shell_is_interactive() || (lines > 2 * screen_height / 3)) {
|
2012-11-19 08:30:30 +08:00
|
|
|
wchar_t *pos;
|
2016-04-19 10:25:12 +08:00
|
|
|
int cut = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
int i;
|
2016-04-19 10:25:12 +08:00
|
|
|
|
2014-01-13 05:33:35 +08:00
|
|
|
is_short = true;
|
2016-04-19 10:25:12 +08:00
|
|
|
|
|
|
|
// First move down 4 lines.
|
2012-11-19 08:30:30 +08:00
|
|
|
pos = str;
|
2016-04-19 10:25:12 +08:00
|
|
|
for (i = 0; (i < 4) && pos && *pos; i++) {
|
|
|
|
pos = wcschr(pos + 1, L'\n');
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
|
|
|
|
if (pos && *pos) {
|
|
|
|
// Then find the next empty line.
|
|
|
|
for (; *pos; pos++) {
|
2016-10-31 10:17:08 +08:00
|
|
|
if (*pos != L'\n') {
|
|
|
|
continue;
|
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
|
2016-10-31 10:17:08 +08:00
|
|
|
int is_empty = 1;
|
|
|
|
wchar_t *pos2;
|
|
|
|
for (pos2 = pos + 1; *pos2; pos2++) {
|
|
|
|
if (*pos2 == L'\n') break;
|
2016-04-19 10:25:12 +08:00
|
|
|
|
2016-10-31 10:17:08 +08:00
|
|
|
if (*pos2 != L'\t' && *pos2 != L' ') {
|
|
|
|
is_empty = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-10-31 10:17:08 +08:00
|
|
|
if (is_empty) {
|
|
|
|
// And cut it.
|
|
|
|
*(pos2 + 1) = L'\0';
|
|
|
|
cut = 1;
|
|
|
|
break;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
|
|
|
|
// We did not find a good place to cut message to shorten it - so we make sure we
|
|
|
|
// don't print anything.
|
|
|
|
if (!cut) {
|
2012-11-19 08:30:30 +08:00
|
|
|
*str = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
|
2012-02-23 02:51:06 +08:00
|
|
|
b.append(str);
|
2016-04-19 10:25:12 +08:00
|
|
|
if (is_short) {
|
2015-09-22 02:24:49 +08:00
|
|
|
b.append_format(_(L"%ls: Type 'help %ls' for related documentation\n\n"), cmd, cmd);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
free(str);
|
|
|
|
}
|
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.
|
2016-04-21 14:00:54 +08:00
|
|
|
void builtin_unknown_option(parser_t &parser, io_streams_t &streams, const wchar_t *cmd,
|
|
|
|
const wchar_t *opt) {
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, opt);
|
|
|
|
builtin_print_help(parser, streams, cmd, streams.err);
|
2007-01-21 22:55:27 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Perform error reporting for encounter with missing argument.
|
2016-04-21 14:00:54 +08:00
|
|
|
void builtin_missing_argument(parser_t &parser, io_streams_t &streams, const wchar_t *cmd,
|
|
|
|
const wchar_t *opt) {
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_MISSING, cmd, opt);
|
|
|
|
builtin_print_help(parser, streams, cmd, streams.err);
|
2007-08-02 03:44:50 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// The builtin builtin, used for giving builtins precedence over functions. Mostly handled by the
|
|
|
|
/// parser. All this code does is some additional operational modes, such as printing a list of all
|
|
|
|
/// builtins, printing help, etc.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_builtin(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
|
|
|
int argc = builtin_count_args(argv);
|
|
|
|
int list = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-06-11 03:30:09 +08:00
|
|
|
static const wchar_t *short_options = L"hn";
|
2016-04-19 10:25:12 +08:00
|
|
|
static const struct woption long_options[] = {
|
|
|
|
{L"names", no_argument, 0, 'n'}, {L"help", no_argument, 0, 'h'}, {0, 0, 0, 0}};
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-06-11 03:30:09 +08:00
|
|
|
int opt;
|
|
|
|
wgetopter_t w;
|
|
|
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
2016-04-19 10:25:12 +08:00
|
|
|
switch (opt) {
|
|
|
|
case 'h': {
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.out);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
|
|
|
case 'n': {
|
|
|
|
list = 1;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
|
|
|
case '?': {
|
|
|
|
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
2016-10-30 08:25:48 +08:00
|
|
|
default: {
|
2017-06-11 03:30:09 +08:00
|
|
|
DIE("unexpected retval from wgetopt_long");
|
2016-10-30 08:25:48 +08:00
|
|
|
break;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-10-17 17:56:03 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
if (list) {
|
2012-11-19 08:30:30 +08:00
|
|
|
wcstring_list_t names = builtin_get_names();
|
|
|
|
sort(names.begin(), names.end());
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
for (size_t i = 0; i < names.size(); i++) {
|
2012-11-19 08:30:30 +08:00
|
|
|
const wchar_t *el = names.at(i).c_str();
|
|
|
|
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.out.append(el);
|
|
|
|
streams.out.append(L"\n");
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-10-17 17:56:03 +08:00
|
|
|
}
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Implementation of the builtin 'command'. Actual command running is handled by the parser, this
|
|
|
|
/// just processes the flags.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
|
|
|
int argc = builtin_count_args(argv);
|
2016-11-28 23:26:01 +08:00
|
|
|
bool find_path = false;
|
|
|
|
bool quiet = false;
|
2014-07-10 09:21:06 +08:00
|
|
|
|
2017-06-11 03:30:09 +08:00
|
|
|
static const wchar_t *short_options = L"hqsv";
|
2016-12-04 12:12:53 +08:00
|
|
|
static const struct woption long_options[] = {{L"quiet", no_argument, NULL, 'q'},
|
|
|
|
{L"search", no_argument, NULL, 's'},
|
|
|
|
{L"help", no_argument, NULL, 'h'},
|
|
|
|
{NULL, 0, NULL, 0}};
|
2014-07-10 09:21:06 +08:00
|
|
|
|
2017-06-11 03:30:09 +08:00
|
|
|
int opt;
|
|
|
|
wgetopter_t w;
|
|
|
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
2016-04-19 10:25:12 +08:00
|
|
|
switch (opt) {
|
|
|
|
case 'h': {
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.out);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
2014-07-11 10:16:32 +08:00
|
|
|
case 's':
|
2016-04-19 10:25:12 +08:00
|
|
|
case 'v': {
|
2016-11-28 23:26:01 +08:00
|
|
|
find_path = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'q': {
|
|
|
|
quiet = true;
|
2014-07-10 09:21:06 +08:00
|
|
|
break;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
|
|
|
case '?': {
|
|
|
|
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
2016-10-30 08:25:48 +08:00
|
|
|
default: {
|
2017-06-11 03:30:09 +08:00
|
|
|
DIE("unexpected retval from wgetopt_long");
|
2016-10-30 08:25:48 +08:00
|
|
|
break;
|
|
|
|
}
|
2014-07-10 09:21:06 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-28 23:26:01 +08:00
|
|
|
if (!find_path) {
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.out);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2014-07-10 09:21:06 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
int found = 0;
|
2014-07-10 09:21:06 +08:00
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
for (int idx = w.woptind; argv[idx]; ++idx) {
|
2014-07-10 09:21:06 +08:00
|
|
|
const wchar_t *command_name = argv[idx];
|
|
|
|
wcstring path;
|
2016-04-19 10:25:12 +08:00
|
|
|
if (path_get_path(command_name, &path)) {
|
2016-11-28 23:26:01 +08:00
|
|
|
if (!quiet) streams.out.append_format(L"%ls\n", path.c_str());
|
2014-07-10 09:21:06 +08:00
|
|
|
++found;
|
|
|
|
}
|
|
|
|
}
|
2017-05-04 15:18:02 +08:00
|
|
|
return found ? STATUS_CMD_OK : STATUS_CMD_ERROR;
|
2014-07-10 09:21:06 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// A generic bultin that only supports showing a help message. This is only a placeholder that
|
|
|
|
/// prints the help message. Useful for commands that live in the parser.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_generic(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
|
|
|
int argc = builtin_count_args(argv);
|
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.
|
|
|
|
if (argc == 1) {
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.out);
|
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-06-11 03:30:09 +08:00
|
|
|
static const wchar_t *short_options = L"h";
|
|
|
|
static const struct woption long_options[] = {{L"help", no_argument, NULL, 'h'},
|
|
|
|
{NULL, 0, NULL, 0}};
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-06-11 03:30:09 +08:00
|
|
|
int opt;
|
|
|
|
wgetopter_t w;
|
|
|
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
|
|
|
switch (opt) { //!OCLINT(too few branches)
|
2016-04-19 10:25:12 +08:00
|
|
|
case 'h': {
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.out);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
|
|
|
case '?': {
|
|
|
|
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
2016-10-30 08:25:48 +08:00
|
|
|
default: {
|
2017-06-11 03:30:09 +08:00
|
|
|
DIE("unexpected retval from wgetopt_long");
|
2016-10-30 08:25:48 +08:00
|
|
|
break;
|
|
|
|
}
|
2012-05-20 07:59:56 +08:00
|
|
|
}
|
2012-03-08 03:35:22 +08:00
|
|
|
}
|
2016-05-09 06:57:56 +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
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// The pwd builtin. We don't respect -P to resolve symbolic links because we
|
|
|
|
/// try to always resolve them.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_pwd(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(parser);
|
|
|
|
if (argv[1] != NULL) {
|
|
|
|
streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, argv[0], 0, builtin_count_args(argv));
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-10-10 05:38:26 +08:00
|
|
|
}
|
|
|
|
|
2016-03-11 10:17:39 +08:00
|
|
|
wcstring res = wgetcwd();
|
2016-04-19 10:25:12 +08:00
|
|
|
if (res.empty()) {
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-05 06:19:47 +08:00
|
|
|
streams.out.append(res);
|
|
|
|
streams.out.push_back(L'\n');
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// The exit builtin. Calls reader_exit to exit and returns the value specified.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2012-11-19 08:30:30 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
|
|
|
|
2016-10-23 11:32:25 +08:00
|
|
|
if (argc > 2) {
|
2016-11-17 14:00:33 +08:00
|
|
|
streams.err.append_format(_(L"%ls: Too many arguments\n"), argv[0]);
|
2016-10-23 11:32:25 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-10-23 11:32:25 +08:00
|
|
|
}
|
2012-11-19 16:31:03 +08:00
|
|
|
|
2016-10-23 11:32:25 +08:00
|
|
|
long ec;
|
|
|
|
if (argc == 1) {
|
|
|
|
ec = proc_get_last_status();
|
|
|
|
} else {
|
2016-11-23 12:24:03 +08:00
|
|
|
ec = fish_wcstol(argv[1]);
|
|
|
|
if (errno) {
|
2016-10-23 11:32:25 +08:00
|
|
|
streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0],
|
|
|
|
argv[1]);
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
reader_exit(1, 0);
|
|
|
|
return (int)ec;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Implementation of the builtin count command, used to count the number of arguments sent to it.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_count(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(parser);
|
2017-06-14 09:27:03 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
2016-04-19 10:25:12 +08:00
|
|
|
streams.out.append_format(L"%d\n", argc - 1);
|
2017-06-14 09:27:03 +08:00
|
|
|
return argc - 1 == 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
|
|
|
/// Implementation of the builtin contains command, used to check if a specified string is part of
|
|
|
|
/// a list.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_contains(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2017-06-11 03:30:09 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
2012-11-19 08:30:30 +08:00
|
|
|
wchar_t *needle;
|
2013-04-29 05:35:00 +08:00
|
|
|
bool should_output_index = false;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-06-11 03:30:09 +08:00
|
|
|
static const wchar_t *short_options = L"+hi";
|
|
|
|
static const struct woption long_options[] = {
|
|
|
|
{L"help", no_argument, NULL, 'h'}, {L"index", no_argument, NULL, 'i'}, {NULL, 0, NULL, 0}};
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-06-11 03:30:09 +08:00
|
|
|
int opt;
|
|
|
|
wgetopter_t w;
|
|
|
|
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
|
2016-04-19 10:25:12 +08:00
|
|
|
switch (opt) {
|
|
|
|
case 'h': {
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.out);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
|
|
|
case ':': {
|
|
|
|
builtin_missing_argument(parser, streams, argv[0], argv[w.woptind - 1]);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
|
|
|
case '?': {
|
|
|
|
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind - 1]);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
|
|
|
case 'i': {
|
2013-04-29 05:35:00 +08:00
|
|
|
should_output_index = true;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
2016-10-30 08:25:48 +08:00
|
|
|
default: {
|
2017-06-11 03:30:09 +08:00
|
|
|
DIE("unexpected retval from wgetopt_long");
|
2016-10-30 08:25:48 +08:00
|
|
|
break;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-26 09:16:00 +08:00
|
|
|
needle = argv[w.woptind];
|
2016-04-19 10:25:12 +08:00
|
|
|
if (!needle) {
|
2015-09-22 02:24:49 +08:00
|
|
|
streams.err.append_format(_(L"%ls: Key not specified\n"), argv[0]);
|
2016-04-19 10:25:12 +08:00
|
|
|
} else {
|
|
|
|
for (int i = w.woptind + 1; i < argc; i++) {
|
|
|
|
if (!wcscmp(needle, argv[i])) {
|
|
|
|
if (should_output_index) streams.out.append_format(L"%d\n", i - w.woptind);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_CMD_OK;
|
2016-03-04 10:49:12 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2007-08-02 06:53:18 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Builtin for putting a job in the foreground.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
|
|
|
job_t *j = NULL;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
if (argv[1] == 0) {
|
|
|
|
// Select last constructed job (I.e. first job in the job que) that is possible to put in
|
|
|
|
// the foreground.
|
2012-11-19 08:30:30 +08:00
|
|
|
job_iterator_t jobs;
|
2016-04-19 10:25:12 +08:00
|
|
|
while ((j = jobs.next())) {
|
2017-01-27 07:06:58 +08:00
|
|
|
if (j->get_flag(JOB_CONSTRUCTED) && (!job_is_completed(j)) &&
|
|
|
|
((job_is_stopped(j) || (!j->get_flag(JOB_FOREGROUND))) &&
|
|
|
|
j->get_flag(JOB_CONTROL))) {
|
2012-11-19 08:30:30 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
if (!j) {
|
|
|
|
streams.err.append_format(_(L"%ls: There are no suitable jobs\n"), argv[0]);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-19 10:25:12 +08:00
|
|
|
} else if (argv[2] != 0) {
|
2016-06-18 22:41:27 +08:00
|
|
|
// Specifying more than one job to put to the foreground is a syntax error, we still
|
2016-04-19 10:25:12 +08:00
|
|
|
// try to locate the job argv[1], since we want to know if this is an ambigous job
|
|
|
|
// specification or if this is an malformed job id.
|
2016-10-02 08:21:40 +08:00
|
|
|
int pid;
|
|
|
|
int found_job = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-11-23 12:24:03 +08:00
|
|
|
pid = fish_wcstoi(argv[1]);
|
|
|
|
if (!(errno || pid < 0)) {
|
2012-11-19 08:30:30 +08:00
|
|
|
j = job_get_from_pid(pid);
|
2016-10-02 08:21:40 +08:00
|
|
|
if (j) found_job = 1;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
if (found_job) {
|
|
|
|
streams.err.append_format(_(L"%ls: Ambiguous job\n"), argv[0]);
|
|
|
|
} else {
|
|
|
|
streams.err.append_format(_(L"%ls: '%ls' is not a job\n"), argv[0], argv[1]);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-10-02 08:21:40 +08:00
|
|
|
j = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
} else {
|
2016-11-23 12:24:03 +08:00
|
|
|
int pid = abs(fish_wcstoi(argv[1]));
|
|
|
|
if (errno) {
|
2016-04-19 10:25:12 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, argv[0], argv[1]);
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2016-04-19 10:25:12 +08:00
|
|
|
} else {
|
2012-11-19 08:30:30 +08:00
|
|
|
j = job_get_from_pid(pid);
|
2017-01-27 07:06:58 +08:00
|
|
|
if (!j || !j->get_flag(JOB_CONSTRUCTED) || job_is_completed(j)) {
|
2016-04-19 10:25:12 +08:00
|
|
|
streams.err.append_format(_(L"%ls: No suitable job: %d\n"), argv[0], pid);
|
2016-10-02 08:21:40 +08:00
|
|
|
j = 0;
|
2017-01-27 07:06:58 +08:00
|
|
|
} else if (!j->get_flag(JOB_CONTROL)) {
|
2016-04-19 10:25:12 +08:00
|
|
|
streams.err.append_format(_(L"%ls: Can't put job %d, '%ls' to foreground because "
|
|
|
|
L"it is not under job control\n"),
|
|
|
|
argv[0], pid, j->command_wcstr());
|
2016-10-02 08:21:40 +08:00
|
|
|
j = 0;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-31 10:17:08 +08:00
|
|
|
if (!j) {
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-10-31 10:17:08 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-10-31 10:17:08 +08:00
|
|
|
if (streams.err_is_redirected) {
|
|
|
|
streams.err.append_format(FG_MSG, j->job_id, j->command_wcstr());
|
|
|
|
} else {
|
|
|
|
// If we aren't redirecting, send output to real stderr, since stuff in sb_err won't get
|
|
|
|
// printed until the command finishes.
|
|
|
|
fwprintf(stderr, FG_MSG, j->job_id, j->command_wcstr());
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-10-31 10:17:08 +08:00
|
|
|
const wcstring ft = tok_first(j->command());
|
|
|
|
if (!ft.empty()) env_set(L"_", ft.c_str(), ENV_EXPORT);
|
|
|
|
reader_write_title(j->command());
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2017-01-27 06:47:32 +08:00
|
|
|
job_promote(j);
|
2017-01-27 07:06:58 +08:00
|
|
|
j->set_flag(JOB_FOREGROUND, true);
|
2016-10-31 10:17:08 +08:00
|
|
|
|
|
|
|
job_continue(j, job_is_stopped(j));
|
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
|
|
|
/// Helper function for builtin_bg().
|
2017-04-13 13:48:32 +08:00
|
|
|
static int send_to_bg(parser_t &parser, io_streams_t &streams, job_t *j) {
|
2017-04-02 23:02:55 +08:00
|
|
|
assert(j != NULL);
|
|
|
|
if (!j->get_flag(JOB_CONTROL)) {
|
2016-04-19 10:25:12 +08:00
|
|
|
streams.err.append_format(
|
|
|
|
_(L"%ls: Can't put job %d, '%ls' to background because it is not under job control\n"),
|
|
|
|
L"bg", j->job_id, j->command_wcstr());
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, L"bg", streams.err);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-05 06:19:47 +08:00
|
|
|
|
|
|
|
streams.err.append_format(_(L"Send job %d '%ls' to background\n"), j->job_id,
|
|
|
|
j->command_wcstr());
|
2017-01-27 06:47:32 +08:00
|
|
|
job_promote(j);
|
2017-01-27 07:06:58 +08:00
|
|
|
j->set_flag(JOB_FOREGROUND, false);
|
2012-11-19 08:30:30 +08:00
|
|
|
job_continue(j, job_is_stopped(j));
|
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
|
|
|
/// Builtin for putting a job in the background.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2017-05-04 15:18:02 +08:00
|
|
|
int res = STATUS_CMD_OK;
|
2006-01-31 03:53:10 +08:00
|
|
|
|
2017-04-13 13:48:32 +08:00
|
|
|
if (!argv[1]) {
|
|
|
|
// No jobs were specified so use the most recent (i.e., last) job.
|
2012-11-19 08:30:30 +08:00
|
|
|
job_t *j;
|
2012-01-30 08:36:21 +08:00
|
|
|
job_iterator_t jobs;
|
2016-04-19 10:25:12 +08:00
|
|
|
while ((j = jobs.next())) {
|
2017-01-27 07:06:58 +08:00
|
|
|
if (job_is_stopped(j) && j->get_flag(JOB_CONTROL) && (!job_is_completed(j))) {
|
2012-11-19 08:30:30 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
if (!j) {
|
|
|
|
streams.err.append_format(_(L"%ls: There are no suitable jobs\n"), argv[0]);
|
2017-05-04 15:18:02 +08:00
|
|
|
res = STATUS_CMD_ERROR;
|
2016-04-19 10:25:12 +08:00
|
|
|
} else {
|
2017-04-13 13:48:32 +08:00
|
|
|
res = send_to_bg(parser, streams, j);
|
2017-04-02 23:02:55 +08:00
|
|
|
}
|
2017-04-13 13:48:32 +08:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The user specified at least one job to be backgrounded.
|
|
|
|
std::vector<int> pids;
|
|
|
|
|
|
|
|
// If one argument is not a valid pid (i.e. integer >= 0), fail without backgrounding anything,
|
|
|
|
// but still print errors for all of them.
|
|
|
|
for (int i = 1; argv[i]; i++) {
|
|
|
|
int pid = fish_wcstoi(argv[i]);
|
|
|
|
if (errno || pid < 0) {
|
|
|
|
streams.err.append_format(_(L"%ls: '%ls' is not a valid job specifier\n"), L"bg",
|
2017-04-14 14:13:55 +08:00
|
|
|
argv[i]);
|
2017-05-05 12:35:41 +08:00
|
|
|
res = STATUS_INVALID_ARGS;
|
2017-04-02 23:02:55 +08:00
|
|
|
}
|
2017-04-13 13:48:32 +08:00
|
|
|
pids.push_back(pid);
|
|
|
|
}
|
2016-10-02 08:21:40 +08:00
|
|
|
|
2017-05-05 12:35:41 +08:00
|
|
|
if (res != STATUS_CMD_OK) return res;
|
2017-04-13 13:48:32 +08:00
|
|
|
|
|
|
|
// Background all existing jobs that match the pids.
|
|
|
|
// Non-existent jobs aren't an error, but information about them is useful.
|
|
|
|
for (auto p : pids) {
|
2017-04-14 14:13:55 +08:00
|
|
|
if (job_t *j = job_get_from_pid(p)) {
|
2017-04-13 13:48:32 +08:00
|
|
|
res |= send_to_bg(parser, streams, j);
|
|
|
|
} else {
|
|
|
|
streams.err.append_format(_(L"%ls: Could not find job '%d'\n"), argv[0], p);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
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.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_break_continue(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
|
|
|
int is_break = (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) {
|
|
|
|
streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], argv[1]);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
// Find the index of the enclosing for or while loop. Recall that incrementing loop_idx goes
|
|
|
|
// 'up' to outer blocks.
|
2013-12-21 09:41:21 +08:00
|
|
|
size_t loop_idx;
|
2016-04-19 10:25:12 +08:00
|
|
|
for (loop_idx = 0; loop_idx < parser.block_count(); loop_idx++) {
|
2013-12-21 09:41:21 +08:00
|
|
|
const block_t *b = parser.block_at_index(loop_idx);
|
2016-04-19 10:25:12 +08:00
|
|
|
if (b->type() == WHILE || b->type() == FOR) break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
if (loop_idx >= parser.block_count()) {
|
|
|
|
streams.err.append_format(_(L"%ls: Not inside of loop\n"), argv[0]);
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2017-01-22 06:15:03 +08:00
|
|
|
// Skip blocks interior to the loop (but not the loop itself)
|
2013-12-21 09:41:21 +08:00
|
|
|
size_t block_idx = loop_idx;
|
2016-04-19 10:25:12 +08:00
|
|
|
while (block_idx--) {
|
2013-12-21 09:41:21 +08:00
|
|
|
parser.block_at_index(block_idx)->skip = true;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2017-01-22 06:15:03 +08:00
|
|
|
// Mark the loop's status
|
2013-12-21 09:41:21 +08:00
|
|
|
block_t *loop_block = parser.block_at_index(loop_idx);
|
|
|
|
loop_block->loop_status = is_break ? LOOP_BREAK : LOOP_CONTINUE;
|
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.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_breakpoint(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2016-10-10 05:38:26 +08:00
|
|
|
if (argv[1] != NULL) {
|
|
|
|
streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, argv[0], 0, builtin_count_args(argv));
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-10-10 05:38:26 +08:00
|
|
|
}
|
|
|
|
|
2017-01-22 07:35:35 +08:00
|
|
|
const breakpoint_block_t *bpb = parser.push_block<breakpoint_block_t>();
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2015-09-22 02:24:49 +08:00
|
|
|
reader_read(STDIN_FILENO, streams.io_chain ? *streams.io_chain : io_chain_t());
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-01-22 07:35:35 +08:00
|
|
|
parser.pop_block(bpb);
|
2012-11-19 08:30:30 +08:00
|
|
|
|
|
|
|
return proc_get_last_status();
|
2006-11-11 18:54:00 +08:00
|
|
|
}
|
|
|
|
|
2016-04-20 09:17:39 +08:00
|
|
|
/// Function for handling the \c return builtin.
|
2016-04-19 10:25:12 +08:00
|
|
|
static int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2012-11-19 08:30:30 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
|
|
|
|
2016-10-23 11:32:25 +08:00
|
|
|
if (argc > 2) {
|
2016-11-18 06:53:50 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, argv[0]);
|
2016-10-23 11:32:25 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-10-23 11:32:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int status;
|
|
|
|
if (argc == 2) {
|
2016-11-23 12:24:03 +08:00
|
|
|
status = fish_wcstoi(argv[1]);
|
|
|
|
if (errno) {
|
2016-10-23 11:32:25 +08:00
|
|
|
streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"), argv[0],
|
|
|
|
argv[1]);
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-04-19 10:25:12 +08:00
|
|
|
}
|
2016-10-23 11:32:25 +08:00
|
|
|
} else {
|
|
|
|
status = proc_get_last_status();
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
// Find the function block.
|
2013-12-21 09:41:21 +08:00
|
|
|
size_t function_block_idx;
|
2016-04-19 10:25:12 +08:00
|
|
|
for (function_block_idx = 0; function_block_idx < parser.block_count(); function_block_idx++) {
|
2013-12-21 09:41:21 +08:00
|
|
|
const block_t *b = parser.block_at_index(function_block_idx);
|
2016-04-19 10:25:12 +08:00
|
|
|
if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW) break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
if (function_block_idx >= parser.block_count()) {
|
|
|
|
streams.err.append_format(_(L"%ls: Not inside of function\n"), argv[0]);
|
2015-09-22 02:24:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.err);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2017-01-22 06:15:03 +08:00
|
|
|
// Skip everything up to and including the function block.
|
|
|
|
for (size_t i = 0; i <= function_block_idx; i++) {
|
2013-12-21 09:41:21 +08:00
|
|
|
block_t *b = parser.block_at_index(i);
|
|
|
|
b->skip = true;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
return status;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
int builtin_true(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(parser);
|
|
|
|
UNUSED(streams);
|
|
|
|
if (argv[1] != NULL) {
|
2017-05-05 12:35:41 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, argv[0], 0, builtin_count_args(argv) - 1);
|
|
|
|
return STATUS_INVALID_ARGS;
|
2016-10-10 05:38:26 +08:00
|
|
|
}
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2014-09-30 04:26:28 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
int builtin_false(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(parser);
|
|
|
|
UNUSED(streams);
|
|
|
|
if (argv[1] != NULL) {
|
2017-05-05 12:35:41 +08:00
|
|
|
streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, argv[0], 0, builtin_count_args(argv) - 1);
|
|
|
|
return STATUS_INVALID_ARGS;
|
2016-10-10 05:38:26 +08:00
|
|
|
}
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2014-09-30 04:26:28 +08:00
|
|
|
}
|
|
|
|
|
2016-04-14 08:14:50 +08:00
|
|
|
/// An implementation of the external realpath command that doesn't support any options. It's meant
|
|
|
|
/// to be used only by scripts which need to be portable. In general scripts shouldn't invoke this
|
|
|
|
/// directly. They should just use `realpath` which will fallback to this builtin if an external
|
|
|
|
/// command cannot be found. This behaves like the external `realpath --canonicalize-existing`;
|
|
|
|
/// that is, it requires all path components, including the final, to exist.
|
2016-10-04 08:51:27 +08:00
|
|
|
int builtin_realpath(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
2016-04-14 08:14:50 +08:00
|
|
|
int argc = builtin_count_args(argv);
|
|
|
|
|
|
|
|
if (argc != 2) {
|
2016-08-17 06:30:49 +08:00
|
|
|
builtin_print_help(parser, streams, argv[0], streams.out);
|
2017-05-05 12:35:41 +08:00
|
|
|
return STATUS_INVALID_ARGS;
|
2016-04-14 08:14:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
wchar_t *real_path = wrealpath(argv[1], NULL);
|
|
|
|
if (real_path) {
|
|
|
|
streams.out.append(real_path);
|
|
|
|
free((void *)real_path);
|
|
|
|
} else {
|
2016-10-04 08:51:27 +08:00
|
|
|
// We don't actually know why it failed. We should check errno.
|
2016-04-14 08:14:50 +08:00
|
|
|
streams.err.append_format(_(L"%ls: Invalid path: %ls\n"), argv[0], argv[1]);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2016-04-14 08:14:50 +08:00
|
|
|
}
|
|
|
|
streams.out.append(L"\n");
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2016-04-14 08:14:50 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
// END OF BUILTIN COMMANDS
|
|
|
|
// Below are functions for handling the builtin commands.
|
|
|
|
// 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.
|
|
|
|
// NOTE: These must be kept in sorted order!
|
|
|
|
static const builtin_data_t builtin_datas[] = {
|
|
|
|
{L"[", &builtin_test, N_(L"Test a condition")},
|
|
|
|
{L"and", &builtin_generic, N_(L"Execute command if previous command suceeded")},
|
|
|
|
{L"begin", &builtin_generic, N_(L"Create a block of code")},
|
|
|
|
{L"bg", &builtin_bg, N_(L"Send job to background")},
|
|
|
|
{L"bind", &builtin_bind, N_(L"Handle fish key bindings")},
|
|
|
|
{L"block", &builtin_block, N_(L"Temporarily block delivery of events")},
|
|
|
|
{L"break", &builtin_break_continue, N_(L"Stop the innermost loop")},
|
|
|
|
{L"breakpoint", &builtin_breakpoint,
|
|
|
|
N_(L"Temporarily halt execution of a script and launch an interactive debug prompt")},
|
|
|
|
{L"builtin", &builtin_builtin, N_(L"Run a builtin command instead of a function")},
|
|
|
|
{L"case", &builtin_generic, N_(L"Conditionally execute a block of commands")},
|
|
|
|
{L"cd", &builtin_cd, N_(L"Change working directory")},
|
|
|
|
{L"command", &builtin_command, N_(L"Run a program instead of a function or builtin")},
|
|
|
|
{L"commandline", &builtin_commandline, N_(L"Set or get the commandline")},
|
|
|
|
{L"complete", &builtin_complete, N_(L"Edit command specific completions")},
|
|
|
|
{L"contains", &builtin_contains, N_(L"Search for a specified string in a list")},
|
|
|
|
{L"continue", &builtin_break_continue,
|
|
|
|
N_(L"Skip the rest of the current lap of the innermost loop")},
|
|
|
|
{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")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"echo", &builtin_echo, N_(L"Print arguments")},
|
|
|
|
{L"else", &builtin_generic, N_(L"Evaluate block if condition is false")},
|
|
|
|
{L"emit", &builtin_emit, N_(L"Emit an event")},
|
|
|
|
{L"end", &builtin_generic, N_(L"End a block of commands")},
|
|
|
|
{L"exec", &builtin_generic, N_(L"Run command in current process")},
|
|
|
|
{L"exit", &builtin_exit, N_(L"Exit the shell")},
|
|
|
|
{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")},
|
|
|
|
{L"not", &builtin_generic, N_(L"Negate exit status of job")},
|
|
|
|
{L"or", &builtin_generic, N_(L"Execute command if previous command failed")},
|
|
|
|
{L"printf", &builtin_printf, N_(L"Prints formatted text")},
|
|
|
|
{L"pwd", &builtin_pwd, N_(L"Print the working directory")},
|
|
|
|
{L"random", &builtin_random, N_(L"Generate random number")},
|
|
|
|
{L"read", &builtin_read, N_(L"Read a line of input into variables")},
|
2016-10-04 08:51:27 +08:00
|
|
|
{L"realpath", &builtin_realpath, N_(L"Convert path to absolute path without symlinks")},
|
2016-04-19 10:25:12 +08:00
|
|
|
{L"return", &builtin_return, N_(L"Stop the currently evaluated function")},
|
|
|
|
{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")},
|
|
|
|
{L"switch", &builtin_generic, N_(L"Conditionally execute a block of commands")},
|
|
|
|
{L"test", &builtin_test, N_(L"Test a condition")},
|
|
|
|
{L"true", &builtin_true, N_(L"Return a successful result")},
|
|
|
|
{L"ulimit", &builtin_ulimit, N_(L"Set or get the shells resource usage limits")},
|
|
|
|
{L"while", &builtin_generic, N_(L"Perform a command multiple times")}};
|
2012-02-01 11:47:56 +08:00
|
|
|
|
|
|
|
#define BUILTIN_COUNT (sizeof builtin_datas / sizeof *builtin_datas)
|
|
|
|
|
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) {
|
2012-02-01 11:47:56 +08:00
|
|
|
const builtin_data_t *array_end = builtin_datas + BUILTIN_COUNT;
|
|
|
|
const builtin_data_t *found = std::lower_bound(builtin_datas, array_end, name);
|
2016-04-19 10:25:12 +08:00
|
|
|
if (found != array_end && name == found->name) {
|
2012-02-01 11:47:56 +08:00
|
|
|
return found;
|
|
|
|
}
|
2016-05-05 06:19:47 +08:00
|
|
|
return NULL;
|
2006-02-05 21:08:40 +08:00
|
|
|
}
|
2006-01-31 03:53:10 +08:00
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Initialize builtin data.
|
2016-04-19 10:25:12 +08:00
|
|
|
void builtin_init() {
|
|
|
|
for (size_t i = 0; i < BUILTIN_COUNT; i++) {
|
2012-11-19 08:30:30 +08:00
|
|
|
intern_static(builtin_datas[i].name);
|
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Destroy builtin data.
|
2016-04-19 10:25:12 +08:00
|
|
|
void builtin_destroy() {}
|
2005-09-20 21:26:39 +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
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// If builtin takes care of printing help itself
|
2017-04-05 12:28:57 +08:00
|
|
|
static const wcstring_list_t help_builtins({L"for", L"while", L"function", L"if", L"end", L"switch",
|
|
|
|
L"case", L"count", L"printf"});
|
2016-08-17 06:30:49 +08:00
|
|
|
static bool builtin_handles_help(const wchar_t *cmd) {
|
2012-11-19 08:30:30 +08:00
|
|
|
CHECK(cmd, 0);
|
2017-04-05 12:28:57 +08:00
|
|
|
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
|
2016-04-19 10:25:12 +08:00
|
|
|
int builtin_run(parser_t &parser, const wchar_t *const *argv, io_streams_t &streams) {
|
2016-11-02 10:12:14 +08:00
|
|
|
UNUSED(parser);
|
|
|
|
UNUSED(streams);
|
2017-05-05 12:35:41 +08:00
|
|
|
if (argv == NULL || argv[0] == NULL) return STATUS_INVALID_ARGS;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2012-02-01 11:47:56 +08:00
|
|
|
const builtin_data_t *data = builtin_lookup(argv[0]);
|
2016-10-23 02:21:13 +08:00
|
|
|
if (argv[1] != NULL && !builtin_handles_help(argv[0]) && argv[2] == NULL &&
|
|
|
|
parse_util_argument_is_help(argv[1], 0)) {
|
|
|
|
builtin_print_help(parser, streams, argv[0], streams.out);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_OK;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2016-04-19 10:25:12 +08:00
|
|
|
if (data != NULL) {
|
2016-11-02 10:12:14 +08:00
|
|
|
// Warning: layering violation and naughty cast. The code originally had a much more
|
|
|
|
// complicated solution to achieve exactly the same result: lie about the constness of argv.
|
|
|
|
// Some of the builtins we call do mutate the array via their calls to wgetopt() which could
|
|
|
|
// result in the pointers being reordered. This is harmless because we only get called once
|
|
|
|
// with a given argv array and nothing else will look at the contents of the array after we
|
|
|
|
// return.
|
|
|
|
return data->func(parser, streams, (wchar_t **)argv);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-05 06:19:47 +08:00
|
|
|
|
|
|
|
debug(0, UNKNOWN_BUILTIN_ERR_MSG, argv[0]);
|
2017-05-04 15:18:02 +08:00
|
|
|
return STATUS_CMD_ERROR;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Returns a list of all builtin names.
|
2016-04-19 10:25:12 +08:00
|
|
|
wcstring_list_t builtin_get_names(void) {
|
2012-02-01 11:47:56 +08:00
|
|
|
wcstring_list_t result;
|
|
|
|
result.reserve(BUILTIN_COUNT);
|
2016-04-19 10:25:12 +08:00
|
|
|
for (size_t i = 0; i < BUILTIN_COUNT; i++) {
|
2012-02-01 11:47:56 +08:00
|
|
|
result.push_back(builtin_datas[i].name);
|
|
|
|
}
|
|
|
|
return result;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-08-17 06:30:49 +08:00
|
|
|
/// Insert all builtin names into list.
|
2016-04-19 10:25:12 +08:00
|
|
|
void builtin_get_names(std::vector<completion_t> *list) {
|
2015-07-28 09:45:47 +08:00
|
|
|
assert(list != NULL);
|
|
|
|
list->reserve(list->size() + BUILTIN_COUNT);
|
2016-04-19 10:25:12 +08:00
|
|
|
for (size_t i = 0; i < BUILTIN_COUNT; i++) {
|
2014-01-08 06:57:58 +08:00
|
|
|
append_completion(list, builtin_datas[i].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.
|
2016-04-19 10:25:12 +08:00
|
|
|
wcstring builtin_get_desc(const wcstring &name) {
|
2012-05-18 10:37:46 +08:00
|
|
|
wcstring result;
|
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) {
|
2012-05-18 10:37:46 +08:00
|
|
|
result = _(builtin->desc);
|
|
|
|
}
|
|
|
|
return result;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|