diff --git a/libgnucash/app-utils/gnc-option.hpp b/libgnucash/app-utils/gnc-option.hpp index b3a750b25e..b1b881d4bd 100644 --- a/libgnucash/app-utils/gnc-option.hpp +++ b/libgnucash/app-utils/gnc-option.hpp @@ -176,31 +176,8 @@ 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 +class GncOptionValue : public OptionClassifier, public OptionUIItem { public: GncOptionValue(const char* section, const char* name, @@ -223,49 +200,8 @@ private: 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 +class GncOptionValidatedValue : public OptionClassifier, public OptionUIItem { public: GncOptionValidatedValue(const char* section, const char* name, @@ -314,25 +250,177 @@ private: ValueType m_validation_data; }; -template<> inline std::ostream& -operator<< >(std::ostream& oss, - const GncOptionValidatedValue& opt) +QofInstance* qof_instance_from_string(const std::string& str, + GncOptionUIType type); +std::string qof_instance_to_string(const QofInstance* inst); + +/* 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 elaborate enable_if so just hide the + * following templates from SWIG. (Ignoring doesn't work because SWIG still has + * to parse the templates to figure out the symbols. + */ +#ifndef SWIG +template> && + !(std::is_same_v, + GncOptionValue> || + std::is_same_v, + GncOptionValidatedValue>), int> = 0> +std::ostream& operator<<(std::ostream& oss, const OptionValueClass& opt) { - oss << qof_instance_to_string(opt.get_value()); - std::cerr << qof_instance_to_string(opt.get_value()); + oss << opt.get_value(); + return oss; +} + +template<> inline std::ostream& +operator<< >(std::ostream& oss, + const GncOptionValue& opt) +{ + oss << (opt.get_value() ? "#t" : "#f"); return oss; } -template<> inline std::istream& -operator>> >(std::istream& iss, - GncOptionValidatedValue& opt) +template, GncOptionValidatedValue> || std::is_same_v, GncOptionValue >, int> = 0> +inline std::ostream& +operator<< (std::ostream& oss, const OptType& opt) +{ + auto value = opt.get_value(); + if (auto type = opt.get_ui_type(); type == GncOptionUIType::COMMODITY || + type == GncOptionUIType::CURRENCY) + { + if (auto type = opt.get_ui_type(); type == GncOptionUIType::COMMODITY) + { + oss << gnc_commodity_get_namespace(GNC_COMMODITY(value)) << " "; + } + oss << gnc_commodity_get_mnemonic(GNC_COMMODITY(value)); + } + else + { + oss << qof_instance_to_string(value); + } + return oss; +} + +template> && + !(std::is_same_v, + GncOptionValue> || + std::is_same_v, + GncOptionValidatedValue>), int> = 0> +std::istream& operator>>(std::istream& iss, OptionValueClass& opt) +{ + std::decay_t value; + iss >> value; + opt.set_value(value); + return iss; +} + +template, GncOptionValidatedValue> || std::is_same_v, GncOptionValue >, int> = 0> +std::istream& +operator>> (std::istream& iss, OptType& opt) { std::string instr; - iss >> instr; + if (auto type = opt.get_ui_type(); type == GncOptionUIType::COMMODITY || + type == GncOptionUIType::CURRENCY) + { + std::string name_space, mnemonic; + if (type = opt.get_ui_type(); type == GncOptionUIType::COMMODITY) + { + iss >> name_space; + } + else + name_space = GNC_COMMODITY_NS_CURRENCY; + iss >> mnemonic; + instr = name_space + ":"; + instr += mnemonic; + } + else + { + iss >> instr; + } opt.set_value(qof_instance_from_string(instr, opt.get_ui_type())); return iss; } +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, GncOptionValidatedValue> || std::is_same_v, GncOptionValue>, int> = 0> +inline std::ostream& +gnc_option_to_scheme (std::ostream& oss, const OptType& opt) +{ + auto value = opt.get_value(); + auto type = opt.get_ui_type(); + if (type == GncOptionUIType::COMMODITY || type == GncOptionUIType::CURRENCY) + { + if (type == GncOptionUIType::COMMODITY) + { + oss << commodity_scm_intro; + oss << "\"" << + gnc_commodity_get_namespace(GNC_COMMODITY(value)) << "\" "; + } + + oss << "\"" << gnc_commodity_get_mnemonic(GNC_COMMODITY(value)) << "\""; + + if (type == GncOptionUIType::COMMODITY) + { + oss << ")"; + } + } + else + { + oss << "\"" << qof_instance_to_string(value) << "\""; + } + return oss; +} + +template, GncOptionValidatedValue> || std::is_same_v, GncOptionValue>, int> = 0> +inline std::istream& +gnc_option_from_scheme (std::istream& iss, OptType& opt) +{ + std::string instr; + auto type = opt.get_ui_type(); + if (type == GncOptionUIType::COMMODITY || type == GncOptionUIType::CURRENCY) + { + std::string name_space, mnemonic; + if (type == GncOptionUIType::COMMODITY) + { + iss.ignore(strlen(commodity_scm_intro) + 1, '"'); + std::getline(iss, name_space, '"'); + iss.ignore(1, '"'); + } + else + name_space = GNC_COMMODITY_NS_CURRENCY; + iss.ignore(1, '"'); + std::getline(iss, mnemonic, '"'); + + if (type == GncOptionUIType::COMMODITY) + iss.ignore(2, ')'); + else + iss.ignore(1, '"'); + + instr = name_space + ":"; + instr += mnemonic; + } + else + { + iss.ignore(1, '"'); + std::getline(iss, instr, '"'); + } + opt.set_value(qof_instance_from_string(instr, opt.get_ui_type())); + return iss; +} +#endif // SWIG + /** * Used for numeric ranges and plot sizes. */ @@ -559,8 +647,15 @@ operator<< (std::ostream& oss, const GncOptionAccountValue& opt) { auto values{opt.get_value()}; + bool first = true; for (auto value : values) - oss << qof_instance_to_string(QOF_INSTANCE(value)) << " "; + { + if (first) + first = false; + else + oss << " "; + oss << qof_instance_to_string(QOF_INSTANCE(value)); + } return oss; } diff --git a/libgnucash/app-utils/test/gtest-gnc-option.cpp b/libgnucash/app-utils/test/gtest-gnc-option.cpp index 2f4a9afd21..530caefd08 100644 --- a/libgnucash/app-utils/test/gtest-gnc-option.cpp +++ b/libgnucash/app-utils/test/gtest-gnc-option.cpp @@ -152,6 +152,28 @@ TEST_F(GncOptionTest, test_budget_ctor) gnc_budget_destroy(budget); } +TEST_F(GncOptionTest, test_budget_out) +{ + auto budget = gnc_budget_new(m_book); + GncOption option{"foo", "bar", "baz", "Phony Option", QOF_INSTANCE(budget)}; + + auto budget_guid{gnc::GUID{*qof_instance_get_guid(QOF_INSTANCE(budget))}.to_string()}; + std::ostringstream oss; + oss << option; + EXPECT_EQ(budget_guid, oss.str()); + gnc_budget_destroy(budget); +} + +TEST_F(GncOptionTest, test_budget_in) +{ + auto budget = gnc_budget_new(m_book); + auto budget_guid{gnc::GUID{*qof_instance_get_guid(QOF_INSTANCE(budget))}.to_string()}; + std::istringstream iss{budget_guid}; + GncOption option{GncOptionValue{"foo", "bar", "baz", "Phony Option", nullptr, GncOptionUIType::BUDGET}}; + iss >> option; + EXPECT_EQ(QOF_INSTANCE(budget), option.get_value()); + gnc_budget_destroy(budget); +} TEST_F(GncOptionTest, test_commodity_ctor) { auto hpe = gnc_commodity_new(m_book, "Hewlett Packard Enterprise, Inc.", @@ -589,23 +611,23 @@ TEST_F(GncOptionAccountTest, test_option_value_limited_constructor) 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}; + GncOption option{GncOptionAccountValue{"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", + GncOption sel_option{GncOptionAccountValue{"foo", "bar", "baz", + "Bogus Option", GncOptionUIType::ACCOUNT_LIST, - accsel, {ACCT_TYPE_BANK}); + accsel, {ACCT_TYPE_BANK}}}; acc_guids = gnc::GUID{*qof_instance_get_guid(accsel[0])}.to_string(); - acc_guids += " "; oss.str(""); oss << sel_option; @@ -615,35 +637,39 @@ TEST_F(GncOptionAccountTest, test_account_list_out) 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}; + GncOption option{GncOptionAccountValue{"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()); + EXPECT_EQ(acclist, option.get_value()); GncOptionAccountList accsel{acclist[0]}; - GncOptionAccountValue sel_option("foo", "bar", "baz", "Bogus Option", + GncOption sel_option{GncOptionAccountValue{"foo", "bar", "baz", + "Bogus Option", GncOptionUIType::ACCOUNT_LIST, - accsel, {ACCT_TYPE_BANK}); + 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()); + EXPECT_EQ(accsel, sel_option.get_value()); + iss.clear(); //Reset the failedbit from the invalid selection type. 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]); + EXPECT_EQ(acclist[1], sel_option.get_value()[0]); +} + } class GncOptionMultichoiceTest : public ::testing::Test @@ -710,6 +736,24 @@ TEST_F(GncMultichoiceOption, test_permissible_value_stuff) m_option.permissible_value_index("xyzzy")); } +TEST_F(GncMultichoiceOption, test_multichoice_out) +{ + std::ostringstream oss; + oss << m_option; + EXPECT_EQ(oss.str(), m_option.get_value()); +} + +TEST_F(GncMultichoiceOption, test_multichoice_in) +{ + std::istringstream iss{"grault"}; + EXPECT_THROW({ + iss >> m_option; + }, std::invalid_argument); + iss.clear(); //reset failedbit + iss.str("pork"); + iss >> m_option; + EXPECT_EQ(iss.str(), m_option.get_value()); +} class GncOptionDateOptionTest : public ::testing::Test { protected: