// Exercises the *registration-time* safety guards: things a programmer // could get wrong while wiring up commands, which should fail loudly and // clearly at startup rather than misbehaving at run time -- plus a few // "this should now succeed" checks for behavior that's supposed to work. #include #include #include #include #include using cliforge::Command; using cliforge::Engine; using cliforge::RegistrationError; enum class Color { Red, Green }; void noop(const std::string&) {} void noop2(const std::string&, bool) {} void noopOptStrStr(const std::optional&, const std::string&) {} void noopBoolOptVec(bool, const std::optional&, const std::vector&) {} void noopWrongType(int32_t) {} void noopColor(Color) {} #define EXPECT_THROW(label, expr) \ do { \ try { \ expr; \ std::printf("[FAIL] %-45s did not throw\n", label); \ } catch (const RegistrationError& e) { \ std::printf("[ OK ] %-45s -> %s\n", label, e.what()); \ } \ } while (0) #define EXPECT_OK(label, expr) \ do { \ try { \ expr; \ std::printf("[ OK ] %-45s (correctly did not throw)\n", label); \ } catch (const RegistrationError& e) { \ std::printf("[FAIL] %-45s threw: %s\n", label, e.what()); \ } \ } while (0) int main() { Engine cli; EXPECT_THROW("keyword after flag", ({ cli.command() .keyword("a") .flag("x", '\0', "") .keyword("b"); // keywords must come before flags/options })); EXPECT_THROW("keyword after variadic parameter", ({ cli.command() .keyword("a") .parameter("p", "", /*variadic=*/true) .keyword("b"); // variadic must be last in the structured part })); EXPECT_THROW("loose parameter after variadic loose parameter", ({ cli.command() .keyword("a") .flag("f", '\0', "") .parameter("p1", "", /*variadic=*/true) .parameter("p2", ""); // variadic must be the last parameter })); EXPECT_THROW("action() arity mismatch", ({ cli.command().keyword("a").parameter("p", "").action(&noop2); // noop2 takes 2 args, only 1 slot declared })); EXPECT_THROW("no keyword at all", ({ cli.command().parameter("p", "").action(&noop); // a command must have at least one keyword })); EXPECT_THROW("option bound to a required (bare) type", ({ cli.command().keyword("a").option("o", '\0', "").action(&noop); // options must be std::optional or std::vector -- never bare T })); EXPECT_THROW("declared type doesn't match action() type", ({ cli.command() .keyword("a") .parameter("p", "") .action(&noopWrongType); // declared string, action expects int32_t })); EXPECT_THROW("enum parameter without choices()", ({ cli.command() .keyword("a") .parameter("c", "") .action(&noopColor); // never called .choices(...) })); EXPECT_THROW("duplicate long flag name", ({ cli.command() .keyword("a") .flag("force", 'f', "") .flag("force", 'g', ""); })); EXPECT_THROW("duplicate short flag name", ({ cli.command() .keyword("a") .flag("force", 'f', "") .flag("full", 'f', ""); })); EXPECT_THROW("reserved --help name", ({ cli.command().keyword("a").flag("help", '\0', ""); })); EXPECT_THROW("reserved -h short name", ({ cli.command().keyword("a").flag("x", 'h', ""); })); EXPECT_THROW("invalid keyword charset", ({ cli.command().keyword("bad keyword!"); })); EXPECT_THROW("keyword starting with dash", ({ cli.command().keyword("-bad"); })); EXPECT_OK("parameter() after option() (loose parameter)", ({ cli.command() .keyword("a") .option("o", '\0', "") .parameter("p", "") .action(&noopOptStrStr); })); EXPECT_OK("flag/option/loose-parameter fully interleaved", ({ cli.command() .keyword("b") .flag("f", '\0', "") .option("o", '\0', "") .parameter("p", "", /*variadic=*/true) .action(&noopBoolOptVec); })); EXPECT_OK("action() taking arguments by const reference", ({ cli.command().keyword("c").parameter("p", "").action(&noop); })); std::printf("\nAll registration-guard checks completed.\n"); return 0; }