mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-14 17:07:44 +00:00
implement means to learn about a functions source
This implements a way to use the `functions` command to perform introspection to learn about the characteristics of a function. Such as where it came from. Fixes #3295
This commit is contained in:
parent
2be1288cac
commit
2e38cf2a4b
8 changed files with 138 additions and 12 deletions
|
@ -3,6 +3,7 @@
|
|||
\subsection functions-synopsis Synopsis
|
||||
\fish{synopsis}
|
||||
functions [ -a | --all ] [ -n | --names ]
|
||||
functions [ -m | --metadata ] [ -v ] FUNCTION
|
||||
functions -c OLDNAME NEWNAME
|
||||
functions -d DESCRIPTION FUNCTION
|
||||
functions [ -e | -q ] FUNCTIONS...
|
||||
|
@ -14,7 +15,7 @@ functions [ -e | -q ] FUNCTIONS...
|
|||
|
||||
The following options are available:
|
||||
|
||||
- `-a` or `--all` lists all functions, even those whose name start with an underscore.
|
||||
- `-a` or `--all` lists all functions, even those whose name starts with an underscore.
|
||||
|
||||
- `-c OLDNAME NEWNAME` or `--copy OLDNAME NEWNAME` creates a new function named NEWNAME, using the definition of the OLDNAME function.
|
||||
|
||||
|
@ -22,10 +23,21 @@ The following options are available:
|
|||
|
||||
- `-e` or `--erase` causes the specified functions to be erased.
|
||||
|
||||
- `-m` or `--metadata` reports the path name where each function is defined or could be autoloaded, `stdin` if the function was defined interactively or on the command line or by reading stdin, and `n/a` if the function isn't available. If the `--verbose` option is also specified then four lines are written:
|
||||
|
||||
-# the pathname as already described,
|
||||
-# `autoloaded`, `not-autoloaded` or `n/a`,
|
||||
-# the line number within the file or zero if not applicable,
|
||||
-# `scope-shadowing` if the function shadows the vars in the calling function (the normal case) else `no-scope-shadowing`, or `n/a` if the function isn't defined.
|
||||
|
||||
You should not assume that only four lines will be written since we may add additional information to the output in the future.
|
||||
|
||||
- `-n` or `--names` lists the names of all defined functions.
|
||||
|
||||
- `-q` or `--query` tests if the specified functions exist.
|
||||
|
||||
- `-v` or `--verbose` will make some output more verbose.
|
||||
|
||||
The default behavior of `functions`, when called with no arguments, is to print the names of all defined functions. Unless the `-a` option is given, no functions starting with underscores are not included in the output.
|
||||
|
||||
If any non-option parameters are given, the definition of the specified functions are printed.
|
||||
|
|
|
@ -1045,6 +1045,41 @@ static wcstring functions_def(const wcstring &name) {
|
|||
return out;
|
||||
}
|
||||
|
||||
static int report_function_metadata(const wchar_t *funcname, bool verbose, io_streams_t &streams,
|
||||
bool metadata_as_comments) {
|
||||
const wchar_t *path = L"n/a";
|
||||
const wchar_t *autoloaded = L"n/a";
|
||||
const wchar_t *shadows_scope = L"n/a";
|
||||
int line_number = 0;
|
||||
|
||||
if (function_exists(funcname)) {
|
||||
path = function_get_definition_file(funcname);
|
||||
if (path) {
|
||||
autoloaded = function_is_autoloaded(funcname) ? L"autoloaded" : L"not-autoloaded";
|
||||
line_number = function_get_definition_offset(funcname);
|
||||
} else {
|
||||
path = L"stdin";
|
||||
}
|
||||
shadows_scope =
|
||||
function_get_shadow_scope(funcname) ? L"scope-shadowing" : L"no-scope-shadowing";
|
||||
}
|
||||
|
||||
if (metadata_as_comments) {
|
||||
if (path != L"stdin") {
|
||||
streams.out.append_format(L"# Defined in %ls @ line %d\n", path, line_number);
|
||||
}
|
||||
} else {
|
||||
streams.out.append_format(L"%ls\n", path);
|
||||
if (verbose) {
|
||||
streams.out.append_format(L"%ls\n", autoloaded);
|
||||
streams.out.append_format(L"%d\n", line_number);
|
||||
streams.out.append_format(L"%ls\n", shadows_scope);
|
||||
}
|
||||
}
|
||||
|
||||
return STATUS_BUILTIN_OK;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
@ -1058,17 +1093,20 @@ static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **
|
|||
int res = STATUS_BUILTIN_OK;
|
||||
int query = 0;
|
||||
int copy = 0;
|
||||
bool report_metadata = false;
|
||||
bool verbose = false;
|
||||
|
||||
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}};
|
||||
{L"erase", no_argument, NULL, 'e'}, {L"description", required_argument, NULL, 'd'},
|
||||
{L"names", no_argument, NULL, 'n'}, {L"all", no_argument, NULL, 'a'},
|
||||
{L"help", no_argument, NULL, 'h'}, {L"query", no_argument, NULL, 'q'},
|
||||
{L"copy", no_argument, NULL, 'c'}, {L"metadata", no_argument, NULL, 'm'},
|
||||
{L"verbose", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0}};
|
||||
|
||||
while (1) {
|
||||
int opt_index = 0;
|
||||
|
||||
int opt = w.wgetopt_long(argc, argv, L"ed:nahqc", long_options, &opt_index);
|
||||
int opt = w.wgetopt_long(argc, argv, L"ed:mnahqcv", long_options, &opt_index);
|
||||
if (opt == -1) break;
|
||||
|
||||
switch (opt) {
|
||||
|
@ -1079,10 +1117,18 @@ static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **
|
|||
builtin_print_help(parser, streams, argv[0], streams.err);
|
||||
return STATUS_BUILTIN_ERROR;
|
||||
}
|
||||
case 'v': {
|
||||
verbose = true;
|
||||
break;
|
||||
}
|
||||
case 'e': {
|
||||
erase = 1;
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
report_metadata = true;
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
desc = w.woptarg;
|
||||
break;
|
||||
|
@ -1152,6 +1198,15 @@ static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **
|
|||
|
||||
function_set_desc(func, desc);
|
||||
return STATUS_BUILTIN_OK;
|
||||
} else if (report_metadata) {
|
||||
if (argc - w.woptind != 1) {
|
||||
streams.err.append_format(
|
||||
_(L"%ls: Expected exactly one function name for --metadata\n"), argv[0]);
|
||||
return STATUS_BUILTIN_ERROR;
|
||||
}
|
||||
|
||||
const wchar_t *funcname = argv[w.woptind];
|
||||
return report_function_metadata(funcname, verbose, streams, false);
|
||||
} else if (list || (argc == w.woptind)) {
|
||||
int is_screen = !streams.out_is_redirected && isatty(STDOUT_FILENO);
|
||||
size_t i;
|
||||
|
@ -1224,8 +1279,9 @@ static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **
|
|||
else {
|
||||
if (!query) {
|
||||
if (i != w.woptind) streams.out.append(L"\n");
|
||||
|
||||
streams.out.append(functions_def(argv[i]));
|
||||
const wchar_t *funcname = argv[w.woptind];
|
||||
report_function_metadata(funcname, verbose, streams, true);
|
||||
streams.out.append(functions_def(funcname));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -259,7 +259,7 @@ std::map<wcstring, env_var_t> function_get_inherit_vars(const wcstring &name) {
|
|||
return func ? func->inherit_vars : std::map<wcstring, env_var_t>();
|
||||
}
|
||||
|
||||
int function_get_shadow_scope(const wcstring &name) {
|
||||
bool function_get_shadow_scope(const wcstring &name) {
|
||||
scoped_lock locker(functions_lock);
|
||||
const function_info_t *func = function_get(name);
|
||||
return func ? func->shadow_scope : false;
|
||||
|
@ -325,6 +325,12 @@ const wchar_t *function_get_definition_file(const wcstring &name) {
|
|||
return func ? func->definition_file : NULL;
|
||||
}
|
||||
|
||||
bool function_is_autoloaded(const wcstring &name) {
|
||||
scoped_lock locker(functions_lock);
|
||||
const function_info_t *func = function_get(name);
|
||||
return func->is_autoload;
|
||||
}
|
||||
|
||||
int function_get_definition_offset(const wcstring &name) {
|
||||
scoped_lock locker(functions_lock);
|
||||
const function_info_t *func = function_get(name);
|
||||
|
|
|
@ -99,6 +99,9 @@ int function_exists_no_autoload(const wcstring &name, const env_vars_snapshot_t
|
|||
/// \param get_hidden whether to include hidden functions, i.e. ones starting with an underscore.
|
||||
wcstring_list_t function_get_names(int get_hidden);
|
||||
|
||||
/// Returns true if the function was autoloaded.
|
||||
bool function_is_autoloaded(const wcstring &name);
|
||||
|
||||
/// Returns tha absolute path of the file where the specified function was defined. Returns 0 if the
|
||||
/// file was defined on the commandline.
|
||||
///
|
||||
|
@ -126,7 +129,7 @@ std::map<wcstring, env_var_t> function_get_inherit_vars(const wcstring &name);
|
|||
bool function_copy(const wcstring &name, const wcstring &new_name);
|
||||
|
||||
/// Returns whether this function shadows variables of the underlying function.
|
||||
int function_get_shadow_scope(const wcstring &name);
|
||||
bool function_get_shadow_scope(const wcstring &name);
|
||||
|
||||
/// Prepares the environment for executing a function.
|
||||
void function_prepare_environment(const wcstring &name, const wchar_t *const *argv,
|
||||
|
|
|
@ -55,6 +55,5 @@ or echo "Function name3a not found as expected"
|
|||
echo Checking that the copied functions are identical other than the name
|
||||
diff (functions name1 | psub) (functions name1a | psub)
|
||||
diff (functions name3 | psub) (functions name3a | psub)
|
||||
# The diff would cause us to exit with a non-zero status even if it produces
|
||||
# the expected output.
|
||||
|
||||
exit 0
|
||||
|
|
0
tests/functions.err
Normal file
0
tests/functions.err
Normal file
50
tests/functions.in
Normal file
50
tests/functions.in
Normal file
|
@ -0,0 +1,50 @@
|
|||
# vim: set filetype=fish:
|
||||
#
|
||||
# Test the `functions` builtin
|
||||
|
||||
function f1
|
||||
end
|
||||
|
||||
# ==========
|
||||
# Verify that `functions --metadata` works as expected when given too many args.
|
||||
set x (functions --metadata f1 f2 2>&1)
|
||||
if test "$x" != "functions: Expected exactly one function name for --metadata"
|
||||
echo "Unexpected output for 'functions --metadata f1 f2': $x" >&2
|
||||
end
|
||||
|
||||
# ==========
|
||||
# Verify that `functions --metadata` works as expected when given the name of a
|
||||
# known function.
|
||||
set x (functions --metadata f1)
|
||||
if test "$x" != "stdin"
|
||||
echo "Unexpected output for 'functions --metadata f1': $x" >&2
|
||||
end
|
||||
|
||||
# ==========
|
||||
# Verify that `functions --metadata` works as expected when given the name of an
|
||||
# unknown function.
|
||||
set x (functions -m f2)
|
||||
if test "$x" != "n/a"
|
||||
echo "Unexpected output for 'functions --metadata f2': $x" >&2
|
||||
end
|
||||
|
||||
# ==========
|
||||
# Verify that `functions --metadata` works as expected when given the name of a
|
||||
# function that could be autoloaded but isn't currently loaded.
|
||||
set x (functions -m abbr)
|
||||
if test (count $x) -ne 1
|
||||
or not string match -q '*/share/functions/abbr.fish' "$x"
|
||||
echo "Unexpected output for 'functions -m abbr': $x" >&2
|
||||
end
|
||||
|
||||
# ==========
|
||||
# Verify that `functions --verbose --metadata` works as expected when given the name of a
|
||||
# function that was autoloaded.
|
||||
set x (functions -v -m abbr)
|
||||
if test (count $x) -ne 4
|
||||
or not string match -q '*/share/functions/abbr.fish' $x[1]
|
||||
or test $x[2] != autoloaded
|
||||
or test $x[3] != 1
|
||||
or test $x[4] != scope-shadowing
|
||||
echo "Unexpected output for 'functions -v -m abbr': $x" >&2
|
||||
end
|
0
tests/functions.out
Normal file
0
tests/functions.out
Normal file
Loading…
Reference in a new issue