fish-shell/src/builtin.cpp

4154 lines
114 KiB
C++
Raw Normal View History

/** \file builtin.c
Functions for executing builtin functions.
How to add a new builtin function:
1). Create a function in builtin.c with the following signature:
2012-06-05 12:24:42 +08:00
<tt>static int builtin_NAME( parser_t &parser, wchar_t ** args )</tt>
where NAME is the name of the builtin, and args is a zero-terminated list of arguments.
2012-02-01 11:47:56 +08:00
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.
2012-12-16 12:23:24 +08:00
4). Use 'git add doc_src/NAME.txt' to start tracking changes to the documentation file.
*/
2015-07-25 23:14:25 +08:00
#include "config.h" // IWYU pragma: keep
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <wctype.h>
#include <time.h>
#include <stack>
2015-07-25 23:14:25 +08:00
#include <assert.h>
#include <algorithm>
#include <map>
#include <string>
#include <utility>
2015-07-25 23:14:25 +08:00
#include "fallback.h" // IWYU pragma: keep
#include "wutil.h"
#include "builtin.h"
#include "function.h"
#include "complete.h"
#include "proc.h"
#include "parser.h"
#include "reader.h"
#include "env.h"
#include "wgetopt.h"
#include "tokenizer.h"
#include "input.h"
#include "intern.h"
#include "event.h"
#include "signal.h"
#include "exec.h"
#include "highlight.h"
#include "parse_util.h"
#include "parser_keywords.h"
#include "expand.h"
#include "path.h"
2012-06-05 12:24:42 +08:00
#include "history.h"
#include "parse_tree.h"
2015-07-25 23:14:25 +08:00
#include "parse_constants.h"
#include "wcstringutil.h"
/**
The default prompt for the read command
*/
#define DEFAULT_READ_PROMPT L"set_color green; echo -n read; set_color normal; echo -n \"> \""
/**
The mode name to pass to history and input
*/
#define READ_MODE_NAME L"fish_read"
/**
The send stuff to foreground message
*/
#define FG_MSG _( L"Send job %d, '%ls' to foreground\n" )
/**
Datastructure to describe a builtin.
*/
2012-02-01 11:47:56 +08:00
struct builtin_data_t
{
/**
Name of the builtin
*/
const wchar_t *name;
/**
Function pointer tothe builtin implementation
*/
int (*func)(parser_t &parser, io_streams_t &streams, wchar_t **argv);
/**
Description of what the builtin does
*/
const wchar_t *desc;
bool operator<(const wcstring &) const;
2012-02-01 11:47:56 +08:00
bool operator<(const builtin_data_t *) const;
};
bool builtin_data_t::operator<(const wcstring &other) const
2012-02-01 11:47:56 +08:00
{
return wcscmp(this->name, other.c_str()) < 0;
2012-02-01 11:47:56 +08:00
}
2012-02-01 11:47:56 +08:00
bool builtin_data_t::operator<(const builtin_data_t *other) const
{
return wcscmp(this->name, other->name) < 0;
}
/**
Counts the number of non null pointers in the specified array
*/
int builtin_count_args(const wchar_t * const * argv)
{
int argc = 1;
while (argv[argc] != NULL)
{
argc++;
}
return argc;
}
/**
This function works like wperror, but it prints its result into
the streams.err string instead of to stderr. Used by the builtin
commands.
*/
static void builtin_wperror(const wchar_t *s, io_streams_t &streams)
{
char *err = strerror(errno);
if (s != NULL)
{
streams.err.append(s);
streams.err.append(L": ");
}
if (err != NULL)
{
2012-12-20 05:31:06 +08:00
const wcstring werr = str2wcstring(err);
streams.err.append(werr);
streams.err.push_back(L'\n');
}
}
/**
Count the number of times the specified character occurs in the specified string
*/
static int count_char(const wchar_t *str, wchar_t c)
{
int res = 0;
for (; *str; str++)
{
res += (*str==c);
}
return res;
}
wcstring builtin_help_get(parser_t &parser, io_streams_t &streams, const wchar_t *name)
{
/* This won't ever work if no_exec is set */
if (no_exec)
return wcstring();
2013-05-05 17:33:17 +08:00
wcstring_list_t lst;
wcstring out;
const wcstring name_esc = escape_string(name, 1);
wcstring cmd = format_string(L"__fish_print_help %ls", name_esc.c_str());
if (!streams.out_is_redirected && isatty(STDOUT_FILENO))
{
// 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());
}
if (exec_subshell(cmd, lst, false /* don't apply exit status */) >= 0)
{
for (size_t i=0; i<lst.size(); i++)
{
out.append(lst.at(i));
out.push_back(L'\n');
}
}
return out;
}
/**
Print help for the specified builtin. If \c b is sb_err, also print
the line information
If \c b is the buffer representing standard error, and the help
message is about to be printed to an interactive screen, it may be
shortened to fit the screen.
*/
void builtin_print_help(parser_t &parser, io_streams_t &streams, const wchar_t *cmd, output_stream_t &b)
{
bool is_stderr = (&b == &streams.err);
if (is_stderr)
{
b.append(parser.current_line());
}
const wcstring h = builtin_help_get(parser, streams, cmd);
if (!h.size())
return;
wchar_t *str = wcsdup(h.c_str());
if (str)
{
bool is_short = false;
if (is_stderr)
{
/*
Interactive mode help to screen - only print synopsis if
the rest won't fit
*/
int screen_height, lines;
screen_height = common_get_height();
lines = count_char(str, L'\n');
if (! get_is_interactive() || (lines > 2*screen_height/3))
{
wchar_t *pos;
int cut=0;
int i;
is_short = true;
/*
First move down 4 lines
*/
pos = str;
for (i=0; (i<4) && pos && *pos; i++)
{
pos = wcschr(pos+1, L'\n');
}
if (pos && *pos)
{
/*
Then find the next empty line
*/
for (; *pos; pos++)
{
if (*pos == L'\n')
{
wchar_t *pos2;
int is_empty = 1;
for (pos2 = pos+1; *pos2; pos2++)
{
if (*pos2 == L'\n')
break;
if (*pos2 != L'\t' && *pos2 !=L' ')
{
is_empty = 0;
break;
}
}
if (is_empty)
{
/*
And cut it
*/
*(pos2+1)=L'\0';
cut = 1;
break;
}
}
}
}
/*
We did not find a good place to cut message to
shorten it - so we make sure we don't print
anything.
*/
if (!cut)
{
*str = 0;
}
}
}
b.append(str);
if (is_short)
{
b.append_format(_(L"%ls: Type 'help %ls' for related documentation\n\n"), cmd, cmd);
}
free(str);
}
}
/**
Perform error reporting for encounter with unknown option
*/
static void builtin_unknown_option(parser_t &parser, io_streams_t &streams, const wchar_t *cmd, const wchar_t *opt)
{
streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, opt);
builtin_print_help(parser, streams, cmd, streams.err);
}
/**
Perform error reporting for encounter with missing argument
*/
static void builtin_missing_argument(parser_t &parser, io_streams_t &streams, const wchar_t *cmd, const wchar_t *opt)
{
streams.err.append_format(BUILTIN_ERR_MISSING, cmd, opt);
builtin_print_help(parser, streams, cmd, streams.err);
}
/*
Here follows the definition of all builtin commands. The function
names are all on the form builtin_NAME where NAME is the name of the
builtin. so the function name for the builtin 'fg' is
'builtin_fg'.
A few builtins, including 'while', 'command' and 'builtin' are not
defined here as they are handled directly by the parser. (They are
not parsed as commands, instead they only alter the parser state)
The builtins 'break' and 'continue' are so closely related that they
share the same implementation, namely 'builtin_break_continue.
Several other builtins, including jobs, ulimit and set are so big
that they have been given their own file. These files are all named
'builtin_NAME.c', where NAME is the name of the builtin. These files
are included directly below.
*/
#include "builtin_set.cpp"
#include "builtin_commandline.cpp"
#include "builtin_complete.cpp"
#include "builtin_ulimit.cpp"
#include "builtin_jobs.cpp"
#include "builtin_set_color.cpp"
2013-01-23 01:07:28 +08:00
#include "builtin_printf.cpp"
2012-03-07 16:54:01 +08:00
/* builtin_test lives in builtin_test.cpp */
int builtin_test(parser_t &parser, io_streams_t &streams, wchar_t **argv);
2012-03-07 16:54:01 +08:00
/* builtin_string lives in builtin_string.cpp */
int builtin_string(parser_t &parser, io_streams_t &streams, wchar_t **argv);
/**
List a single key binding.
Returns false if no binding with that sequence and mode exists.
*/
static bool builtin_bind_list_one(const wcstring &seq, const wcstring &bind_mode, io_streams_t &streams)
{
std::vector<wcstring> ecmds;
wcstring sets_mode;
if (!input_mapping_get(seq, bind_mode, &ecmds, &sets_mode))
{
return false;
}
streams.out.append(L"bind");
2013-12-31 08:52:41 +08:00
// Append the mode flags if applicable
if (bind_mode != DEFAULT_BIND_MODE)
{
const wcstring emode = escape_string(bind_mode, ESCAPE_ALL);
streams.out.append(L" -M ");
streams.out.append(emode);
}
if (sets_mode != bind_mode)
{
const wcstring esets_mode = escape_string(sets_mode, ESCAPE_ALL);
streams.out.append(L" -m ");
streams.out.append(esets_mode);
}
2014-04-01 01:01:39 +08:00
// Append the name
wcstring tname;
if (input_terminfo_get_name(seq, &tname))
{
// Note that we show -k here because we have an input key name
streams.out.append_format( L" -k %ls", tname.c_str());
}
else
{
// No key name, so no -k; we show the escape sequence directly
const wcstring eseq = escape_string(seq, ESCAPE_ALL);
streams.out.append_format( L" %ls", eseq.c_str());
}
2014-09-23 03:37:11 +08:00
// Now show the list of commands
for (size_t i = 0; i < ecmds.size(); i++)
{
const wcstring &ecmd = ecmds.at(i);
const wcstring escaped_ecmd = escape_string(ecmd, ESCAPE_ALL);
streams.out.push_back(' ');
streams.out.append(escaped_ecmd);
}
streams.out.push_back(L'\n');
2014-09-23 03:37:11 +08:00
return true;
}
2014-09-23 03:37:11 +08:00
/**
List all current key bindings
*/
static void builtin_bind_list(const wchar_t *bind_mode, io_streams_t &streams)
{
const std::vector<input_mapping_name_t> lst = input_mapping_get_names();
2014-09-23 03:37:11 +08:00
for (std::vector<input_mapping_name_t>::const_iterator it = lst.begin(), end = lst.end();
it != end;
++it)
{
if (bind_mode != NULL && bind_mode != it->mode)
{
continue;
}
builtin_bind_list_one(it->seq, it->mode, streams);
}
}
/**
Print terminfo key binding names to string buffer used for standard output.
\param all if set, all terminfo key binding names will be
printed. If not set, only ones that are defined for this terminal
are printed.
*/
static void builtin_bind_key_names(int all, io_streams_t &streams)
{
const wcstring_list_t names = input_terminfo_get_names(!all);
for (size_t i=0; i<names.size(); i++)
{
const wcstring &name = names.at(i);
streams.out.append_format( L"%ls\n", name.c_str());
}
}
/**
Print all the special key binding functions to string buffer used for standard output.
*/
static void builtin_bind_function_names(io_streams_t &streams)
{
wcstring_list_t names = input_function_get_names();
for (size_t i=0; i<names.size(); i++)
{
const wchar_t *seq = names.at(i).c_str();
streams.out.append_format( L"%ls\n", seq);
}
}
// Wraps input_terminfo_get_sequence(), appending the correct error messages as needed.
static int get_terminfo_sequence(const wchar_t *seq, wcstring *out_seq, io_streams_t &streams)
{
if (input_terminfo_get_sequence(seq, out_seq))
{
return 1;
}
wcstring eseq = escape_string(seq, 0);
switch (errno)
{
case ENOENT:
{
streams.err.append_format(_(L"%ls: No key with name '%ls' found\n"), L"bind", eseq.c_str());
break;
}
case EILSEQ:
{
streams.err.append_format(_(L"%ls: Key with name '%ls' does not have any mapping\n"), L"bind", eseq.c_str());
break;
}
default:
{
streams.err.append_format(_(L"%ls: Unknown error trying to bind to key named '%ls'\n"), L"bind", eseq.c_str());
break;
}
}
return 0;
}
/**
Add specified key binding.
*/
static int builtin_bind_add(const wchar_t *seq, const wchar_t * const *cmds, size_t cmds_len,
const wchar_t *mode, const wchar_t *sets_mode, int terminfo,
io_streams_t &streams)
{
if (terminfo)
{
wcstring seq2;
if (get_terminfo_sequence(seq, &seq2, streams))
{
input_mapping_add(seq2.c_str(), cmds, cmds_len, mode, sets_mode);
}
else
{
return 1;
}
}
else
{
input_mapping_add(seq, cmds, cmds_len, mode, sets_mode);
}
return 0;
}
/**
Erase specified key bindings
\param seq an array of all key bindings to erase
\param all if specified, _all_ key bindings will be erased
\param mode if specified, only bindings from that mode will be erased. If not given and \c all is \c false, \c DEFAULT_BIND_MODE will be used.
*/
static int builtin_bind_erase(wchar_t **seq, int all, const wchar_t *mode, int use_terminfo, io_streams_t &streams)
{
if (all)
{
const std::vector<input_mapping_name_t> lst = input_mapping_get_names();
for (std::vector<input_mapping_name_t>::const_iterator it = lst.begin(), end = lst.end();
it != end;
++it)
{
if (mode == NULL || mode == it->mode)
{
input_mapping_erase(it->seq, it->mode);
}
}
return 0;
}
else
{
int res = 0;
if (mode == NULL) mode = DEFAULT_BIND_MODE;
while (*seq)
{
if (use_terminfo)
{
wcstring seq2;
if (get_terminfo_sequence(*seq++, &seq2, streams))
{
input_mapping_erase(seq2, mode);
}
else
{
res = 1;
}
}
else
{
input_mapping_erase(*seq++, mode);
}
}
return res;
}
}
/**
The bind builtin, used for setting character sequences
*/
static int builtin_bind(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wgetopter_t w;
enum
{
BIND_INSERT,
BIND_ERASE,
BIND_KEY_NAMES,
BIND_FUNCTION_NAMES
};
int argc=builtin_count_args(argv);
int mode = BIND_INSERT;
int res = STATUS_BUILTIN_OK;
int all = 0;
2013-12-31 08:52:41 +08:00
const wchar_t *bind_mode = DEFAULT_BIND_MODE;
bool bind_mode_given = false;
const wchar_t *sets_bind_mode = DEFAULT_BIND_MODE;
bool sets_bind_mode_given = false;
2013-12-31 08:52:41 +08:00
int use_terminfo = 0;
w.woptind=0;
static const struct woption long_options[] =
{
{ L"all", no_argument, 0, 'a' },
{ L"erase", no_argument, 0, 'e' },
{ L"function-names", no_argument, 0, 'f' },
{ L"help", no_argument, 0, 'h' },
{ L"key", no_argument, 0, 'k' },
{ L"key-names", no_argument, 0, 'K' },
{ L"mode", required_argument, 0, 'M' },
{ L"sets-mode", required_argument, 0, 'm' },
{ 0, 0, 0, 0 }
};
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"aehkKfM:m:",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case 'a':
all = 1;
break;
2012-11-19 16:31:03 +08:00
case 'e':
mode = BIND_ERASE;
break;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
2012-11-19 16:31:03 +08:00
case 'k':
use_terminfo = 1;
break;
2012-11-19 16:31:03 +08:00
case 'K':
mode = BIND_KEY_NAMES;
break;
2012-11-19 16:31:03 +08:00
case 'f':
mode = BIND_FUNCTION_NAMES;
break;
2013-12-31 08:52:41 +08:00
case 'M':
bind_mode = w.woptarg;
bind_mode_given = true;
2013-12-31 08:52:41 +08:00
break;
case 'm':
sets_bind_mode = w.woptarg;
sets_bind_mode_given = true;
2013-12-31 08:52:41 +08:00
break;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2013-12-31 08:52:41 +08:00
}
}
/*
2014-04-01 01:01:39 +08:00
* if mode is given, but not new mode, default to new mode to mode
*/
2014-04-01 01:01:39 +08:00
if (bind_mode_given && !sets_bind_mode_given)
{
2014-04-01 01:01:39 +08:00
sets_bind_mode = bind_mode;
}
switch (mode)
{
2012-11-19 16:31:03 +08:00
case BIND_ERASE:
{
if (builtin_bind_erase(&argv[w.woptind], all, bind_mode_given ? bind_mode : NULL, use_terminfo, streams))
{
res = STATUS_BUILTIN_ERROR;
}
break;
}
2012-11-19 16:31:03 +08:00
case BIND_INSERT:
{
switch (argc-w.woptind)
2012-11-19 16:31:03 +08:00
{
case 0:
{
builtin_bind_list(bind_mode_given ? bind_mode : NULL, streams);
2012-11-19 16:31:03 +08:00
break;
}
case 1:
2012-11-19 16:31:03 +08:00
{
wcstring seq;
if (use_terminfo)
{
if (!get_terminfo_sequence(argv[w.woptind], &seq, streams))
{
res = STATUS_BUILTIN_ERROR;
// get_terminfo_sequence already printed the error
break;
}
}
else
{
seq = argv[w.woptind];
}
if (!builtin_bind_list_one(seq, bind_mode, streams))
{
res = STATUS_BUILTIN_ERROR;
wcstring eseq = escape_string(argv[w.woptind], 0);
if (use_terminfo)
{
streams.err.append_format(_(L"%ls: No binding found for key '%ls'\n"), argv[0], eseq.c_str());
}
else
{
streams.err.append_format(_(L"%ls: No binding found for sequence '%ls'\n"), argv[0], eseq.c_str());
}
}
2012-11-19 16:31:03 +08:00
break;
}
default:
{
if (builtin_bind_add(argv[w.woptind], argv + (w.woptind + 1), argc - (w.woptind + 1), bind_mode, sets_bind_mode, use_terminfo, streams))
{
res = STATUS_BUILTIN_ERROR;
}
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
}
break;
}
2012-11-19 16:31:03 +08:00
case BIND_KEY_NAMES:
{
builtin_bind_key_names(all, streams);
break;
}
2012-11-19 16:31:03 +08:00
case BIND_FUNCTION_NAMES:
{
builtin_bind_function_names(streams);
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
default:
{
res = STATUS_BUILTIN_ERROR;
streams.err.append_format(_(L"%ls: Invalid state\n"), argv[0]);
2012-11-19 16:31:03 +08:00
break;
}
}
return res;
}
2013-12-31 08:52:41 +08:00
/**
The block builtin, used for temporarily blocking events
*/
static int builtin_block(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wgetopter_t w;
enum
{
UNSET,
GLOBAL,
LOCAL,
}
;
int scope=UNSET;
int erase = 0;
int argc=builtin_count_args(argv);
w.woptind=0;
static const struct woption
long_options[] =
{
{
L"erase", no_argument, 0, 'e'
}
,
{
L"local", no_argument, 0, 'l'
}
,
{
L"global", no_argument, 0, 'g'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"elgh",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
2012-11-19 16:31:03 +08:00
case 'g':
scope = GLOBAL;
break;
2012-11-19 16:31:03 +08:00
case 'l':
scope = LOCAL;
break;
2012-11-19 16:31:03 +08:00
case 'e':
erase = 1;
break;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
}
if (erase)
{
if (scope != UNSET)
{
streams.err.append_format(_(L"%ls: Can not specify scope when removing block\n"), argv[0]);
return STATUS_BUILTIN_ERROR;
}
if (parser.global_event_blocks.empty())
{
streams.err.append_format(_(L"%ls: No blocks defined\n"), argv[0]);
return STATUS_BUILTIN_ERROR;
}
parser.global_event_blocks.pop_front();
}
else
{
size_t block_idx = 0;
block_t *block = parser.block_at_index(block_idx);
event_blockage_t eb = {};
eb.typemask = (1<<EVENT_ANY);
switch (scope)
{
2012-11-19 16:31:03 +08:00
case LOCAL:
{
// If this is the outermost block, then we're global
if (block_idx + 1 >= parser.block_count())
{
block = NULL;
}
2012-11-19 16:31:03 +08:00
break;
}
case GLOBAL:
{
block=NULL;
2012-11-19 16:31:03 +08:00
}
case UNSET:
{
while (block != NULL && block->type() != FUNCTION_CALL && block->type() != FUNCTION_CALL_NO_SHADOW)
{
// Set it in function scope
block = parser.block_at_index(++block_idx);
}
2012-11-19 16:31:03 +08:00
}
}
if (block)
{
block->event_blocks.push_front(eb);
}
else
{
parser.global_event_blocks.push_front(eb);
}
}
return STATUS_BUILTIN_OK;
}
/**
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.
*/
static int builtin_builtin(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
int argc=builtin_count_args(argv);
int list=0;
wgetopter_t w;
static const struct woption
long_options[] =
{
{
L"names", no_argument, 0, 'n'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"nh",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
2012-11-19 16:31:03 +08:00
case 'n':
list=1;
break;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
}
if (list)
{
wcstring_list_t names = builtin_get_names();
sort(names.begin(), names.end());
for (size_t i=0; i<names.size(); i++)
{
const wchar_t *el = names.at(i).c_str();
streams.out.append(el);
streams.out.append(L"\n");
}
}
return STATUS_BUILTIN_OK;
}
/**
Implementation of the builtin emit command, used to create events.
*/
static int builtin_emit(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wgetopter_t w;
int argc=builtin_count_args(argv);
static const struct woption
long_options[] =
{
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"h",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
}
if (!argv[w.woptind])
2012-12-23 04:21:31 +08:00
{
streams.err.append_format(L"%ls: expected event name\n", argv[0]);
2012-12-23 04:21:31 +08:00
return STATUS_BUILTIN_ERROR;
}
const wchar_t *eventname = argv[w.woptind];
wcstring_list_t args(argv + w.woptind + 1, argv + argc);
2012-12-20 08:11:55 +08:00
event_fire_generic(eventname, &args);
return STATUS_BUILTIN_OK;
}
/**
Implementation of the builtin 'command'. Actual command running is handled by
the parser, this just processes the flags.
*/
static int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wgetopter_t w;
int argc=builtin_count_args(argv);
int print_path=0;
w.woptind=0;
static const struct woption
long_options[] =
{
{ L"search", no_argument, 0, 's' },
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"svh",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
return STATUS_BUILTIN_OK;
case 's':
case 'v':
print_path=1;
break;
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
return STATUS_BUILTIN_ERROR;
}
}
if (!print_path)
{
builtin_print_help(parser, streams, argv[0], streams.out);
return STATUS_BUILTIN_ERROR;
}
int found=0;
for (int idx = w.woptind; argv[idx]; ++idx)
{
const wchar_t *command_name = argv[idx];
wcstring path;
if (path_get_path(command_name, &path))
{
streams.out.append_format( L"%ls\n", path.c_str());
++found;
}
}
return found ? STATUS_BUILTIN_OK : STATUS_BUILTIN_ERROR;
}
/**
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.
*/
static int builtin_generic(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wgetopter_t w;
int argc=builtin_count_args(argv);
/* Hackish - if we have no arguments other than the command, we are a "naked invocation" and we just print help */
if (argc == 1)
{
builtin_print_help(parser, streams, argv[0], streams.out);
return STATUS_BUILTIN_ERROR;
}
static const struct woption
long_options[] =
{
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"h",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
}
return STATUS_BUILTIN_ERROR;
}
/**
Return a definition of the specified function. Used by the functions builtin.
*/
static wcstring functions_def(const wcstring &name)
{
CHECK(! name.empty(), L"");
wcstring out;
wcstring desc, def;
function_get_desc(name, &desc);
function_get_definition(name, &def);
event_t search(EVENT_ANY);
search.function_name = name;
std::vector<event_t *> ev;
2012-12-20 18:48:36 +08:00
event_get(search, &ev);
out.append(L"function ");
/* Typically we prefer to specify the function name first, e.g. "function foo --description bar"
But If the function name starts with a -, we'll need to output it after all the options. */
bool defer_function_name = (name.at(0) == L'-');
if (! defer_function_name)
{
out.append(escape_string(name, true));
}
if (! desc.empty())
{
wcstring esc_desc = escape_string(desc, true);
out.append(L" --description ");
out.append(esc_desc);
}
if (!function_get_shadows(name))
{
out.append(L" --no-scope-shadowing");
}
for (size_t i=0; i<ev.size(); i++)
{
event_t *next = ev.at(i);
switch (next->type)
{
2012-11-19 16:31:03 +08:00
case EVENT_SIGNAL:
{
append_format(out, L" --on-signal %ls", sig2wcs(next->param1.signal));
break;
}
2012-11-19 16:31:03 +08:00
case EVENT_VARIABLE:
{
append_format(out, L" --on-variable %ls", next->str_param1.c_str());
break;
}
2012-11-19 16:31:03 +08:00
case EVENT_EXIT:
{
if (next->param1.pid > 0)
append_format(out, L" --on-process-exit %d", next->param1.pid);
else
append_format(out, L" --on-job-exit %d", -next->param1.pid);
break;
}
2012-11-19 16:31:03 +08:00
case EVENT_JOB_ID:
{
const job_t *j = job_get(next->param1.job_id);
if (j)
append_format(out, L" --on-job-exit %d", j->pgid);
break;
}
2012-11-19 16:31:03 +08:00
case EVENT_GENERIC:
{
append_format(out, L" --on-event %ls", next->str_param1.c_str());
break;
}
}
}
wcstring_list_t named = function_get_named_arguments(name);
if (! named.empty())
{
append_format(out, L" --argument");
for (size_t i=0; i < named.size(); i++)
{
append_format(out, L" %ls", named.at(i).c_str());
}
}
/* Output the function name if we deferred it */
if (defer_function_name)
{
out.append(L" -- ");
out.append(escape_string(name, true));
}
/* Output any inherited variables as `set -l` lines */
std::map<wcstring,env_var_t> inherit_vars = function_get_inherit_vars(name);
for (std::map<wcstring,env_var_t>::const_iterator it = inherit_vars.begin(), end = inherit_vars.end(); it != end; ++it)
{
wcstring_list_t lst;
if (!it->second.missing())
{
tokenize_variable_array(it->second, lst);
}
/* This forced tab is crummy, but we don't know what indentation style the function uses */
append_format(out, L"\n\tset -l %ls", it->first.c_str());
for (wcstring_list_t::const_iterator arg_it = lst.begin(), arg_end = lst.end(); arg_it != arg_end; ++arg_it)
{
wcstring earg = escape_string(*arg_it, ESCAPE_ALL);
out.push_back(L' ');
out.append(earg);
}
}
/* This forced tab is sort of crummy - not all functions start with a tab */
append_format(out, L"\n\t%ls", def.c_str());
/* Append a newline before the 'end', unless there already is one there */
if (! string_suffixes_string(L"\n", def))
{
out.push_back(L'\n');
}
out.append(L"end\n");
return out;
}
/**
The functions builtin, used for listing and erasing functions.
*/
static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wgetopter_t w;
int i;
int erase=0;
wchar_t *desc=0;
int argc=builtin_count_args(argv);
int list=0;
int show_hidden=0;
int res = STATUS_BUILTIN_OK;
int query = 0;
int copy = 0;
static const struct woption
long_options[] =
{
{
L"erase", no_argument, 0, 'e'
}
,
{
L"description", required_argument, 0, 'd'
}
,
{
L"names", no_argument, 0, 'n'
}
,
{
L"all", no_argument, 0, 'a'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
L"query", no_argument, 0, 'q'
}
,
{
L"copy", no_argument, 0, 'c'
}
,
{
0, 0, 0, 0
}
}
;
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"ed:nahqc",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case 'e':
erase=1;
break;
2012-11-19 16:31:03 +08:00
case 'd':
desc=w.woptarg;
2012-11-19 16:31:03 +08:00
break;
2012-11-19 16:31:03 +08:00
case 'n':
list=1;
break;
2012-11-19 16:31:03 +08:00
case 'a':
show_hidden=1;
break;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
2012-11-19 16:31:03 +08:00
case 'q':
query = 1;
break;
2012-11-19 16:31:03 +08:00
case 'c':
copy = 1;
break;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
}
/*
Erase, desc, query, copy and list are mutually exclusive
*/
if ((erase + (!!desc) + list + query + copy) > 1)
{
streams.err.append_format(_(L"%ls: Invalid combination of options\n"),
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
if (erase)
{
int i;
for (i=w.woptind; i<argc; i++)
function_remove(argv[i]);
return STATUS_BUILTIN_OK;
}
else if (desc)
{
wchar_t *func;
if (argc-w.woptind != 1)
{
streams.err.append_format(_(L"%ls: Expected exactly one function name\n"),
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
func = argv[w.woptind];
if (!function_exists(func))
{
streams.err.append_format(_(L"%ls: Function '%ls' does not exist\n"),
argv[0],
func);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
function_set_desc(func, desc);
return STATUS_BUILTIN_OK;
}
else if (list || (argc==w.woptind))
{
int is_screen = !streams.out_is_redirected && isatty(STDOUT_FILENO);
size_t i;
wcstring_list_t names = function_get_names(show_hidden);
std::sort(names.begin(), names.end());
if (is_screen)
{
wcstring buff;
for (i=0; i<names.size(); i++)
{
buff.append(names.at(i));
buff.append(L", ");
}
streams.out.append(reformat_for_screen(buff));
}
else
{
for (i=0; i<names.size(); i++)
{
streams.out.append(names.at(i).c_str());
streams.out.append(L"\n");
}
}
return STATUS_BUILTIN_OK;
}
else if (copy)
{
wcstring current_func;
wcstring new_func;
if (argc-w.woptind != 2)
{
streams.err.append_format(_(L"%ls: Expected exactly two names (current function name, and new function name)\n"),
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
current_func = argv[w.woptind];
new_func = argv[w.woptind+1];
if (!function_exists(current_func))
{
streams.err.append_format(_(L"%ls: Function '%ls' does not exist\n"),
argv[0],
current_func.c_str());
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
if ((wcsfuncname(new_func) != 0) || parser_keywords_is_reserved(new_func))
{
streams.err.append_format(_(L"%ls: Illegal function name '%ls'\n"),
argv[0],
new_func.c_str());
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
// keep things simple: don't allow existing names to be copy targets.
if (function_exists(new_func))
{
streams.err.append_format(_(L"%ls: Function '%ls' already exists. Cannot create copy '%ls'\n"),
argv[0],
new_func.c_str(),
current_func.c_str());
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
if (function_copy(current_func, new_func))
return STATUS_BUILTIN_OK;
return STATUS_BUILTIN_ERROR;
}
for (i=w.woptind; i<argc; i++)
{
if (!function_exists(argv[i]))
res++;
else
{
if (!query)
{
if (i != w.woptind)
streams.out.append(L"\n");
streams.out.append(functions_def(argv[i]));
}
}
}
return res;
}
static unsigned int builtin_echo_digit(wchar_t wc, unsigned int base)
{
// base must be hex or octal
assert(base == 8 || base == 16);
switch (wc)
{
2012-11-19 16:31:03 +08:00
case L'0':
return 0;
case L'1':
return 1;
case L'2':
return 2;
case L'3':
return 3;
case L'4':
return 4;
case L'5':
return 5;
case L'6':
return 6;
case L'7':
return 7;
}
if (base == 16) switch (wc)
{
2012-11-19 16:31:03 +08:00
case L'8':
return 8;
case L'9':
return 9;
case L'a':
case L'A':
return 10;
case L'b':
case L'B':
return 11;
case L'c':
case L'C':
return 12;
case L'd':
case L'D':
return 13;
case L'e':
case L'E':
return 14;
case L'f':
case L'F':
return 15;
}
return UINT_MAX;
}
/* Parse a numeric escape sequence in str, returning whether we succeeded.
Also return the number of characters consumed and the resulting value.
Supported escape sequences:
\0nnn: octal value, zero to three digits
\nnn: octal value, one to three digits
\xhh: hex value, one to two digits
*/
static bool builtin_echo_parse_numeric_sequence(const wchar_t *str, size_t *consumed, unsigned char *out_val)
{
bool success = false;
unsigned char val = 0; //resulting character
unsigned int start = 0; //the first character of the numeric part of the sequence
unsigned int base = 0, max_digits = 0;
if (builtin_echo_digit(str[0], 8) != UINT_MAX)
{
// Octal escape
base = 8;
// If the first digit is a 0, we allow four digits (including that zero)
// Otherwise we allow 3.
max_digits = (str[0] == L'0' ? 4 : 3);
}
else if (str[0] == L'x')
{
// Hex escape
base = 16;
max_digits = 2;
// Skip the x
start = 1;
}
if (base != 0)
{
unsigned int idx;
for (idx = start; idx < start + max_digits; idx++)
{
unsigned int digit = builtin_echo_digit(str[idx], base);
if (digit == UINT_MAX) break;
val = val * base + digit;
}
// We succeeded if we consumed at least one digit
if (idx > start)
{
*consumed = idx;
*out_val = val;
success = true;
}
}
return success;
}
/** The echo builtin.
bash only respects -n if it's the first argument. We'll do the same.
We also support a new option -s to mean "no spaces"
*/
static int builtin_echo(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
/* Skip first arg */
if (! *argv++)
return STATUS_BUILTIN_ERROR;
/* Process options. Options must come at the beginning - the first non-option kicks us out. */
bool print_newline = true, print_spaces = true, interpret_special_chars = false;
size_t option_idx = 0;
for (option_idx = 0; argv[option_idx] != NULL; option_idx++)
{
const wchar_t *arg = argv[option_idx];
assert(arg != NULL);
bool arg_is_valid_option = false;
if (arg[0] == L'-')
{
// We have a leading dash. Ensure that every subseqnent character is a valid option.
size_t i = 1;
while (arg[i] != L'\0' && wcschr(L"nesE", arg[i]) != NULL)
{
i++;
}
// We must have at least two characters to be a valid option, and have consumed the whole string
arg_is_valid_option = (i >= 2 && arg[i] == L'\0');
}
if (! arg_is_valid_option)
{
// This argument is not an option, so there are no more options
break;
}
// Ok, we are sure the argument is an option. Parse it.
assert(arg_is_valid_option);
for (size_t i=1; arg[i] != L'\0'; i++)
{
switch (arg[i])
{
case L'n':
print_newline = false;
break;
case L'e':
interpret_special_chars = true;
break;
case L's':
print_spaces = false;
break;
case L'E':
interpret_special_chars = false;
break;
default:
assert(0 && "Unexpected character in builtin_echo argument");
break;
}
}
}
/* The special character \c can be used to indicate no more output */
bool continue_output = true;
/* Skip over the options */
const wchar_t * const *args_to_echo = argv + option_idx;
for (size_t idx = 0; continue_output && args_to_echo[idx] != NULL; idx++)
{
if (print_spaces && idx > 0)
{
streams.out.push_back(' ');
}
const wchar_t *str = args_to_echo[idx];
for (size_t j=0; continue_output && str[j]; j++)
{
if (! interpret_special_chars || str[j] != L'\\')
{
/* Not an escape */
streams.out.push_back(str[j]);
}
else
{
/* Most escapes consume one character in addition to the backslash; the numeric sequences may consume more, while an unrecognized escape sequence consumes none. */
wchar_t wc;
size_t consumed = 1;
switch (str[j+1])
{
2012-11-19 16:31:03 +08:00
case L'a':
wc = L'\a';
break;
case L'b':
wc = L'\b';
break;
case L'e':
2013-07-12 15:40:29 +08:00
wc = L'\x1B';
2012-11-19 16:31:03 +08:00
break;
case L'f':
wc = L'\f';
break;
case L'n':
wc = L'\n';
break;
case L'r':
wc = L'\r';
break;
case L't':
wc = L'\t';
break;
case L'v':
wc = L'\v';
break;
case L'\\':
wc = L'\\';
break;
2012-11-19 16:31:03 +08:00
case L'c':
wc = 0;
continue_output = false;
break;
2012-11-19 16:31:03 +08:00
default:
{
2012-11-19 16:31:03 +08:00
/* Octal and hex escape sequences */
unsigned char narrow_val = 0;
if (builtin_echo_parse_numeric_sequence(str + j + 1, &consumed, &narrow_val))
{
/* Here consumed must have been set to something. The narrow_val is a literal byte that we want to output (#1894) */
wc = ENCODE_DIRECT_BASE + narrow_val % 256;
2012-11-19 16:31:03 +08:00
}
else
{
/* Not a recognized escape. We consume only the backslash. */
wc = L'\\';
consumed = 0;
}
break;
}
}
/* Skip over characters that were part of this escape sequence (but not the backslash, which will be handled by the loop increment */
j += consumed;
if (continue_output)
{
streams.out.push_back(wc);
}
}
}
}
if (print_newline && continue_output)
{
streams.out.push_back('\n');
}
return STATUS_BUILTIN_OK;
}
/** The pwd builtin. We don't respect -P to resolve symbolic links because we try to always resolve them. */
static int builtin_pwd(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wchar_t dir_path[4096];
wchar_t *res = wgetcwd(dir_path, 4096);
if (res == NULL)
{
return STATUS_BUILTIN_ERROR;
}
else
{
streams.out.append(dir_path);
streams.out.push_back(L'\n');
return STATUS_BUILTIN_OK;
}
}
/** Adds a function to the function set. It calls into function.cpp to perform any heavy lifting. */
int define_function(parser_t &parser, io_streams_t &streams, const wcstring_list_t &c_args, const wcstring &contents, int definition_line_offset, wcstring *out_err)
{
wgetopter_t w;
assert(out_err != NULL);
/* wgetopt expects 'function' as the first argument. Make a new wcstring_list with that property. */
wcstring_list_t args;
args.push_back(L"function");
args.insert(args.end(), c_args.begin(), c_args.end());
/* Hackish const_cast matches the one in builtin_run */
const null_terminated_array_t<wchar_t> argv_array(args);
wchar_t **argv = const_cast<wchar_t **>(argv_array.get());
int argc = builtin_count_args(argv);
int res=STATUS_BUILTIN_OK;
wchar_t *desc=0;
std::vector<event_t> events;
bool has_named_arguments = false;
wcstring_list_t named_arguments;
wcstring_list_t inherit_vars;
bool shadows = true;
wcstring_list_t wrap_targets;
/* If -a/--argument-names is specified before the function name,
then the function name is the last positional, e.g. `function -a arg1 arg2 name`.
If it is specified after the function name (or not specified at all) then the
function name is the first positional. This is the common case. */
bool name_is_first_positional = true;
wcstring_list_t positionals;
2013-04-13 16:32:07 +08:00
const struct woption long_options[] =
{
2013-04-13 16:32:07 +08:00
{ L"description", required_argument, 0, 'd' },
{ L"on-signal", required_argument, 0, 's' },
{ L"on-job-exit", required_argument, 0, 'j' },
{ L"on-process-exit", required_argument, 0, 'p' },
{ L"on-variable", required_argument, 0, 'v' },
{ L"on-event", required_argument, 0, 'e' },
{ L"wraps", required_argument, 0, 'w' },
2013-04-13 16:32:07 +08:00
{ L"help", no_argument, 0, 'h' },
{ L"argument-names", no_argument, 0, 'a' },
{ L"no-scope-shadowing", no_argument, 0, 'S' },
{ L"inherit-variable", required_argument, 0, 'V' },
2013-04-13 16:32:07 +08:00
{ 0, 0, 0, 0 }
};
while (1 && (!res))
{
int opt_index = 0;
// The leading - here specifies RETURN_IN_ORDER
int opt = w.wgetopt_long(argc,
argv,
L"-d:s:j:p:v:e:haSV:",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
append_format(*out_err,
2012-11-19 16:31:03 +08:00
BUILTIN_ERR_UNKNOWN,
argv[0],
long_options[opt_index].name);
2012-11-19 16:31:03 +08:00
res = 1;
break;
2012-11-19 16:31:03 +08:00
case 'd':
desc=w.woptarg;
2012-11-19 16:31:03 +08:00
break;
2012-11-19 16:31:03 +08:00
case 's':
{
int sig = wcs2sig(w.woptarg);
2012-11-19 16:31:03 +08:00
if (sig < 0)
{
append_format(*out_err,
_(L"%ls: Unknown signal '%ls'"),
2012-11-19 16:31:03 +08:00
argv[0],
w.woptarg);
2012-11-19 16:31:03 +08:00
res=1;
break;
}
events.push_back(event_t::signal_event(sig));
break;
}
2012-11-19 16:31:03 +08:00
case 'v':
{
if (wcsvarname(w.woptarg))
2012-11-19 16:31:03 +08:00
{
append_format(*out_err,
_(L"%ls: Invalid variable name '%ls'"),
2012-11-19 16:31:03 +08:00
argv[0],
w.woptarg);
2012-11-19 16:31:03 +08:00
res=STATUS_BUILTIN_ERROR;
break;
}
events.push_back(event_t::variable_event(w.woptarg));
break;
}
2012-11-19 16:31:03 +08:00
case 'e':
{
events.push_back(event_t::generic_event(w.woptarg));
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
case 'j':
case 'p':
{
2012-11-19 16:31:03 +08:00
pid_t pid;
wchar_t *end;
event_t e(EVENT_ANY);
2012-11-19 16:31:03 +08:00
if ((opt == 'j') &&
(wcscasecmp(w.woptarg, L"caller") == 0))
{
2012-11-19 16:31:03 +08:00
int job_id = -1;
2012-11-19 16:31:03 +08:00
if (is_subshell)
{
size_t block_idx = 0;
/* Find the outermost substitution block */
for (block_idx = 0; ; block_idx++)
2012-11-19 16:31:03 +08:00
{
const block_t *b = parser.block_at_index(block_idx);
if (b == NULL || b->type() == SUBST)
break;
2012-11-19 16:31:03 +08:00
}
/* Go one step beyond that, to get to the caller */
const block_t *caller_block = parser.block_at_index(block_idx + 1);
if (caller_block != NULL && caller_block->job != NULL)
2012-11-19 16:31:03 +08:00
{
job_id = caller_block->job->job_id;
2012-11-19 16:31:03 +08:00
}
}
if (job_id == -1)
{
append_format(*out_err,
_(L"%ls: Cannot find calling job for event handler"),
2012-11-19 16:31:03 +08:00
argv[0]);
res=1;
}
2012-11-19 16:31:03 +08:00
else
{
2012-11-19 16:31:03 +08:00
e.type = EVENT_JOB_ID;
e.param1.job_id = job_id;
}
}
else
{
2012-11-19 16:31:03 +08:00
errno = 0;
pid = fish_wcstoi(w.woptarg, &end, 10);
2012-11-19 16:31:03 +08:00
if (errno || !end || *end)
{
append_format(*out_err,
_(L"%ls: Invalid process id %ls"),
2012-11-19 16:31:03 +08:00
argv[0],
w.woptarg);
2012-11-19 16:31:03 +08:00
res=1;
break;
}
2012-11-19 16:31:03 +08:00
e.type = EVENT_EXIT;
e.param1.pid = (opt=='j'?-1:1)*abs(pid);
}
if (res)
{
2012-11-19 16:31:03 +08:00
/* nothing */
}
2012-11-19 16:31:03 +08:00
else
{
events.push_back(e);
}
break;
}
2012-11-19 16:31:03 +08:00
case 'a':
has_named_arguments = true;
/* The function name is the first positional unless -a comes before all positionals */
name_is_first_positional = ! positionals.empty();
2012-11-19 16:31:03 +08:00
break;
2012-11-19 16:31:03 +08:00
case 'S':
shadows = 0;
break;
case 'w':
wrap_targets.push_back(w.woptarg);
break;
case 'V':
{
if (wcsvarname(w.woptarg))
{
append_format(*out_err, _(L"%ls: Invalid variable name '%ls'"), argv[0], w.woptarg);
res = STATUS_BUILTIN_ERROR;
break;
}
inherit_vars.push_back(w.woptarg);
break;
}
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
case 1:
assert(w.woptarg != NULL);
positionals.push_back(w.woptarg);
break;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
res = 1;
break;
}
}
if (!res)
{
/* Determine the function name, and remove it from the list of positionals */
wcstring function_name;
bool name_is_missing = positionals.empty();
if (! name_is_missing)
{
if (name_is_first_positional)
{
function_name = positionals.front();
positionals.erase(positionals.begin());
}
else
{
function_name = positionals.back();
positionals.erase(positionals.end() - 1);
}
}
if (name_is_missing)
{
append_format(*out_err,
_(L"%ls: Expected function name"),
argv[0]);
res=1;
}
else if (wcsfuncname(function_name))
{
append_format(*out_err,
_(L"%ls: Illegal function name '%ls'"),
argv[0],
function_name.c_str());
res=1;
}
else if (parser_keywords_is_reserved(function_name))
{
append_format(*out_err,
_(L"%ls: The name '%ls' is reserved,\nand can not be used as a function name"),
argv[0],
function_name.c_str());
res=1;
}
else if (function_name.empty())
{
append_format(*out_err, _(L"%ls: No function name given"), argv[0]);
res=1;
}
else
{
if (has_named_arguments)
{
/* All remaining positionals are named arguments */
named_arguments.swap(positionals);
for (size_t i=0; i < named_arguments.size(); i++)
{
if (wcsvarname(named_arguments.at(i)))
{
append_format(*out_err,
_(L"%ls: Invalid variable name '%ls'"),
argv[0],
named_arguments.at(i).c_str());
res = STATUS_BUILTIN_ERROR;
break;
}
}
}
else if (! positionals.empty())
{
// +1 because we already got the function name
append_format(*out_err,
_(L"%ls: Expected one argument, got %lu"),
argv[0],
(unsigned long)(positionals.size() + 1));
res=1;
}
}
if (!res)
{
/* Here we actually define the function! */
function_data_t d;
d.name = function_name;
if (desc)
d.description = desc;
d.events.swap(events);
d.shadows = shadows;
d.named_arguments.swap(named_arguments);
d.inherit_vars.swap(inherit_vars);
for (size_t i=0; i<d.events.size(); i++)
{
event_t &e = d.events.at(i);
e.function_name = d.name;
}
d.definition = contents.c_str();
function_add(d, parser, definition_line_offset);
// Handle wrap targets
for (size_t w=0; w < wrap_targets.size(); w++)
{
complete_add_wrapper(function_name, wrap_targets.at(w));
}
}
}
return res;
}
/**
The random builtin. For generating random numbers.
*/
static int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
static int seeded=0;
static struct drand48_data seed_buffer;
int argc = builtin_count_args(argv);
wgetopter_t w;
static const struct woption
long_options[] =
{
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"h",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
break;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
}
switch (argc-w.woptind)
{
2012-11-19 16:31:03 +08:00
case 0:
{
2012-11-19 16:31:03 +08:00
long res;
if (!seeded)
{
seeded=1;
srand48_r(time(0), &seed_buffer);
}
lrand48_r(&seed_buffer, &res);
// The labs() shouldn't be necessary since lrand48 is supposed to
// return only positive integers but we're going to play it safe.
streams.out.append_format(L"%ld\n", labs(res % 32768));
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
case 1:
{
long foo;
wchar_t *end=0;
2012-11-19 16:31:03 +08:00
errno=0;
foo = wcstol(argv[w.woptind], &end, 10);
2012-11-19 16:31:03 +08:00
if (errno || *end)
{
streams.err.append_format(_(L"%ls: Seed value '%ls' is not a valid number\n"),
2012-11-19 16:31:03 +08:00
argv[0],
argv[w.woptind]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
seeded=1;
srand48_r(foo, &seed_buffer);
break;
}
default:
{
streams.err.append_format(_(L"%ls: Expected zero or one argument, got %d\n"),
argv[0],
argc-w.woptind);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
}
return STATUS_BUILTIN_OK;
}
/**
The read builtin. Reads from stdin and stores the values in environment variables.
*/
static int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wgetopter_t w;
wcstring buff;
int i, argc = builtin_count_args(argv);
int place = ENV_USER;
const wchar_t *prompt = DEFAULT_READ_PROMPT;
const wchar_t *right_prompt = L"";
const wchar_t *commandline = L"";
int exit_res=STATUS_BUILTIN_OK;
const wchar_t *mode_name = READ_MODE_NAME;
int nchars=0;
wchar_t *end;
int shell = 0;
int array = 0;
bool split_null = false;
while (1)
{
static const struct woption
long_options[] =
{
{
L"export", no_argument, 0, 'x'
}
,
{
L"global", no_argument, 0, 'g'
}
,
{
L"local", no_argument, 0, 'l'
}
,
{
L"universal", no_argument, 0, 'U'
}
,
{
L"unexport", no_argument, 0, 'u'
}
,
{
L"prompt", required_argument, 0, 'p'
}
,
{
L"right-prompt", required_argument, 0, 'R'
}
,
{
L"command", required_argument, 0, 'c'
}
,
{
L"mode-name", required_argument, 0, 'm'
}
,
{
L"nchars", required_argument, 0, 'n'
}
,
{
L"shell", no_argument, 0, 's'
}
,
{
L"array", no_argument, 0, 'a'
}
,
{
L"null", no_argument, 0, 'z'
}
,
{
L"help", no_argument, 0, 'h'
}
,
{
0, 0, 0, 0
}
}
;
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"xglUup:R:c:hm:n:saz",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case L'x':
place |= ENV_EXPORT;
break;
2012-11-19 16:31:03 +08:00
case L'g':
place |= ENV_GLOBAL;
break;
2012-11-19 16:31:03 +08:00
case L'l':
place |= ENV_LOCAL;
break;
2012-11-19 16:31:03 +08:00
case L'U':
place |= ENV_UNIVERSAL;
break;
2012-11-19 16:31:03 +08:00
case L'u':
place |= ENV_UNEXPORT;
break;
2012-11-19 16:31:03 +08:00
case L'p':
prompt = w.woptarg;
2012-11-19 16:31:03 +08:00
break;
case L'R':
right_prompt = w.woptarg;
break;
2012-11-19 16:31:03 +08:00
case L'c':
commandline = w.woptarg;
2012-11-19 16:31:03 +08:00
break;
2012-11-19 16:31:03 +08:00
case L'm':
mode_name = w.woptarg;
2012-11-19 16:31:03 +08:00
break;
case L'n':
errno = 0;
nchars = fish_wcstoi(w.woptarg, &end, 10);
if (errno || *end != 0)
{
switch (errno)
{
case ERANGE:
streams.err.append_format( _(L"%ls: Argument '%ls' is out of range\n"),
argv[0],
w.woptarg);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
default:
streams.err.append_format( _(L"%ls: Argument '%ls' must be an integer\n"),
argv[0],
w.woptarg);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
}
break;
2012-11-19 16:31:03 +08:00
case 's':
shell = 1;
break;
case 'a':
array = 1;
break;
case L'z':
split_null = true;
break;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
2012-11-19 16:31:03 +08:00
case L'?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
}
if ((place & ENV_UNEXPORT) && (place & ENV_EXPORT))
{
streams.err.append_format(BUILTIN_ERR_EXPUNEXP,
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
if ((place&ENV_LOCAL?1:0) + (place & ENV_GLOBAL?1:0) + (place & ENV_UNIVERSAL?1:0) > 1)
{
streams.err.append_format(BUILTIN_ERR_GLOCAL,
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
if (array && w.woptind+1 != argc)
{
streams.err.append_format(_(L"%ls: --array option requires a single variable name.\n"), argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
/*
Verify all variable names
*/
for (i=w.woptind; i<argc; i++)
{
wchar_t *src;
if (!wcslen(argv[i]))
{
streams.err.append_format(BUILTIN_ERR_VARNAME_ZERO, argv[0]);
return STATUS_BUILTIN_ERROR;
}
for (src=argv[i]; *src; src++)
{
if ((!iswalnum(*src)) && (*src != L'_'))
{
streams.err.append_format(BUILTIN_ERR_VARCHAR, argv[0], *src);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
}
}
/*
The call to reader_readline may change woptind, so we save it away here
*/
i=w.woptind;
/*
Check if we should read interactively using \c reader_readline()
*/
if (isatty(0) && streams.stdin_fd == STDIN_FILENO && !split_null)
{
const wchar_t *line;
reader_push(mode_name);
reader_set_left_prompt(prompt);
reader_set_right_prompt(right_prompt);
if (shell)
{
reader_set_complete_function(&complete);
reader_set_highlight_function(&highlight_shell);
reader_set_test_function(&reader_shell_test);
}
/* No autosuggestions or abbreviations in builtin_read */
reader_set_allow_autosuggesting(false);
reader_set_expand_abbreviations(false);
reader_set_exit_on_interrupt(true);
reader_set_buffer(commandline, wcslen(commandline));
proc_push_interactive(1);
event_fire_generic(L"fish_prompt");
line = reader_readline(nchars);
proc_pop_interactive();
if (line)
{
if (0 < nchars && nchars < wcslen(line))
{
// line may be longer than nchars if a keybinding used `commandline -i`
// note: we're deliberately throwing away the tail of the commandline.
// It shouldn't be unread because it was produced with `commandline -i`,
// not typed.
buff = wcstring(line, nchars);
}
else
{
buff = wcstring(line);
}
}
else
{
exit_res = STATUS_BUILTIN_ERROR;
}
reader_pop();
}
else
{
int eof=0;
buff.clear();
while (1)
{
int finished=0;
wchar_t res=0;
2014-09-22 11:47:03 +08:00
mbstate_t state = {};
while (!finished)
{
char b;
if (read_blocked(streams.stdin_fd, &b, 1) <= 0)
{
eof=1;
break;
}
size_t sz = mbrtowc(&res, &b, 1, &state);
switch (sz)
{
2012-11-19 16:31:03 +08:00
case (size_t)(-1):
memset(&state, '\0', sizeof(state));
break;
2012-11-19 16:31:03 +08:00
case (size_t)(-2):
break;
case 0:
finished = 1;
break;
2012-11-19 16:31:03 +08:00
default:
finished=1;
break;
}
}
if (eof)
break;
if (!split_null && res == L'\n')
break;
if (split_null && res == L'\0')
break;
buff.push_back(res);
if (0 < nchars && (size_t)nchars <= buff.size())
{
break;
}
}
if (buff.empty() && eof)
{
exit_res = 1;
}
}
if (i != argc && !exit_res)
{
env_var_t ifs = env_get_string(L"IFS");
if (ifs.missing_or_empty())
{
/* Every character is a separate token */
size_t bufflen = buff.size();
if (array)
{
if (bufflen > 0)
{
wcstring chars(bufflen+(bufflen-1), ARRAY_SEP);
wcstring::iterator out = chars.begin();
for (wcstring::const_iterator it = buff.begin(), end = buff.end(); it != end; ++it)
{
*out = *it;
out += 2;
}
env_set(argv[i], chars.c_str(), place);
}
else
{
env_set(argv[i], NULL, place);
}
}
else
{
size_t j = 0;
for (; i+1 < argc; ++i)
{
if (j < bufflen)
{
wchar_t buffer[2] = {buff[j++], 0};
env_set(argv[i], buffer, place);
}
else
{
env_set(argv[i], L"", place);
}
}
if (i < argc) env_set(argv[i], &buff[j], place);
}
}
else if (array)
{
wcstring tokens;
tokens.reserve(buff.size());
bool empty = true;
for (wcstring_range loc = wcstring_tok(buff, ifs); loc.first != wcstring::npos; loc = wcstring_tok(buff, ifs, loc))
{
if (!empty) tokens.push_back(ARRAY_SEP);
tokens.append(buff, loc.first, loc.second);
empty = false;
}
env_set(argv[i], empty ? NULL : tokens.c_str(), place);
}
else
{
wcstring_range loc = wcstring_range(0,0);
while (i<argc)
{
loc = wcstring_tok(buff, (i+1<argc) ? ifs : L"", loc);
env_set(argv[i], loc.first == wcstring::npos ? L"" : &buff.c_str()[loc.first], place);
++i;
}
}
}
return exit_res;
}
/**
The status builtin. Gives various status information on fish.
*/
static int builtin_status(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
wgetopter_t w;
enum
{
NORMAL,
IS_SUBST,
IS_BLOCK,
IS_INTERACTIVE,
IS_LOGIN,
IS_FULL_JOB_CONTROL,
IS_INTERACTIVE_JOB_CONTROL,
IS_NO_JOB_CONTROL,
STACK_TRACE,
DONE,
CURRENT_FILENAME,
CURRENT_LINE_NUMBER
}
;
int mode = NORMAL;
int argc = builtin_count_args(argv);
int res=STATUS_BUILTIN_OK;
const struct woption
long_options[] =
{
{
L"help", no_argument, 0, 'h'
}
,
{
L"is-command-substitution", no_argument, 0, 'c'
}
,
{
L"is-block", no_argument, 0, 'b'
}
,
{
L"is-interactive", no_argument, 0, 'i'
}
,
{
L"is-login", no_argument, 0, 'l'
}
,
{
L"is-full-job-control", no_argument, &mode, IS_FULL_JOB_CONTROL
}
,
{
L"is-interactive-job-control", no_argument, &mode, IS_INTERACTIVE_JOB_CONTROL
}
,
{
L"is-no-job-control", no_argument, &mode, IS_NO_JOB_CONTROL
}
,
{
L"current-filename", no_argument, 0, 'f'
}
,
{
L"current-line-number", no_argument, 0, 'n'
}
,
{
L"job-control", required_argument, 0, 'j'
}
,
{
L"print-stack-trace", no_argument, 0, 't'
}
,
{
0, 0, 0, 0
}
}
;
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L":cbilfnhj:t",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case 'c':
mode = IS_SUBST;
break;
2012-11-19 16:31:03 +08:00
case 'b':
mode = IS_BLOCK;
break;
2012-11-19 16:31:03 +08:00
case 'i':
mode = IS_INTERACTIVE;
break;
2012-11-19 16:31:03 +08:00
case 'l':
mode = IS_LOGIN;
break;
2012-11-19 16:31:03 +08:00
case 'f':
mode = CURRENT_FILENAME;
break;
2012-11-19 16:31:03 +08:00
case 'n':
mode = CURRENT_LINE_NUMBER;
break;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
case 'j':
if (wcscmp(w.woptarg, L"full") == 0)
2012-11-19 16:31:03 +08:00
job_control_mode = JOB_CONTROL_ALL;
else if (wcscmp(w.woptarg, L"interactive") == 0)
2012-11-19 16:31:03 +08:00
job_control_mode = JOB_CONTROL_INTERACTIVE;
else if (wcscmp(w.woptarg, L"none") == 0)
2012-11-19 16:31:03 +08:00
job_control_mode = JOB_CONTROL_NONE;
else
{
streams.err.append_format(L"%ls: Invalid job control mode '%ls'\n",
L"status", w.woptarg);
2012-11-19 16:31:03 +08:00
res = 1;
}
mode = DONE;
break;
2012-11-19 16:31:03 +08:00
case 't':
mode = STACK_TRACE;
break;
2012-11-19 16:31:03 +08:00
case ':':
builtin_missing_argument(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
}
if (!res)
{
switch (mode)
{
2012-11-19 16:31:03 +08:00
case CURRENT_FILENAME:
{
const wchar_t *fn = parser.current_filename();
2012-11-19 16:31:03 +08:00
if (!fn)
fn = _(L"Standard input");
streams.out.append_format( L"%ls\n", fn);
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
case CURRENT_LINE_NUMBER:
{
streams.out.append_format( L"%d\n", parser.get_lineno());
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
case IS_INTERACTIVE:
return !is_interactive_session;
2012-11-19 16:31:03 +08:00
case IS_SUBST:
return !is_subshell;
2012-11-19 16:31:03 +08:00
case IS_BLOCK:
return !is_block;
2012-11-19 16:31:03 +08:00
case IS_LOGIN:
return !is_login;
2012-11-19 16:31:03 +08:00
case IS_FULL_JOB_CONTROL:
return job_control_mode != JOB_CONTROL_ALL;
2012-11-19 16:31:03 +08:00
case IS_INTERACTIVE_JOB_CONTROL:
return job_control_mode != JOB_CONTROL_INTERACTIVE;
2012-11-19 16:31:03 +08:00
case IS_NO_JOB_CONTROL:
return job_control_mode != JOB_CONTROL_NONE;
2012-11-19 16:31:03 +08:00
case STACK_TRACE:
{
streams.out.append(parser.stack_trace());
2012-11-19 16:31:03 +08:00
break;
}
2012-11-19 16:31:03 +08:00
case NORMAL:
{
if (is_login)
streams.out.append_format( _(L"This is a login shell\n"));
2012-11-19 16:31:03 +08:00
else
streams.out.append_format( _(L"This is not a login shell\n"));
streams.out.append_format( _(L"Job control: %ls\n"),
2012-11-19 16:31:03 +08:00
job_control_mode==JOB_CONTROL_INTERACTIVE?_(L"Only on interactive jobs"):
(job_control_mode==JOB_CONTROL_NONE ? _(L"Never") : _(L"Always")));
streams.out.append(parser.stack_trace());
2012-11-19 16:31:03 +08:00
break;
}
}
}
return res;
}
/**
The exit builtin. Calls reader_exit to exit and returns the value specified.
*/
static int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
int argc = builtin_count_args(argv);
long ec=0;
switch (argc)
{
2012-11-19 16:31:03 +08:00
case 1:
{
ec = proc_get_last_status();
break;
}
2012-11-19 16:31:03 +08:00
case 2:
{
wchar_t *end;
errno = 0;
ec = wcstol(argv[1],&end,10);
if (errno || *end != 0)
{
streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"),
2012-11-19 16:31:03 +08:00
argv[0],
argv[1]);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
break;
}
default:
{
streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS,
2012-11-19 16:31:03 +08:00
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
}
reader_exit(1, 0);
return (int)ec;
}
/**
The cd builtin. Changes the current directory to the one specified
or to $HOME if none is specified. The directory can be relative to
any directory in the CDPATH variable.
*/
static int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
env_var_t dir_in;
wcstring dir;
int res=STATUS_BUILTIN_OK;
if (argv[1] == NULL)
{
dir_in = env_get_string(L"HOME");
if (dir_in.missing_or_empty())
{
streams.err.append_format(_(L"%ls: Could not find home directory\n"),
argv[0]);
}
}
else
{
dir_in = argv[1];
}
bool got_cd_path = false;
if (! dir_in.missing())
{
got_cd_path = path_get_cdpath(dir_in, &dir);
}
if (!got_cd_path)
{
if (errno == ENOTDIR)
{
streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"),
argv[0],
dir_in.c_str());
}
else if (errno == ENOENT)
{
streams.err.append_format(_(L"%ls: The directory '%ls' does not exist\n"),
argv[0],
dir_in.c_str());
}
else if (errno == EROTTEN)
{
streams.err.append_format(_(L"%ls: '%ls' is a rotten symlink\n"),
argv[0],
dir_in.c_str());
}
else
{
streams.err.append_format(_(L"%ls: Unknown error trying to locate directory '%ls'\n"),
argv[0],
dir_in.c_str());
}
if (!get_is_interactive())
{
streams.err.append(parser.current_line());
}
res = 1;
}
else if (wchdir(dir) != 0)
{
struct stat buffer;
int status;
status = wstat(dir, &buffer);
if (!status && S_ISDIR(buffer.st_mode))
{
streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"),
argv[0],
dir.c_str());
}
else
{
streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"),
argv[0],
dir.c_str());
}
if (!get_is_interactive())
{
streams.err.append(parser.current_line());
}
res = 1;
}
else if (!env_set_pwd())
{
res=1;
streams.err.append_format(_(L"%ls: Could not set PWD variable\n"), argv[0]);
}
return res;
}
/**
Implementation of the builtin count command, used to count the
number of arguments sent to it.
*/
static int builtin_count(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
int argc;
argc = builtin_count_args(argv);
streams.out.append_format( L"%d\n", argc-1);
return !(argc-1);
}
/**
Implementation of the builtin contains command, used to check if a
specified string is part of a list.
*/
static int builtin_contains(parser_t &parser, io_streams_t &streams, wchar_t ** argv)
{
wgetopter_t w;
int argc;
argc = builtin_count_args(argv);
wchar_t *needle;
2013-04-29 05:35:00 +08:00
bool should_output_index = false;
2013-04-29 05:35:00 +08:00
const struct woption long_options[] =
{
2013-04-29 05:35:00 +08:00
{ L"help", no_argument, 0, 'h' } ,
{ L"index", no_argument, 0, 'i' },
{ 0, 0, 0, 0 }
};
while (1)
{
int opt_index = 0;
int opt = w.wgetopt_long(argc,
argv,
L"+hi",
long_options,
&opt_index);
if (opt == -1)
break;
switch (opt)
{
2012-11-19 16:31:03 +08:00
case 0:
assert(opt_index >= 0 && (size_t)opt_index < sizeof long_options / sizeof *long_options);
if (long_options[opt_index].flag != 0)
break;
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
2012-11-19 16:31:03 +08:00
argv[0],
long_options[opt_index].name);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
2012-11-19 16:31:03 +08:00
case ':':
builtin_missing_argument(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +08:00
case '?':
builtin_unknown_option(parser, streams, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-11-19 16:31:03 +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;
}
}
needle = argv[w.woptind];
if (!needle)
{
streams.err.append_format(_(L"%ls: Key not specified\n"), argv[0]);
}
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);
return 0;
}
}
return 1;
}
/**
The . (dot) builtin, sometimes called source. Evaluates the contents of a file.
*/
static int builtin_source(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
ASSERT_IS_MAIN_THREAD();
int fd;
int res = STATUS_BUILTIN_OK;
struct stat buf;
int argc;
argc = builtin_count_args(argv);
const wchar_t *fn, *fn_intern;
if (argc < 2 || (wcscmp(argv[1], L"-") == 0))
{
fn = L"-";
fn_intern = fn;
fd = dup(streams.stdin_fd);
}
else
{
if ((fd = wopen_cloexec(argv[1], O_RDONLY)) == -1)
{
streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), argv[0], argv[1]);
builtin_wperror(L"source", streams);
return STATUS_BUILTIN_ERROR;
}
if (fstat(fd, &buf) == -1)
{
close(fd);
streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), argv[0], argv[1]);
builtin_wperror(L"source", streams);
return STATUS_BUILTIN_ERROR;
}
if (!S_ISREG(buf.st_mode))
{
close(fd);
streams.err.append_format(_(L"%ls: '%ls' is not a file\n"), argv[0], argv[1]);
return STATUS_BUILTIN_ERROR;
}
fn_intern = intern(argv[1]);
}
parser.push_block(new source_block_t(fn_intern));
reader_push_current_filename(fn_intern);
env_set_argv((argc>2)?(argv+2):(argv+1));
res = reader_read(fd, streams.io_chain ? *streams.io_chain : io_chain_t());
parser.pop_block();
if (res)
{
streams.err.append_format(_(L"%ls: Error while reading file '%ls'\n"),
argv[0],
fn_intern == intern_static(L"-") ? L"<stdin>" : fn_intern);
}
else
{
res = proc_get_last_status();
}
/*
Do not close fd after calling reader_read. reader_read
automatically closes it before calling eval.
*/
reader_pop_current_filename();
return res;
}
/**
Make the specified job the first job of the job list. Moving jobs
around in the list makes the list reflect the order in which the
jobs were used.
*/
static void make_first(job_t *j)
{
job_promote(j);
}
/**
Builtin for putting a job in the foreground
*/
static int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
job_t *j=NULL;
if (argv[1] == 0)
{
/*
Select last constructed job (I.e. first job in the job que)
that is possible to put in the foreground
*/
job_iterator_t jobs;
while ((j = jobs.next()))
{
if (job_get_flag(j, JOB_CONSTRUCTED) && (!job_is_completed(j)) &&
((job_is_stopped(j) || (!job_get_flag(j, JOB_FOREGROUND))) && job_get_flag(j, JOB_CONTROL)))
{
break;
}
}
if (!j)
{
streams.err.append_format(_(L"%ls: There are no suitable jobs\n"),
argv[0]);
}
}
else if (argv[2] != 0)
{
/*
Specifying what more than one job to put to the foreground
is a syntax error, we still 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
*/
wchar_t *endptr;
int pid;
int found_job = 0;
errno = 0;
pid = fish_wcstoi(argv[1], &endptr, 10);
if (!(*endptr || errno))
{
j = job_get_from_pid(pid);
if (j)
found_job = 1;
}
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]);
}
builtin_print_help(parser, streams, argv[0], streams.err);
j=0;
}
else
{
wchar_t *end;
int pid;
errno = 0;
pid = abs(fish_wcstoi(argv[1], &end, 10));
if (*end || errno)
{
streams.err.append_format(BUILTIN_ERR_NOT_NUMBER,
argv[0],
argv[1]);
builtin_print_help(parser, streams, argv[0], streams.err);
}
else
{
j = job_get_from_pid(pid);
if (!j || !job_get_flag(j, JOB_CONSTRUCTED) || job_is_completed(j))
{
streams.err.append_format(_(L"%ls: No suitable job: %d\n"),
argv[0],
pid);
builtin_print_help(parser, streams, argv[0], streams.err);
j=0;
}
else if (!job_get_flag(j, JOB_CONTROL))
{
streams.err.append_format(_(L"%ls: Can't put job %d, '%ls' to foreground because it is not under job control\n"),
argv[0],
pid,
j->command_wcstr());
builtin_print_help(parser, streams, argv[0], streams.err);
j=0;
}
}
}
if (j)
{
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());
}
const wcstring ft = tok_first(j->command());
if (! ft.empty())
env_set(L"_", ft.c_str(), ENV_EXPORT);
reader_write_title(j->command());
make_first(j);
job_set_flag(j, JOB_FOREGROUND, 1);
job_continue(j, job_is_stopped(j));
}
return j != 0;
}
/**
Helper function for builtin_bg()
*/
static int send_to_bg(parser_t &parser, io_streams_t &streams, job_t *j, const wchar_t *name)
{
if (j == 0)
{
streams.err.append_format(_(L"%ls: Unknown job '%ls'\n"),
L"bg",
name);
builtin_print_help(parser, streams, L"bg", streams.err);
return STATUS_BUILTIN_ERROR;
}
else if (!job_get_flag(j, JOB_CONTROL))
{
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());
builtin_print_help(parser, streams, L"bg", streams.err);
return STATUS_BUILTIN_ERROR;
}
else
{
streams.err.append_format(_(L"Send job %d '%ls' to background\n"),
j->job_id,
j->command_wcstr());
}
make_first(j);
job_set_flag(j, JOB_FOREGROUND, 0);
job_continue(j, job_is_stopped(j));
return STATUS_BUILTIN_OK;
}
/**
Builtin for putting a job in the background
*/
static int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
int res = STATUS_BUILTIN_OK;
if (argv[1] == 0)
{
job_t *j;
2012-01-30 08:36:21 +08:00
job_iterator_t jobs;
while ((j = jobs.next()))
{
if (job_is_stopped(j) && job_get_flag(j, JOB_CONTROL) && (!job_is_completed(j)))
{
break;
}
}
if (!j)
{
streams.err.append_format(_(L"%ls: There are no suitable jobs\n"),
argv[0]);
res = 1;
}
else
{
res = send_to_bg(parser, streams, j, _(L"(default)"));
}
}
else
{
wchar_t *end;
int i;
int pid;
int err = 0;
for (i=1; argv[i]; i++)
{
errno=0;
pid = fish_wcstoi(argv[i], &end, 10);
if (errno || pid < 0 || *end || !job_get_from_pid(pid))
{
streams.err.append_format(_(L"%ls: '%ls' is not a job\n"),
argv[0],
argv[i]);
err = 1;
break;
}
}
if (!err)
{
for (i=1; !res && argv[i]; i++)
{
pid = fish_wcstoi(argv[i], 0, 10);
res |= send_to_bg(parser, streams, job_get_from_pid(pid), *argv);
}
}
}
return res;
}
/**
This function handles both the 'continue' and the 'break' builtins
that are used for loop control.
*/
static int builtin_break_continue(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
int is_break = (wcscmp(argv[0],L"break")==0);
int argc = builtin_count_args(argv);
if (argc != 1)
{
streams.err.append_format(BUILTIN_ERR_UNKNOWN,
argv[0],
argv[1]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
/* Find the index of the enclosing for or while loop. Recall that incrementing loop_idx goes 'up' to outer blocks */
size_t loop_idx;
for (loop_idx = 0; loop_idx < parser.block_count(); loop_idx++)
{
const block_t *b = parser.block_at_index(loop_idx);
if (b->type() == WHILE || b->type() == FOR)
break;
}
if (loop_idx >= parser.block_count())
{
streams.err.append_format(_(L"%ls: Not inside of loop\n"),
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
/* Skip blocks interior to the loop */
size_t block_idx = loop_idx;
while (block_idx--)
{
parser.block_at_index(block_idx)->skip = true;
}
/* Skip the loop itself */
block_t *loop_block = parser.block_at_index(loop_idx);
loop_block->skip = true;
loop_block->loop_status = is_break ? LOOP_BREAK : LOOP_CONTINUE;
return STATUS_BUILTIN_OK;
}
/**
Implementation of the builtin breakpoint command, used to launch the
interactive debugger.
*/
static int builtin_breakpoint(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
parser.push_block(new breakpoint_block_t());
reader_read(STDIN_FILENO, streams.io_chain ? *streams.io_chain : io_chain_t());
parser.pop_block();
return proc_get_last_status();
}
/**
Function for handling the \c return builtin
*/
static int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
int argc = builtin_count_args(argv);
int status = proc_get_last_status();
switch (argc)
{
2012-11-19 16:31:03 +08:00
case 1:
break;
case 2:
{
2012-11-19 16:31:03 +08:00
wchar_t *end;
errno = 0;
status = fish_wcstoi(argv[1],&end,10);
if (errno || *end != 0)
{
streams.err.append_format(_(L"%ls: Argument '%ls' must be an integer\n"),
2012-11-19 16:31:03 +08:00
argv[0],
argv[1]);
builtin_print_help(parser, streams, argv[0], streams.err);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
}
break;
}
default:
streams.err.append_format(_(L"%ls: Too many arguments\n"),
2012-11-19 16:31:03 +08:00
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
/* Find the function block */
size_t function_block_idx;
for (function_block_idx = 0; function_block_idx < parser.block_count(); function_block_idx++)
{
const block_t *b = parser.block_at_index(function_block_idx);
if (b->type() == FUNCTION_CALL || b->type() == FUNCTION_CALL_NO_SHADOW)
break;
}
if (function_block_idx >= parser.block_count())
{
streams.err.append_format(_(L"%ls: Not inside of function\n"),
argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR;
}
/* Skip everything up to (and then including) the function block */
for (size_t i=0; i < function_block_idx; i++)
{
block_t *b = parser.block_at_index(i);
b->skip = true;
}
parser.block_at_index(function_block_idx)->skip = true;
return status;
}
2012-06-05 12:24:42 +08:00
/**
History of commands executed by user
*/
static int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv)
2012-06-05 12:24:42 +08:00
{
int argc = builtin_count_args(argv);
bool search_history = false;
2012-06-05 12:24:42 +08:00
bool delete_item = false;
bool search_prefix = false;
bool save_history = false;
bool clear_history = false;
bool merge_history = false;
2012-06-05 12:24:42 +08:00
static const struct woption long_options[] =
{
{ L"prefix", no_argument, 0, 'p' },
{ L"delete", no_argument, 0, 'd' },
{ L"search", no_argument, 0, 's' },
{ L"contains", no_argument, 0, 'c' },
{ L"save", no_argument, 0, 'v' },
{ L"clear", no_argument, 0, 'l' },
{ L"merge", no_argument, 0, 'm' },
{ L"help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
2012-06-05 12:24:42 +08:00
int opt = 0;
int opt_index = 0;
wgetopter_t w;
2012-06-05 12:24:42 +08:00
history_t *history = reader_get_history();
/* Use the default history if we have none (which happens if invoked non-interactively, e.g. from webconfig.py */
if (! history)
history = &history_t::history_with_name(L"fish");
while ((opt = w.wgetopt_long_only(argc, argv, L"pdscvl", long_options, &opt_index)) != EOF)
2012-06-05 12:24:42 +08:00
{
switch (opt)
2012-06-05 12:24:42 +08:00
{
2012-11-19 16:31:03 +08:00
case 'p':
search_prefix = true;
break;
case 'd':
delete_item = true;
break;
case 's':
search_history = true;
break;
case 'c':
break;
case 'v':
save_history = true;
break;
case 'l':
clear_history = true;
break;
case 'm':
merge_history = true;
break;
2012-11-19 16:31:03 +08:00
case 'h':
builtin_print_help(parser, streams, argv[0], streams.out);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_OK;
break;
case '?':
streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
break;
default:
streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], argv[w.woptind-1]);
2012-11-19 16:31:03 +08:00
return STATUS_BUILTIN_ERROR;
2012-06-05 12:24:42 +08:00
}
}
2012-06-05 12:24:42 +08:00
/* Everything after is an argument */
const wcstring_list_t args(argv + w.woptind, argv + argc);
2012-06-05 12:24:42 +08:00
if (argc == 1)
{
wcstring full_history;
history->get_string_representation(&full_history, wcstring(L"\n"));
streams.out.append(full_history);
streams.out.push_back('\n');
2012-06-05 12:24:42 +08:00
return STATUS_BUILTIN_OK;
}
if (merge_history)
{
history->incorporate_external_changes();
}
2012-06-05 12:24:42 +08:00
if (search_history)
{
int res = STATUS_BUILTIN_ERROR;
for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter)
2012-06-05 12:24:42 +08:00
{
const wcstring &search_string = *iter;
if (search_string.empty())
{
streams.err.append_format(BUILTIN_ERR_COMBO2, argv[0], L"Use --search with either --contains or --prefix");
return res;
}
history_search_t searcher = history_search_t(*history, search_string, search_prefix?HISTORY_SEARCH_TYPE_PREFIX:HISTORY_SEARCH_TYPE_CONTAINS);
while (searcher.go_backwards())
{
streams.out.append(searcher.current_string());
streams.out.append(L"\n");
res = STATUS_BUILTIN_OK;
}
2012-06-05 12:24:42 +08:00
}
return res;
}
if (delete_item)
{
for (wcstring_list_t::const_iterator iter = args.begin(); iter != args.end(); ++iter)
{
wcstring delete_string = *iter;
if (delete_string[0] == '"' && delete_string[delete_string.length() - 1] == '"')
delete_string = delete_string.substr(1, delete_string.length() - 2);
history->remove(delete_string);
}
2012-06-05 12:24:42 +08:00
return STATUS_BUILTIN_OK;
}
if (save_history)
{
history->save();
return STATUS_BUILTIN_OK;
}
if (clear_history)
{
history->clear();
history->save();
return STATUS_BUILTIN_OK;
}
return STATUS_BUILTIN_ERROR;
}
int builtin_parse(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
2013-07-01 06:38:31 +08:00
struct sigaction act;
sigemptyset(& act.sa_mask);
act.sa_flags=0;
act.sa_handler=SIG_DFL;
sigaction(SIGINT, &act, 0);
std::vector<char> txt;
for (;;)
{
char buff[256];
ssize_t amt = read_loop(streams.stdin_fd, buff, sizeof buff);
if (amt <= 0) break;
txt.insert(txt.end(), buff, buff + amt);
}
if (! txt.empty())
{
const wcstring src = str2wcstring(&txt.at(0), txt.size());
parse_node_tree_t parse_tree;
parse_error_list_t errors;
bool success = parse_tree_from_string(src, parse_flag_include_comments, &parse_tree, &errors);
if (! success)
{
streams.out.append(L"Parsing failed:\n");
for (size_t i=0; i < errors.size(); i++)
{
streams.out.append(errors.at(i).describe(src));
streams.out.push_back(L'\n');
}
streams.out.append(L"(Reparsed with continue after error)\n");
parse_tree.clear();
errors.clear();
parse_tree_from_string(src, parse_flag_continue_after_error | parse_flag_include_comments, &parse_tree, &errors);
}
const wcstring dump = parse_dump_tree(parse_tree, src);
streams.out.append(dump);
}
return STATUS_BUILTIN_OK;
}
2012-06-05 12:24:42 +08:00
int builtin_true(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
return STATUS_BUILTIN_OK;
}
int builtin_false(parser_t &parser, io_streams_t &streams, wchar_t **argv)
{
return STATUS_BUILTIN_ERROR;
}
/*
END OF BUILTIN COMMANDS
2012-02-01 11:47:56 +08:00
Below are functions for handling the builtin commands.
THESE MUST BE SORTED BY NAME! Completion lookup uses binary search.
*/
/**
2012-02-01 11:47:56 +08:00
Data about all the builtin commands in fish.
Functions that are bound to builtin_generic are handled directly by the parser.
2012-09-01 16:46:14 +08:00
NOTE: These must be kept in sorted order!
*/
2012-02-01 11:47:56 +08:00
static const builtin_data_t builtin_datas[]=
{
2013-01-05 17:30:03 +08:00
{ L"[", &builtin_test, N_(L"Test a condition") },
#if 0
2014-01-09 07:13:08 +08:00
{ L"__fish_parse", &builtin_parse, N_(L"Try out the new parser") },
#endif
{ 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") },
{ 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") },
2013-01-23 01:07:28 +08:00
{ 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") },
{ 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") },
Merge new string builtin This adds the new builtin 'string' which supports various string manipulation and matching algorithms, including PCRE based regular expressions. Fixes #2296 Squashed commit of the following: commit 4c3eaeb6e57d76463e9683c327142b0aeafb92b8 Author: ridiculousfish <corydoras@ridiculousfish.com> Date: Sat Sep 12 12:51:30 2015 -0700 Remove testdata and doc dirs from pcre2 source commit b2a8b4b50f2398b204fb72cfe4b5ba77ece2e1ab Merge: 11c8a47 7974aab Author: ridiculousfish <corydoras@ridiculousfish.com> Date: Sat Sep 12 12:32:40 2015 -0700 Merge branch 'string' of git://github.com/msteed/fish-shell into string-test commit 7974aab6d367f999f1140ab34c2535cef5cf3b00 Author: Michael Steed <msteed@saltstack.com> Date: Fri Sep 11 13:00:02 2015 -0600 build pcre2 lib only, no docs commit eb20b43d2d96b7e6d24618158ce71078de83c40b Merge: 1a09e70 5f519cb Author: Michael Steed <msteed68@gmail.com> Date: Thu Sep 10 20:00:47 2015 -0600 Merge branch 'string' of github.com:msteed/fish-shell into string commit 1a09e709d028393c9e9e6dc9a84278f399a15f3d Author: Michael Steed <msteed68@gmail.com> Date: Thu Sep 10 19:58:24 2015 -0600 rebase on master & address the fallout commit a0ec9772cd1a0a548a501a7633be05dab4e5ee46 Author: Michael Steed <msteed68@gmail.com> Date: Thu Sep 10 19:26:45 2015 -0600 use fish's wildcard_match() for glob matching commit 64c25a01e3f7234f220ba13545cf658a7492b1a4 Author: Michael Steed <msteed68@gmail.com> Date: Thu Aug 27 08:19:23 2015 -0600 some fixes from review - string_get_arg_stdin(): simplify and don't discard the argument when the trailing newline is absent - fix calls to pcre2 for e.g. string match -r -a 'a*' 'b' - correct test for args coming from stdin commit ece7f35ec5f4093763627d68d671b6c0c876896d Author: Michael Steed <msteed68@gmail.com> Date: Sat Aug 22 19:35:56 2015 -0600 fixes from review - Makefile.in: restore iwyu target - regex_replacer_t::replace_matches(): correct size passed to realloc() commit 9ff7477a926c4572e26171cab3cd42f8086be678 Author: Michael Steed <msteed68@gmail.com> Date: Thu Aug 20 13:08:33 2015 -0600 Minor doc improvements commit baf4e096b22dde3063b85b833795eb570d660ba7 Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 19 18:29:02 2015 -0600 another attempt to fix the ci build commit 896a2c2b279a419747bea26102229fbe84534a6f Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 19 18:03:49 2015 -0600 Updates after review comments - make match/replace without -a operate on the first match on each argument - use different exit codes for "no operation performed" and errors, as grep does - refactor regex compile code - use human-friendly error messages from pcre2 - improve error handling & reporting elsewhere - add a few tests - make some doc fixes - some simplification & cleanup - fix ci build failure (I hope) commit efd47dcbda2ca247d58bee56a7774cd75a1062fd Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 12 00:26:07 2015 -0600 fix dependencies for parallel make commit ed0850e2db467362066a3d94e3ececd17c1756cd Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 23:37:22 2015 -0600 Add missing pcre2 files + .gitignore commit 9492e7a7e929c03554336be1ddf80ca6b37f53c5 Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 22:44:05 2015 -0600 add pcre2-10.20 and update license.hdr commit 1a60b933718feb20c0bf7c9e257b8e495014ea1b Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 22:41:19 2015 -0600 add string builtin files - string builtin source, tests, & docs - changes to configure.ac & Makefile.in commit 5f519cb2a2c05213e0a88a7add7af288bc1c1352 Author: Michael Steed <msteed68@gmail.com> Date: Thu Sep 10 19:26:45 2015 -0600 use fish's wildcard_match() for glob matching commit 2ecd24f79500879e2de5bdf1b4c19dd44fc6ac85 Author: Michael Steed <msteed68@gmail.com> Date: Thu Aug 27 08:19:23 2015 -0600 some fixes from review - string_get_arg_stdin(): simplify and don't discard the argument when the trailing newline is absent - fix calls to pcre2 for e.g. string match -r -a 'a*' 'b' - correct test for args coming from stdin commit 45b777e4dc85c05cd4a186f4bdcae543c21aaf08 Author: Michael Steed <msteed68@gmail.com> Date: Sat Aug 22 19:35:56 2015 -0600 fixes from review - Makefile.in: restore iwyu target - regex_replacer_t::replace_matches(): correct size passed to realloc() commit 981cbb6ddf742a5fe8881af916e7b870b7e6422a Author: Michael Steed <msteed68@gmail.com> Date: Thu Aug 20 13:08:33 2015 -0600 Minor doc improvements commit ddb6a2a8fdb6aa31aad41e80d5481bb32c6ed8ff Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 19 18:29:02 2015 -0600 another attempt to fix the ci build commit 1e34e3191b028162863d263e9868052f75194aa5 Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 19 18:03:49 2015 -0600 Updates after review comments - make match/replace without -a operate on the first match on each argument - use different exit codes for "no operation performed" and errors, as grep does - refactor regex compile code - use human-friendly error messages from pcre2 - improve error handling & reporting elsewhere - add a few tests - make some doc fixes - some simplification & cleanup - fix ci build failure (I hope) commit 34232e152df17a3cfbf0a094dd51d148a4f04e6f Author: Michael Steed <msteed68@gmail.com> Date: Wed Aug 12 00:26:07 2015 -0600 fix dependencies for parallel make commit 00d7e781697f53454beb91c1d0fc4b2d28d6e034 Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 23:37:22 2015 -0600 Add missing pcre2 files + .gitignore commit 4498aa5f576e09634f7f619443e74d2f33c108e4 Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 22:44:05 2015 -0600 add pcre2-10.20 and update license.hdr commit 290c58c72e22db644ccf6fa9088051644980ed0a Author: Michael Steed <msteed68@gmail.com> Date: Tue Aug 11 22:41:19 2015 -0600 add string builtin files - string builtin source, tests, & docs - changes to configure.ac & Makefile.in
2015-09-13 03:59:40 +08:00
{ 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)
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);
if (found != array_end && name == found->name)
{
2012-02-01 11:47:56 +08:00
return found;
}
else
{
2012-02-01 11:47:56 +08:00
return NULL;
}
}
void builtin_init()
{
for (size_t i=0; i < BUILTIN_COUNT; i++)
{
intern_static(builtin_datas[i].name);
}
}
void builtin_destroy()
{
}
int builtin_exists(const wcstring &cmd)
{
return !!builtin_lookup(cmd);
}
/**
Return true if the specified builtin should handle it's own help,
false otherwise.
*/
static int internal_help(const wchar_t *cmd)
{
CHECK(cmd, 0);
return contains(cmd, L"for", L"while", L"function",
L"if", L"end", L"switch", L"case", L"count", L"printf");
}
int builtin_run(parser_t &parser, const wchar_t * const *argv, io_streams_t &streams)
{
int (*cmd)(parser_t &parser, io_streams_t &streams, const wchar_t * const *argv)=NULL;
CHECK(argv, STATUS_BUILTIN_ERROR);
CHECK(argv[0], STATUS_BUILTIN_ERROR);
2012-02-01 11:47:56 +08:00
const builtin_data_t *data = builtin_lookup(argv[0]);
cmd = (int (*)(parser_t &parser, io_streams_t &streams, const wchar_t * const*))(data ? data->func : NULL);
if (argv[1] != 0 && !internal_help(argv[0]))
{
if (argv[2] == 0 && (parse_util_argument_is_help(argv[1], 0)))
{
builtin_print_help(parser, streams, argv[0], streams.out);
return STATUS_BUILTIN_OK;
}
}
if (data != NULL)
{
int status;
status = cmd(parser, streams, argv);
return status;
}
else
{
debug(0, UNKNOWN_BUILTIN_ERR_MSG, argv[0]);
}
return STATUS_BUILTIN_ERROR;
}
2012-02-01 11:47:56 +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);
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;
}
void builtin_get_names(std::vector<completion_t> *list)
{
assert(list != NULL);
list->reserve(list->size() + BUILTIN_COUNT);
for (size_t i=0; i < BUILTIN_COUNT; i++)
{
append_completion(list, builtin_datas[i].name);
}
}
wcstring builtin_get_desc(const wcstring &name)
{
wcstring result;
const builtin_data_t *builtin = builtin_lookup(name);
if (builtin)
{
result = _(builtin->desc);
}
return result;
}