From 76172af239edcfb3df3bfba94c41b3c5dbb5bd25 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Thu, 5 Dec 2019 18:00:07 -0800 Subject: [PATCH] Implement saving and loading OptionDB items to/from scheme and key-value string representations. --- libgnucash/app-utils/gnc-optiondb.cpp | 73 ++++++++++++++++--- libgnucash/app-utils/gnc-optiondb.hpp | 25 ++++--- .../app-utils/test/gtest-gnc-optiondb.cpp | 69 ++++++++++++++++++ 3 files changed, 143 insertions(+), 24 deletions(-) diff --git a/libgnucash/app-utils/gnc-optiondb.cpp b/libgnucash/app-utils/gnc-optiondb.cpp index 7c66f05c60..239a21f068 100644 --- a/libgnucash/app-utils/gnc-optiondb.cpp +++ b/libgnucash/app-utils/gnc-optiondb.cpp @@ -22,7 +22,10 @@ \********************************************************************/ #include "gnc-optiondb.hpp" +#include +#include +auto constexpr stream_max = std::numeric_limits::max(); GncOptionDB::GncOptionDB() : m_default_section{std::nullopt} {} GncOptionDB::GncOptionDB(QofBook* book) : GncOptionDB() {} @@ -175,24 +178,53 @@ GncOptionDB::make_internal(const char* section, const char* name) } std::ostream& -GncOptionDB::serialize_option_scheme(std::ostream& oss, - const char* option_prolog, - const char* section, const char* name) const noexcept +GncOptionDB::save_option_scheme(std::ostream& oss, + const char* option_prolog, + const std::string& section, + const std::string& name) const noexcept { auto db_opt = find_option(section, name); + if (!db_opt || !db_opt->get().is_changed()) return oss; - oss << c_scheme_serialization_elements[0] << option_prolog; - oss << c_scheme_serialization_elements[1] << section; - oss << c_scheme_serialization_elements[1] << name; //repeats, not an error! -// oss << c_scheme_serialization_elements[2] << db_opt->get(); - oss << c_scheme_serialization_elements[3]; + oss << scheme_tags[0] << option_prolog << "\n"; + oss << scheme_tags[1] << '"' << section.substr(0, classifier_size_max) << "\"\n"; + oss << scheme_tags[1] << '"' << name.substr(0, classifier_size_max) << '"'; + oss << scheme_tags[2] << "\n" << scheme_tags[3]; + db_opt->get().to_scheme(oss); + oss << scheme_tags[4] << "\n\n"; return oss; } +std::istream& +GncOptionDB::load_option_scheme(std::istream& iss) noexcept +{ + std::string section{classifier_size_max}; + std::string name{classifier_size_max}; + iss.ignore(strlen(scheme_tags[0]) + 7, '\n'); //throw away the scheme noise; + iss >> section; // Whitespace automatically discarded. + iss >> name; // Ditto + if (section.size() > 2) + section = section.substr(1, section.size() - 2); //Trim the quotes + if (name.size() > 2 + strlen(scheme_tags[2])) + name = name.substr(1, name.size() - (2 + strlen(scheme_tags[2]))); + auto option = find_option(section.c_str(), name.c_str()); + if (!option) + { + std::cerr << "Option " << section << ":" << name << " not found." << std::endl; + iss.ignore(stream_max, '\n'); // No option, discard the line + iss.ignore(stream_max, '\n'); // And the trailing newline + return iss; + } + iss.ignore(strlen(scheme_tags[2]) +1, '\n'); + iss.ignore(strlen(scheme_tags[3])); + option->get().from_scheme(iss); + iss.ignore(strlen(scheme_tags[4]) + 2, '\n'); //discard the noise at the end. + return iss; +} std::ostream& -GncOptionDB::serialize_option_key_value(std::ostream& oss, +GncOptionDB::save_option_key_value(std::ostream& oss, const char* section, const char* name) const noexcept { @@ -200,13 +232,30 @@ GncOptionDB::serialize_option_key_value(std::ostream& oss, auto db_opt = find_option(section, name); if (!db_opt || !db_opt->get().is_changed()) return oss; - oss << section << ":" << name << "=" /* << db_opt->get() */ << ";"; + oss << section << ":" << name << "=" << db_opt->get() << ";"; return oss; } -void -GncOptionDB::commit() +std::istream& +GncOptionDB::load_option_key_value(std::istream& iss) { + + char section[classifier_size_max], name[classifier_size_max]; + iss.getline(section, classifier_size_max, ':'); + iss.getline(name, classifier_size_max, '='); + if (!iss) + throw std::invalid_argument("Section or name delimiter not found or values too long"); + auto option = find_option(section, name); + if (!option) + iss.ignore(stream_max, ';'); + else + { + std::string value; + std::getline(iss, value, ';'); + std::istringstream item_iss{value}; + item_iss >> option->get(); + } + return iss; } GncOptionDBPtr diff --git a/libgnucash/app-utils/gnc-optiondb.hpp b/libgnucash/app-utils/gnc-optiondb.hpp index 8d6187400b..078224f6b0 100644 --- a/libgnucash/app-utils/gnc-optiondb.hpp +++ b/libgnucash/app-utils/gnc-optiondb.hpp @@ -87,30 +87,31 @@ public: std::optional> find_option(const std::string& section, const std::string& name) { return static_cast(*this).find_option(section, name); } -private: - std::ostream& serialize_option_scheme(std::ostream& oss, std::optional> find_option(const std::string& section, const std::string& name) const; - const char* option_prolog, - const char* section, const char* name) const noexcept; - std::ostream& serialize_option_key_value(std::ostream& oss, + std::ostream& save_option_scheme(std::ostream& oss, + const char* option_prolog, + const std::string& section, + const std::string& name) const noexcept; + std::istream& load_option_scheme(std::istream& iss) noexcept; + std::ostream& save_option_key_value(std::ostream& oss, const char* section, const char* name) const noexcept; - void load_option_scheme(std::istream iss); - void load_option_key_value(std::istream iss); + std::istream& load_option_key_value(std::istream& iss); +private: std::optional> m_default_section; std::vector m_sections; bool m_dirty = false; std::function m_get_ui_value; std::function m_set_ui_value; - static constexpr char const* const c_scheme_serialization_elements[] + static constexpr char const* const scheme_tags[] { "(let ((option (gnc:lookup-option ", - "\n ", - ")))\n ((lambda (o) (if o (gnc:option-set-value o", - "))) option))\n\n" + " ", + ")))", + " ((lambda (o) (if o (gnc:option-set-value o ", + "))) option))" }; - }; /** diff --git a/libgnucash/app-utils/test/gtest-gnc-optiondb.cpp b/libgnucash/app-utils/test/gtest-gnc-optiondb.cpp index 1dd088dc12..9ef8884f31 100644 --- a/libgnucash/app-utils/test/gtest-gnc-optiondb.cpp +++ b/libgnucash/app-utils/test/gtest-gnc-optiondb.cpp @@ -276,3 +276,72 @@ TEST_F(GncOptionDBUITest, test_option_value_from_ui) }); EXPECT_STREQ(value, m_db->lookup_string_option("foo", "bar").c_str()); } + +class GncOptionDBIOTest : public ::testing::Test +{ +protected: + GncOptionDBIOTest() : m_db{gnc_option_db_new()} + { + gnc_register_string_option(m_db, "foo", "bar", "baz", "Phony Option", + std::string{"waldo"}); + gnc_register_text_option(m_db, "foo", "sausage", "links", + "Phony Option", std::string{"waldo"}); + gnc_register_string_option(m_db, "qux", "grault", "baz", "Phony Option", + std::string{""}); + gnc_register_text_option(m_db, "qux", "garply", "fred", + "Phony Option", std::string{"waldo"}); + } + + GncOptionDBPtr m_db; +}; + +TEST_F(GncOptionDBIOTest, test_option_scheme_output) +{ + std::ostringstream oss; + m_db->save_option_scheme(oss, "option", "foo", "sausage"); + EXPECT_STREQ("", oss.str().c_str()); + oss.clear(); + m_db->set_option("foo", "sausage", std::string{"pepper"}); + EXPECT_STREQ("pepper", m_db->lookup_string_option("foo", "sausage").c_str()); + EXPECT_TRUE(m_db->find_option("foo", "sausage")->get().is_changed()); + oss.flush(); + m_db->save_option_scheme(oss, "option", "foo", "sausage"); + EXPECT_STREQ("(let ((option (gnc:lookup-option option\n" + " \"foo\"\n" + " \"sausage\")))\n" + " ((lambda (o) (if o (gnc:option-set-value o \"pepper\"" + "))) option))\n\n", oss.str().c_str()); +} + +TEST_F(GncOptionDBIOTest, test_option_scheme_input) +{ + const char* input{"(let ((option (gnc:lookup-option option\n" + " \"foo\"\n" + " \"sausage\")))\n" + " ((lambda (o) (if o (gnc:option-set-value o \"pepper\"" + "))) option))\n\n"}; + std::istringstream iss{input}; + EXPECT_STREQ("waldo", m_db->lookup_string_option("foo", "sausage").c_str()); + m_db->load_option_scheme(iss); + EXPECT_STREQ("pepper", m_db->lookup_string_option("foo", "sausage").c_str()); +} + +TEST_F(GncOptionDBIOTest, test_option_key_value_output) +{ + std::ostringstream oss; + m_db->save_option_key_value(oss, "foo", "sausage"); + EXPECT_STREQ("", oss.str().c_str()); + m_db->set_option("foo", "sausage", std::string{"pepper"}); +// EXPECT_STREQ("pepper", m_db->lookup_string_option("foo", "sausage").c_str()); +// EXPECT_TRUE(m_db->find_option("foo", "sausage")->get().is_changed()); + m_db->save_option_key_value(oss, "foo", "sausage"); + EXPECT_STREQ("foo:sausage=pepper;", oss.str().c_str()); +} + +TEST_F(GncOptionDBIOTest, test_option_key_value_input) +{ + std::istringstream iss{"foo:sausage=pepper;"}; + EXPECT_STREQ("waldo", m_db->lookup_string_option("foo", "sausage").c_str()); + m_db->load_option_key_value(iss); + EXPECT_STREQ("pepper", m_db->lookup_string_option("foo", "sausage").c_str()); +}