From adc69f94da8c8ba4911edd11d6378c78895bb82f Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sat, 20 Oct 2018 17:20:49 -0700 Subject: [PATCH] Support parsing the new universal variable format --- src/env_universal_common.cpp | 113 +++++++++++++++++++++++++---------- src/env_universal_common.h | 3 + src/fish_tests.cpp | 27 +++++++++ 3 files changed, 111 insertions(+), 32 deletions(-) diff --git a/src/env_universal_common.cpp b/src/env_universal_common.cpp index d0c257c54..9762b105f 100644 --- a/src/env_universal_common.cpp +++ b/src/env_universal_common.cpp @@ -77,6 +77,15 @@ /// Version for fish 3.0 #define UVARS_VERSION_3_0 "3.0" +// Fields used in fish 3.0 uvars +namespace fish3_uvars { +namespace { +constexpr const wchar_t *SETUVAR = L"SETUVAR"; +constexpr const wchar_t *EXPORT = L"--export"; +constexpr const wchar_t *PATH = L"--path"; +} // namespace +} // namespace fish3_uvars + /// The different types of messages found in the fishd file. enum class uvar_message_type_t { set, set_export }; @@ -108,12 +117,13 @@ static maybe_t default_vars_path() { } /// Test if the message msg contains the command cmd. -static bool match(const wchar_t *msg, const wchar_t *cmd) { +/// On success, updates the cursor to just past the command. +static bool match(const wchar_t **inout_cursor, const wchar_t *cmd) { + const wchar_t *cursor = *inout_cursor; size_t len = wcslen(cmd); - if (wcsncasecmp(msg, cmd, len) != 0) return false; - - if (msg[len] && msg[len] != L' ' && msg[len] != L'\t') return false; - + if (wcsncasecmp(cursor, cmd, len) != 0) return false; + if (cursor[len] && cursor[len] != L' ' && cursor[len] != L'\t') return false; + *inout_cursor = cursor + len; return true; } @@ -824,46 +834,85 @@ void env_universal_t::populate_variables(const std::string &s, var_table_t *out_ } } +static const wchar_t *skip_spaces(const wchar_t *str) { + while (*str == L' ' || *str == L'\t') str++; + return str; +} + +bool env_universal_t::populate_1_variable(const wchar_t *input, env_var_t::env_var_flags_t flags, + var_table_t *vars, wcstring *storage) { + const wchar_t *str = skip_spaces(input); + const wchar_t *colon = wcschr(str, L':'); + if (!colon) return false; + + // Parse out the value into storage, and decode it into a variable. + storage->clear(); + if (!unescape_string(colon + 1, storage, 0)) { + return false; + } + env_var_t var{decode_serialized(*storage), flags}; + + // Parse out the key and write into the map. + storage->assign(str, colon - str); + const wcstring &key = *storage; + (*vars)[key] = std::move(var); + return true; +} + /// Parse message msg per fish 3.0 format. void env_universal_t::parse_message_30_internal(const wcstring &msgstr, var_table_t *vars, wcstring *storage) { - // TODO. + namespace f3 = fish3_uvars; + const wchar_t *const msg = msgstr.c_str(); + if (msg[0] == L'#') return; + + const wchar_t *cursor = msg; + if (!match(&cursor, f3::SETUVAR)) { + debug(1, PARSE_ERR, msg); + return; + } + // Parse out flags. + env_var_t::env_var_flags_t flags = 0; + for (;;) { + cursor = skip_spaces(cursor); + if (*cursor != L'-') break; + if (match(&cursor, f3::EXPORT)) { + flags |= env_var_t::flag_export; + } else if (match(&cursor, f3::PATH)) { + flags |= env_var_t::flag_pathvar; + } else { + // Skip this unknown flag, for future proofing. + while (*cursor && *cursor != L' ' && *cursor != L'\t') cursor++; + } + } + + // Populate the variable with these flags. + if (!populate_1_variable(cursor, flags, vars, storage)) { + debug(1, PARSE_ERR, msg); + } } /// Parse message msg per fish 2.x format. void env_universal_t::parse_message_2x_internal(const wcstring &msgstr, var_table_t *vars, wcstring *storage) { - const wchar_t *msg = msgstr.c_str(); + const wchar_t *const msg = msgstr.c_str(); + const wchar_t *cursor = msg; // debug(3, L"parse_message( %ls );", msg); - if (msg[0] == L'#') return; + if (cursor[0] == L'#') return; - bool is_set_export = match(msg, SET_EXPORT_STR); - bool is_set = !is_set_export && match(msg, SET_STR); - if (is_set || is_set_export) { - const wchar_t *name, *tmp; - const bool exportv = is_set_export; - - name = msg + (exportv ? wcslen(SET_EXPORT_STR) : wcslen(SET_STR)); - while (name[0] == L'\t' || name[0] == L' ') name++; - - tmp = wcschr(name, L':'); - if (tmp) { - // Use 'storage' to hold our key to avoid allocations. - storage->assign(name, tmp - name); - const wcstring &key = *storage; - - wcstring val; - if (unescape_string(tmp + 1, &val, 0)) { - env_var_t &entry = (*vars)[key]; - entry.set_exports(exportv); - entry.set_vals(decode_serialized(val)); - } - } else { - debug(1, PARSE_ERR, msg); - } + env_var_t::env_var_flags_t flags = 0; + if (match(&cursor, SET_EXPORT_STR)) { + flags |= env_var_t::flag_export; + } else if (match(&cursor, SET_STR)) { + flags |= 0; } else { debug(1, PARSE_ERR, msg); + return; + } + + if (!populate_1_variable(cursor, flags, vars, storage)) { + debug(1, PARSE_ERR, msg); } } diff --git a/src/env_universal_common.h b/src/env_universal_common.h index 263eb297c..caa4f774d 100644 --- a/src/env_universal_common.h +++ b/src/env_universal_common.h @@ -72,6 +72,9 @@ class env_universal_t { // vars_to_acquire. void acquire_variables(var_table_t &vars_to_acquire); + static bool populate_1_variable(const wchar_t *str, env_var_t::env_var_flags_t flags, + var_table_t *vars, wcstring *storage); + static void parse_message_2x_internal(const wcstring &msg, var_table_t *vars, wcstring *storage); static void parse_message_30_internal(const wcstring &msg, var_table_t *vars, diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 7ccf73bdc..dfc9ede61 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2903,6 +2903,32 @@ static void test_universal_output() { static void test_universal_parsing() { say(L"Testing universal variable parsing"); + const char *input = + "# This file contains fish universal variable definitions.\n" + "# VERSION: 3.0\n" + "SETUVAR varA:ValA1\\x1eValA2\n" + "SETUVAR --export varB:ValB1\n" + "SETUVAR --nonsenseflag varC:ValC1\n" + "SETUVAR --export --path varD:ValD1\n" + "SETUVAR --path --path varE:ValE1\\x1eValE2\n"; + + const env_var_t::env_var_flags_t flag_export = env_var_t::flag_export; + const env_var_t::env_var_flags_t flag_pathvar = env_var_t::flag_pathvar; + + var_table_t vars; + vars[L"varA"] = env_var_t(wcstring_list_t{L"ValA1", L"ValA2"}, 0); + vars[L"varB"] = env_var_t(wcstring_list_t{L"ValB1"}, flag_export); + vars[L"varC"] = env_var_t(wcstring_list_t{L"ValC1"}, 0); + vars[L"varD"] = env_var_t(wcstring_list_t{L"ValD1"}, flag_export | flag_pathvar); + vars[L"varE"] = env_var_t(wcstring_list_t{L"ValE1", L"ValE2"}, flag_pathvar); + + var_table_t parsed_vars; + env_universal_t::populate_variables(input, &parsed_vars); + do_test(vars == parsed_vars); +} + +static void test_universal_parsing_legacy() { + say(L"Testing universal variable legacy parsing"); const char *input = "# This file contains fish universal variable definitions.\n" "SET varA:ValA1\\x1eValA2\n" @@ -4883,6 +4909,7 @@ int main(int argc, char **argv) { if (should_test_function("universal")) test_universal(); if (should_test_function("universal")) test_universal_output(); if (should_test_function("universal")) test_universal_parsing(); + if (should_test_function("universal")) test_universal_parsing_legacy(); if (should_test_function("universal")) test_universal_callbacks(); if (should_test_function("universal")) test_universal_formats(); if (should_test_function("notifiers")) test_universal_notifiers();