diff --git a/source/CLIForge/include/CLIForge/Command.hpp b/source/CLIForge/include/CLIForge/Command.hpp index fc4fdce..1532bcd 100644 --- a/source/CLIForge/include/CLIForge/Command.hpp +++ b/source/CLIForge/include/CLIForge/Command.hpp @@ -20,8 +20,9 @@ // const reference all work). Mismatches are RegistrationErrors caught at // startup, not silent surprises at run time. -#include #include + +#include #include #include #include @@ -43,37 +44,35 @@ namespace cliforge { // --- small type-trait helpers used only for action() binding ---------- - template - struct is_optional : std::false_type {}; - - template - struct is_optional> : std::true_type { - using inner = T; + template struct IsOptional : std::false_type + { }; - template - inline constexpr bool is_optional_v = is_optional::value; - - template - struct is_vector : std::false_type {}; - - template - struct is_vector> : std::true_type { - using inner = T; + template struct IsOptional> : std::true_type + { + using Inner = T; }; - template - inline constexpr bool is_vector_v = is_vector::value; + template inline constexpr bool IsOptionalV = IsOptional::value; + + template struct IsVector : std::false_type + { + }; + + template struct IsVector> : std::true_type + { + using Inner = T; + }; + + template inline constexpr bool IsVectorV = IsVector::value; template - inline constexpr bool is_builtin_scalar_v = - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || - std::is_same_v; + inline constexpr bool IsBuiltinScalarV = + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v || std::is_same_v || std::is_same_v; // A token is where the unstructured (flags/options) part begins if it // starts with a dash and isn't shaped like a negative number -- that way @@ -87,10 +86,9 @@ namespace cliforge // RegistrationError messages. Only built-in scalars and enums are ever // legal here (anything else is already rejected by parameter()'s own // static_assert), so this never needs to handle the general case. - template - std::string friendlyTypeName() + template std::string friendlyTypeName() { - if constexpr (is_builtin_scalar_v) + if constexpr (IsBuiltinScalarV) { return TypeOf::get().displayName; } @@ -110,8 +108,8 @@ namespace cliforge struct StructuredMatch { bool success = false; - std::size_t consumed = 0; // tokens belonging to the structured part - int keywordScore = 0; // number of literal keywords matched + std::size_t consumed = 0; // tokens belonging to the structured part + int keywordScore = 0; // number of literal keywords matched std::vector paramValues; // Set when every literal keyword matched but a parameter's value // either failed to parse or was never given -- lets the Engine @@ -126,7 +124,7 @@ namespace cliforge // Keywords carry no description of their own -- .describe() on the // command is what shows up in help; a keyword is purely a literal // token to match. - Command& keyword(std::string literal); + Command& keyword(const std::string& literal); // T is explicit and required: every parameter is typed at the point // it's declared, not inferred later from action(). Called before any @@ -150,55 +148,55 @@ namespace cliforge s.cppType = std::type_index(typeid(T)); if constexpr (std::is_enum_v) { - s.awaitingChoices = true; // choices(...) must follow immediately + s.awaitingChoices = true; // choices(...) must follow immediately } else { - static_assert(detail::is_builtin_scalar_v, + static_assert(detail::IsBuiltinScalarV, "cliforge: unsupported parameter type -- use a fixed-width int, " "float/double, std::string, char, bool, or an enum (with " "choices() called immediately after)"); s.type = TypeOf::get(); } - if (!unstructuredStarted_) + if (!m_unstructuredStarted) { - if (structuredSealed_) + if (m_structuredSealed) { - throw RegistrationError( - "can't declare parameter('" + s.name + - "') after a variadic parameter -- a variadic parameter " - "must be the last thing in the structured part"); + throw RegistrationError("can't declare parameter('" + s.name + + "') after a variadic parameter -- a variadic parameter " + "must be the last thing in the structured part"); } - structured_.push_back(std::move(s)); - lastSlot_ = &structured_.back(); + m_structured.push_back(std::move(s)); + m_lastSlot = &m_structured.back(); if (variadic) - structuredSealed_ = true; + m_structuredSealed = true; } else { - if (looseSealed_) + if (m_looseSealed) { - throw RegistrationError( - "can't declare parameter('" + s.name + - "') after a variadic parameter -- a variadic parameter " - "must be the last parameter declared"); + throw RegistrationError("can't declare parameter('" + s.name + + "') after a variadic parameter -- a variadic parameter " + "must be the last parameter declared"); } - unstructured_.push_back(std::move(s)); - lastSlot_ = &unstructured_.back(); - if (variadic) looseSealed_ = true; + m_unstructured.push_back(std::move(s)); + m_lastSlot = &m_unstructured.back(); + if (variadic) + m_looseSealed = true; } return *this; } // shortName is 0 (the default) for "no short form". - Command& flag(std::string longName, char shortName, std::string description); + Command& flag(std::string longName, char shortName, const std::string& description); // T is explicit and required, same as parameter(). Options are // always optional in the calling convention (see action()) -- there // is no way to declare a "required" option, by design. template - Command& option(std::string longName, char shortName, std::string description, bool variadic = false) + Command& option(std::string longName, char shortName, std::string description, + bool variadic = false) { longName = stripDashes(std::move(longName)); validateLongName(longName); @@ -217,20 +215,19 @@ namespace cliforge } else { - static_assert(detail::is_builtin_scalar_v, - "cliforge: unsupported option type -- use a fixed-width int, " - "float/double, std::string, char, bool, or an enum (with " - "choices() called immediately after)" - ); + static_assert(detail::IsBuiltinScalarV, + "cliforge: unsupported option type -- use a fixed-width int, " + "float/double, std::string, char, bool, or an enum (with " + "choices() called immediately after)"); s.type = TypeOf::get(); } - unstructured_.push_back(std::move(s)); - lastSlot_ = &unstructured_.back(); - unstructuredStarted_ = true; + m_unstructured.push_back(std::move(s)); + m_lastSlot = &m_unstructured.back(); + m_unstructuredStarted = true; return *this; } - Command& describe(std::string description); + Command& describe(const std::string& description); // Restricts the most-recently-declared parameter/option to a fixed // set of named values -- this is how custom enums plug in (T is @@ -238,25 +235,26 @@ namespace cliforge // and it doubles as a generic "choice" restriction for any type. T // must match the type declared at parameter()/option(). template - Command& choices(std::initializer_list> values, std::string typeName = "") { - if (lastSlot_ == nullptr || (lastSlot_->kind != SlotKind::Parameter && lastSlot_->kind != SlotKind::Option)) + Command& choices(std::initializer_list> values, + std::string typeName = "") + { + if (m_lastSlot == nullptr || + (m_lastSlot->kind != SlotKind::Parameter && m_lastSlot->kind != SlotKind::Option)) { - throw RegistrationError( - "choices<>() must directly follow the parameter() or " - "option() it restricts" - ); + throw RegistrationError("choices<>() must directly follow the parameter() or " + "option() it restricts"); } - if (!lastSlot_->cppType || *lastSlot_->cppType != std::type_index(typeid(T))) + if (!m_lastSlot->cppType || *m_lastSlot->cppType != std::type_index(typeid(T))) { throw RegistrationError("choices<>() type doesn't match the type declared for '" + - lastSlot_->name + "'" - ); + m_lastSlot->name + "'"); } std::vector> table(values); - lastSlot_->type = makeChoiceTypeInfo(typeName.empty() ? std::string("enum") : std::move(typeName), table); - lastSlot_->awaitingChoices = false; + m_lastSlot->type = makeChoiceTypeInfo( + typeName.empty() ? std::string("enum") : std::move(typeName), table); + m_lastSlot->awaitingChoices = false; return *this; } @@ -267,56 +265,55 @@ namespace cliforge // matched, in order, to [parameters in declaration order] then // [flags/options/loose-parameters in declaration order]. Arguments // may be taken by value, by reference, or by const reference. - template - Command& action(Func f) + template Command& action(Func f) { - using Traits = detail::function_traits>; - if (structured_.empty() || std::none_of(structured_.begin(), structured_.end(), [](const Slot& s) + using Traits = detail::FunctionTraits>; + if (m_structured.empty() || + std::none_of(m_structured.begin(), m_structured.end(), + [](const Slot& s) { return s.kind == SlotKind::Keyword; })) { - return s.kind == SlotKind::Keyword; - })) - { - throw RegistrationError( - "a command must declare at least one keyword() so it can " - "be identified on the command line"); + throw RegistrationError("a command must declare at least one keyword() so it can " + "be identified on the command line"); } - for (const Slot& s : structured_) checkNotAwaitingChoices(s); - for (const Slot& s : unstructured_) checkNotAwaitingChoices(s); + for (const Slot& s : m_structured) + checkNotAwaitingChoices(s); + for (const Slot& s : m_unstructured) + checkNotAwaitingChoices(s); std::vector ordered = orderedBindableSlots(); - if (ordered.size() != Traits::arity) + if (ordered.size() != Traits::Arity) { throw RegistrationError( - "action() function takes " + std::to_string(Traits::arity) + - " argument(s) but this command declared " + - std::to_string(ordered.size()) + + "action() function takes " + std::to_string(Traits::Arity) + + " argument(s) but this command declared " + std::to_string(ordered.size()) + " parameter/flag/option slot(s) -- these must match 1:1, " "in declaration order (parameters first, then flags/options)"); } - bindArgs(std::function{f}, ordered, std::make_index_sequence{}); - sealed_ = true; + bindArgs(std::function{f}, ordered, std::make_index_sequence{}); + m_sealed = true; return *this; } - bool sealed() const; - const std::string& description() const; + [[nodiscard]] bool sealed() const; + [[nodiscard]] const std::string& description() const; // --- matching / parsing entry points, used by Engine ------------- - StructuredMatch tryMatchStructured(const std::vector& tokens) const; - + [[nodiscard]] StructuredMatch tryMatchStructured( + const std::vector& tokens) const; // Parses the (order-agnostic) flags/options/loose-parameters tail. // Throws CliError (with a specific ErrorKind) describing exactly what // went wrong: an unrecognized flag/option, a missing option value, a // missing/extra positional argument, or a type mismatch. - std::vector parseUnstructured(const std::vector& tokens) const; + [[nodiscard]] std::vector parseUnstructured( + const std::vector& tokens) const; void invoke(std::vector allValues) const; - std::size_t structuredSlotCount() const; + [[nodiscard]] std::size_t structuredSlotCount() const; // Lenient prefix check used only for --help / `help ` : does // `tokens` look like the start of (or a complete instance of) this @@ -326,34 +323,36 @@ namespace cliforge // what makes it a "prefix"). Used to find *every* command consistent // with what's been typed so far, so an ambiguous prefix can show help // for all of them instead of guessing one. - bool couldMatchPrefix(const std::vector& tokens) const; + [[nodiscard]] bool couldMatchPrefix(const std::vector& tokens) const; // --- help / usage -------------------------------------------------- - std::string usageLine() const; + [[nodiscard]] std::string usageLine() const; - std::string helpText(const std::string& progName) const; + [[nodiscard]] std::string helpText(const std::string& progName) const; - std::vector flagOptionNames() const; + [[nodiscard]] std::vector flagOptionNames() const; // Fuzzy structural distance used for "did you mean...?" suggestions. - struct FuzzyScore { + struct FuzzyScore + { std::size_t distance = 0; - std::size_t keywordChars = 0; // denominator for a relative threshold + std::size_t keywordChars = 0; // denominator for a relative threshold }; - FuzzyScore fuzzyStructuralDistance(const std::vector& tokens) const; + [[nodiscard]] FuzzyScore fuzzyStructuralDistance( + const std::vector& tokens) const; private: - std::vector structured_; - std::vector unstructured_; - std::string description_; - std::function&)> invoke_; - Slot* lastSlot_ = nullptr; - bool unstructuredStarted_ = false; - bool structuredSealed_ = false; - bool looseSealed_ = false; - bool sealed_ = false; + std::vector m_structured; + std::vector m_unstructured; + std::string m_description; + std::function&)> m_invoke; + Slot* m_lastSlot = nullptr; + bool m_unstructuredStarted = false; + bool m_structuredSealed = false; + bool m_looseSealed = false; + bool m_sealed = false; static std::string stripDashes(std::string s); @@ -368,69 +367,94 @@ namespace cliforge std::vector orderedBindableSlots(); // Validates (never invents) that a slot's declared type matches T. - template - static void checkTypeMatches(const Slot& slot) { - if (!slot.cppType || *slot.cppType != std::type_index(typeid(T))) { - throw RegistrationError("'" + slot.name + "' was declared as " + slot.type.displayName + - " but action() expects " + detail::friendlyTypeName()); + template static void checkTypeMatches(const Slot& slot) + { + if (!slot.cppType || *slot.cppType != std::type_index(typeid(T))) + { + throw RegistrationError("'" + slot.name + "' was declared as " + + slot.type.displayName + " but action() expects " + + detail::friendlyTypeName()); } } // ArgT is the *raw* action() parameter type, e.g. `const std::string&` // or `std::vector` -- validated after stripping cv/ref. - template - static void configureSlot(Slot& slot) { + template static void configureSlot(Slot& slot) + { using Stripped = std::remove_cvref_t; - if (slot.kind == SlotKind::Flag) { - if constexpr (!std::is_same_v) { - throw RegistrationError("flag '--" + slot.name + "' must bind to bool in action()"); + if (slot.kind == SlotKind::Flag) + { + if constexpr (!std::is_same_v) + { + throw RegistrationError("flag '--" + slot.name + + "' must bind to bool in action()"); } return; } - if constexpr (detail::is_optional_v) { - using Inner = typename detail::is_optional::inner; - if (slot.kind == SlotKind::Parameter) { + if constexpr (detail::IsOptionalV) + { + using Inner = typename detail::IsOptional::Inner; + if (slot.kind == SlotKind::Parameter) + { throw RegistrationError("parameter '<" + slot.name + - ">' can't bind to std::optional<> -- positional " - "parameters are always required"); + ">' can't bind to std::optional<> -- positional " + "parameters are always required"); } - if (slot.variadic) { - throw RegistrationError("option '--" + slot.name + - "' is variadic; bind it to std::vector<>, not std::optional<>"); + if (slot.variadic) + { + throw RegistrationError( + "option '--" + slot.name + + "' is variadic; bind it to std::vector<>, not std::optional<>"); } checkTypeMatches(slot); - } else if constexpr (detail::is_vector_v) { - using Inner = typename detail::is_vector::inner; - if (!slot.variadic) { - throw RegistrationError("'" + slot.name + - "' binds to std::vector<> but wasn't declared variadic=true"); + } + else if constexpr (detail::IsVectorV) + { + using Inner = typename detail::IsVector::Inner; + if (!slot.variadic) + { + throw RegistrationError( + "'" + slot.name + + "' binds to std::vector<> but wasn't declared variadic=true"); } checkTypeMatches(slot); - } else { - if (slot.variadic) { - throw RegistrationError("'" + slot.name + - "' was declared variadic=true; bind it to std::vector<>"); + } + else + { + if (slot.variadic) + { + throw RegistrationError( + "'" + slot.name + "' was declared variadic=true; bind it to std::vector<>"); } - if (slot.kind == SlotKind::Option) { - throw RegistrationError("option '--" + slot.name + - "' must bind to std::optional (or std::vector if " - "variadic) -- options are always optional, never required"); + if (slot.kind == SlotKind::Option) + { + throw RegistrationError( + "option '--" + slot.name + + "' must bind to std::optional (or std::vector if " + "variadic) -- options are always optional, never required"); } checkTypeMatches(slot); } } - template - static ArgT extractArg(const Value& v) { - if constexpr (std::is_same_v) { + template static ArgT extractArg(const Value& v) + { + if constexpr (std::is_same_v) + { return v.as(); - } else if constexpr (detail::is_optional_v) { - using Inner = typename detail::is_optional::inner; + } + else if constexpr (detail::IsOptionalV) + { + using Inner = typename detail::IsOptional::Inner; return v.hasValue() ? std::optional(v.as()) : std::nullopt; - } else if constexpr (detail::is_vector_v) { - using Inner = typename detail::is_vector::inner; + } + else if constexpr (detail::IsVectorV) + { + using Inner = typename detail::IsVector::Inner; return v.asVector(); - } else { + } + else + { return v.as(); } } @@ -442,20 +466,22 @@ namespace cliforge // still bind correctly. template void bindArgs(std::function f, std::vector& ordered, - std::index_sequence) { + std::index_sequence) + { (configureSlot(*ordered[I]), ...); - invoke_ = [f = std::move(f)](std::vector& values) { + m_invoke = [f = std::move(f)](std::vector& values) + { std::tuple...> extracted{ extractArg>(values[I])...}; std::apply(f, extracted); }; } - int findByLong(std::string_view name) const; + [[nodiscard]] int findByLong(std::string_view name) const; - int findByShort(char c) const; + [[nodiscard]] int findByShort(char c) const; - std::string suggestFlagOption(const std::string& given) const; + [[nodiscard]] std::string suggestFlagOption(const std::string& given) const; [[noreturn]] void throwUnknown(std::string_view tok) const; diff --git a/source/CLIForge/include/CLIForge/Engine.hpp b/source/CLIForge/include/CLIForge/Engine.hpp index 40af18b..e062025 100644 --- a/source/CLIForge/include/CLIForge/Engine.hpp +++ b/source/CLIForge/include/CLIForge/Engine.hpp @@ -20,11 +20,12 @@ namespace cliforge { - class Engine { + class Engine + { public: explicit Engine(std::string programName = ""); - Engine& describe(std::string description); + Engine& describe(const std::string& description); // Registers a new, empty command and returns a reference to it so it // can be built up with the fluent .keyword()/.parameter()/... API. @@ -34,9 +35,9 @@ namespace cliforge int run(int argc, char** argv); private: - std::string programName_; - std::string description_; - std::vector> commands_; + std::string m_programName; + std::string m_description; + std::vector> m_commands; static std::string basename(const std::string& path); static std::string join(const std::vector& tokens); @@ -45,7 +46,7 @@ namespace cliforge // shows its help -- one command's help if there's exactly one match, // all of them (clearly separated) if the prefix is genuinely // ambiguous, or the global command list plus a hint if there's none. - int showHelp(const std::vector& queryTokens) const; + [[nodiscard]] int showHelp(const std::vector& queryTokens) const; void printGlobalHelp(std::ostream& os) const; void suggestCommands(const std::vector& tokens, std::ostream& os) const; diff --git a/source/CLIForge/include/CLIForge/FunctionTraits.hpp b/source/CLIForge/include/CLIForge/FunctionTraits.hpp index ca03740..07bae73 100644 --- a/source/CLIForge/include/CLIForge/FunctionTraits.hpp +++ b/source/CLIForge/include/CLIForge/FunctionTraits.hpp @@ -8,54 +8,54 @@ // having to spell the types out twice. #include + #include #include namespace cliforge::detail { - template struct function_traits; + template struct FunctionTraits; // Plain free function: void foo(std::string, int) - template struct function_traits + template struct FunctionTraits { - using return_type = Ret; - static constexpr std::size_t arity = sizeof...(Args); - template using arg = std::tuple_element_t>; - using args_tuple = std::tuple; + using ReturnType = Ret; + static constexpr std::size_t Arity = sizeof...(Args); + template using Arg = std::tuple_element_t>; + using ArgsTuple = std::tuple; }; // std::function - template struct function_traits> + template struct FunctionTraits> { - using return_type = Ret; - static constexpr std::size_t arity = sizeof...(Args); - template using arg = std::tuple_element_t>; - using args_tuple = std::tuple; + using ReturnType = Ret; + static constexpr std::size_t Arity = sizeof...(Args); + template using Arg = std::tuple_element_t>; + using ArgsTuple = std::tuple; }; // Pointer-to-member-function of operator(), const and non-const, used to // peel apart lambdas (which are just anonymous functors under the hood). template - struct function_traits + struct FunctionTraits { - using return_type = Ret; - static constexpr std::size_t arity = sizeof...(Args); - template using arg = std::tuple_element_t>; - using args_tuple = std::tuple; + using ReturnType = Ret; + static constexpr std::size_t Arity = sizeof...(Args); + template using Arg = std::tuple_element_t>; + using ArgsTuple = std::tuple; }; - template - struct function_traits + template struct FunctionTraits { - using return_type = Ret; - static constexpr std::size_t arity = sizeof...(Args); - template using arg = std::tuple_element_t>; - using args_tuple = std::tuple; + using ReturnType = Ret; + static constexpr std::size_t Arity = sizeof...(Args); + template using Arg = std::tuple_element_t>; + using ArgsTuple = std::tuple; }; // Fallback: any other callable (lambda, functor) -- peel off operator(). - template struct function_traits : function_traits + template struct FunctionTraits : FunctionTraits { }; } diff --git a/source/CLIForge/include/CLIForge/Levenshtein.hpp b/source/CLIForge/include/CLIForge/Levenshtein.hpp index cfea1ea..1097363 100644 --- a/source/CLIForge/include/CLIForge/Levenshtein.hpp +++ b/source/CLIForge/include/CLIForge/Levenshtein.hpp @@ -16,6 +16,6 @@ namespace cliforge::detail // ordered by distance. Anything farther than a generous relative // threshold is dropped so we never suggest something wildly unrelated. std::vector closestMatches(const std::string& query, - const std::vector& pool, - std::size_t limit = 3); + const std::vector& pool, + std::size_t limit = 3); } diff --git a/source/CLIForge/include/CLIForge/Slot.hpp b/source/CLIForge/include/CLIForge/Slot.hpp index 1738bb2..7eb3f8e 100644 --- a/source/CLIForge/include/CLIForge/Slot.hpp +++ b/source/CLIForge/include/CLIForge/Slot.hpp @@ -58,7 +58,7 @@ namespace cliforge // this is false for every slot before binding. bool awaitingChoices = false; - std::string longFlag() const; - std::string shortFlag() const; + [[nodiscard]] std::string longFlag() const; + [[nodiscard]] std::string shortFlag() const; }; } diff --git a/source/CLIForge/include/CLIForge/Value.hpp b/source/CLIForge/include/CLIForge/Value.hpp index 4be2824..d0a5e8c 100644 --- a/source/CLIForge/include/CLIForge/Value.hpp +++ b/source/CLIForge/include/CLIForge/Value.hpp @@ -9,9 +9,10 @@ // rarer enum/choice-restricted case pays for a one-time, shared, heap // allocation to hold its name table. +#include + #include #include -#include #include #include #include @@ -47,10 +48,10 @@ namespace cliforge { public: explicit CliError(const std::string& msg, ErrorKind kind = ErrorKind::Generic); - ErrorKind kind() const noexcept; + [[nodiscard]] ErrorKind kind() const noexcept; private: - ErrorKind kind_; + ErrorKind m_kind; }; // A token couldn't be converted to the type a slot expects -- always @@ -58,7 +59,7 @@ namespace cliforge class ParseError : public CliError { public: - explicit ParseError(const std::string& msg); + explicit ParseError(const std::string& msg); }; // Something is wrong with how a command was *built* (mismatched arity @@ -69,7 +70,7 @@ namespace cliforge // them at compile time via static_assert), not at end-user parse time. struct RegistrationError : CliError { - explicit RegistrationError(const std::string& msg); + explicit RegistrationError(const std::string& msg); }; // --------------------------------------------------------------------- @@ -121,7 +122,7 @@ namespace cliforge { std::vector> entries; - Scalar parse(std::string_view token, std::string_view label) const; + [[nodiscard]] Scalar parse(std::string_view token, std::string_view label) const; }; struct TypeInfo @@ -134,9 +135,8 @@ namespace cliforge // Populated only for choices()-restricted slots. std::shared_ptr choiceTable; - bool valid() const; - Scalar parse(std::string_view token, std::string_view label) const; - + [[nodiscard]] bool valid() const; + [[nodiscard]] Scalar parse(std::string_view token, std::string_view label) const; }; namespace detail @@ -326,12 +326,12 @@ namespace cliforge static Value ofScalar(Scalar s); static Value ofVector(std::vector v); - bool hasValue() const; - bool isVector() const; + [[nodiscard]] bool hasValue() const; + [[nodiscard]] bool isVector() const; - template T as() const + template [[nodiscard]] T as() const { - const Scalar& s = std::get(data_); + const Scalar& s = std::get(m_data); if constexpr (std::is_enum_v) { return static_cast(std::get(s).value); @@ -344,7 +344,7 @@ namespace cliforge template std::vector asVector() const { - const auto& vec = std::get>(data_); + const auto& vec = std::get>(m_data); std::vector out; out.reserve(vec.size()); for (const Scalar& s : vec) @@ -362,6 +362,6 @@ namespace cliforge } private: - std::variant> data_; + std::variant> m_data; }; } diff --git a/source/CLIForge/src/Command.cpp b/source/CLIForge/src/Command.cpp index aa617c3..38091a0 100644 --- a/source/CLIForge/src/Command.cpp +++ b/source/CLIForge/src/Command.cpp @@ -33,15 +33,15 @@ namespace cliforge } } - Command& Command::keyword(std::string literal) + Command& Command::keyword(const std::string& literal) { - if (unstructuredStarted_) + if (m_unstructuredStarted) { throw RegistrationError("can't declare keyword('" + literal + "') after a flag()/option() was added -- keywords must " "come entirely before the unstructured part"); } - if (structuredSealed_) + if (m_structuredSealed) { throw RegistrationError("can't declare keyword('" + literal + "') after a variadic parameter -- a variadic parameter " @@ -56,13 +56,13 @@ namespace cliforge Slot s; s.kind = SlotKind::Keyword; s.name = std::move(literal); - structured_.push_back(std::move(s)); - lastSlot_ = &structured_.back(); + m_structured.push_back(std::move(s)); + m_lastSlot = &m_structured.back(); return *this; } // shortName is 0 (the default) for "no short form". - Command& Command::flag(std::string longName, char shortName, std::string description) + Command& Command::flag(std::string longName, char shortName, const std::string& description) { longName = stripDashes(std::move(longName)); validateLongName(longName); @@ -73,26 +73,26 @@ namespace cliforge s.name = std::move(longName); s.shortName = shortName; s.description = std::move(description); - unstructured_.push_back(std::move(s)); - lastSlot_ = &unstructured_.back(); - unstructuredStarted_ = true; + m_unstructured.push_back(std::move(s)); + m_lastSlot = &m_unstructured.back(); + m_unstructuredStarted = true; return *this; } - Command& Command::describe(std::string description) + Command& Command::describe(const std::string& description) { - description_ = std::move(description); + m_description = std::move(description); return *this; } bool Command::sealed() const { - return sealed_; + return m_sealed; } const std::string& Command::description() const { - return description_; + return m_description; } // --- matching / parsing entry points, used by Engine ------------- @@ -103,10 +103,10 @@ namespace cliforge std::size_t i = 0; try { - for (std::size_t s = 0; s < structured_.size(); ++s) + for (std::size_t s = 0; s < m_structured.size(); ++s) { - const Slot& slot = structured_[s]; - bool isLast = (s + 1 == structured_.size()); + const Slot& slot = m_structured[s]; + bool isLast = (s + 1 == m_structured.size()); if (slot.kind == SlotKind::Keyword) { if (i >= tokens.size() || tokens[i] != slot.name) @@ -135,9 +135,9 @@ namespace cliforge // this is almost certainly the intended command // -- just missing a required argument. throw CliError("missing required argument '<" + slot.name + - ">': expected a value of type " + - slot.type.displayName, - ErrorKind::MissingArgument); + ">': expected a value of type " + + slot.type.displayName, + ErrorKind::MissingArgument); } m.paramValues.push_back( @@ -168,10 +168,9 @@ namespace cliforge // missing/extra positional argument, or a type mismatch. std::vector Command::parseUnstructured(const std::vector& tokens) const { - std::vector provided(unstructured_.size(), false); - std::vector> collected(unstructured_.size()); - std::vector - looseTokens; // views into `tokens`, which outlives this call + std::vector provided(m_unstructured.size(), false); + std::vector> collected(m_unstructured.size()); + std::vector looseTokens; // views into `tokens`, which outlives this call looseTokens.reserve(tokens.size()); std::size_t i = 0; @@ -192,8 +191,7 @@ namespace cliforge auto eq = rest.find('='); bool hasInline = (eq != std::string_view::npos); std::string_view longName = hasInline ? rest.substr(0, eq) : rest; - std::string_view inlineValue = - hasInline ? rest.substr(eq + 1) : std::string_view{}; + std::string_view inlineValue = hasInline ? rest.substr(eq + 1) : std::string_view{}; int idx = findByLong(longName); if (idx < 0) throwUnknown(tok); @@ -222,16 +220,16 @@ namespace cliforge if (idx < 0) { throw CliError(std::string("unknown flag '-") + c + - "' in combined group '" + std::string(tok) + - "'" + suggestFlagOption(std::string("-") + c), - ErrorKind::UnknownOption); + "' in combined group '" + std::string(tok) + "'" + + suggestFlagOption(std::string("-") + c), + ErrorKind::UnknownOption); } - if (unstructured_[static_cast(idx)].kind != SlotKind::Flag) + if (m_unstructured[static_cast(idx)].kind != SlotKind::Flag) { throw CliError(std::string("'-") + c + "' in combined group '" + - std::string(tok) + - "' is an option (needs a value), so it can't " - "be combined with other short flags"); + std::string(tok) + + "' is an option (needs a value), so it can't " + "be combined with other short flags"); } provided[static_cast(idx)] = true; } @@ -244,16 +242,16 @@ namespace cliforge // Parameter slots, positionally, in declaration order -- this is // independent of where those tokens fell relative to flags/options. std::vector looseParamIdx; - for (std::size_t idx = 0; idx < unstructured_.size(); ++idx) + for (std::size_t idx = 0; idx < m_unstructured.size(); ++idx) { - if (unstructured_[idx].kind == SlotKind::Parameter) + if (m_unstructured[idx].kind == SlotKind::Parameter) looseParamIdx.push_back(idx); } std::vector looseValues(looseParamIdx.size()); std::size_t li = 0; for (std::size_t k = 0; k < looseParamIdx.size(); ++k) { - const Slot& slot = unstructured_[looseParamIdx[k]]; + const Slot& slot = m_unstructured[looseParamIdx[k]]; bool isLastLoose = (k + 1 == looseParamIdx.size()); if (slot.variadic && isLastLoose) { @@ -271,8 +269,8 @@ namespace cliforge if (li >= looseTokens.size()) { throw CliError("missing required argument '<" + slot.name + - ">': expected a value of type " + slot.type.displayName, - ErrorKind::MissingArgument); + ">': expected a value of type " + slot.type.displayName, + ErrorKind::MissingArgument); } looseValues[k] = Value::ofScalar(slot.type.parse(looseTokens[li], slot.name)); ++li; @@ -288,14 +286,14 @@ namespace cliforge throw CliError(msg, ErrorKind::TooManyArguments); } - // Assemble the final values in unstructured_'s declaration order -- + // Assemble the final values in m_unstructured's declaration order -- // this is what gets paired with the bound function's arguments. std::vector out; - out.reserve(unstructured_.size()); + out.reserve(m_unstructured.size()); std::size_t looseCursor = 0; - for (std::size_t idx = 0; idx < unstructured_.size(); ++idx) + for (std::size_t idx = 0; idx < m_unstructured.size(); ++idx) { - const Slot& slot = unstructured_[idx]; + const Slot& slot = m_unstructured[idx]; if (slot.kind == SlotKind::Parameter) { out.push_back(std::move(looseValues[looseCursor++])); @@ -322,23 +320,23 @@ namespace cliforge void Command::invoke(std::vector allValues) const { - invoke_(allValues); + m_invoke(allValues); } std::size_t Command::structuredSlotCount() const { - return structured_.size(); + return m_structured.size(); } bool Command::couldMatchPrefix(const std::vector& tokens) const { std::size_t i = 0; - for (std::size_t s = 0; s < structured_.size(); ++s) + for (std::size_t s = 0; s < m_structured.size(); ++s) { if (i >= tokens.size()) break; // ran out of input -- a valid, incomplete prefix - const Slot& slot = structured_[s]; - bool isLast = (s + 1 == structured_.size()); + const Slot& slot = m_structured[s]; + bool isLast = (s + 1 == m_structured.size()); if (slot.kind == SlotKind::Keyword) { if (tokens[i] != slot.name) @@ -360,7 +358,7 @@ namespace cliforge // Whatever tokens remain must be plausible flags/options (assumed // fine without deep validation) or fit a declared loose parameter. std::vector looseParams; - for (const Slot& s : unstructured_) + for (const Slot& s : m_unstructured) { if (s.kind == SlotKind::Parameter) looseParams.push_back(&s); @@ -383,9 +381,9 @@ namespace cliforge std::string Command::usageLine() const { std::ostringstream os; - for (std::size_t s = 0; s < structured_.size(); ++s) + for (std::size_t s = 0; s < m_structured.size(); ++s) { - const Slot& slot = structured_[s]; + const Slot& slot = m_structured[s]; if (s) os << ' '; if (slot.kind == SlotKind::Keyword) @@ -398,11 +396,11 @@ namespace cliforge } } bool anyFlagOrOption = - std::any_of(unstructured_.begin(), unstructured_.end(), + std::any_of(m_unstructured.begin(), m_unstructured.end(), [](const Slot& s) { return s.kind != SlotKind::Parameter; }); if (anyFlagOrOption) os << " [OPTIONS]"; - for (const Slot& slot : unstructured_) + for (const Slot& slot : m_unstructured) { if (slot.kind != SlotKind::Parameter) continue; @@ -415,49 +413,45 @@ namespace cliforge { std::ostringstream os; os << "Usage: " << progName << " " << usageLine() << "\n"; - if (!description_.empty()) - os << "\n" << description_ << "\n"; + if (!m_description.empty()) + os << "\n" << m_description << "\n"; - bool anyParams = - std::any_of(structured_.begin(), structured_.end(), - [](const Slot& s) { return s.kind == SlotKind::Parameter; }) || - std::any_of(unstructured_.begin(), unstructured_.end(), - [](const Slot& s) { return s.kind == SlotKind::Parameter; }); + bool anyParams = std::any_of(m_structured.begin(), m_structured.end(), + [](const Slot& s) { return s.kind == SlotKind::Parameter; }) || + std::any_of(m_unstructured.begin(), m_unstructured.end(), + [](const Slot& s) { return s.kind == SlotKind::Parameter; }); if (anyParams) { os << "\nArguments:\n"; - for (const Slot& slot : structured_) + for (const Slot& slot : m_structured) { if (slot.kind != SlotKind::Parameter) continue; std::string label = "<" + slot.name + (slot.variadic ? "..." : "") + ">"; - os << " " << padRight(label, 22) << describeType(slot) << slot.description - << "\n"; + os << " " << padRight(label, 22) << describeType(slot) << slot.description << "\n"; } - for (const Slot& slot : unstructured_) + for (const Slot& slot : m_unstructured) { if (slot.kind != SlotKind::Parameter) continue; std::string label = "<" + slot.name + (slot.variadic ? "..." : "") + ">"; - os << " " << padRight(label, 22) << describeType(slot) << slot.description - << "\n"; + os << " " << padRight(label, 22) << describeType(slot) << slot.description << "\n"; } } bool anyFlagOrOption = - std::any_of(unstructured_.begin(), unstructured_.end(), + std::any_of(m_unstructured.begin(), m_unstructured.end(), [](const Slot& s) { return s.kind != SlotKind::Parameter; }); os << "\nOptions:\n"; if (anyFlagOrOption) { - for (const Slot& slot : unstructured_) + for (const Slot& slot : m_unstructured) { if (slot.kind == SlotKind::Parameter) continue; - std::string names = - slot.shortName == '\0' - ? (" --" + slot.name) - : (std::string("-") + slot.shortName + ", --" + slot.name); + std::string names = slot.shortName == '\0' + ? (" --" + slot.name) + : (std::string("-") + slot.shortName + ", --" + slot.name); if (slot.kind == SlotKind::Option) { names += " <" + slot.type.displayName + (slot.variadic ? "...>" : ">"); @@ -472,7 +466,7 @@ namespace cliforge std::vector Command::flagOptionNames() const { std::vector out; - for (const Slot& s : unstructured_) + for (const Slot& s : m_unstructured) { if (s.kind == SlotKind::Parameter) continue; @@ -483,14 +477,15 @@ namespace cliforge return out; } - Command::FuzzyScore Command::fuzzyStructuralDistance(const std::vector& tokens) const + Command::FuzzyScore Command::fuzzyStructuralDistance( + const std::vector& tokens) const { std::size_t i = 0; FuzzyScore score; - for (std::size_t s = 0; s < structured_.size(); ++s) + for (std::size_t s = 0; s < m_structured.size(); ++s) { - const Slot& slot = structured_[s]; - bool isLast = (s + 1 == structured_.size()); + const Slot& slot = m_structured[s]; + bool isLast = (s + 1 == m_structured.size()); if (slot.kind == SlotKind::Keyword) { score.keywordChars += slot.name.size(); @@ -555,7 +550,7 @@ namespace cliforge void Command::checkNameFree(const std::string& longName, char shortName) const { - for (const Slot& s : unstructured_) + for (const Slot& s : m_unstructured) { if (s.kind == SlotKind::Parameter) continue; // different namespace (bare, not "--name") @@ -565,8 +560,7 @@ namespace cliforge } if (shortName != '\0' && s.shortName == shortName) { - throw RegistrationError(std::string("duplicate short name '-") + shortName + - "'"); + throw RegistrationError(std::string("duplicate short name '-") + shortName + "'"); } } } @@ -584,21 +578,21 @@ namespace cliforge std::vector Command::orderedBindableSlots() { std::vector out; - for (Slot& s : structured_) + for (Slot& s : m_structured) { if (s.kind == SlotKind::Parameter) out.push_back(&s); } - for (Slot& s : unstructured_) + for (Slot& s : m_unstructured) out.push_back(&s); return out; } int Command::findByLong(std::string_view name) const { - for (std::size_t i = 0; i < unstructured_.size(); ++i) + for (std::size_t i = 0; i < m_unstructured.size(); ++i) { - if (unstructured_[i].kind != SlotKind::Parameter && unstructured_[i].name == name) + if (m_unstructured[i].kind != SlotKind::Parameter && m_unstructured[i].name == name) { return static_cast(i); } @@ -610,9 +604,9 @@ namespace cliforge { if (c == '\0') return -1; - for (std::size_t i = 0; i < unstructured_.size(); ++i) + for (std::size_t i = 0; i < m_unstructured.size(); ++i) { - if (unstructured_[i].kind != SlotKind::Parameter && unstructured_[i].shortName == c) + if (m_unstructured[i].kind != SlotKind::Parameter && m_unstructured[i].shortName == c) { return static_cast(i); } @@ -634,8 +628,8 @@ namespace cliforge [[noreturn]] void Command::throwUnknown(std::string_view tok) const { throw CliError("unknown flag/option '" + std::string(tok) + "' for this command" + - suggestFlagOption(std::string(tok)), - ErrorKind::UnknownOption); + suggestFlagOption(std::string(tok)), + ErrorKind::UnknownOption); } std::string Command::describeType(const Slot& slot) @@ -651,14 +645,13 @@ namespace cliforge } void Command::consumeSlot(const Command& self, int idxIn, bool hasInline, - std::string_view inlineValue, - const std::vector& tokens, std::size_t& i, - std::vector& provided, - std::vector>& collected, - std::string_view tokForError) + std::string_view inlineValue, const std::vector& tokens, + std::size_t& i, std::vector& provided, + std::vector>& collected, + std::string_view tokForError) { std::size_t idx = static_cast(idxIn); - const Slot& slot = self.unstructured_[idx]; + const Slot& slot = self.m_unstructured[idx]; if (slot.kind == SlotKind::Flag) { if (hasInline) @@ -692,8 +685,8 @@ namespace cliforge if (i == before) { throw CliError("option '" + std::string(tokForError) + - "' requires at least one value", - ErrorKind::MissingValue); + "' requires at least one value", + ErrorKind::MissingValue); } provided[idx] = true; } @@ -702,7 +695,7 @@ namespace cliforge if (i >= tokens.size() || detail::looksLikeOptionStart(tokens[i])) { throw CliError("option '" + std::string(tokForError) + "' requires a value", - ErrorKind::MissingValue); + ErrorKind::MissingValue); } collected[idx] = {slot.type.parse(tokens[i], slot.name)}; provided[idx] = true; diff --git a/source/CLIForge/src/Engine.cpp b/source/CLIForge/src/Engine.cpp index b6eec48..fea57b7 100644 --- a/source/CLIForge/src/Engine.cpp +++ b/source/CLIForge/src/Engine.cpp @@ -3,11 +3,11 @@ namespace cliforge { - Engine::Engine(std::string programName) : programName_(std::move(programName)) {} + Engine::Engine(std::string programName) : m_programName(std::move(programName)) {} - Engine& Engine::describe(std::string description) + Engine& Engine::describe(const std::string& description) { - description_ = std::move(description); + m_description = std::move(description); return *this; } @@ -16,15 +16,15 @@ namespace cliforge // The Engine owns it for the rest of the program's lifetime. Command& Engine::command() { - commands_.push_back(std::make_unique()); - return *commands_.back(); + m_commands.push_back(std::make_unique()); + return *m_commands.back(); } int Engine::run(int argc, char** argv) { - if (programName_.empty() && argc > 0) - programName_ = basename(argv[0]); - for (auto& cmd : commands_) + if (m_programName.empty() && argc > 0) + m_programName = basename(argv[0]); + for (auto& cmd : m_commands) { if (!cmd->sealed()) { @@ -69,14 +69,14 @@ namespace cliforge struct Candidate { - Command* cmd; + Command* cmd{}; StructuredMatch match; }; std::vector candidates; std::string bestPartialError; std::vector bestPartialCmds; // every command tied for the best partial score int bestPartialScore = -1; - for (auto& cmd : commands_) + for (auto& cmd : m_commands) { StructuredMatch m = cmd->tryMatchStructured(tokens); if (m.success) @@ -115,7 +115,7 @@ namespace cliforge std::cerr << "Error: " << bestPartialError << "\n"; if (bestPartialCmds.size() == 1) { - std::cerr << "Usage: " << programName_ << " " + std::cerr << "Usage: " << m_programName << " " << bestPartialCmds.front()->usageLine() << "\n"; } else @@ -124,7 +124,7 @@ namespace cliforge << " commands:\n"; for (auto* cmd : bestPartialCmds) { - std::string line = programName_ + " " + cmd->usageLine(); + std::string line = m_programName + " " + cmd->usageLine(); std::cerr << " " << padRight(line, 46) << firstLine(cmd->description()) << "\n"; } @@ -177,7 +177,7 @@ namespace cliforge std::cerr << "Error: " << firstError << "\n"; if (firstErrorCmd) - std::cerr << "Usage: " << programName_ << " " << firstErrorCmd->usageLine() << "\n"; + std::cerr << "Usage: " << m_programName << " " << firstErrorCmd->usageLine() << "\n"; return 1; } @@ -211,7 +211,7 @@ namespace cliforge return 0; } std::vector matches; - for (auto& cmd : commands_) + for (auto& cmd : m_commands) { if (cmd->couldMatchPrefix(queryTokens)) matches.push_back(cmd.get()); @@ -224,7 +224,7 @@ namespace cliforge } if (matches.size() == 1) { - std::cout << matches.front()->helpText(programName_); + std::cout << matches.front()->helpText(m_programName); return 0; } std::cout << "'" << join(queryTokens) << "' matches " << matches.size() << " commands:\n\n"; @@ -232,30 +232,30 @@ namespace cliforge { if (i) std::cout << "\n"; - std::cout << matches[i]->helpText(programName_); + std::cout << matches[i]->helpText(m_programName); } return 0; } void Engine::printGlobalHelp(std::ostream& os) const { - os << "Usage: " << programName_ << " [ARGUMENTS] [OPTIONS]\n"; - if (!description_.empty()) - os << "\n" << description_ << "\n"; + os << "Usage: " << m_programName << " [ARGUMENTS] [OPTIONS]\n"; + if (!m_description.empty()) + os << "\n" << m_description << "\n"; os << "\nCommands:\n"; - for (auto& cmd : commands_) + for (auto& cmd : m_commands) { - std::string line = programName_ + " " + cmd->usageLine(); + std::string line = m_programName + " " + cmd->usageLine(); os << " " << padRight(line, 46) << firstLine(cmd->description()) << "\n"; } - os << "\nRun '" << programName_ << " help ' or add --help/-h to any command " + os << "\nRun '" << m_programName << " help ' or add --help/-h to any command " << "for details.\n"; } void Engine::suggestCommands(const std::vector& tokens, std::ostream& os) const { std::vector> scored; - for (auto& cmd : commands_) + for (auto& cmd : m_commands) { Command::FuzzyScore s = cmd->fuzzyStructuralDistance(tokens); double ratio = s.keywordChars ? static_cast(s.distance) / @@ -277,7 +277,7 @@ namespace cliforge return; os << "\nDid you mean:\n"; for (auto* cmd : close) - os << " " << programName_ << " " << cmd->usageLine() << "\n"; + os << " " << m_programName << " " << cmd->usageLine() << "\n"; } std::string Engine::firstLine(const std::string& s) diff --git a/source/CLIForge/src/Levenshtein.cpp b/source/CLIForge/src/Levenshtein.cpp index 9265218..d6fadf9 100644 --- a/source/CLIForge/src/Levenshtein.cpp +++ b/source/CLIForge/src/Levenshtein.cpp @@ -26,8 +26,7 @@ namespace cliforge::detail // ordered by distance. Anything farther than a generous relative // threshold is dropped so we never suggest something wildly unrelated. std::vector closestMatches(const std::string& query, - const std::vector& pool, - std::size_t limit) + const std::vector& pool, std::size_t limit) { std::vector> scored; scored.reserve(pool.size()); diff --git a/source/CLIForge/src/Value.cpp b/source/CLIForge/src/Value.cpp index 86bffd6..767cd05 100644 --- a/source/CLIForge/src/Value.cpp +++ b/source/CLIForge/src/Value.cpp @@ -4,16 +4,20 @@ namespace cliforge { CliError::CliError(const std::string& msg, ErrorKind kind) - : std::runtime_error(msg), kind_(kind) {} + : std::runtime_error(msg), m_kind(kind) + { + } ErrorKind CliError::kind() const noexcept { - return kind_; + return kind(); } ParseError::ParseError(const std::string& msg) : CliError(msg, ErrorKind::TypeMismatch) {} - RegistrationError::RegistrationError(const std::string& msg) : CliError(msg, ErrorKind::Generic) {} + RegistrationError::RegistrationError(const std::string& msg) : CliError(msg, ErrorKind::Generic) + { + } Scalar ChoiceTable::parse(std::string_view token, std::string_view label) const { @@ -59,22 +63,22 @@ namespace cliforge Value Value::ofScalar(Scalar s) { Value v; - v.data_ = std::move(s); + v.m_data = std::move(s); return v; } Value Value::ofVector(std::vector v) { Value out; - out.data_ = std::move(v); + out.m_data = std::move(v); return out; } bool Value::hasValue() const { - return !std::holds_alternative(data_); + return !std::holds_alternative(m_data); } bool Value::isVector() const { - return std::holds_alternative>(data_); + return std::holds_alternative>(m_data); } } diff --git a/source/Example/main.cpp b/source/Example/main.cpp index 62f79d6..4fbe2e0 100644 --- a/source/Example/main.cpp +++ b/source/Example/main.cpp @@ -8,6 +8,7 @@ // take their arguments by value, by reference, or by const reference, // whichever fits. #include + #include #include #include @@ -20,10 +21,30 @@ using cliforge::Engine; // --------------------------------------------------------------------- // Custom enums -- plain C++ enum class, nothing cliforge-specific here. // --------------------------------------------------------------------- -enum class ProjectTemplate { Cpp, Python, Node }; -enum class Editor { Vim, Emacs, Nano }; -enum class TwoFactorMethod { App, Sms, Email }; -enum class UpdateChannel { Stable, Beta, Nightly }; +enum class ProjectTemplate +{ + Cpp, + Python, + Node +}; +enum class Editor +{ + Vim, + Emacs, + Nano +}; +enum class TwoFactorMethod +{ + App, + Sms, + Email +}; +enum class UpdateChannel +{ + Stable, + Beta, + Nightly +}; // --------------------------------------------------------------------- // Every command handler is a completely ordinary free function. There is @@ -33,56 +54,70 @@ enum class UpdateChannel { Stable, Beta, Nightly }; // const reference, or reference, whichever avoids unnecessary copies. // --------------------------------------------------------------------- -void projectCreate(const std::string& name, std::optional tmpl, bool git) { - static const char* names[] = {"cpp", "python", "node"}; - ProjectTemplate t = tmpl.value_or(ProjectTemplate::Cpp); - std::printf("[create] project '%s' from template '%s' (git init: %s)\n", name.c_str(), - names[static_cast(t)], git ? "yes" : "no"); +void projectCreate(const std::string& name, std::optional tmpl, bool git) +{ + static const char* s_names[] = {"cpp", "python", "node"}; + ProjectTemplate t = tmpl.value_or(ProjectTemplate::Cpp); + std::printf("[create] project '%s' from template '%s' (git init: %s)\n", name.c_str(), + s_names[static_cast(t)], git ? "yes" : "no"); } -void projectDelete(const std::string& name, bool force, const std::optional& reason) { - std::printf("[delete] project '%s' (force=%s, reason=%s)\n", name.c_str(), - force ? "yes" : "no", reason ? reason->c_str() : ""); +void projectDelete(const std::string& name, bool force, const std::optional& reason) +{ + std::printf("[delete] project '%s' (force=%s, reason=%s)\n", name.c_str(), force ? "yes" : "no", + reason ? reason->c_str() : ""); } -void projectInfo(const std::string& name, bool json) { - if (json) { - std::printf("{\"project\": \"%s\", \"status\": \"ok\"}\n", name.c_str()); - } else { - std::printf("[info] project '%s': status ok\n", name.c_str()); - } +void projectInfo(const std::string& name, bool json) +{ + if (json) + { + std::printf("{\"project\": \"%s\", \"status\": \"ok\"}\n", name.c_str()); + } + else + { + std::printf("[info] project '%s': status ok\n", name.c_str()); + } } void build(const std::vector& targets, bool noAssets, bool debug, bool verbose, - const std::vector& architectures, std::optional editor, - std::optional threshold) { - static const char* editorNames[] = {"vim", "emacs", "nano"}; - std::printf("[build] targets=["); - for (size_t i = 0; i < targets.size(); ++i) std::printf("%s%d", i ? "," : "", targets[i]); - std::printf("] noAssets=%d debug=%d verbose=%d architectures=[", noAssets, debug, verbose); - for (size_t i = 0; i < architectures.size(); ++i) - std::printf("%s%s", i ? "," : "", architectures[i].c_str()); - std::printf("] editor=%s threshold=%s\n", - editor ? editorNames[static_cast(*editor)] : "", - threshold ? std::to_string(*threshold).c_str() : ""); + const std::vector& architectures, std::optional editor, + std::optional threshold) +{ + static const char* s_editorNames[] = {"vim", "emacs", "nano"}; + std::printf("[build] targets=["); + for (size_t i = 0; i < targets.size(); ++i) + std::printf("%s%d", i ? "," : "", targets[i]); + std::printf("] noAssets=%d debug=%d verbose=%d architectures=[", noAssets, debug, verbose); + for (size_t i = 0; i < architectures.size(); ++i) + std::printf("%s%s", i ? "," : "", architectures[i].c_str()); + std::printf("] editor=%s threshold=%s\n", + editor ? s_editorNames[static_cast(*editor)] : "", + threshold ? std::to_string(*threshold).c_str() : ""); } -void enable2fa(std::optional method) { - static const char* names[] = {"app", "sms", "email"}; - TwoFactorMethod m = method.value_or(TwoFactorMethod::App); - std::printf("[2fa] enabled via %s\n", names[static_cast(m)]); +void enable2fa(std::optional method) +{ + static const char* s_names[] = {"app", "sms", "email"}; + TwoFactorMethod m = method.value_or(TwoFactorMethod::App); + std::printf("[2fa] enabled via %s\n", s_names[static_cast(m)]); } -void disable2fa() { std::printf("[2fa] disabled\n"); } - -void selfUpdate(std::optional channel, bool checkOnly) { - static const char* names[] = {"stable", "beta", "nightly"}; - std::printf("[self-update] channel=%s checkOnly=%d\n", - channel ? names[static_cast(*channel)] : "stable(default)", checkOnly); +void disable2fa() +{ + std::printf("[2fa] disabled\n"); } -void status(bool json) { - std::printf(json ? "{\"status\":\"ok\"}\n" : "All systems operational.\n"); +void selfUpdate(std::optional channel, bool checkOnly) +{ + static const char* s_names[] = {"stable", "beta", "nightly"}; + std::printf("[self-update] channel=%s checkOnly=%d\n", + channel ? s_names[static_cast(*channel)] : "stable(default)", checkOnly); +} + +void status(bool json) +{ + std::printf(json ? "{\"status\":\"ok\"}\n" : "All systems operational.\n"); } // `deploy` showcases a *loose* parameter: `hosts` is declared after @@ -91,10 +126,13 @@ void status(bool json) { // packctl deploy web1 --env prod web2 -n web3 // still collects hosts=[web1,web2,web3], matched by position among // themselves, not by where they fall relative to the option/flag. -void deploy(bool dryRun, const std::optional& env, const std::vector& hosts) { - std::printf("[deploy] dryRun=%d env=%s hosts=[", dryRun, env ? env->c_str() : ""); - for (size_t i = 0; i < hosts.size(); ++i) std::printf("%s%s", i ? "," : "", hosts[i].c_str()); - std::printf("]\n"); +void deploy(bool dryRun, const std::optional& env, + const std::vector& hosts) +{ + std::printf("[deploy] dryRun=%d env=%s hosts=[", dryRun, env ? env->c_str() : ""); + for (size_t i = 0; i < hosts.size(); ++i) + std::printf("%s%s", i ? "," : "", hosts[i].c_str()); + std::printf("]\n"); } // --------------------------------------------------------------------- @@ -103,90 +141,91 @@ void deploy(bool dryRun, const std::optional& env, const std::vecto // the bound function's arguments are consistent with what was declared. // --------------------------------------------------------------------- -int main(int argc, char** argv) { - Engine cli("packctl"); - cli.describe("A demo package/project CLI built with cliforge."); +int main(int argc, char** argv) +{ + Engine cli("packctl"); + cli.describe("A demo package/project CLI built with cliforge."); - cli.command() - .keyword("project") - .parameter("name", "Project name") - .keyword("create") - .option("template", 't', "Project template to scaffold (default: cpp)") - .choices({{"cpp", ProjectTemplate::Cpp}, - {"python", ProjectTemplate::Python}, - {"node", ProjectTemplate::Node}}) - .flag("git", 'g', "Initialize a git repository") - .describe("Scaffold a new project from a template") - .action(&projectCreate); + cli.command() + .keyword("project") + .parameter("name", "Project name") + .keyword("create") + .option("template", 't', "Project template to scaffold (default: cpp)") + .choices({{"cpp", ProjectTemplate::Cpp}, + {"python", ProjectTemplate::Python}, + {"node", ProjectTemplate::Node}}) + .flag("git", 'g', "Initialize a git repository") + .describe("Scaffold a new project from a template") + .action(&projectCreate); - cli.command() - .keyword("project") - .parameter("name", "Project name") - .keyword("delete") - .flag("force", 'f', "Skip the confirmation prompt") - .option("reason", 'r', "Why the project is being deleted") - .describe("Permanently delete a project by name") - .action(&projectDelete); + cli.command() + .keyword("project") + .parameter("name", "Project name") + .keyword("delete") + .flag("force", 'f', "Skip the confirmation prompt") + .option("reason", 'r', "Why the project is being deleted") + .describe("Permanently delete a project by name") + .action(&projectDelete); - cli.command() - .keyword("project") - .parameter("name", "Project name") - .flag("json", 'j', "Print machine-readable JSON") - .describe("Show information about a project") - .action(&projectInfo); + cli.command() + .keyword("project") + .parameter("name", "Project name") + .flag("json", 'j', "Print machine-readable JSON") + .describe("Show information about a project") + .action(&projectInfo); - cli.command() - .keyword("build") - .parameter("targets", "Target ids to build", /*variadic=*/true) - .flag("no-assets", 'n', "Skip packaging assets") - .flag("debug", 'd', "Build with debug symbols") - .flag("verbose", 'i', "Verbose build output") - .option("architectures", 'a', "Target architectures", /*variadic=*/true) - .option("editor", 'e', "Editor to open on completion") - .choices({{"vim", Editor::Vim}, {"emacs", Editor::Emacs}, {"nano", Editor::Nano}}) - .option("threshold", 't', "Warning threshold (0 = strict)") - .describe("Build the given target ids") - .action(&build); + cli.command() + .keyword("build") + .parameter("targets", "Target ids to build", /*variadic=*/true) + .flag("no-assets", 'n', "Skip packaging assets") + .flag("debug", 'd', "Build with debug symbols") + .flag("verbose", 'i', "Verbose build output") + .option("architectures", 'a', "Target architectures", /*variadic=*/true) + .option("editor", 'e', "Editor to open on completion") + .choices({{"vim", Editor::Vim}, {"emacs", Editor::Emacs}, {"nano", Editor::Nano}}) + .option("threshold", 't', "Warning threshold (0 = strict)") + .describe("Build the given target ids") + .action(&build); - cli.command() - .keyword("2fa") - .keyword("enable") - .option("method", 'm', "Verification method (default: app)") - .choices({{"app", TwoFactorMethod::App}, - {"sms", TwoFactorMethod::Sms}, - {"email", TwoFactorMethod::Email}}) - .describe("Turn on two-factor authentication") - .action(&enable2fa); + cli.command() + .keyword("2fa") + .keyword("enable") + .option("method", 'm', "Verification method (default: app)") + .choices({{"app", TwoFactorMethod::App}, + {"sms", TwoFactorMethod::Sms}, + {"email", TwoFactorMethod::Email}}) + .describe("Turn on two-factor authentication") + .action(&enable2fa); - cli.command() - .keyword("2fa") - .keyword("disable") - .describe("Turn off two-factor authentication") - .action(&disable2fa); + cli.command() + .keyword("2fa") + .keyword("disable") + .describe("Turn off two-factor authentication") + .action(&disable2fa); - cli.command() - .keyword("self-update") - .option("channel", 'c', "Release channel to update from") - .choices({{"stable", UpdateChannel::Stable}, - {"beta", UpdateChannel::Beta}, - {"nightly", UpdateChannel::Nightly}}) - .flag("check-only", 'k', "Check for updates without installing") - .describe("Update packctl to the latest version") - .action(&selfUpdate); + cli.command() + .keyword("self-update") + .option("channel", 'c', "Release channel to update from") + .choices({{"stable", UpdateChannel::Stable}, + {"beta", UpdateChannel::Beta}, + {"nightly", UpdateChannel::Nightly}}) + .flag("check-only", 'k', "Check for updates without installing") + .describe("Update packctl to the latest version") + .action(&selfUpdate); - cli.command() - .keyword("status") - .flag("json", 'j', "Print machine-readable JSON") - .describe("Show whether everything is operational") - .action(&status); + cli.command() + .keyword("status") + .flag("json", 'j', "Print machine-readable JSON") + .describe("Show whether everything is operational") + .action(&status); - cli.command() - .keyword("deploy") - .flag("dry-run", 'n', "Show what would happen without doing it") - .option("env", 'e', "Environment name") - .parameter("hosts", "Target hostnames", /*variadic=*/true) // loose! - .describe("Deploy to the given hosts, in any order relative to --env/-n") - .action(&deploy); + cli.command() + .keyword("deploy") + .flag("dry-run", 'n', "Show what would happen without doing it") + .option("env", 'e', "Environment name") + .parameter("hosts", "Target hostnames", /*variadic=*/true) // loose! + .describe("Deploy to the given hosts, in any order relative to --env/-n") + .action(&deploy); - return cli.run(argc, argv); + return cli.run(argc, argv); }