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:
Kurtis Rader 2017-01-09 22:49:33 -08:00
parent 2be1288cac
commit 2e38cf2a4b
8 changed files with 138 additions and 12 deletions

View file

@ -3,6 +3,7 @@
\subsection functions-synopsis Synopsis \subsection functions-synopsis Synopsis
\fish{synopsis} \fish{synopsis}
functions [ -a | --all ] [ -n | --names ] functions [ -a | --all ] [ -n | --names ]
functions [ -m | --metadata ] [ -v ] FUNCTION
functions -c OLDNAME NEWNAME functions -c OLDNAME NEWNAME
functions -d DESCRIPTION FUNCTION functions -d DESCRIPTION FUNCTION
functions [ -e | -q ] FUNCTIONS... functions [ -e | -q ] FUNCTIONS...
@ -14,7 +15,7 @@ functions [ -e | -q ] FUNCTIONS...
The following options are available: 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. - `-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. - `-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. - `-n` or `--names` lists the names of all defined functions.
- `-q` or `--query` tests if the specified functions exist. - `-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. 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. If any non-option parameters are given, the definition of the specified functions are printed.

View file

@ -1045,6 +1045,41 @@ static wcstring functions_def(const wcstring &name) {
return out; 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. /// The functions builtin, used for listing and erasing functions.
static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **argv) { static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
wgetopter_t w; 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 res = STATUS_BUILTIN_OK;
int query = 0; int query = 0;
int copy = 0; int copy = 0;
bool report_metadata = false;
bool verbose = false;
static const struct woption long_options[] = { static const struct woption long_options[] = {
{L"erase", no_argument, 0, 'e'}, {L"description", required_argument, 0, 'd'}, {L"erase", no_argument, NULL, 'e'}, {L"description", required_argument, NULL, 'd'},
{L"names", no_argument, 0, 'n'}, {L"all", no_argument, 0, 'a'}, {L"names", no_argument, NULL, 'n'}, {L"all", no_argument, NULL, 'a'},
{L"help", no_argument, 0, 'h'}, {L"query", no_argument, 0, 'q'}, {L"help", no_argument, NULL, 'h'}, {L"query", no_argument, NULL, 'q'},
{L"copy", no_argument, 0, 'c'}, {0, 0, 0, 0}}; {L"copy", no_argument, NULL, 'c'}, {L"metadata", no_argument, NULL, 'm'},
{L"verbose", no_argument, NULL, 'v'}, {NULL, 0, NULL, 0}};
while (1) { while (1) {
int opt_index = 0; 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; if (opt == -1) break;
switch (opt) { 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); builtin_print_help(parser, streams, argv[0], streams.err);
return STATUS_BUILTIN_ERROR; return STATUS_BUILTIN_ERROR;
} }
case 'v': {
verbose = true;
break;
}
case 'e': { case 'e': {
erase = 1; erase = 1;
break; break;
} }
case 'm': {
report_metadata = true;
break;
}
case 'd': { case 'd': {
desc = w.woptarg; desc = w.woptarg;
break; break;
@ -1152,6 +1198,15 @@ static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **
function_set_desc(func, desc); function_set_desc(func, desc);
return STATUS_BUILTIN_OK; 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)) { } else if (list || (argc == w.woptind)) {
int is_screen = !streams.out_is_redirected && isatty(STDOUT_FILENO); int is_screen = !streams.out_is_redirected && isatty(STDOUT_FILENO);
size_t i; size_t i;
@ -1224,8 +1279,9 @@ static int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **
else { else {
if (!query) { if (!query) {
if (i != w.woptind) streams.out.append(L"\n"); if (i != w.woptind) streams.out.append(L"\n");
const wchar_t *funcname = argv[w.woptind];
streams.out.append(functions_def(argv[i])); report_function_metadata(funcname, verbose, streams, true);
streams.out.append(functions_def(funcname));
} }
} }
} }

View file

@ -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>(); 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); scoped_lock locker(functions_lock);
const function_info_t *func = function_get(name); const function_info_t *func = function_get(name);
return func ? func->shadow_scope : false; 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; 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) { int function_get_definition_offset(const wcstring &name) {
scoped_lock locker(functions_lock); scoped_lock locker(functions_lock);
const function_info_t *func = function_get(name); const function_info_t *func = function_get(name);

View file

@ -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. /// \param get_hidden whether to include hidden functions, i.e. ones starting with an underscore.
wcstring_list_t function_get_names(int get_hidden); 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 /// Returns tha absolute path of the file where the specified function was defined. Returns 0 if the
/// file was defined on the commandline. /// 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); bool function_copy(const wcstring &name, const wcstring &new_name);
/// Returns whether this function shadows variables of the underlying function. /// 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. /// Prepares the environment for executing a function.
void function_prepare_environment(const wcstring &name, const wchar_t *const *argv, void function_prepare_environment(const wcstring &name, const wchar_t *const *argv,

View file

@ -55,6 +55,5 @@ or echo "Function name3a not found as expected"
echo Checking that the copied functions are identical other than the name echo Checking that the copied functions are identical other than the name
diff (functions name1 | psub) (functions name1a | psub) diff (functions name1 | psub) (functions name1a | psub)
diff (functions name3 | psub) (functions name3a | 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 exit 0

0
tests/functions.err Normal file
View file

50
tests/functions.in Normal file
View 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
View file