diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 097d879..0a7f527 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,7 +41,7 @@ PRIVATE yycc/carton/binstore/types.cpp yycc/carton/binstore/setting.cpp yycc/carton/binstore/configuration.cpp - yycc/carton/binstore/storage.cpp + #yycc/carton/binstore/storage.cpp ) target_sources(YYCCommonplace PUBLIC @@ -110,7 +110,7 @@ FILES yycc/carton/binstore/serializer.hpp yycc/carton/binstore/setting.hpp yycc/carton/binstore/configuration.hpp - yycc/carton/binstore/storage.hpp + #yycc/carton/binstore/storage.hpp yycc/carton/fft.hpp ) # Setup header infomations diff --git a/src/yycc/carton/binstore/serializer.hpp b/src/yycc/carton/binstore/serializer.hpp index 0e50da2..d84c637 100644 --- a/src/yycc/carton/binstore/serializer.hpp +++ b/src/yycc/carton/binstore/serializer.hpp @@ -84,8 +84,9 @@ namespace yycc::carton::binstore::serializer { struct FloatingPointSerDes { YYCC_DEFAULT_COPY_MOVE(FloatingPointSerDes) - static_assert(std::isfinite(TMin)); - static_assert(std::isfinite(TMax)); + // TODO: Use static_assert once 3 common STL make this become constexpr. + //static_assert(std::isfinite(TMin)); + //static_assert(std::isfinite(TMax)); static_assert(TMin <= TMax); static_assert(TDefault >= TMin && TDefault <= TMax); diff --git a/src/yycc/carton/binstore/storage.hpp b/src/yycc/carton/binstore/storage.hpp index 8e1dc9e..abf71bd 100644 --- a/src/yycc/carton/binstore/storage.hpp +++ b/src/yycc/carton/binstore/storage.hpp @@ -11,15 +11,37 @@ #include #define NS_YYCC_BINSTORE_TYPES ::yycc::carton::binstore::types -#define NS_YYCC_BINSTORE_SERIALIZER ::yycc::carton::binstore::serializer +#define NS_YYCC_BINSTORE_CFG ::yycc::carton::binstore::configuration +#define NS_YYCC_BINSTORE_SERDES ::yycc::carton::binstore::serializer namespace yycc::carton::binstore::storage { /// @brief The strategy when loading from storage. enum class LoadStrategy { - OnlyCurrent, ///< Only the file with exactly matched version identifier can be loaded. Any other version will cause load error. - LegacyAndCurrent, ///< Accept all old version and current version. Any other version will cause load error. - AcceptAll, ///< Accept all versions. + /** + * @brief Only accept matched version. + * @details + * Any loading of other versions will explicitly cause error return. + * This is convenient for developer who want control migration by themselves. + * They can specify this strategy and try to load data with different version configurations + * from older to newer one by one. + */ + OnlyCurrent, + /** + * @brief Try to migrate old version. + * @details + * Accept mateched and any older versions. + * Any newer versions will explicitly cause error return. + * This strategy is good for developer who are lazy to treat this manually. + */ + MigrateOld, + /** + * @brief Accept all version. + * @details + * This strategy is not suggested. + * This strategy only suit for quick demo. + */ + AcceptAll, }; /// @brief The operation result state when getting setting's value. @@ -38,11 +60,12 @@ namespace yycc::carton::binstore::storage { class Storage { private: /** - * @brief All stored settings in raw format. + * @brief All stored values of setting in raw format. * @details Key is the token to already registered settings. * Valus is its stored value in raw data format. */ std::map raws; + NS_YYCC_BINSTORE_CFG::Configuration configuration; ///< The configuration associated with this storage. public: Storage(); @@ -50,38 +73,78 @@ namespace yycc::carton::binstore::storage { public: NS_YYCC_BINSTORE_TYPES::BinstoreResult load_from_file(const std::filesystem::path& fpath, LoadStrategy strategy); - NS_YYCC_BINSTORE_TYPES::BinstoreResult load_from_io(const std::istream& s, LoadStrategy strategy); + NS_YYCC_BINSTORE_TYPES::BinstoreResult load_from_io(std::istream& s, LoadStrategy strategy); NS_YYCC_BINSTORE_TYPES::BinstoreResult save_into_file(const std::filesystem::path& fpath); - NS_YYCC_BINSTORE_TYPES::BinstoreResult save_into_io(const std::ostream& s); + NS_YYCC_BINSTORE_TYPES::BinstoreResult save_into_io(std::ostream& s); + /** + * @brief Reset all values of setting to their default value. + * @details Please note that all stored value will be wiped before assign them with default value.65 + */ void reset(); private: bool has_value(NS_YYCC_BINSTORE_TYPES::Token token) const; - std::optional get_raw_value(NS_YYCC_BINSTORE_TYPES::Token token) const; + const NS_YYCC_BINSTORE_TYPES::ByteArray& get_raw_value(NS_YYCC_BINSTORE_TYPES::Token token) const; void set_raw_value(NS_YYCC_BINSTORE_TYPES::Token token, NS_YYCC_BINSTORE_TYPES::ByteArray&& ba); public: - template - NS_YYCC_BINSTORE_TYPES::BinstoreResult>> get_value( + template + NS_YYCC_BINSTORE_TYPES::BinstoreResult>> get_value( NS_YYCC_BINSTORE_TYPES::Token token, const T& serdes = T()) { - // Fetch from raw value. - auto rv_raw_value = this->get_raw_value(token); - if (!rv_raw_value.has_value()) return std::unexpected(rv_raw_value.error()); - auto& [state, raw_value] = rv_raw_value.value(); + // If we have value, we fetch it first + if (this->has_value(token)) { + // Get raw value. + const auto& ba = this->get_raw_value(token); + // Try to deserialize it. + auto value = serdes.deserialize(raw_value); + // If the result is okey, return it. + if (value.has_value()) { + return std::make_pair(GetValueState::Ok, std::move(value.value())); + } + // Otherwise we need reset it into default value. + } - // Pass into deserializer. - auto rv_des = serdes.deserialize(raw_value); - if (rv_des.has_value()) return rv_des.value(); - - // If we can not set it, we force reset it and try again. - this->set_raw_value() + // If we do not have this setting, + // or we need reset it into default value, + // we need execute following code. + // First decide the return state. + GetValueState state = this->has_value(token) ? GetValueState::Reset : GetValueState::Default; + { + // Fetch the default raw data. + auto ba = serdes.reset(); + // Set it for this setting. + this->set_raw_value(token, std::move(ba)); + } + // The get it and deserialize it. + const auto& ba = this->get_raw_value(token); + auto value = serdes.deserialize(ba); + // Default must can be deserialized. + // If not, throw exception. + if (value.has_value()) { + return std::make_pair(state, std::move(value)); + } else { + throw std::logic_error("default value can not be deserialized"); + } } - template + template NS_YYCC_BINSTORE_TYPES::BinstoreResult set_value(NS_YYCC_BINSTORE_TYPES::Token token, - const NS_YYCC_BINSTORE_SERIALIZER::SerDesValueType& value, + const NS_YYCC_BINSTORE_SERDES::SerDesValueType& value, const T& serdes = T()) { - + // First we try assign it. + { + // Convert it into raw format. + auto ba = serdes.serialize(value); + // If we can not serialize it, we need reset it into default value. + // Otherwise assign it and return. + if (ba.has_value()) { + this->set_raw_value(token, ) + } + } } }; } // namespace yycc::carton::binstore::storage + +#undef NS_YYCC_BINSTORE_SERDES +#undef NS_YYCC_BINSTORE_CFG +#undef NS_YYCC_BINSTORE_TYPES diff --git a/src/yycc/carton/clap/parser.hpp b/src/yycc/carton/clap/parser.hpp index 771821a..f3480b4 100644 --- a/src/yycc/carton/clap/parser.hpp +++ b/src/yycc/carton/clap/parser.hpp @@ -45,8 +45,8 @@ namespace yycc::carton::clap::parser { bool has_option(NS_YYCC_CLAP_TYPES::Token token) const; NS_YYCC_CLAP_TYPES::ClapResult get_flag_option(NS_YYCC_CLAP_TYPES::Token token) const; template - NS_YYCC_CLAP_TYPES::ClapResult> get_value_option( - NS_YYCC_CLAP_TYPES::Token token, const T& validator = T()) const { + NS_YYCC_CLAP_TYPES::ClapResult> get_value_option(NS_YYCC_CLAP_TYPES::Token token, + const T& validator = T()) const { auto raw_value = this->get_raw_value_option(token); if (raw_value.has_value()) { auto value = validator.validate(raw_value.value()); diff --git a/src/yycc/carton/clap/validator.hpp b/src/yycc/carton/clap/validator.hpp index 385dd98..19f72fa 100644 --- a/src/yycc/carton/clap/validator.hpp +++ b/src/yycc/carton/clap/validator.hpp @@ -47,7 +47,7 @@ namespace yycc::carton::clap::validator { static_assert(TMin <= TMax); using ReturnType = T; - std::optional validate(const std::u8string_view& sv) { + std::optional validate(const std::u8string_view& sv) const { auto rv = NS_YYCC_NUM_PARSE::parse(sv); if (rv) { auto value = rv.value(); @@ -61,12 +61,13 @@ namespace yycc::carton::clap::validator { struct FloatingPointValidator { YYCC_DEFAULT_COPY_MOVE(FloatingPointValidator) - static_assert(std::isfinite(TMin)); - static_assert(std::isfinite(TMax)); + // TODO: Use static_assert once 3 common STL make this become constexpr. + //static_assert(std::isfinite(TMin)); + //static_assert(std::isfinite(TMax)); static_assert(TMin <= TMax); using ReturnType = T; - std::optional validate(const std::u8string_view& sv) { + std::optional validate(const std::u8string_view& sv) const { auto rv = NS_YYCC_NUM_PARSE::parse(sv); if (rv) { auto value = rv.value(); @@ -80,7 +81,7 @@ namespace yycc::carton::clap::validator { YYCC_DEFAULT_COPY_MOVE(BoolValidator) using ReturnType = bool; - std::optional validate(const std::u8string_view& sv) { + std::optional validate(const std::u8string_view& sv) const { auto rv = NS_YYCC_NUM_PARSE::parse(sv); if (rv) return rv.value(); else return std::nullopt; @@ -91,7 +92,7 @@ namespace yycc::carton::clap::validator { YYCC_DEFAULT_COPY_MOVE(StringValidator) using ReturnType = std::u8string; - std::optional validate(const std::u8string_view& sv) { return std::u8string(sv); } + std::optional validate(const std::u8string_view& sv) const { return std::u8string(sv); } }; } // namespace yycc::carton::clap::validator diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c94d50b..3c72060 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -41,6 +41,7 @@ PRIVATE yycc/carton/wcwidth.cpp yycc/carton/tabulate.cpp yycc/carton/clap.cpp + yycc/carton/binstore.cpp yycc/carton/fft.cpp ) target_sources(YYCCTest diff --git a/test/yycc/carton/binstore.cpp b/test/yycc/carton/binstore.cpp new file mode 100644 index 0000000..e69de29 diff --git a/test/yycc/carton/clap.cpp b/test/yycc/carton/clap.cpp index e69de29..578c6b2 100644 --- a/test/yycc/carton/clap.cpp +++ b/test/yycc/carton/clap.cpp @@ -0,0 +1,271 @@ +#include +#include +#include +#include + +#define CLAP ::yycc::carton::clap + +namespace yycctest::carton::clap { + + using Token = CLAP::types::Token; + namespace validator = CLAP::validator; + + TEST(CartonClap, BadOption) { + // No short name and long name. + EXPECT_ANY_THROW(auto opt = CLAP::option::Option(std::nullopt, std::nullopt, std::nullopt, u8"")); + // Empty short name or long name. + EXPECT_ANY_THROW(auto opt = CLAP::option::Option(u8"", std::nullopt, std::nullopt, u8"")); + EXPECT_ANY_THROW(auto opt = CLAP::option::Option(std::nullopt, u8"", std::nullopt, u8"")); + // Bad short name + EXPECT_ANY_THROW(auto opt = CLAP::option::Option(u8"fuck", std::nullopt, std::nullopt, u8"")); + EXPECT_ANY_THROW(auto opt = CLAP::option::Option(u8"-", std::nullopt, std::nullopt, u8"")); + } + + TEST(CartonClap, BadOptions) { + // Duplicated. + { + auto options = CLAP::option::OptionCollection(); + options.add_option(CLAP::option::Option(u8"a", std::nullopt, std::nullopt, u8"")); + EXPECT_ANY_THROW(options.add_option(CLAP::option::Option(u8"a", std::nullopt, std::nullopt, u8""))); + } + // Duplicate between long name and short name. + { + auto options = CLAP::option::OptionCollection(); + options.add_option(CLAP::option::Option(u8"a", std::nullopt, std::nullopt, u8"")); + EXPECT_ANY_THROW(options.add_option(CLAP::option::Option(std::nullopt, u8"a", std::nullopt, u8""))); + } + } + + TEST(CartonClap, BadVariable) { + // Empty name + EXPECT_ANY_THROW(auto var = CLAP::variable::Variable(u8"", u8"", true)); + } + + TEST(CartonClap, BadVariables) { + // Duplicated. + { + auto variables = CLAP::variable::VariableCollection(); + variables.add_variable(CLAP::variable::Variable(u8"a", u8"", true)); + EXPECT_ANY_THROW(variables.add_variable(CLAP::variable::Variable(u8"a", u8"", true))); + } + } + + class CartonClap : public ::testing::Test { + protected: + CartonClap() : + // YYC MARK: + // The fucking C++ must order I initialize this class which do not have default ctor in there. + // So I forcely initialize it with dummy data and reset it in ctor body. + // The reason why I do not use static function to intialize this is that I also need specify some class members when creating this. + app(CLAP::summary::Summary(u8"", u8"", u8"", u8""), CLAP::option::OptionCollection(), CLAP::variable::VariableCollection()) { + // Setup basic infos + auto summary = CLAP::summary::Summary(u8"TestClap", u8"yyc12345", u8"1.0.0", u8"This is the test of clap."); + + // Add options + auto options = CLAP::option::OptionCollection(); + int_option = options.add_option(CLAP::option::Option(u8"i", u8"int", u8"", u8"integral argument")); + float_option = options.add_option(CLAP::option::Option(u8"f", std::nullopt, u8"", u8"")); + string_option = options.add_option(CLAP::option::Option(std::nullopt, u8"string", u8"", u8"")); + clamped_float_option = options.add_option(CLAP::option::Option(std::nullopt, u8"clamped-float", u8"", u8"")); + novalue_option = options.add_option(CLAP::option::Option(u8"b", std::nullopt, std::nullopt, u8"")); + + // Add variables + auto variables = CLAP::variable::VariableCollection(); + int_variable = variables.add_variable(CLAP::variable::Variable(u8"int", u8"", true)); + clamped_int_variable = variables.add_variable(CLAP::variable::Variable(u8"clamped-int", u8"", true)); + novalue_variable = variables.add_variable(CLAP::variable::Variable(u8"b", u8"", false)); + + // Create final application + app = CLAP::application::Application(std::move(summary), std::move(options), std::move(variables)); + } + ~CartonClap() override = default; + + // Options + Token int_option; + using IntOptionValidator = validator::IntegralValidator; + Token float_option; + using FloatOptionValidator = validator::FloatingPointValidator; + Token string_option; + using StringOptionValidator = validator::StringValidator; + Token clamped_float_option; + using ClampedFloatOptionValidator = validator::FloatingPointValidator; + Token novalue_option; + // Variables + Token int_variable; + using IntVariableValidator = validator::IntegralValidator; + Token clamped_int_variable; + using ClampedFloatVariableValidator = validator::FloatingPointValidator; + Token novalue_variable; + + // Application + CLAP::application::Application app; + }; + + TEST_F(CartonClap, Manual) { + std::stringstream ss; + auto manual = CLAP::manual::Manual(app); + + // TODO: It may be more precious check for this. + // But I don't want to write it anymore. + + // Two manual output should not be empty. + ss.str(""); + manual.print_version(ss); + EXPECT_FALSE(ss.view().empty()); + + ss.str(""); + manual.print_help(ss); + EXPECT_FALSE(ss.view().empty()); + } + + TEST_F(CartonClap, ParserNormal) { + const std::vector args = {u8"exec", u8"-i", u8"114514"}; + auto result = CLAP::parser::Parser::from_user(app, args); + ASSERT_TRUE(result.has_value()); + auto parser = std::move(result.value()); + + // Check that int argument was captured with correct value + { + auto value = parser.get_value_option(int_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 114514); + } + // Check that other arguments were not captured + { + auto value = parser.get_value_option(float_option); + EXPECT_FALSE(value.has_value()); + } + { + auto value = parser.get_value_option(string_option); + EXPECT_FALSE(value.has_value()); + } + { + auto value = parser.get_value_option(clamped_float_option); + EXPECT_FALSE(value.has_value()); + } + { + auto value = parser.get_flag_option(novalue_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), false); + } + } + + TEST_F(CartonClap, ParserNothing) { + const std::vector args = {u8"exec"}; + auto result = CLAP::parser::Parser::from_user(app, args); + ASSERT_TRUE(result.has_value()); + auto parser = std::move(result.value()); + + // Check that all arguments were not captured + { + auto value = parser.get_value_option(int_option); + EXPECT_FALSE(value.has_value()); + } + { + auto value = parser.get_value_option(float_option); + EXPECT_FALSE(value.has_value()); + } + { + auto value = parser.get_value_option(string_option); + EXPECT_FALSE(value.has_value()); + } + { + auto value = parser.get_value_option(clamped_float_option); + EXPECT_FALSE(value.has_value()); + } + { + auto value = parser.get_flag_option(novalue_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), false); + } + } + + TEST_F(CartonClap, ParserInvalidName) { + const std::vector args = {u8"exec", u8"-?", u8"114514"}; + auto result = CLAP::parser::Parser::from_user(app, args); + ASSERT_FALSE(result.has_value()); + } + + TEST_F(CartonClap, ParserDuplicatedAssign) { + const std::vector args = {u8"exec", u8"-i", u8"114514", u8"--int", u8"114514"}; + auto result = CLAP::parser::Parser::from_user(app, args); + ASSERT_FALSE(result.has_value()); + } + + TEST_F(CartonClap, ParserUnexpectedValue) { + const std::vector args = {u8"exec", u8"-i", u8"114514", u8"1919810"}; + auto result = CLAP::parser::Parser::from_user(app, args); + ASSERT_FALSE(result.has_value()); + } + + TEST_F(CartonClap, ParserLostValue) { + const std::vector args = {u8"exec", u8"-i"}; + auto result = CLAP::parser::Parser::from_user(app, args); + ASSERT_FALSE(result.has_value()); + } + + TEST_F(CartonClap, ParserBadCast) { + const std::vector args = {u8"exec", u8"-i", u8"114514", u8"--clamped-float", u8"114.0"}; + auto result = CLAP::parser::Parser::from_user(app, args); + ASSERT_TRUE(result.has_value()); + auto parser = std::move(result.value()); + + // Check okey cast + { + auto value = parser.get_value_option(int_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 114514); + } + // Check bad cast + { + auto value = parser.get_value_option(clamped_float_option); + EXPECT_FALSE(value.has_value()); + } + } + + TEST_F(CartonClap, ParserFull) { + const std::vector args = { + u8"exec", // Program self. + u8"-i", // Int option + u8"114514", + u8"-f", // Float option + u8"2.0", + u8"--string", // String option + u8"fuck", + u8"--clamped-float", // Clamped flpat option + u8"0.5", + u8"-b", // No value option + }; + auto result = CLAP::parser::Parser::from_user(app, args); + ASSERT_TRUE(result.has_value()); + auto parser = std::move(result.value()); + + // Check that all options were not captured + { + auto value = parser.get_value_option(int_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 114514); + } + { + auto value = parser.get_value_option(float_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 2.0f); + } + { + auto value = parser.get_value_option(string_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), u8"fuck"); + } + { + auto value = parser.get_value_option(clamped_float_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), 0.5f); + } + { + auto value = parser.get_flag_option(novalue_option); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value.value(), true); + } + } + +} // namespace yycctest::carton::clap \ No newline at end of file