diff --git a/src/fish.cpp b/src/fish.cpp index 5568086b7..431739928 100644 --- a/src/fish.cpp +++ b/src/fish.cpp @@ -44,6 +44,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA #include "fallback.h" // IWYU pragma: keep #include "fish_version.h" #include "function.h" +#include "future_feature_flags.h" #include "history.h" #include "io.h" #include "parser.h" @@ -61,6 +62,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA // container to hold the options specified within the command line class fish_cmd_opts_t { public: + // Future feature flags values string + wcstring features; // Commands to be executed in place of interactive shell. std::vector batch_cmds; // Commands to execute after the shell's config has been read. @@ -238,9 +241,10 @@ int run_command_list(std::vector *cmds, const io_chain_t &io) { /// Parse the argument list, return the index of the first non-flag arguments. static int fish_parse_opt(int argc, char **argv, fish_cmd_opts_t *opts) { - static const char *short_opts = "+hilnvc:C:p:d:D:"; + static const char *short_opts = "+hilnvc:C:p:d:f:D:"; static const struct option long_opts[] = {{"command", required_argument, NULL, 'c'}, {"init-command", required_argument, NULL, 'C'}, + {"features", required_argument, NULL, 'f'}, {"debug-level", required_argument, NULL, 'd'}, {"debug-stack-frames", required_argument, NULL, 'D'}, {"interactive", no_argument, NULL, 'i'}, @@ -277,6 +281,10 @@ static int fish_parse_opt(int argc, char **argv, fish_cmd_opts_t *opts) { } break; } + case 'f': { + opts->features = str2wcstring(optarg); + break; + } case 'h': { opts->batch_cmds.push_back("__fish_print_help fish"); break; @@ -375,6 +383,8 @@ int main(int argc, char **argv) { const struct config_paths_t paths = determine_config_directory_paths(argv[0]); env_init(&paths); + // Set features early in case other initialization depends on them. + mutable_fish_features().set_from_string(opts.features); proc_init(); builtin_init(); misc_init(); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index e022fa166..8a97ea4a4 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1363,6 +1363,11 @@ static void test_feature_flags() { f.set(ft::stderr_nocaret, false); do_test(!f.test(ft::stderr_nocaret)); + f.set_from_string(L"stderr-nocaret,nonsense"); + do_test(f.test(ft::stderr_nocaret)); + f.set_from_string(L"stderr-nocaret,no-stderr-nocaret,nonsense"); + do_test(!f.test(ft::stderr_nocaret)); + // Ensure every metadata is represented once. size_t counts[ft::flag_count] = {}; for (const auto &md : ft::metadata) { diff --git a/src/future_feature_flags.cpp b/src/future_feature_flags.cpp index 26b854975..276ea32f2 100644 --- a/src/future_feature_flags.cpp +++ b/src/future_feature_flags.cpp @@ -21,3 +21,37 @@ const struct features_t::metadata_t *features_t::metadata_for(const wchar_t *nam } return nullptr; } + +void features_t::set_from_string(const wcstring &str) { + wcstring_list_t entries = split_string(str, L','); + const wchar_t *whitespace = L"\t\n\v\f\r "; + for (wcstring entry : entries) { + if (entry.empty()) continue; + + // Trim leading and trailing whitespace + entry.erase(0, entry.find_first_not_of(whitespace)); + entry.erase(entry.find_last_not_of(whitespace) + 1); + + const wchar_t *name = entry.c_str(); + bool value = true; + // A "no-" prefix inverts the sense. + if (string_prefixes_string(L"no-", name)) { + value = false; + name += 3; // wcslen(L"no-") + } + // Look for a feature with this name. If we don't find it, assume it's a group name and set + // all features whose group contain it. Do nothing even if the string is unrecognized; this + // is to allow uniform invocations of fish (e.g. disable a feature that is only present in + // future versions). + // The special name 'all' may be used for those who like to live on the edge. + if (const metadata_t *md = metadata_for(name)) { + this->set(md->flag, value); + } else { + for (const metadata_t &md : metadata) { + if (wcsstr(md.groups, name) || !wcscmp(name, L"all")) { + this->set(md.flag, value); + } + } + } + } +} diff --git a/src/future_feature_flags.h b/src/future_feature_flags.h index d8373e064..6765252e9 100644 --- a/src/future_feature_flags.h +++ b/src/future_feature_flags.h @@ -3,6 +3,9 @@ #define FISH_FUTURE_FEATURE_FLAGS_H #include +#include + +#include "common.h" class features_t { public: @@ -27,6 +30,12 @@ class features_t { values[f] = value; } + /// Parses a comma-separated feature-flag string, updating ourselves with the values. + /// Feature names or group names may be prefixed with "no-" to disable them. + /// The special group name "all" may be used for those who like to live on the edge. + /// Unknown features are silently ignored. + void set_from_string(const wcstring &str); + /// Metadata about feature flags. struct metadata_t { /// The flag itself. diff --git a/tests/invocation/features-nocaret1.invoke b/tests/invocation/features-nocaret1.invoke new file mode 100644 index 000000000..91243cd8e --- /dev/null +++ b/tests/invocation/features-nocaret1.invoke @@ -0,0 +1 @@ +--features 'no-stderr-nocaret' -c 'status test-feature stderr-nocaret; echo nocaret: $status' diff --git a/tests/invocation/features-nocaret1.out b/tests/invocation/features-nocaret1.out new file mode 100644 index 000000000..057e3967b --- /dev/null +++ b/tests/invocation/features-nocaret1.out @@ -0,0 +1 @@ +nocaret: 1 diff --git a/tests/invocation/features-nocaret2.invoke b/tests/invocation/features-nocaret2.invoke new file mode 100644 index 000000000..4a20f5f95 --- /dev/null +++ b/tests/invocation/features-nocaret2.invoke @@ -0,0 +1 @@ +--features 'stderr-nocaret' -c 'status test-feature stderr-nocaret; echo nocaret: $status' diff --git a/tests/invocation/features-nocaret2.out b/tests/invocation/features-nocaret2.out new file mode 100644 index 000000000..2b8a87b31 --- /dev/null +++ b/tests/invocation/features-nocaret2.out @@ -0,0 +1 @@ +nocaret: 0