Build out support for multiple file formats in uvars

This is in preparation for adjusting the file format to support path
variables.
This commit is contained in:
ridiculousfish 2018-10-20 14:38:49 -07:00
parent ce1463bde6
commit 11d523e61a
5 changed files with 119 additions and 6 deletions

View file

@ -1744,8 +1744,7 @@ int common_get_width() { return get_current_winsize().ws_col; }
int common_get_height() { return get_current_winsize().ws_row; } int common_get_height() { return get_current_winsize().ws_row; }
bool string_prefixes_string(const wchar_t *proposed_prefix, const wcstring &value) { bool string_prefixes_string(const wchar_t *proposed_prefix, const wcstring &value) {
size_t prefix_size = wcslen(proposed_prefix); return string_prefixes_string(proposed_prefix, value.c_str());
return prefix_size <= value.size() && value.compare(0, prefix_size, proposed_prefix) == 0;
} }
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value) { bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value) {
@ -1763,6 +1762,17 @@ bool string_prefixes_string(const wchar_t *proposed_prefix, const wchar_t *value
return true; return true;
} }
bool string_prefixes_string(const char *proposed_prefix, const std::string &value) {
return string_prefixes_string(proposed_prefix, value.c_str());
}
bool string_prefixes_string(const char *proposed_prefix, const char *value) {
for (size_t idx = 0; proposed_prefix[idx] != L'\0'; idx++) {
if (proposed_prefix[idx] != value[idx]) return false;
}
return true;
}
bool string_prefixes_string_case_insensitive(const wcstring &proposed_prefix, bool string_prefixes_string_case_insensitive(const wcstring &proposed_prefix,
const wcstring &value) { const wcstring &value) {
size_t prefix_size = proposed_prefix.size(); size_t prefix_size = proposed_prefix.size();

View file

@ -342,6 +342,8 @@ std::string wcs2string(const wcstring &input);
bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value); bool string_prefixes_string(const wcstring &proposed_prefix, const wcstring &value);
bool string_prefixes_string(const wchar_t *proposed_prefix, const wcstring &value); bool string_prefixes_string(const wchar_t *proposed_prefix, const wcstring &value);
bool string_prefixes_string(const wchar_t *proposed_prefix, const wchar_t *value); bool string_prefixes_string(const wchar_t *proposed_prefix, const wchar_t *value);
bool string_prefixes_string(const char *proposed_prefix, const std::string &value);
bool string_prefixes_string(const char *proposed_prefix, const char *value);
/// Test if a string is a suffix of another. /// Test if a string is a suffix of another.
bool string_suffixes_string(const wcstring &proposed_suffix, const wcstring &value); bool string_suffixes_string(const wcstring &proposed_suffix, const wcstring &value);

View file

@ -74,6 +74,9 @@
/// Small note about not editing ~/.fishd manually. Inserted at the top of all .fishd files. /// Small note about not editing ~/.fishd manually. Inserted at the top of all .fishd files.
#define SAVE_MSG "# This file contains fish universal variable definitions.\n" #define SAVE_MSG "# This file contains fish universal variable definitions.\n"
/// Version for fish 3.0
#define UVARS_VERSION_3_0 "3.0"
/// The different types of messages found in the fishd file. /// The different types of messages found in the fishd file.
enum class uvar_message_type_t { set, set_export }; enum class uvar_message_type_t { set, set_export };
@ -764,7 +767,7 @@ var_table_t env_universal_t::read_message_internal(int fd) {
// Process it if it's a newline (which is true if we are before the end of the buffer). // Process it if it's a newline (which is true if we are before the end of the buffer).
if (cursor < bufflen && !line.empty()) { if (cursor < bufflen && !line.empty()) {
if (utf8_to_wchar(line.data(), line.size(), &wide_line, 0)) { if (utf8_to_wchar(line.data(), line.size(), &wide_line, 0)) {
env_universal_t::parse_message_internal(wide_line, &result, &storage); env_universal_t::parse_message_2x_internal(wide_line, &result, &storage);
} }
line.clear(); line.clear();
} }
@ -778,8 +781,70 @@ var_table_t env_universal_t::read_message_internal(int fd) {
return result; return result;
} }
/// Parse message msg/ /// \return the format corresponding to file contents \p s.
void env_universal_t::parse_message_internal(const wcstring &msgstr, var_table_t *vars, uvar_format_t env_universal_t::format_for_contents(const std::string &s) {
// Walk over leading comments, looking for one like '# version'
line_iterator_t<std::string> iter{s};
while (iter.next()) {
const std::string &line = iter.line();
if (line.empty()) continue;
if (line.front() != L'#') {
// Exhausted leading comments.
break;
}
// Note scanf %s is max characters to write; add 1 for null terminator.
char versionbuf[64 + 1];
if (sscanf(line.c_str(), "# VERSION: %64s", versionbuf) != 1) continue;
// Try reading the version.
if (!strcmp(versionbuf, UVARS_VERSION_3_0)) {
return uvar_format_t::fish_3_0;
} else {
// Unknown future version.
return uvar_format_t::future;
}
}
// No version found, assume 2.x
return uvar_format_t::fish_2_x;
}
void env_universal_t::populate_variables(const std::string &s, var_table_t *out_vars) {
// Decide on the format.
const uvar_format_t format = format_for_contents(s);
line_iterator_t<std::string> iter{s};
wcstring wide_line;
wcstring storage;
while (iter.next()) {
const std::string &line = iter.line();
// Skip empties and constants.
if (line.empty() || line.front() == L'#') continue;
// Convert to UTF8.
wide_line.clear();
if (!utf8_to_wchar(line.data(), line.size(), &wide_line, 0)) continue;
switch (format) {
case uvar_format_t::fish_2_x:
env_universal_t::parse_message_2x_internal(wide_line, out_vars, &storage);
break;
case uvar_format_t::fish_3_0:
// For future formats, just try with the most recent one.
case uvar_format_t::future:
env_universal_t::parse_message_30_internal(wide_line, out_vars, &storage);
break;
}
}
}
/// 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.
}
/// 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) { wcstring *storage) {
const wchar_t *msg = msgstr.c_str(); const wchar_t *msg = msgstr.c_str();

View file

@ -30,6 +30,10 @@ struct callback_data_t {
typedef std::vector<struct callback_data_t> callback_data_list_t; typedef std::vector<struct callback_data_t> callback_data_list_t;
// List of fish universal variable formats.
// This is exposed for testing.
enum class uvar_format_t { fish_2_x, fish_3_0, future };
bool get_hostname_identifier(wcstring &result); bool get_hostname_identifier(wcstring &result);
/// Class representing universal variables. /// Class representing universal variables.
class env_universal_t { class env_universal_t {
@ -68,7 +72,10 @@ class env_universal_t {
// vars_to_acquire. // vars_to_acquire.
void acquire_variables(var_table_t &vars_to_acquire); void acquire_variables(var_table_t &vars_to_acquire);
static void parse_message_internal(const wcstring &msg, 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,
wcstring *storage);
static var_table_t read_message_internal(int fd); static var_table_t read_message_internal(int fd);
public: public:
@ -95,6 +102,13 @@ class env_universal_t {
/// Reads and writes variables at the correct path. Returns true if modified variables were /// Reads and writes variables at the correct path. Returns true if modified variables were
/// written. /// written.
bool sync(callback_data_list_t &callbacks); bool sync(callback_data_list_t &callbacks);
/// Populate a variable table \p out_vars from a \p s string.
/// This is exposed for testing only.
static void populate_variables(const std::string &s, var_table_t *out_vars);
/// Guess a file format. Exposed for testing only.
static uvar_format_t format_for_contents(const std::string &s);
}; };
/// The "universal notifier" is an object responsible for broadcasting and receiving universal /// The "universal notifier" is an object responsible for broadcasting and receiving universal

View file

@ -2940,6 +2940,27 @@ static void test_universal_callbacks() {
(void)system("rm -Rf test/fish_uvars_test/"); (void)system("rm -Rf test/fish_uvars_test/");
} }
static void test_universal_formats() {
say(L"Testing universal format detection");
const struct {
const char *str;
uvar_format_t format;
} tests[] = {
{"# VERSION: 3.0", uvar_format_t::fish_3_0},
{"# version: 3.0", uvar_format_t::fish_2_x},
{"# blah blahVERSION: 3.0", uvar_format_t::fish_2_x},
{"stuff\n# blah blahVERSION: 3.0", uvar_format_t::fish_2_x},
{"# blah\n# VERSION: 3.0", uvar_format_t::fish_3_0},
{"# blah\n#VERSION: 3.0", uvar_format_t::fish_3_0},
{"# blah\n#VERSION:3.0", uvar_format_t::fish_3_0},
{"# blah\n#VERSION:3.1", uvar_format_t::future},
};
for (const auto &test : tests) {
uvar_format_t format = env_universal_t::format_for_contents(test.str);
do_test(format == test.format);
}
}
bool poll_notifier(const std::unique_ptr<universal_notifier_t> &note) { bool poll_notifier(const std::unique_ptr<universal_notifier_t> &note) {
bool result = false; bool result = false;
if (note->usec_delay_between_polls() > 0) { if (note->usec_delay_between_polls() > 0) {
@ -4824,6 +4845,7 @@ int main(int argc, char **argv) {
if (should_test_function("line_iterator")) test_line_iterator(); if (should_test_function("line_iterator")) test_line_iterator();
if (should_test_function("universal")) test_universal(); if (should_test_function("universal")) test_universal();
if (should_test_function("universal")) test_universal_callbacks(); if (should_test_function("universal")) test_universal_callbacks();
if (should_test_function("universal")) test_universal_formats();
if (should_test_function("notifiers")) test_universal_notifiers(); if (should_test_function("notifiers")) test_universal_notifiers();
if (should_test_function("completion_insertions")) test_completion_insertions(); if (should_test_function("completion_insertions")) test_completion_insertions();
if (should_test_function("autosuggestion_ignores")) test_autosuggestion_ignores(); if (should_test_function("autosuggestion_ignores")) test_autosuggestion_ignores();