From cfcd2253ed34f9d53264022ebcbba0cc12f4e66a Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Sun, 5 Sep 2021 12:02:41 +0200 Subject: [PATCH] Added support for parameter incompatibility constraints --- Hazelnupp/CmdArgsInterface.cpp | 54 +++++++++++- Hazelnupp/CmdArgsInterface.h | 4 +- Hazelnupp/HazelnuppException.h | 17 ++++ Hazelnupp/ParamConstraint.h | 31 ++++++- Hazelnupp/Version.h | 2 + StaticTestProject/main.cpp | 6 +- Test_Hazelnupp/Constraints.cpp | 152 +++++++++++++++++++++++++++++++++ 7 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 Hazelnupp/Version.h diff --git a/Hazelnupp/CmdArgsInterface.cpp b/Hazelnupp/CmdArgsInterface.cpp index 39d998a..7340613 100644 --- a/Hazelnupp/CmdArgsInterface.cpp +++ b/Hazelnupp/CmdArgsInterface.cpp @@ -45,6 +45,7 @@ void CmdArgsInterface::Parse(const int argc, const char* const* argv) executableName = std::string(rawArgs[0]); + // Read and parse all parameters std::size_t i = 1; while (i < rawArgs.size()) { @@ -65,13 +66,13 @@ void CmdArgsInterface::Parse(const int argc, const char* const* argv) if ((!catchHelp) || (!HasParam("--help"))) ApplyConstraints(); } - catch (const HazelnuppConstraintTypeMissmatch& exc) + catch (const HazelnuppConstraintIncompatibleParameters& exc) { if (crashOnFail) { std::cout << GenerateDocumentation() << std::endl << std::endl; std::cerr << "Parameter error: " << exc.What() << std::endl; - quick_exit(-1009); + quick_exit(-1000); } else throw exc; // yeet @@ -82,7 +83,40 @@ void CmdArgsInterface::Parse(const int argc, const char* const* argv) { std::cout << GenerateDocumentation() << std::endl << std::endl; std::cerr << "Parameter error: " << exc.What() << std::endl; - quick_exit(-1010); + quick_exit(-1001); + } + else + throw exc; // yeet + } + catch (const HazelnuppConstraintTypeMissmatch& exc) + { + if (crashOnFail) + { + std::cout << GenerateDocumentation() << std::endl << std::endl; + std::cerr << "Parameter error: " << exc.What() << std::endl; + quick_exit(-1002); + } + else + throw exc; // yeet + } + catch (const HazelnuppConstraintException& exc) + { + if (crashOnFail) + { + std::cout << GenerateDocumentation() << std::endl << std::endl; + std::cerr << "Parameter error: " << exc.What() << std::endl; + quick_exit(-1003); + } + else + throw exc; // yeet + } + catch (const HazelnuppException& exc) + { + if (crashOnFail) + { + std::cout << GenerateDocumentation() << std::endl << std::endl; + std::cerr << "Parameter error: " << exc.What() << std::endl; + quick_exit(-1004); } else throw exc; // yeet @@ -513,7 +547,6 @@ void CmdArgsInterface::ApplyConstraints() if (pc.second.defaultValue.size() > 0) { // Then create it now, by its default value - Value* tmp = ParseValue(pc.second.defaultValue, &pc.second); parameters.insert(std::pair( pc.second.key, @@ -535,6 +568,19 @@ void CmdArgsInterface::ApplyConstraints() ); } } + // The parameter in question IS supplied + else + { + // Enforce parameter incompatibility + + // Is ANY parameter present listed as incompatible with our current one? + for (const std::string& incompatibility : pc.second.incompatibleParameters) + for (const std::pair& otherParam : parameters) + { + if (otherParam.first == incompatibility) + throw HazelnuppConstraintIncompatibleParameters(pc.second.key, incompatibility); + } + } return; } diff --git a/Hazelnupp/CmdArgsInterface.h b/Hazelnupp/CmdArgsInterface.h index ccd4d01..37964cc 100644 --- a/Hazelnupp/CmdArgsInterface.h +++ b/Hazelnupp/CmdArgsInterface.h @@ -4,6 +4,8 @@ #include #include +#include "Version.h" + namespace Hazelnp { /** The main class to interface with @@ -48,7 +50,7 @@ namespace Hazelnp //! Will register a constraint for a parameter. //! IMPORTANT: Any parameter can only have ONE constraint. Applying a new one will overwrite the old one! - //! Construct the ParamConstraint struct yourself to combine Require and TypeSafety! You can also use the ParamConstraint constructor! + //! Construct the ParamConstraint struct yourself to combine Require, TypeSafety and Incompatibilities! You can also use the ParamConstraint constructor! void RegisterConstraint(const std::string& key, const ParamConstraint& constraint); //! Will return the constraint information for a specific parameter diff --git a/Hazelnupp/HazelnuppException.h b/Hazelnupp/HazelnuppException.h index 303391e..7014da6 100644 --- a/Hazelnupp/HazelnuppException.h +++ b/Hazelnupp/HazelnuppException.h @@ -95,4 +95,21 @@ namespace Hazelnp return; }; }; + + /** Gets thrown when a parameter constrained to be incompatible with other parameters gets supplied alongside at least one of those incompatible ones + */ + class HazelnuppConstraintIncompatibleParameters : public HazelnuppConstraintException + { + public: + HazelnuppConstraintIncompatibleParameters() : HazelnuppConstraintException() {}; + HazelnuppConstraintIncompatibleParameters(const std::string& key1, const std::string& key2) + { + // Generate descriptive error message + std::stringstream ss; + ss << "Parameter \"" << key1 << "\" is NOT compatible with parameter \"" << key2 << "\"!"; + + message = ss.str(); + return; + }; + }; } diff --git a/Hazelnupp/ParamConstraint.h b/Hazelnupp/ParamConstraint.h index 2820336..d6b9af7 100644 --- a/Hazelnupp/ParamConstraint.h +++ b/Hazelnupp/ParamConstraint.h @@ -13,7 +13,7 @@ namespace Hazelnp //! Constructs a require constraint. //! Think of the default value like of a list ofparameters. Like {"--width", "800"} - static ParamConstraint Require(const std::vector& defaultValue = {}, bool required = true) + static ParamConstraint Require(const std::initializer_list& defaultValue = {}, bool required = true) { ParamConstraint pc; pc.defaultValue = defaultValue; @@ -32,13 +32,35 @@ namespace Hazelnp return pc; } + //! Constructs an incompatibility constraint. + //! This means, that the following parameters are NOT compatible with this one and will throw an error if passed together + static ParamConstraint Incompatibility(const std::initializer_list& incompatibleParameters) + { + ParamConstraint pc; + pc.incompatibleParameters = incompatibleParameters; + + return pc; + } + + //! Constructs an incompatibility constraint. + //! This means, that the following parameters are NOT compatible with this one and will throw an error if passed together. + //! Syntactical-sugar proxy method that will convert the lonely string to an initializer list for you :3 + static ParamConstraint Incompatibility(const std::string& incompatibleParameters) + { + ParamConstraint pc; + pc.incompatibleParameters = { incompatibleParameters }; + + return pc; + } + //! Whole constructor - ParamConstraint(bool constrainType, DATA_TYPE requiredType, const std::vector& defaultValue, bool required) + ParamConstraint(bool constrainType, DATA_TYPE requiredType, const std::initializer_list& defaultValue, bool required, const std::initializer_list& incompatibleParameters) : constrainType{ constrainType }, requiredType{ requiredType }, defaultValue{ defaultValue }, - required{ required } + required{ required }, + incompatibleParameters{incompatibleParameters} { return; } @@ -59,6 +81,9 @@ namespace Hazelnp //! an error will be produced if this parameter is not supplied by the user. bool required = false; + //! Parameters that are incompatible with this parameter + std::vector incompatibleParameters; + private: //! The parameter this constraint is for. //! This value is automatically set by Hazelnupp. diff --git a/Hazelnupp/Version.h b/Hazelnupp/Version.h new file mode 100644 index 0000000..ca90f4d --- /dev/null +++ b/Hazelnupp/Version.h @@ -0,0 +1,2 @@ +#pragma once +#define HAZELNUPP_VERSION (1.1) diff --git a/StaticTestProject/main.cpp b/StaticTestProject/main.cpp index c769ddd..07ce55f 100644 --- a/StaticTestProject/main.cpp +++ b/StaticTestProject/main.cpp @@ -22,8 +22,10 @@ int main(int argc, char** argv) args.RegisterConstraint("--width", ParamConstraint::TypeSafety(DATA_TYPE::FLOAT)); args.RegisterConstraint("--depth", ParamConstraint::TypeSafety(DATA_TYPE::FLOAT)); - args.RegisterConstraint("--name", ParamConstraint(true, DATA_TYPE::LIST, { "peter", "hannes" }, true)); - args.RegisterConstraint("--fruit", ParamConstraint(true, DATA_TYPE::STRING, {}, true)); + args.RegisterConstraint("--name", ParamConstraint(true, DATA_TYPE::LIST, { "peter", "hannes" }, true, {})); + args.RegisterConstraint("--fruit", ParamConstraint(true, DATA_TYPE::STRING, {}, true, {})); + + args.RegisterConstraint("--make-food-delicious", ParamConstraint::Incompatibility("--make-food-disgusting")); args.Parse(argc, argv); diff --git a/Test_Hazelnupp/Constraints.cpp b/Test_Hazelnupp/Constraints.cpp index f55013e..cb13f07 100644 --- a/Test_Hazelnupp/Constraints.cpp +++ b/Test_Hazelnupp/Constraints.cpp @@ -524,5 +524,157 @@ namespace TestHazelnupp return; } + + // Tests that an HazelnuppConstraintIncompatibleParameters gets raised if a required parameter + // is incompatible with another parameter passed alongside + // This test will use the single-string-to-initializer-list proxy method + TEST_METHOD(Exception_Constraint_Incompatible_Parameters_ProxyMethod) + { + // Setup + ArgList args({ + "/my/fake/path/wahoo.out", + "--make-background-glow", + "--make-background-transparent", + }); + + Assert::ExpectException( + [args] + { + CmdArgsInterface cmdArgsI; + cmdArgsI.SetCrashOnFail(false); + + cmdArgsI.RegisterConstraint( + "--make-background-glow", + ParamConstraint::Incompatibility("--make-background-transparent") + ); + + cmdArgsI.Parse(C_Ify(args)); + } + ); + + return; + } + + // Tests that an HazelnuppConstraintIncompatibleParameters gets raised if a required parameter + // is incompatible with another parameter passed alongside + TEST_METHOD(Exception_Constraint_Incompatible_Parameters) + { + // Setup + ArgList args({ + "/my/fake/path/wahoo.out", + "--make-background-glow", + "--make-background-transparent", + }); + + Assert::ExpectException( + [args] + { + CmdArgsInterface cmdArgsI; + cmdArgsI.SetCrashOnFail(false); + + cmdArgsI.RegisterConstraint( + "--make-background-glow", + ParamConstraint::Incompatibility({"--make-background-transparent"}) + ); + + cmdArgsI.Parse(C_Ify(args)); + } + ); + + return; + } + + // Tests that an HazelnuppConstraintIncompatibleParameters gets raised if a required parameter + // is incompatible with another parameter passed alongside + // This test will register multiple incompatibilities + TEST_METHOD(Exception_Constraint_Incompatible_Parameters_Multiple_Incompatibilities) + { + // Setup + ArgList args({ + "/my/fake/path/wahoo.out", + "--make-background-blue", + "--make-background-transparent", + "--make-background-glow", + "--make-background-green", + }); + + Assert::ExpectException( + [args] + { + CmdArgsInterface cmdArgsI; + cmdArgsI.SetCrashOnFail(false); + + cmdArgsI.RegisterConstraint( + "--make-background-glow", + ParamConstraint::Incompatibility({ + "--make-background-transparent", + "--make-background-green", + "--make-background-blue", + }) + ); + + cmdArgsI.Parse(C_Ify(args)); + } + ); + + return; + } + + // Tests that an HazelnuppConstraintIncompatibleParameters is NOT raised, if incompatible parameters + // are NOT supplied + TEST_METHOD(Constraint_Incompatible_Parameters_Are_Not_Incompatible) + { + // Setup + ArgList args({ + "/my/fake/path/wahoo.out", + "--make-background-glow", + "--set-food-delicious", + }); + + { + CmdArgsInterface cmdArgsI; + cmdArgsI.SetCrashOnFail(false); + + cmdArgsI.RegisterConstraint( + "--make-background-glow", + ParamConstraint::Incompatibility({ "--make-background-transparent" }) + ); + + cmdArgsI.RegisterConstraint( + "--set-food-delicious", + ParamConstraint::Incompatibility({ "--set-food-disgusting" }) + ); + + cmdArgsI.Parse(C_Ify(args)); + } + + return; + } + + // Tests that an HazelnuppConstraintIncompatibleParameters is NOT raised, if the incompatible + // paremter IS supplied, but not the one attached to the constraint + TEST_METHOD(Constraint_Incompatible_Parameters_Are_Not_Incompatible_Constrained_Parameter_Not_Passed) + { + // Setup + ArgList args({ + "/my/fake/path/wahoo.out", + "--make-background-transparent", + "--set-food-delicious", + }); + + { + CmdArgsInterface cmdArgsI; + cmdArgsI.SetCrashOnFail(false); + + cmdArgsI.RegisterConstraint( + "--make-background-glow", + ParamConstraint::Incompatibility({ "--make-background-transparent" }) + ); + + cmdArgsI.Parse(C_Ify(args)); + } + + return; + } }; }