From e8254159174a5da90ecf05f5e069240bcf7ce41f Mon Sep 17 00:00:00 2001 From: Kurtis Rader Date: Tue, 1 Aug 2017 20:58:40 -0700 Subject: [PATCH] implement `set --show` This adds a new capability to the `set` command. It is similar to running `set` with no other arguments but provides far more detail about each variable. Such as whether it is set in each of the local, global, and universal scopes. And the values in each scope. You can also ask for specific variables to be shown. Fixes #4265 --- doc_src/set.txt | 3 ++ src/builtin_set.cpp | 127 +++++++++++++++++++++++++++++++++++++++----- tests/set.err | 2 + tests/set.in | 15 ++++++ tests/set.out | 22 ++++++++ 5 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 tests/set.err create mode 100644 tests/set.in create mode 100644 tests/set.out diff --git a/doc_src/set.txt b/doc_src/set.txt index ce35f2253..db6732add 100644 --- a/doc_src/set.txt +++ b/doc_src/set.txt @@ -8,6 +8,7 @@ set [OPTIONS] VARIABLE_NAME[INDICES]... VALUES... set ( -q | --query ) [SCOPE_OPTIONS] VARIABLE_NAMES... set ( -e | --erase ) [SCOPE_OPTIONS] VARIABLE_NAME set ( -e | --erase ) [SCOPE_OPTIONS] VARIABLE_NAME[INDICES]... +set ( -S | --show ) [SCOPE_OPTIONS] [VARIABLE_NAME]... \endfish \subsection set-description Description @@ -39,6 +40,8 @@ The following options are available: - `-n` or `--names` List only the names of all defined variables, not their value. The names are guaranteed to be sorted. +- `-S` or `--show` Shows information about the given variables. If no variable names are given then all variables are shown in sorted order. No other flags can be used with this option. The information shown includes whether or not it is set in each of the local, global, and universal scopes. If it is set in one of those scopes whether or not it is exported is reported. The individual elements are also shown along with the length of each element. + - `-L` or `--long` do not abbreviate long values when printing set variables diff --git a/src/builtin_set.cpp b/src/builtin_set.cpp index fca3bfff4..5fc9966de 100644 --- a/src/builtin_set.cpp +++ b/src/builtin_set.cpp @@ -30,6 +30,7 @@ class parser_t; struct set_cmd_opts_t { bool print_help = false; + bool show = false; bool local = false; bool global = false; bool exportv = false; @@ -45,18 +46,14 @@ struct set_cmd_opts_t { // Variables used for parsing the argument list. This command is atypical in using the "+" // (REQUIRE_ORDER) option for flag parsing. This is not typical of most fish commands. It means // we stop scanning for flags when the first non-flag argument is seen. -static const wchar_t *short_options = L"+:LUeghlnqux"; -static const struct woption long_options[] = {{L"export", no_argument, NULL, 'x'}, - {L"global", no_argument, NULL, 'g'}, - {L"local", no_argument, NULL, 'l'}, - {L"erase", no_argument, NULL, 'e'}, - {L"names", no_argument, NULL, 'n'}, - {L"unexport", no_argument, NULL, 'u'}, - {L"universal", no_argument, NULL, 'U'}, - {L"long", no_argument, NULL, 'L'}, - {L"query", no_argument, NULL, 'q'}, - {L"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0}}; +static const wchar_t *short_options = L"+:LSUeghlnqux"; +static const struct woption long_options[] = { + {L"export", no_argument, NULL, 'x'}, {L"global", no_argument, NULL, 'g'}, + {L"local", no_argument, NULL, 'l'}, {L"erase", no_argument, NULL, 'e'}, + {L"names", no_argument, NULL, 'n'}, {L"unexport", no_argument, NULL, 'u'}, + {L"universal", no_argument, NULL, 'U'}, {L"long", no_argument, NULL, 'L'}, + {L"query", no_argument, NULL, 'q'}, {L"show", no_argument, NULL, 'S'}, + {L"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; // Error message for invalid path operations. #define BUILTIN_SET_PATH_ERROR _(L"%ls: Warning: $%ls entry \"%ls\" is not valid (%s)\n") @@ -117,6 +114,11 @@ static int parse_cmd_opts(set_cmd_opts_t &opts, int *optind, //!OCLINT(high ncs opts.preserve_failure_exit_status = false; break; } + case 'S': { + opts.show = true; + opts.preserve_failure_exit_status = false; + break; + } case 'h': { opts.print_help = true; break; @@ -140,8 +142,8 @@ static int parse_cmd_opts(set_cmd_opts_t &opts, int *optind, //!OCLINT(high ncs return STATUS_CMD_OK; } -static int validate_cmd_opts(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, - parser_t &parser, io_streams_t &streams) { +static int validate_cmd_opts(const wchar_t *cmd, set_cmd_opts_t &opts, //!OCLINT(npath complexity) + int argc, parser_t &parser, io_streams_t &streams) { // Can't query and erase or list. if (opts.query && (opts.erase || opts.list)) { streams.err.append_format(BUILTIN_ERR_COMBO, cmd); @@ -177,6 +179,14 @@ static int validate_cmd_opts(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, return STATUS_INVALID_ARGS; } + // The --show flag cannot be combined with any other flag. + if (opts.show && + (opts.local || opts.global || opts.erase || opts.list || opts.exportv || opts.universal)) { + streams.err.append_format(BUILTIN_ERR_COMBO, cmd); + builtin_print_help(parser, streams, cmd, streams.err); + return STATUS_INVALID_ARGS; + } + if (argc == 0 && opts.erase) { streams.err.append_format(BUILTIN_SET_ERASE_NO_VAR, cmd); builtin_print_help(parser, streams, cmd, streams.err); @@ -495,6 +505,93 @@ static int builtin_set_query(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, return retval; } +static void show_scope(const wchar_t *var_name, int scope, io_streams_t &streams) { + const wchar_t *scope_name; + switch (scope) { + case ENV_LOCAL: { + scope_name = L"local"; + break; + } + case ENV_GLOBAL: { + scope_name = L"global"; + break; + } + case ENV_UNIVERSAL: { + scope_name = L"universal"; + break; + } + default: { + DIE("invalid scope"); + break; + } + } + + if (env_exist(var_name, scope)) { + const env_var_t evar = env_get_string(var_name, scope | ENV_EXPORT | ENV_USER); + const wchar_t *exportv = evar.missing() ? _(L"unexported") : _(L"exported"); + + const env_var_t var = env_get_string(var_name, scope | ENV_USER); + wcstring_list_t result; + if (!var.empty()) tokenize_variable_array(var, result); + + streams.out.append_format(_(L"$%ls: set in %ls scope, %ls, with %d elements\n"), var_name, + scope_name, exportv, result.size()); + for (size_t i = 0; i < result.size(); i++) { + if (result.size() > 100) { + if (i == 50) streams.out.append(L"...\n"); + if (i >= 50 && i < result.size() - 50) continue; + } + const wcstring value = result[i]; + const wcstring escaped_val = + escape_string(value.c_str(), ESCAPE_NO_QUOTED, STRING_STYLE_SCRIPT); + streams.out.append_format(_(L"$%ls[%d]: length=%d value=|%ls|\n"), var_name, i, + value.size(), escaped_val.c_str()); + } + } else { + streams.out.append_format(_(L"$%ls: not set in %ls scope\n"), var_name, scope_name); + } +} + +/// Show mode. Show information about the named variable(s). +static int builtin_set_show(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, + parser_t &parser, io_streams_t &streams) { + UNUSED(opts); + + if (argc == 0) { // show all vars + wcstring_list_t names = env_get_names(ENV_USER); + sort(names.begin(), names.end()); + for (auto it : names) { + show_scope(it.c_str(), ENV_LOCAL, streams); + show_scope(it.c_str(), ENV_GLOBAL, streams); + show_scope(it.c_str(), ENV_UNIVERSAL, streams); + streams.out.push_back(L'\n'); + } + } else { + for (int i = 0; i < argc; i++) { + wchar_t *arg = argv[i]; + + if (!valid_var_name(arg)) { + streams.err.append_format(_(L"$%ls: invalid var name\n"), arg); + continue; + } + + if (wcschr(arg, L'[')) { + streams.err.append_format( + _(L"%ls: `set --show` does not allow slices with the var names\n"), cmd); + builtin_print_help(parser, streams, cmd, streams.err); + return STATUS_CMD_ERROR; + } + + show_scope(arg, ENV_LOCAL, streams); + show_scope(arg, ENV_GLOBAL, streams); + show_scope(arg, ENV_UNIVERSAL, streams); + streams.out.push_back(L'\n'); + } + } + + return STATUS_CMD_OK; +} + /// Erase a variable. static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { @@ -634,6 +731,8 @@ int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) { retval = builtin_set_erase(cmd, opts, argc, argv, parser, streams); } else if (opts.list) { // explicit list the vars we know about retval = builtin_set_list(cmd, opts, argc, argv, parser, streams); + } else if (opts.show) { + retval = builtin_set_show(cmd, opts, argc, argv, parser, streams); } else if (argc == 0) { // implicit list the vars we know about retval = builtin_set_list(cmd, opts, argc, argv, parser, streams); } else { diff --git a/tests/set.err b/tests/set.err new file mode 100644 index 000000000..45484fa01 --- /dev/null +++ b/tests/set.err @@ -0,0 +1,2 @@ +# Verify behavior of `set --show` given an invalid var name +$argle bargle: invalid var name diff --git a/tests/set.in b/tests/set.in new file mode 100644 index 000000000..8eb9809bd --- /dev/null +++ b/tests/set.in @@ -0,0 +1,15 @@ +# Test various behaviors of the `set` command. + +logmsg Verify behavior of `set --show` given an invalid var name +set --show 'argle bargle' + +echo '# Verify behavior of `set --show`' +set -U var1 hello +set --show var1 + +set -l var1 +set -g var1 goodbye "and don't come back" +set --show var1 + +set -g var2 +set --show _unset_var var2 diff --git a/tests/set.out b/tests/set.out new file mode 100644 index 000000000..28a004f7f --- /dev/null +++ b/tests/set.out @@ -0,0 +1,22 @@ +# Verify behavior of `set --show` given an invalid var name +# Verify behavior of `set --show` +$var1: not set in local scope +$var1: not set in global scope +$var1: set in universal scope, unexported, with 1 elements +$var1[0]: length=5 value=|hello| + +$var1: set in local scope, unexported, with 0 elements +$var1: set in global scope, unexported, with 2 elements +$var1[0]: length=7 value=|goodbye| +$var1[1]: length=19 value=|and don\'t come back| +$var1: set in universal scope, unexported, with 1 elements +$var1[0]: length=5 value=|hello| + +$_unset_var: not set in local scope +$_unset_var: not set in global scope +$_unset_var: not set in universal scope + +$var2: not set in local scope +$var2: set in global scope, unexported, with 0 elements +$var2: not set in universal scope +