Skip to content

Commit

Permalink
Implement saving and loading OptionDB items to/from scheme and
Browse files Browse the repository at this point in the history
key-value string representations.
  • Loading branch information
jralls committed Jul 11, 2021
1 parent 6ab5618 commit 76172af
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 24 deletions.
73 changes: 61 additions & 12 deletions libgnucash/app-utils/gnc-optiondb.cpp
Expand Up @@ -22,7 +22,10 @@
\********************************************************************/

#include "gnc-optiondb.hpp"
#include <limits>
#include <sstream>

auto constexpr stream_max = std::numeric_limits<std::streamsize>::max();
GncOptionDB::GncOptionDB() : m_default_section{std::nullopt} {}

GncOptionDB::GncOptionDB(QofBook* book) : GncOptionDB() {}
Expand Down Expand Up @@ -175,38 +178,84 @@ 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
{

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
Expand Down
25 changes: 13 additions & 12 deletions libgnucash/app-utils/gnc-optiondb.hpp
Expand Up @@ -87,30 +87,31 @@ class GncOptionDB
std::optional<std::reference_wrapper<GncOption>> find_option(const std::string& section, const std::string& name) {
return static_cast<const GncOptionDB&>(*this).find_option(section, name);
}
private:
std::ostream& serialize_option_scheme(std::ostream& oss,
std::optional<std::reference_wrapper<GncOption>> 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<std::reference_wrapper<GncOptionSection>> m_default_section;
std::vector<GncOptionSection> m_sections;
bool m_dirty = false;

std::function<GncOptionUIItem*()> m_get_ui_value;
std::function<void(GncOptionUIItem*)> 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))"
};

};

/**
Expand Down
69 changes: 69 additions & 0 deletions libgnucash/app-utils/test/gtest-gnc-optiondb.cpp
Expand Up @@ -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());
}

0 comments on commit 76172af

Please sign in to comment.