From 44bc63d17d20ee974a775705625ee13cd6f3d4e0 Mon Sep 17 00:00:00 2001 From: killua Date: Tue, 7 Oct 2025 02:30:31 +0400 Subject: [PATCH] update(conventions): extend windows conventions to show more capability. --- include/conventions/base_convention.hpp | 15 ++ .../windows_argument_convention.hpp | 133 +++++++++++------- src/main.cpp | 2 + 3 files changed, 98 insertions(+), 52 deletions(-) diff --git a/include/conventions/base_convention.hpp b/include/conventions/base_convention.hpp index f115e72..796e4ac 100644 --- a/include/conventions/base_convention.hpp +++ b/include/conventions/base_convention.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #ifndef BASE_CONVENTION_HPP @@ -31,4 +32,18 @@ namespace argument_parser::conventions { using convention = base_convention; } +namespace argument_parser::conventions::helpers { + static std::string to_lower(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return std::tolower(c); }); + return s; + } + + static std::string to_upper(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return std::toupper(c); }); + return s; + } +} + #endif \ No newline at end of file diff --git a/include/conventions/implementations/windows_argument_convention.hpp b/include/conventions/implementations/windows_argument_convention.hpp index 1c5af9d..ab8f9c6 100644 --- a/include/conventions/implementations/windows_argument_convention.hpp +++ b/include/conventions/implementations/windows_argument_convention.hpp @@ -5,87 +5,116 @@ #ifndef WINDOWS_ARGUMENT_CONVENTION_HPP #define WINDOWS_ARGUMENT_CONVENTION_HPP +#ifndef ALLOW_DASH_FOR_WINDOWS +#define ALLOW_DASH_FOR_WINDOWS 1 +#endif + namespace argument_parser::conventions::implementations { class windows_argument_convention : public base_convention { public: - // Windows style options are prefixed with a slash ('/'). And there's no difference for short vs long options. + explicit windows_argument_convention(bool accept_dash = true) + : accept_dash_(accept_dash) { + } + parsed_argument get_argument(std::string const& raw) const override { - if (raw.starts_with(long_prec())) - return { argument_type::INTERCHANGABLE, raw.substr(1) }; - else - return { argument_type::ERROR, "Windows standard requires non-positional arguments with a preceeding slash ('\\')." }; + if (raw.empty()) { + return { argument_type::ERROR, "Empty argument token." }; + } + const char c0 = raw[0]; + const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); + if (!ok_prefix) { + return { argument_type::ERROR, + accept_dash_ + ? "Windows-style expects options to start with '/' (or '-' in compat mode)." + : "Windows-style expects options to start with '/'." }; + } + + if (raw.find_first_of("=:") != std::string::npos) { + return { argument_type::ERROR, + "Inline values are not allowed in this convention; provide the value in the next token." }; + } + + std::string name = helpers::to_lower(raw.substr(1)); + if (name.empty()) { + return { argument_type::ERROR, "Option name cannot be empty after '/'." }; + } + + return { argument_type::INTERCHANGABLE, std::move(name) }; } std::string extract_value(std::string const& /*raw*/) const override { - // In non-equal GNU, value comes in next token - throw std::runtime_error("No inline value in standard GNU convention."); + throw std::runtime_error("No inline value; value must be provided in the next token."); } - bool requires_next_token() const override { - return true; - } + bool requires_next_token() const override { return true; } - std::string name() const override { - return "Windows style options"; - } + std::string name() const override { return "Windows style options (next-token values)"; } + std::string short_prec() const override { return accept_dash_ ? "-" : "/"; } + std::string long_prec() const override { return "/"; } - std::string short_prec() const override { - return "/"; - } - - std::string long_prec() const override { - return "/"; - } - - static windows_argument_convention instance; + static windows_argument_convention instance; private: - windows_argument_convention() = default; + bool accept_dash_; }; - class windows_equal_argument_convention : public base_convention { + class windows_kv_argument_convention : public base_convention { public: - // Windows style options are prefixed with a slash ('/'). And there's no difference for short vs long options. + explicit windows_kv_argument_convention(bool accept_dash = true) + : accept_dash_(accept_dash) { + } + parsed_argument get_argument(std::string const& raw) const override { - auto pos = raw.find('='); - auto arg = pos != std::string::npos ? raw.substr(0, pos) : raw; - if (arg.starts_with(long_prec())) - return { argument_type::INTERCHANGABLE, arg.substr(1) }; - else - return { argument_type::ERROR, "Windows standard requires non-positional arguments with a preceeding slash ('\\')." }; + if (raw.empty()) { + return { argument_type::ERROR, "Empty argument token." }; + } + const char c0 = raw[0]; + const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); + if (!ok_prefix) { + return { argument_type::ERROR, + accept_dash_ + ? "Windows-style expects options to start with '/' (or '-' in compat mode)." + : "Windows-style expects options to start with '/'." }; + } + + const std::size_t sep = raw.find_first_of("=:"); + if (sep == std::string::npos) { + return { argument_type::ERROR, + "Expected an inline value using '=' or ':' (e.g., /opt=value or /opt:value)." }; + } + if (sep == 1) { + return { argument_type::ERROR, "Option name cannot be empty before '=' or ':'." }; + } + + std::string name = helpers::to_lower(raw.substr(1, sep - 1)); + return { argument_type::INTERCHANGABLE, std::move(name) }; } std::string extract_value(std::string const& raw) const override { - auto pos = raw.find('='); - if (pos == std::string::npos || pos + 1 >= raw.size()) - throw std::runtime_error("Expected value after '='."); - return raw.substr(pos + 1); + const std::size_t sep = raw.find_first_of("=:"); + if (sep == std::string::npos || sep + 1 >= raw.size()) + throw std::runtime_error("Expected a value after '=' or ':'."); + return raw.substr(sep + 1); } - bool requires_next_token() const override { - return false; - } + bool requires_next_token() const override { return false; } - std::string name() const override { - return "Windows style options (equal signed form)"; - } + std::string name() const override { return "Windows style options (inline values via '=' or ':')"; } + std::string short_prec() const override { return accept_dash_ ? "-" : "/"; } + std::string long_prec() const override { return "/"; } - std::string short_prec() const override { - return "/"; - } - - std::string long_prec() const override { - return "/"; - } - - static windows_equal_argument_convention instance; + static windows_kv_argument_convention instance; private: - windows_equal_argument_convention() = default; + bool accept_dash_; }; + + + inline windows_argument_convention windows_argument_convention::instance = windows_argument_convention(bool(ALLOW_DASH_FOR_WINDOWS)); + inline windows_kv_argument_convention windows_kv_argument_convention::instance = windows_kv_argument_convention(bool(ALLOW_DASH_FOR_WINDOWS)); } namespace argument_parser::conventions { static inline const implementations::windows_argument_convention windows_argument_convention = implementations::windows_argument_convention::instance; - static inline const implementations::windows_equal_argument_convention windows_equal_argument_convention = implementations::windows_equal_argument_convention::instance; + static inline const implementations::windows_kv_argument_convention windows_equal_argument_convention = implementations::windows_kv_argument_convention::instance; } #endif // WINDOWS_ARGUMENT_CONVENTION_HPP \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b342d28..07d21ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,5 @@ +#define ALLOW_DASH_FOR_WINDOWS 0 + #include #include #include