From 883127a59d7081ac2a791331ae9e9aa1af3959dc Mon Sep 17 00:00:00 2001 From: John Ralls Date: Fri, 22 Nov 2019 15:45:42 -0800 Subject: [PATCH] Implement operators >> and << on GncOption. --- libgnucash/app-utils/gnc-option.cpp | 122 +++++ libgnucash/app-utils/gnc-option.hpp | 171 +++++- libgnucash/app-utils/gnc-optiondb.cpp | 38 +- libgnucash/app-utils/gnc-optiondb.hpp | 22 +- libgnucash/app-utils/gnc-optiondb.i | 2 - .../app-utils/test/gtest-gnc-option.cpp | 507 ++++++++++++++++-- 6 files changed, 817 insertions(+), 45 deletions(-) diff --git a/libgnucash/app-utils/gnc-option.cpp b/libgnucash/app-utils/gnc-option.cpp index 06f2abf9bd..3587c93726 100644 --- a/libgnucash/app-utils/gnc-option.cpp +++ b/libgnucash/app-utils/gnc-option.cpp @@ -24,9 +24,11 @@ //#include "options.h" #include "gnc-option.hpp" #include +#include extern "C" { #include "gnc-accounting-period.h" +#include "gnc-ui-util.h" } bool @@ -129,6 +131,72 @@ GncOptionDateValue::get_value() const set_day_and_time(now, m_type == DateType::STARTING); return static_cast(GncDateTime(now)); } +static const char* date_type_str[] {"absolute", "relative"}; +static const std::array date_period_str +{ + "today", "today", + "start-this-month", "end-this-month", + "start-prev-month", "end-prev-month", + "start-current-quarter", "end-current-quarter", + "start-prev-quarter", "end-prev-quarter", + "start-cal-year", "end-cal-year", + "start-prev-year", "end-prev-year", + "start-prev-fin-year", "end-prev-fin-year" +}; + + +std::ostream& +GncOptionDateValue::out_stream(std::ostream& oss) const noexcept +{ + if (m_type == DateType::ABSOLUTE) + oss << date_type_str[0] << " . " << m_date; + else + { + int n{ m_type == DateType::STARTING ? 0 : 1}; + n += 2 * static_cast(m_period); + oss << date_type_str[1] << " . " << date_period_str[n]; + } + return oss; +} + +std::istream& +GncOptionDateValue::in_stream(std::istream& iss) +{ + std::string type_str; + std::getline(iss, type_str, '.'); + if (type_str == "absolute ") + { + time64 time; + iss >> time; + set_value(time); + } + else if (type_str == "relative ") + { + std::string period_str; + iss >> period_str; + auto period = std::find(date_period_str.begin(), date_period_str.end(), + period_str); + if (period == date_period_str.end()) + { + std::string err{"Unknown period string in date option: '"}; + err += period_str; + err += "'"; + throw std::invalid_argument(err); + } + + int64_t index = period - date_period_str.begin(); + DateType type = index % 2 ? DateType::ENDING : DateType::STARTING; + set_value(std::make_pair(type, index / 2)); + } + else + { + std::string err{"Unknown date type string in date option: '"}; + err += type_str; + err += "'"; + throw std::invalid_argument{err}; + } + return iss; +} void GncOptionDateValue::set_value(DateSetterValue value) @@ -145,3 +213,57 @@ GncOptionDateValue::set_value(DateSetterValue value) m_period = static_cast(val); m_date = 0; } + + +QofInstance* +qof_instance_from_string(const std::string& str, GncOptionUIType type) +{ + auto guid{static_cast(gnc::GUID::from_string(str))}; + QofIdType qof_type; + switch(type) + { + case GncOptionUIType::CURRENCY: + case GncOptionUIType::COMMODITY: + qof_type = "Commodity"; + break; + case GncOptionUIType::BUDGET: + qof_type = "Budget"; + break; + case GncOptionUIType::OWNER: + qof_type = "gncOwner"; + break; + case GncOptionUIType::CUSTOMER: + qof_type = "gncCustomer"; + break; + case GncOptionUIType::VENDOR: + qof_type = "gncVendor"; + break; + case GncOptionUIType::EMPLOYEE: + qof_type = "gncEmployee"; + break; + case GncOptionUIType::INVOICE: + qof_type = "gncInvoice"; + break; + case GncOptionUIType::TAX_TABLE: + qof_type = "gncTaxtable"; + break; + case GncOptionUIType::QUERY: + qof_type = "gncQuery"; + break; + case GncOptionUIType::ACCOUNT_LIST: + case GncOptionUIType::ACCOUNT_SEL: + default: + qof_type = "Account"; + break; + } + auto book{gnc_get_current_book()}; + auto col{qof_book_get_collection(book, qof_type)}; + return QOF_INSTANCE(qof_collection_lookup_entity(col, &guid)); +} + +std::string +qof_instance_to_string(const QofInstance* inst) +{ + gnc::GUID guid{*qof_instance_get_guid(inst)}; + return guid.to_string(); +} diff --git a/libgnucash/app-utils/gnc-option.hpp b/libgnucash/app-utils/gnc-option.hpp index d8e483943e..ece53edc34 100644 --- a/libgnucash/app-utils/gnc-option.hpp +++ b/libgnucash/app-utils/gnc-option.hpp @@ -33,7 +33,6 @@ extern "C" #include } #include -#include #include #include #include @@ -41,6 +40,7 @@ extern "C" #include #include #include +#include /* * Unused base class to document the structure of the current Scheme option @@ -176,6 +176,28 @@ private: GncOptionUIType m_ui_type; }; +/* These will work when m_value is a built-in class; GnuCash class and container + * values will need specialization unless they happen to define operators << and + * >>. + * Note that SWIG 3.0.12 chokes on the typename = std::enable_if_t<> form so we + * have to use the non-type parameter form. + */ +template>, int> = 0> +std::ostream& operator<<(std::ostream& oss, const OptionValueClass& opt) +{ + oss << opt.get_value(); + return oss; +} + +template>, int> = 0> +std::istream& operator>>(std::istream& iss, OptionValueClass& opt) +{ + std::decay_t value; + iss >> value; + opt.set_value(value); + return iss; +} + template class GncOptionValue : public OptionClassifier, public OptionUIItem @@ -195,11 +217,52 @@ public: ValueType get_value() const { return m_value; } ValueType get_default_value() const { return m_default_value; } void set_value(ValueType new_value) { m_value = new_value; } + bool is_changed() const noexcept { return m_value != m_default_value; } private: ValueType m_value; ValueType m_default_value; }; +QofInstance* qof_instance_from_string(const std::string& str, + GncOptionUIType type); +std::string qof_instance_to_string(const QofInstance* inst); + +template<> inline std::ostream& +operator<< >(std::ostream& oss, + const GncOptionValue& opt) +{ + oss << (opt.get_value() ? "#t" : "#f"); + return oss; +} + +template<> inline std::ostream& +operator<< >(std::ostream& oss, + const GncOptionValue& opt) +{ + oss << qof_instance_to_string(opt.get_value()); + return oss; +} + +template<> inline std::istream& +operator>> >(std::istream& iss, + GncOptionValue& opt) +{ + std::string instr; + iss >> instr; + opt.set_value(instr == "#t" ? true : false); + return iss; +} + +template<> inline std::istream& +operator>> >(std::istream& iss, + GncOptionValue& opt) +{ + std::string instr; + iss >> instr; + opt.set_value(qof_instance_from_string(instr, opt.get_ui_type())); + return iss; +} + template class GncOptionValidatedValue : public OptionClassifier, public OptionUIItem @@ -243,6 +306,7 @@ public: else throw std::invalid_argument("Validation failed, value not set."); } + bool is_changed() const noexcept { return m_value != m_default_value; } private: ValueType m_value; ValueType m_default_value; @@ -250,6 +314,25 @@ private: ValueType m_validation_data; }; +template<> inline std::ostream& +operator<< >(std::ostream& oss, + const GncOptionValidatedValue& opt) +{ + oss << qof_instance_to_string(opt.get_value()); + std::cerr << qof_instance_to_string(opt.get_value()); + return oss; +} + +template<> inline std::istream& +operator>> >(std::istream& iss, + GncOptionValidatedValue& opt) +{ + std::string instr; + iss >> instr; + opt.set_value(qof_instance_from_string(instr, opt.get_ui_type())); + return iss; +} + /** * Used for numeric ranges and plot sizes. */ @@ -283,6 +366,7 @@ public: else throw std::invalid_argument("Validation failed, value not set."); } + bool is_changed() const noexcept { return m_value != m_default_value; } private: ValueType m_value; ValueType m_default_value; @@ -376,6 +460,7 @@ public: { return std::get<2>(m_choices.at(index)); } + bool is_changed() const noexcept { return m_value != m_default_value; } private: std::size_t find_key (const std::string& key) const noexcept { @@ -462,13 +547,41 @@ public: //throw! m_value = values; } - + bool is_changed() const noexcept { return m_value != m_default_value; } private: GncOptionAccountList m_value; GncOptionAccountList m_default_value; GncOptionAccountTypeList m_allowed; }; +template<> inline std::ostream& +operator<< (std::ostream& oss, + const GncOptionAccountValue& opt) +{ + auto values{opt.get_value()}; + for (auto value : values) + oss << qof_instance_to_string(QOF_INSTANCE(value)) << " "; + return oss; +} + +template<> inline std::istream& +operator>> (std::istream& iss, + GncOptionAccountValue& opt) +{ + GncOptionAccountList values; + while (true) + { + std::string str; + std::getline(iss, str, ' '); + if (!str.empty()) + values.emplace_back((Account*)qof_instance_from_string(str, opt.get_ui_type())); + else + break; + } + opt.set_value(values); + iss.clear(); + return iss; +} /** Date options * A legal date value is a pair of either and a RelativeDatePeriod, the absolute * flag and a time64, or for legacy purposes the absolute flag and a timespec. @@ -503,7 +616,7 @@ enum class RelativeDatePeriod : int64_t ACCOUNTING_PERIOD }; -using DateSetterValue = std::pair; +using DateSetterValue = std::tuple; class GncOptionDateValue : public OptionClassifier, public OptionUIItem { public: @@ -519,23 +632,39 @@ public: GncOptionDateValue& operator=(GncOptionDateValue&&) = default; time64 get_value() const; time64 get_default_value() const { return static_cast(GncDateTime()); } + std::ostream& out_stream(std::ostream& oss) const noexcept; + std::istream& in_stream(std::istream& iss); void set_value(DateSetterValue); void set_value(time64 time) { m_type = DateType::ABSOLUTE; m_period = RelativeDatePeriod::TODAY; m_date = time; } + bool is_changed() const noexcept { return true; } private: DateType m_type; RelativeDatePeriod m_period; time64 m_date; }; +template<> inline std::ostream& +operator<< (std::ostream& oss, + const GncOptionDateValue& opt) +{ + return opt.out_stream(oss); +} + +template<> inline std::istream& +operator>> (std::istream& iss, + GncOptionDateValue& opt) +{ + return opt.in_stream(iss); +} + using GncOptionVariant = std::variant, GncOptionValue, GncOptionValue, GncOptionValue, - GncOptionValue, GncOptionAccountValue, GncOptionMultichoiceValue, GncOptionRangeValue, @@ -631,9 +760,43 @@ public: option.make_internal(); }, m_option); } + bool is_changed() + { + return std::visit([](const auto& option)->bool { + return option.is_changed(); + }, m_option); + } + + std::ostream& out_stream(std::ostream& oss) const + { + return std::visit([&oss](auto& option) -> std::ostream& { + oss << option; + return oss; + }, m_option); + } + std::istream& in_stream(std::istream& iss) + { + return std::visit([&iss](auto& option) -> std::istream& { + iss >> option; + return iss; + }, m_option); + } + GncOptionVariant& _get_option() const { return m_option; } private: mutable GncOptionVariant m_option; }; +inline std::ostream& +operator<<(std::ostream& oss, const GncOption& opt) +{ + return opt.out_stream(oss); +} + +inline std::istream& +operator>>(std::istream& iss, GncOption& opt) +{ + return opt.in_stream(iss); +} + #endif //GNC_OPTION_HPP_ diff --git a/libgnucash/app-utils/gnc-optiondb.cpp b/libgnucash/app-utils/gnc-optiondb.cpp index 8fac00eb83..e93e7f2490 100644 --- a/libgnucash/app-utils/gnc-optiondb.cpp +++ b/libgnucash/app-utils/gnc-optiondb.cpp @@ -138,9 +138,9 @@ GncOptionDB::find_section(const char* section) } std::optional> -GncOptionDB::find_option(const char* section, const char* name) +GncOptionDB::find_option(const char* section, const char* name) const { - auto db_section = find_section(section); + auto db_section = const_cast(this)->find_section(section); if (!db_section) return std::nullopt; auto db_opt = std::find_if( @@ -173,6 +173,36 @@ GncOptionDB::make_internal(const char* section, const char* name) db_opt->get().make_internal(); } +std::ostream& +GncOptionDB::serialize_option_scheme(std::ostream& oss, + const char* option_prolog, + 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 << 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]; + + return oss; +} + +std::ostream& +GncOptionDB::serialize_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() */ << ";"; + return oss; +} + void GncOptionDB::commit() { @@ -304,7 +334,7 @@ gnc_register_account_list_limited_option(const GncOptionDBPtr& db, } catch (const std::invalid_argument& err) { - std::cerr << "Account List Limited Option, value failed validation, option not registerd.\n"; + std::cerr << "Account List Limited Option, value failed validation, option not registered.\n"; } } @@ -405,7 +435,7 @@ gnc_register_query_option(const GncOptionDBPtr& db, const char* section, const char* name, const char* key, const char* doc_string, QofQuery* value) { - GncOption option{section, name, key, doc_string, value, + GncOption option{section, name, key, doc_string, QOF_INSTANCE(value), GncOptionUIType::INTERNAL}; db->register_option(section, std::move(option)); } diff --git a/libgnucash/app-utils/gnc-optiondb.hpp b/libgnucash/app-utils/gnc-optiondb.hpp index 34a1af78df..81564448cf 100644 --- a/libgnucash/app-utils/gnc-optiondb.hpp +++ b/libgnucash/app-utils/gnc-optiondb.hpp @@ -28,6 +28,7 @@ #include #include #include +#include extern "C" { #include @@ -83,14 +84,33 @@ public: void make_internal(const char* section, const char* name); void commit(); std::optional> find_section(const char* section); - std::optional> find_option(const char* section, const char* name); + std::optional> find_option(const char* section, const char* name) { + return static_cast(*this).find_option(section, name); + } + std::optional> find_option(const char* section, const char* name) const; private: + std::ostream& serialize_option_scheme(std::ostream& oss, + const char* option_prolog, + const char* section, const char* name) const noexcept; + std::ostream& serialize_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::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[] + { + "(let ((option (gnc:lookup-option ", + "\n ", + ")))\n ((lambda (o) (if o (gnc:option-set-value o", + "))) option))\n\n" + }; + }; /** diff --git a/libgnucash/app-utils/gnc-optiondb.i b/libgnucash/app-utils/gnc-optiondb.i index e92ea84edb..f3e58f2260 100644 --- a/libgnucash/app-utils/gnc-optiondb.i +++ b/libgnucash/app-utils/gnc-optiondb.i @@ -337,5 +337,3 @@ wrap_unique_ptr(GncOptionDBPtr, GncOptionDB); GncOption_set_value_from_scm(&(db_opt->get()), new_value); } %} - -*/ diff --git a/libgnucash/app-utils/test/gtest-gnc-option.cpp b/libgnucash/app-utils/test/gtest-gnc-option.cpp index 7491b4333c..9876cb81a8 100644 --- a/libgnucash/app-utils/test/gtest-gnc-option.cpp +++ b/libgnucash/app-utils/test/gtest-gnc-option.cpp @@ -23,10 +23,13 @@ #include #include +#include extern "C" { #include #include +#include +#include } TEST(GncOption, test_string_ctor) @@ -64,6 +67,23 @@ TEST(GncOption, test_string_value) }); } +TEST(GncOption, test_string_stream_out) +{ + GncOption option("foo", "bar", "baz", "Phony Option", std::string{"waldo"}); + std::ostringstream oss; + oss << option; + EXPECT_EQ(oss.str(), option.get_value()); +} + +TEST(GncOption, test_string_stream_in) +{ + GncOption option("foo", "bar", "baz", "Phony Option", std::string{"waldo"}); + std::string pepper{"pepper"}; + std::istringstream iss{pepper}; + iss >> option; + EXPECT_EQ(pepper, option.get_value()); +} + TEST(GncOption, test_int64_t_value) { GncOption option("foo", "bar", "baz", "Phony Option", INT64_C(123456789)); @@ -72,35 +92,81 @@ TEST(GncOption, test_int64_t_value) EXPECT_EQ(INT64_C(987654321), option.get_value()); } -TEST(GNCOption, test_budget_ctor) +TEST(GncOption, test_int64_stream_out) { - auto book = qof_book_new(); - auto budget = gnc_budget_new(book); + GncOption option("foo", "bar", "baz", "Phony Option", INT64_C(123456789)); + std::ostringstream oss; + oss << option; + EXPECT_STREQ(oss.str().c_str(), "123456789"); +} + +TEST(GncOption, test_int64_stream_in) +{ + GncOption option("foo", "bar", "baz", "Phony Option", INT64_C(123456789)); + std::string number{"987654321"}; + std::istringstream iss{number}; + iss >> option; + EXPECT_EQ(INT64_C(987654321), option.get_value()); +} + +TEST(GncOption, test_bool_stream_out) +{ + GncOption option("foo", "bar", "baz", "Phony Option", false); + std::ostringstream oss; + oss << option; + EXPECT_STREQ(oss.str().c_str(), "#f"); + oss.str(""); + option.set_value(true); + oss << option; + EXPECT_STREQ(oss.str().c_str(), "#t"); +} + +TEST(GncOption, test_bool_stream_in) +{ + GncOption option("foo", "bar", "baz", "Phony Option", false); + std::istringstream iss("#t"); + iss >> option; + EXPECT_TRUE(option.get_value()); + iss.str("#f"); + iss >> option; + EXPECT_FALSE(option.get_value()); +} + +class GncOptionTest : public ::testing::Test +{ +protected: + GncOptionTest() : m_session{gnc_get_current_session()}, m_book{gnc_get_current_book()} {} + ~GncOptionTest() { gnc_clear_current_session(); } + + QofSession* m_session; + QofBook* m_book; +}; + +TEST_F(GncOptionTest, test_budget_ctor) +{ + auto budget = gnc_budget_new(m_book); EXPECT_NO_THROW({ GncOption option("foo", "bar", "baz", "Phony Option", QOF_INSTANCE(budget)); }); gnc_budget_destroy(budget); - qof_book_destroy(book); } -TEST(GNCOption, test_commodity_ctor) +TEST_F(GncOptionTest, test_commodity_ctor) { - auto book = qof_book_new(); - auto hpe = gnc_commodity_new(book, "Hewlett Packard Enterprise, Inc.", + auto hpe = gnc_commodity_new(m_book, "Hewlett Packard Enterprise, Inc.", "NYSE", "HPE", NULL, 1); EXPECT_NO_THROW({ GncOption option("foo", "bar", "baz", "Phony Option", QOF_INSTANCE(hpe)); }); gnc_commodity_destroy(hpe); - qof_book_destroy(book); } static GncOption make_currency_option (const char* section, const char* name, const char* key, const char* doc_string, - gnc_commodity *value) + gnc_commodity *value, bool is_currency=false) { GncOption option{GncOptionValidatedValue{ section, name, key, doc_string, QOF_INSTANCE(value), @@ -108,52 +174,48 @@ make_currency_option (const char* section, const char* name, { return GNC_IS_COMMODITY (new_value) && gnc_commodity_is_currency(GNC_COMMODITY(new_value)); - } - }}; + }, is_currency ? GncOptionUIType::CURRENCY : GncOptionUIType::COMMODITY} + }; return option; } -TEST(GNCOption, test_currency_ctor) +TEST_F(GncOptionTest, test_currency_ctor) { - auto book = qof_book_new(); auto table = gnc_commodity_table_new(); - qof_book_set_data(book, GNC_COMMODITY_TABLE, table); - auto hpe = gnc_commodity_new(book, "Hewlett Packard Enterprise, Inc.", + qof_book_set_data(m_book, GNC_COMMODITY_TABLE, table); + auto hpe = gnc_commodity_new(m_book, "Hewlett Packard Enterprise, Inc.", "NYSE", "HPE", NULL, 1); EXPECT_THROW({ auto option = make_currency_option("foo", "bar", "baz", - "Phony Option", hpe); + "Phony Option", hpe, false); }, std::invalid_argument); gnc_commodity_destroy(hpe); - auto eur = gnc_commodity_new(book, "Euro", "ISO4217", "EUR", NULL, 100); + auto eur = gnc_commodity_new(m_book, "Euro", "ISO4217", "EUR", NULL, 100); EXPECT_NO_THROW({ auto option = make_currency_option("foo", "bar", "baz", - "Phony Option", eur); + "Phony Option", eur, true); }); gnc_commodity_destroy(eur); - auto usd = gnc_commodity_new(book, "United States Dollar", + auto usd = gnc_commodity_new(m_book, "United States Dollar", "CURRENCY", "USD", NULL, 100); EXPECT_NO_THROW({ auto option = make_currency_option("foo", "bar", "baz", - "Phony Option",usd); + "Phony Option", usd, true); }); gnc_commodity_destroy(usd); - qof_book_set_data(book, GNC_COMMODITY_TABLE, nullptr); + qof_book_set_data(m_book, GNC_COMMODITY_TABLE, nullptr); gnc_commodity_table_destroy(table); - qof_book_destroy(book); } -TEST(GNCOption, test_currency_setter) +TEST_F(GncOptionTest, test_currency_setter) { - auto book = qof_book_new(); auto table = gnc_commodity_table_new(); - qof_book_set_data(book, GNC_COMMODITY_TABLE, table); - auto hpe = gnc_commodity_new(book, "Hewlett Packard Enterprise, Inc.", + qof_book_set_data(m_book, GNC_COMMODITY_TABLE, table); + auto hpe = gnc_commodity_new(m_book, "Hewlett Packard Enterprise, Inc.", "NYSE", "HPE", NULL, 1); - auto eur = gnc_commodity_new(book, "Euro", "ISO4217", "EUR", NULL, 100); - auto option = make_currency_option("foo", "bar", "baz", - "Phony Option",eur); - auto usd = gnc_commodity_new(book, "United States Dollar", + auto eur = gnc_commodity_new(m_book, "Euro", "ISO4217", "EUR", NULL, 100); + auto option = make_currency_option("foo", "bar", "baz", "Phony Option", eur, true); + auto usd = gnc_commodity_new(m_book, "United States Dollar", "CURRENCY", "USD", NULL, 100); EXPECT_NO_THROW({ option.set_value(QOF_INSTANCE(usd)); @@ -166,9 +228,52 @@ TEST(GNCOption, test_currency_setter) gnc_commodity_destroy(hpe); gnc_commodity_destroy(usd); gnc_commodity_destroy(eur); - qof_book_set_data(book, GNC_COMMODITY_TABLE, nullptr); + qof_book_set_data(m_book, GNC_COMMODITY_TABLE, nullptr); + gnc_commodity_table_destroy(table); +} + +TEST_F(GncOptionTest, test_qofinstance_out) +{ + auto table = gnc_commodity_table_new(); + qof_book_set_data(m_book, GNC_COMMODITY_TABLE, table); + auto eur = gnc_commodity_new(m_book, "Euro", "ISO4217", "EUR", NULL, 100); + auto option = make_currency_option("foo", "bar", "baz", "Phony Option", eur, true); + + std::string eur_guid{gnc::GUID{*qof_instance_get_guid(eur)}.to_string()}; + std::ostringstream oss; + oss << option; + EXPECT_EQ(eur_guid, oss.str()); + gnc_commodity_destroy(eur); + qof_book_set_data(m_book, GNC_COMMODITY_TABLE, nullptr); + gnc_commodity_table_destroy(table); +} + +TEST_F(GncOptionTest, test_qofinstance_in) +{ + auto table = gnc_commodity_table_new(); + qof_book_set_data(m_book, GNC_COMMODITY_TABLE, table); + auto eur = gnc_commodity_new(m_book, "Euro", "ISO4217", "EUR", NULL, 100); + auto usd = gnc_commodity_new(m_book, "United States Dollar", + "CURRENCY", "USD", NULL, 100); + auto hpe = gnc_commodity_new(m_book, "Hewlett Packard Enterprise, Inc.", + "NYSE", "HPE", NULL, 1); + auto option = make_currency_option("foo", "bar", "baz", "Phony Option", eur, true); + + EXPECT_THROW({ + std::string hpe_guid{gnc::GUID{*qof_instance_get_guid(hpe)}.to_string()}; + std::istringstream iss{hpe_guid}; + iss >> option; + }, std::invalid_argument); + EXPECT_NO_THROW({ + std::string usd_guid{gnc::GUID{*qof_instance_get_guid(usd)}.to_string()}; + std::istringstream iss{usd_guid}; + iss >> option; + EXPECT_EQ(QOF_INSTANCE(usd), option.get_value()); + }); + gnc_commodity_destroy(eur); + gnc_commodity_destroy(usd); + qof_book_set_data(m_book, GNC_COMMODITY_TABLE, nullptr); gnc_commodity_table_destroy(table); - qof_book_destroy(book); } class GncUIItem @@ -246,6 +351,24 @@ TEST_F(GncRangeOption, test_setter) EXPECT_EQ(1.5, m_doubleoption.get_default_value()); } +TEST_F(GncRangeOption, test_range_out) +{ + std::ostringstream oss; + oss << "Integer " << m_intoption << " Double " << m_doubleoption << "."; + EXPECT_STREQ("Integer 15 Double 1.5.", oss.str().c_str()); +} + +TEST_F(GncRangeOption, test_range_in) +{ + std::istringstream iss{std::string{"45 4.5 20 2.0"}}; + EXPECT_THROW({ iss >> m_intoption; }, std::invalid_argument); + EXPECT_THROW({ iss >> m_doubleoption; }, std::invalid_argument); + EXPECT_NO_THROW({ iss >> m_intoption; }); + EXPECT_NO_THROW({ iss >> m_doubleoption; }); + EXPECT_EQ(20, m_intoption.get_value()); + EXPECT_EQ(2.0, m_doubleoption.get_value()); +} + using AccountPair = std::pair; static void @@ -264,7 +387,8 @@ class GncOptionAccountTest : public ::testing::Test { protected: GncOptionAccountTest() : - m_book{qof_book_new()}, m_root{gnc_account_create_root(m_book)} + m_session{gnc_get_current_session()}, m_book{gnc_get_current_book()}, + m_root{gnc_account_create_root(m_book)} { auto create_account = [this](Account* parent, GNCAccountType type, const char* name)->Account* { @@ -296,7 +420,7 @@ protected: { xaccAccountBeginEdit(m_root); xaccAccountDestroy(m_root); //It does the commit - qof_book_destroy(m_book); + gnc_clear_current_session(); } GncOptionAccountList list_of_types(const GncOptionAccountTypeList& types) { @@ -307,6 +431,7 @@ protected: return list; } + QofSession* m_session; QofBook* m_book; Account* m_root; }; @@ -389,6 +514,65 @@ TEST_F(GncOptionAccountTest, test_option_value_limited_constructor) EXPECT_EQ(false, option.validate(acclistbad)); } +TEST_F(GncOptionAccountTest, test_account_list_out) +{ + GncOptionAccountList acclist{list_of_types({ACCT_TYPE_BANK})}; + GncOptionAccountValue option{"foo", "bar", "baz", "Bogus Option", + GncOptionUIType::ACCOUNT_LIST, acclist}; + std::ostringstream oss; + std::string acc_guids{gnc::GUID{*qof_instance_get_guid(acclist[0])}.to_string()}; + acc_guids += " "; + acc_guids += gnc::GUID{*qof_instance_get_guid(acclist[1])}.to_string(); + acc_guids += " "; + + oss << option; + EXPECT_EQ(acc_guids, oss.str()); + + GncOptionAccountList accsel{acclist[0]}; + GncOptionAccountValue sel_option("foo", "bar", "baz", "Bogus Option", + GncOptionUIType::ACCOUNT_LIST, + accsel, {ACCT_TYPE_BANK}); + acc_guids = gnc::GUID{*qof_instance_get_guid(accsel[0])}.to_string(); + acc_guids += " "; + + oss.str(""); + oss << sel_option; + EXPECT_EQ(acc_guids, oss.str()); +} + +TEST_F(GncOptionAccountTest, test_account_list_in) +{ + GncOptionAccountList acclist{list_of_types({ACCT_TYPE_BANK})}; + GncOptionAccountValue option{"foo", "bar", "baz", "Bogus Option", + GncOptionUIType::ACCOUNT_LIST, acclist}; + std::string acc_guids{gnc::GUID{*qof_instance_get_guid(acclist[0])}.to_string()}; + acc_guids += " "; + acc_guids += gnc::GUID{*qof_instance_get_guid(acclist[1])}.to_string(); + acc_guids += " "; + + std::istringstream iss{acc_guids}; + iss >> option; + EXPECT_EQ(acclist, option.get_value()); + + GncOptionAccountList accsel{acclist[0]}; + GncOptionAccountValue sel_option("foo", "bar", "baz", "Bogus Option", + GncOptionUIType::ACCOUNT_LIST, + accsel, {ACCT_TYPE_BANK}); + GncOptionAccountList acclistbad{list_of_types({ACCT_TYPE_STOCK})}; + acc_guids = gnc::GUID{*qof_instance_get_guid(acclistbad[1])}.to_string(); + acc_guids += " "; + + iss.str(acc_guids); + iss >> sel_option; + EXPECT_EQ(accsel, sel_option.get_value()); + + acc_guids = gnc::GUID{*qof_instance_get_guid(acclist[1])}.to_string(); + EXPECT_NO_THROW({ + iss.str(acc_guids); + iss >> sel_option; + }); + EXPECT_EQ(acclist[1], sel_option.get_value()[0]); +} class GncOptionMultichoiceTest : public ::testing::Test { @@ -611,3 +795,258 @@ TEST_F(GncDateOption, test_set_and_get_prev_year_end) EXPECT_EQ(time1, m_option.get_value()); } +TEST_F(GncDateOption, test_stream_out) +{ + time64 time1{static_cast(GncDateTime("2019-07-19 15:32:26 +05:00"))}; + DateSetterValue value1{DateType::ABSOLUTE, time1}; + m_option.set_value(value1); + std::ostringstream oss; + oss << time1; + std::string timestr{"absolute . "}; + timestr += oss.str(); + oss.str(""); + oss << m_option; + EXPECT_EQ(oss.str(), timestr); + + DateSetterValue value2{DateType::STARTING, static_cast(RelativeDatePeriod::TODAY)}; + m_option.set_value(value2); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . today"); + + DateSetterValue value3{DateType::ENDING, static_cast(RelativeDatePeriod::TODAY)}; + m_option.set_value(value3); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . today"); + + DateSetterValue value4{DateType::STARTING, static_cast(RelativeDatePeriod::THIS_MONTH)}; + m_option.set_value(value4); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . start-this-month"); + + DateSetterValue value5{DateType::ENDING, static_cast(RelativeDatePeriod::THIS_MONTH)}; + m_option.set_value(value5); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . end-this-month"); + + DateSetterValue value6{DateType::STARTING, static_cast(RelativeDatePeriod::PREV_MONTH)}; + m_option.set_value(value6); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . start-prev-month"); + + DateSetterValue value7{DateType::ENDING, static_cast(RelativeDatePeriod::PREV_MONTH)}; + m_option.set_value(value7); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . end-prev-month"); + + DateSetterValue value8{DateType::STARTING, static_cast(RelativeDatePeriod::CURRENT_QUARTER)}; + m_option.set_value(value8); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . start-current-quarter"); + + DateSetterValue value9{DateType::ENDING, static_cast(RelativeDatePeriod::CURRENT_QUARTER)}; + m_option.set_value(value9); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . end-current-quarter"); + + DateSetterValue value10{DateType::STARTING, static_cast(RelativeDatePeriod::PREV_QUARTER)}; + m_option.set_value(value10); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . start-prev-quarter"); + + DateSetterValue value11{DateType::ENDING, static_cast(RelativeDatePeriod::PREV_QUARTER)}; + m_option.set_value(value11); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . end-prev-quarter"); + + DateSetterValue value12{DateType::STARTING, static_cast(RelativeDatePeriod::CAL_YEAR)}; + m_option.set_value(value12); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . start-cal-year"); + + DateSetterValue value13{DateType::ENDING, static_cast(RelativeDatePeriod::CAL_YEAR)}; + m_option.set_value(value13); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . end-cal-year"); + + DateSetterValue value14{DateType::STARTING, static_cast(RelativeDatePeriod::PREV_YEAR)}; + m_option.set_value(value14); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . start-prev-year"); + + DateSetterValue value15{DateType::ENDING, static_cast(RelativeDatePeriod::PREV_YEAR)}; + m_option.set_value(value15); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . end-prev-year"); + + DateSetterValue value16{DateType::STARTING, static_cast(RelativeDatePeriod::ACCOUNTING_PERIOD)}; + m_option.set_value(value16); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . start-prev-fin-year"); + + DateSetterValue value17{DateType::ENDING, static_cast(RelativeDatePeriod::ACCOUNTING_PERIOD)}; + m_option.set_value(value17); + oss.str(""); + oss << m_option; + EXPECT_STREQ(oss.str().c_str(), "relative . end-prev-fin-year"); +} + +TEST_F(GncDateOption, test_stream_in_absolute) +{ + time64 time1{static_cast(GncDateTime("2019-07-19 15:32:26 +05:00"))}; + std::ostringstream oss; + oss << time1; + std::string timestr{"absolute . "}; + timestr += oss.str(); + + std::istringstream iss{timestr}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_month_start) +{ + GDate month_start; + g_date_set_time_t(&month_start, time(nullptr)); + gnc_gdate_set_month_start(&month_start); + time64 time1{time64_from_gdate(&month_start, DayPart::start)}; + std::istringstream iss{"relative . start-this-month"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + + +TEST_F(GncDateOption, test_stream_in_month_end) +{ + GDate month_end; + g_date_set_time_t(&month_end, time(nullptr)); + gnc_gdate_set_month_end(&month_end); + time64 time1{time64_from_gdate(&month_end, DayPart::end)}; + std::istringstream iss{"relative . end-this-month"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_prev_month_start) +{ + GDate prev_month_start; + g_date_set_time_t(&prev_month_start, time(nullptr)); + gnc_gdate_set_prev_month_start(&prev_month_start); + time64 time1{time64_from_gdate(&prev_month_start, DayPart::start)}; + std::istringstream iss{"relative . start-prev-month"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_prev_month_end) +{ + GDate prev_month_end; + g_date_set_time_t(&prev_month_end, time(nullptr)); + gnc_gdate_set_prev_month_end(&prev_month_end); + time64 time1{time64_from_gdate(&prev_month_end, DayPart::end)}; + std::istringstream iss{"relative . end-prev-month"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_quarter_start) +{ + GDate quarter_start; + g_date_set_time_t(&quarter_start, time(nullptr)); + gnc_gdate_set_quarter_start(&quarter_start); + time64 time1{time64_from_gdate(&quarter_start, DayPart::start)}; + std::istringstream iss{"relative . start-current-quarter"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_quarter_end) +{ + GDate quarter_end; + g_date_set_time_t(&quarter_end, time(nullptr)); + gnc_gdate_set_quarter_end(&quarter_end); + time64 time1{time64_from_gdate(&quarter_end, DayPart::end)}; + std::istringstream iss{"relative . end-current-quarter"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_prev_quarter_start) +{ + GDate prev_quarter_start; + g_date_set_time_t(&prev_quarter_start, time(nullptr)); + gnc_gdate_set_prev_quarter_start(&prev_quarter_start); + time64 time1{time64_from_gdate(&prev_quarter_start, DayPart::start)}; + std::istringstream iss{"relative . start-prev-quarter"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_prev_quarter_end) +{ + GDate prev_quarter_end; + g_date_set_time_t(&prev_quarter_end, time(nullptr)); + gnc_gdate_set_prev_quarter_end(&prev_quarter_end); + time64 time1{time64_from_gdate(&prev_quarter_end, DayPart::end)}; + std::istringstream iss{"relative . end-prev-quarter"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_year_start) +{ + GDate year_start; + g_date_set_time_t(&year_start, time(nullptr)); + gnc_gdate_set_year_start(&year_start); + time64 time1{time64_from_gdate(&year_start, DayPart::start)}; + std::istringstream iss{"relative . start-cal-year"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_year_end) +{ + GDate year_end; + g_date_set_time_t(&year_end, time(nullptr)); + gnc_gdate_set_year_end(&year_end); + time64 time1{time64_from_gdate(&year_end, DayPart::end)}; + std::istringstream iss{"relative . end-cal-year"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_prev_year_start) +{ + GDate prev_year_start; + g_date_set_time_t(&prev_year_start, time(nullptr)); + gnc_gdate_set_prev_year_start(&prev_year_start); + time64 time1{time64_from_gdate(&prev_year_start, DayPart::start)}; + std::istringstream iss{"relative . start-prev-year"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +} + +TEST_F(GncDateOption, test_stream_in_prev_year_end) +{ + GDate prev_year_end; + g_date_set_time_t(&prev_year_end, time(nullptr)); + gnc_gdate_set_prev_year_end(&prev_year_end); + time64 time1{time64_from_gdate(&prev_year_end, DayPart::end)}; + std::istringstream iss{"relative . end-prev-year"}; + iss >> m_option; + EXPECT_EQ(time1, m_option.get_value()); +}