Program Listing for File cmdlnparser.h#

Return to documentation for file (cmdlnparser\cmdlnparser.h)

#ifndef CMDLN_PARSER_H
#define CMDLN_PARSER_H


#include <iostream>
#include <string>
#include <sstream>
#include <map>
#include <list>
#include <set>
#include <stdexcept>
#include <iostream>
#include <filesystem>
#include <vector>
#include <memory>
#include <optional>
#include <queue>
#include <support/pointer.h>
#include <support/string.h>
#include <support/sequence.h>

#ifdef min
#undef min
#endif

#ifdef max
#undef max
#endif

namespace helper
{
    inline std::string rtrim(const std::string& rss)
    {
        const std::string ssWhitespace = " \t\n\r\f\v";
        std::string ssResult = rss;
        ssResult.erase(ssResult.find_last_not_of(ssWhitespace) + 1);
        return ssResult;
    }

    inline std::string ltrim(const std::string& rss)
    {
        const std::string ssWhitespace = " \t\n\r\f\v";
        std::string ssResult = rss;
        ssResult.erase(0, ssResult.find_first_not_of(ssWhitespace));
        return ssResult;
    }

    inline std::string trim(const std::string& rss)
    {
        return ltrim(rtrim(rss));
    }
} // namespace helper

// Forward declaration
class CArgumentDefBase;

template <typename TVar, typename TEnable = void>
class CArgumentDefT;

struct SGroupDef
{
    std::string     ssTitle;
    std::string     ssDescription;
};

class CArgumentIterator
{
public:
    template <typename TCharType>
    CArgumentIterator(size_t nArgs, const TCharType** rgszArgs);

    std::optional<std::string> GetNext();

    size_t GetIndexOfLastArg() const;

private:
    size_t                      m_nCounter = 0;
    std::queue<std::string>     m_queueArguments;
};

class CCommandLine
{
    friend CArgumentDefBase;

public:
    enum class EParseFlags : uint32_t
    {
        assignment_character = 0x10,
        no_assignment_character = 0x20,
        assignment_next_arg = 0x40,
    };

    CCommandLine(uint32_t uiFlags = static_cast<uint32_t>(EParseFlags::assignment_character));

    ~CCommandLine();

    std::filesystem::path GetApplicationPath() const;

    uint32_t GetParseFlags() const { return m_uiParseFlags; }

    bool CheckParseFlag(EParseFlags eParseFlag) const { return m_uiParseFlags & static_cast<uint32_t>(eParseFlag); }

    template <typename TCharType>
    void Parse(size_t nArgs, const TCharType** rgszArgs);

    void DefineGroup(const std::string& rssTitle, const std::string& rssDescription = std::string{});

    template <typename TVar>
    CArgumentDefT<TVar>& DefineDefaultArgument(TVar& rtVar, const std::string& rssHelpText);

    template <typename TVar, typename... TArgumentGroup>
    CArgumentDefT<TVar>& DefineOption(const std::string& rssArgument, TVar& rtVar, const std::string& rssHelpText,
        bool bCaseSensitive = true, size_t nArgumentGroup = 0, TArgumentGroup... nAdditionalGroups);

    template <typename TVar, typename... TArgumentGroup>
    CArgumentDefT<TVar>& DefineSubOption(const std::string& rssArgument, TVar& rtVar, const std::string& rssHelpText,
        bool bCaseSensitive = true, size_t nArgumentGroup = 0, TArgumentGroup... nAdditionalGroups);

    template <typename... TArgumentGroup>
    CArgumentDefT<bool>& DefineFlagOption(const std::string& rssArgument, bool& rbFlag, const std::string& rssHelpText,
        bool bCaseSensitive = true, size_t nArgumentGroup = 0, TArgumentGroup... nAdditionalGroups);

    template <typename... TArgumentGroup>
    CArgumentDefT<bool>& DefineFlagSubOption(const std::string& rssArgument, bool& rbFlag, const std::string& rssHelpText,
        bool bCaseSensitive = true, size_t nArgumentGroup = 0, TArgumentGroup... nAdditionalGroups);

    void PrintFixedWidth(size_t nWidth);

    size_t PrintFixedWidth() const;

    void PrintMaxWidth(size_t nWidth);

    size_t PrintMaxWidth() const;

    void PrintSyntax(bool bEnable);

    bool PrintSyntax() const;

    void PrintHelp(std::ostream& rstream, const std::string& rssHelpText = std::string{}, size_t nArgumentGroup = 0) const;

    static void PrintHelpText(std::ostream& rstream, const std::string& rssHelpText, size_t nPrintWidth = 0);

    void DumpArguments(std::ostream& rstream, bool bAll = true) const;

    std::vector<std::string> IncompatibleArguments(size_t nArgumentGroup, bool bFull = true) const;

private:
    uint32_t                                        m_uiParseFlags = 0;
    std::shared_ptr<CArgumentDefBase>               m_ptrDefaultArg;
    std::list<std::shared_ptr<CArgumentDefBase>>    m_lstOptionArgs;
    std::map<std::string, CArgumentDefBase&, std::greater<std::string>> m_mapSortedOptions;
    std::map<std::string, CArgumentDefBase&, std::greater<std::string>> m_mapSortedSubOptions;
    std::shared_ptr<SGroupDef>                      m_ptrCurrentGroup;
    std::list<std::pair<std::reference_wrapper<CArgumentDefBase>, std::string>> m_lstSupplied;
    size_t                                          m_nFixedWidth = 0;
    size_t                                          m_nMaxWidth = 0;
    bool                                            m_bSyntaxPrint = true;
};

struct SArgumentParseException : std::exception
{
    SArgumentParseException(const std::string& rssDescription) : m_ssDescription(rssDescription)
    {
        Compose();
    }

    virtual const char* what() const noexcept
    {
        return m_ssWhat.c_str();
    }

    void AddIndex(size_t nIndex)
    {
        m_nIndex = nIndex; Compose();
    }

    void AddArgument(const std::string& rssArg)
    {
        m_ssArgument = rssArg; Compose();
    }

private:
    void Compose()
    {
        std::stringstream sstream;
        if (m_nIndex != static_cast<size_t>(-1))
            sstream << "Argument #" << m_nIndex;
        if (!m_ssArgument.empty())
        {
            if (!sstream.str().empty()) sstream << " ";
            sstream << "'" << m_ssArgument << "'";
        }
        if (!sstream.str().empty())
            sstream << ": ";
        sstream << m_ssDescription;
        m_ssWhat = std::move(sstream.str());
    }

    std::string     m_ssWhat;
    std::string     m_ssDescription;
    std::string     m_ssArgument;
    size_t          m_nIndex = static_cast<size_t>(-1);
};

void PrintBlock(std::ostream& rstream, const std::string& rssText, size_t nIndentPos, size_t nCurrentPos, size_t nMaxPos);

struct IArgumentProvide
{
    virtual void ArgumentAssign(const std::string& rssValue) = 0;

    virtual std::string GetArgumentOptionMarkup() = 0;

    virtual std::string GetArgumentOptionDetails(size_t nMaxStringLen) = 0;

    virtual std::string GetArgumentValueString() = 0;

    virtual bool IsArgumentAssigned() = 0;

    virtual bool AllowMultiArgumentAssign() = 0;
};

enum class EArgumentFlags : uint32_t
{
    default_argument = 1,
    option_argument = 0x10,
    sub_option_argument = 0x20,
    bool_option = 0x100,
    flag_option = 0x200,
    case_sensitive = 0x1000,
};

class CArgumentDefBase
{
protected:
    template <typename TVar, typename... TArgumentGroup>
    CArgumentDefBase(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, TVar& rtVar, TArgumentGroup... nArgumentGroup);

    virtual ~CArgumentDefBase();

public:
    struct SOptionName
    {
        std::string     ssName;
        uint32_t        uiFlags = 0;
    };

    const CCommandLine& GetCLParser() const { return m_rCLParser; }

    bool CompareNameAndAssign(CArgumentIterator& rargit, const std::string& rssArgument, const std::string& rssOptionName,
        bool bPartial) const;

    void AddExample(const std::string& rssExample);

    const std::vector<SOptionName>& GetOptionNames() const { return m_vecOptionNames; }

    const std::string& GetHelpText() const { return m_ssHelpText; }

    const std::shared_ptr<IArgumentProvide>& GetArgumentVar() const { return m_ptrArgProvide; }

    const std::list<std::string>& GetExamples() const { return m_lstExamples; }

    bool CheckFlag(EArgumentFlags eFlag) const { return m_uiFlags & static_cast<uint32_t>(eFlag); }

    void AddOptionName(const std::string& rssOption)
    {
        SOptionName sOption{ rssOption, static_cast<uint32_t>(EArgumentFlags::option_argument) };
        m_vecOptionNames.push_back(sOption);
        m_rCLParser.m_mapSortedOptions.emplace(rssOption, *this);
    }

    void AddSubOptionName(const std::string& rssSubOption)
    {
        SOptionName sSubOption{ rssSubOption, static_cast<uint32_t>(EArgumentFlags::sub_option_argument) };
        m_vecOptionNames.push_back(sSubOption);
        m_rCLParser.m_mapSortedSubOptions.emplace(rssSubOption, *this);
    }

    std::shared_ptr<SGroupDef> GetGroup() const { return m_ptrGroup; }

    bool PartOfArgumentGroup(size_t nGroup) const
    {
        return m_setArgumentGroups.find(nGroup) != m_setArgumentGroups.end() ||
            m_setArgumentGroups.find(0) != m_setArgumentGroups.end();
    }

    bool OptionAvailableOnCommandLine() const { return m_bAvailableOnCommandLine; }

private:
    CCommandLine&                       m_rCLParser;
    std::shared_ptr<SGroupDef>          m_ptrGroup;
    std::vector<SOptionName>            m_vecOptionNames;
    std::string                         m_ssHelpText;
    std::shared_ptr<IArgumentProvide>   m_ptrArgProvide;
    std::list<std::string>              m_lstExamples;
    uint32_t                            m_uiFlags = 0;
    std::set<size_t>                    m_setArgumentGroups;
    mutable bool                        m_bAvailableOnCommandLine = false;
};

template <typename TVar>
class CArgumentDefT<TVar, typename std::enable_if_t<std::is_arithmetic<TVar>::value>> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, TVar& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <typename TVar>
class CArgumentDefT<std::vector<TVar>, typename std::enable_if_t<std::is_arithmetic<TVar>::value>> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::vector<TVar>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <typename TVar>
class CArgumentDefT<sdv::sequence<TVar>, typename std::enable_if_t<std::is_arithmetic<TVar>::value>> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, sdv::sequence<TVar>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <typename TVar>
class CArgumentDefT<std::list<TVar>, typename std::enable_if_t<std::is_arithmetic<TVar>::value>> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::list<TVar>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<std::filesystem::path, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::filesystem::path& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<std::vector<std::filesystem::path>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::vector<std::filesystem::path>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<sdv::sequence<std::filesystem::path>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, sdv::sequence<std::filesystem::path>& rtVar,
        TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<std::list<std::filesystem::path>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::list<std::filesystem::path>& rtVar,
        TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<std::string, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::string& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<std::vector<std::string>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::vector<std::string>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<sdv::sequence<std::string>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, sdv::sequence<std::string>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<std::list<std::string>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::list<std::string>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<sdv::u8string, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, sdv::u8string& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<std::vector<sdv::u8string>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::vector<sdv::u8string>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<sdv::sequence<sdv::u8string>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, sdv::sequence<sdv::u8string>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <>
class CArgumentDefT<std::list<sdv::u8string>, void> : public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::list<sdv::u8string>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <typename TEnum>
struct SEnumArgumentAssoc
{
    TEnum           eValue;
    std::string     ssValueText;
    std::string     ssDescription;
};

template <typename TEnum>
class CEnumArgumentDefBase
{
public:
    void AddAssociation(TEnum eEnumValue, const std::string& rssValueText, const std::string& rssDescription)
    {
        if (rssValueText.empty()) return;   // At least a value text is needed
        SEnumAssociation sEnumAssociation;
        sEnumAssociation.eValue = eEnumValue;
        sEnumAssociation.ssValueText = rssValueText;
        sEnumAssociation.ssDescr = rssDescription.empty() ? rssValueText : rssDescription;
        m_lstEnumAssociations.push_back(sEnumAssociation);
    }

    void AddAssociation(const SEnumArgumentAssoc<TEnum>& rsAssociation)
    {
        if (rsAssociation.ssValueText.empty()) return;  // At least a value text is needed
        SEnumAssociation sEnumAssociation;
        sEnumAssociation.eValue = rsAssociation.eValue;
        sEnumAssociation.ssValueText = rsAssociation.ssValueText;
        sEnumAssociation.ssDescription =
            rsAssociation.ssDescription.empty() ? rsAssociation.ssValueText : rsAssociation.ssDescription;
        m_lstEnumAssociations.push_back(sEnumAssociation);
    }

    template <size_t nSize>
    void AddAssociations(const SEnumArgumentAssoc<TEnum>(& rgsAssociations)[nSize])
    {
        for (size_t n = 0; n < nSize; n++)
            AddAssociation(rgsAssociations[n]);
    }

    struct SEnumAssociation
    {
        TEnum           eValue;
        std::string     ssValueText;
        std::string     ssDescription;
    };

    const std::list<SEnumAssociation>& GetAssociations() const
    {
        return m_lstEnumAssociations;
    }

private:
    std::list<SEnumAssociation>     m_lstEnumAssociations;
};

template <typename TEnum>
class CArgumentDefT <TEnum, typename std::enable_if_t<std::is_enum<TEnum>::value>> :
    public CEnumArgumentDefBase<TEnum>, public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, TEnum& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <typename TEnum>
class CArgumentDefT<std::vector<TEnum>, typename std::enable_if_t<std::is_enum<TEnum>::value>> :
    public CEnumArgumentDefBase<TEnum>, public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::vector<TEnum>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <typename TEnum>
class CArgumentDefT<sdv::sequence<TEnum>, typename std::enable_if_t<std::is_enum<TEnum>::value>> :
    public CEnumArgumentDefBase<TEnum>, public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, sdv::sequence<TEnum>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <typename TEnum>
class CArgumentDefT<std::list<TEnum>, typename std::enable_if_t<std::is_enum<TEnum>::value>> :
    public CEnumArgumentDefBase<TEnum>, public CArgumentDefBase
{
public:
    template <typename... TArgumentGroup>
    CArgumentDefT(CCommandLine& rCLParser, const std::string& rssArgument, const std::shared_ptr<SGroupDef>& rptrGroup,
        const std::string& rssHelpText, uint32_t uiFlags, std::list<TEnum>& rtVar, TArgumentGroup... nArgumentGroup) :
        CArgumentDefBase(rCLParser, rssArgument, rptrGroup, rssHelpText, uiFlags, rtVar, nArgumentGroup...)
    {}
};

template <typename TVar>
class CArgValueImpl
{
public:
    CArgValueImpl(CArgumentDefT<TVar>& rArgumentDef) : m_rArgumentDef(rArgumentDef)
    {
    }

    void Parse(TVar& /*rtArgument*/, const std::string& /*rssValue*/) {}

    std::string GetArgumentOptionMarkup() { return std::string(); }

    std::string GetOptionDetails(size_t /*nMaxStringLen*/) { return std::string(); }

    std::string GetArgumentValueString(const TVar& /*rtArgument*/) { return std::string(); }

    bool MultiArgument() { return false; }

protected:
    CArgumentDefT<TVar>&    m_rArgumentDef;
};

template <class TBase, typename TVarClass, typename TVar = TVarClass>
class CValueAssignment : public TBase
{
public:
    CValueAssignment(CArgumentDefT<TVarClass>& rArgumentDef) :
        TBase(rArgumentDef),
        m_bNoAssignChar(rArgumentDef.GetOptionNames().empty() ||
            rArgumentDef.GetCLParser().CheckParseFlag(CCommandLine::EParseFlags::no_assignment_character) ||
            rArgumentDef.GetCLParser().CheckParseFlag(CCommandLine::EParseFlags::assignment_next_arg))
    {}

    void Parse(TVarClass& rtArgument, const std::string& rssValue)
    {
        // Value existing?
        if (!m_bNoAssignChar && !rssValue.size())
            throw SArgumentParseException("Incorrect value (assignment expected)!");

        // Assignment expected?
        if (!m_bNoAssignChar && rssValue[0] != '=' && rssValue[0] != ':')
            throw SArgumentParseException("Incorrect value (assignment expected)!");

        // Forward the request to the base
        TBase::Parse(rtArgument, rssValue.substr(m_bNoAssignChar ? 0 : 1));
    }

    std::string GetArgumentOptionMarkup()
    {
        // Request the markup of the argument(s)
        // Option markup is composed of ':' with the markup of the argument type(s)
        return (m_bNoAssignChar ? "" : ":") + TBase::GetArgumentOptionMarkup();
    }

private:
    bool m_bNoAssignChar = false;
};

template <class TContainer, class TBase, typename TVar>
class CContainerArgValue : public TBase
{
public:
    CContainerArgValue(CArgumentDefT<TContainer>& rArgumentDef) : TBase(rArgumentDef)   {}

    void Parse(TContainer& rvectArgument, const std::string& rssValue)
    {
        // Parse through the value ('\\' and '"' are escape characters)
        std::size_t nPos = 0;
        std::string ssValue;
        enum class EState { value_or_quote, value_or_comma, quote_string, comma_or_end } eState = EState::value_or_quote;
        bool bEscape = false;
        size_t nDepth = 0;

        do
        {
            bool bSkip = false;
            bool bProcess = false;

            // Use the '\0' to identify the end of the string
            char ch = nPos >= rssValue.size() ? '\0' : rssValue[nPos];

            // interpret the character
            switch (ch)
            {
            case '(':
            case '{':
                if (!bEscape && eState != EState::quote_string)
                    nDepth++;
                break;
            case ')':
            case '}':
                if (nDepth && !bEscape && eState != EState::quote_string)
                    nDepth--;
                break;
            case '\\':
                if (bEscape)    // Keep this character
                {
                    bEscape = false;
                    break;
                }
                // Escape character
                bEscape = true;
                bSkip = true;
                break;
            case '"':
                if (bEscape)    // Keep this character
                {
                    bEscape = false;
                    break;
                }
                if (eState == EState::value_or_comma || eState == EState::comma_or_end) // Invalid state, quoted string cannot start in the middle
                    throw SArgumentParseException("Incorrect value (quoted string cannot start in the middle of another string)!");
                if (eState == EState::value_or_quote) // Start of quoted string
                {
                    eState = EState::quote_string;
                    bSkip = true;
                }
                else if (eState == EState::quote_string) // End of quoted string
                {
                    eState = EState::comma_or_end;
                    bSkip = true;
                }
                break;
            case ',':
            case ';':
                if (bEscape) // Keep this character
                {
                    bEscape = false;
                    break;
                }
                if (eState == EState::quote_string) // Comma is allowed in quoted strings
                    break;
                if (nDepth) // Comma is allowed when within brackets
                    break;
                if (eState == EState::value_or_quote)
                    throw SArgumentParseException("Incorrect value (string part cannot start with a comma)!");
                if (eState == EState::value_or_comma || eState == EState::comma_or_end) // Invalid state, quoted string cannot start in the middle
                {
                    bSkip = true;
                    bProcess = true;
                    eState = EState::value_or_quote;
                }
                break;
            case '\0':
                bProcess = true;
                bSkip = true;
                break;
            case ' ':
            case '\t':
            case '\n':
            case '\a':
            default:
                if (bEscape)
                {
                    bEscape = false;    // When the value previously was escaped... this was not wanted
                    nPos--;
                    ch = '\\';
                }
                if (eState != EState::quote_string)
                    eState = EState::value_or_comma;
                break;
            }


            // Add the character to the current string
            if (!bSkip)
                ssValue += ch;

            // Process the current value
            if (bProcess)
            {
                // To allow proper initialization, the value is first added
                // to the container and then assigned.
                TVar tVar{};
                rvectArgument.push_back(tVar);
                TBase::Parse(rvectArgument.back(), ssValue);
                ssValue.clear();
            }
        } while (++nPos <= rssValue.size());
    }

    std::string GetArgumentOptionMarkup()
    {
        // Request the markup of the argument(s)
        // Option markup is composed of ':' with the markup of the argument type(s)
        return TBase::GetArgumentOptionMarkup() + "[," + TBase::GetArgumentOptionMarkup() + "]";
    }

    std::string GetArgumentValueString(const TContainer& rvectArgument)
    {
        std::string ssValue;
        for (const TVar& tVar : rvectArgument)
        {
            if (ssValue.size())
                ssValue += ",";
            ssValue += TBase::GetArgumentValueString(tVar);
        }
        return ssValue;
    }

    bool MultiArgument() { return true; }
};

template <typename TVarBase = std::string, typename TVar = std::string>
class CStdStringValue : public CArgValueImpl<TVarBase>
{
public:
    CStdStringValue(CArgumentDefT<TVarBase>& rArgumentDef) : CArgValueImpl<TVarBase>(rArgumentDef)  {}

    void Parse(TVar& rssArgument, const std::string& rssValue) const
    {
        // Assign the string
        // Does the string have a quote at the beginning and at the end?
        if (rssValue.size() >= 2 && rssValue.front() == '\"' && rssValue.back() == '\"')
            rssArgument = rssValue.substr(1, rssValue.size() - 2);
        else
            rssArgument = rssValue;
    }

    std::string GetArgumentOptionMarkup() const
    {
        return "<string>";
    }

    std::string GetArgumentValueString(const TVar& rssArgument) const
    {
        return rssArgument;
    }
};

template <typename TVarBase = sdv::u8string, typename TVar = sdv::u8string>
class CSdvStringValue : public CArgValueImpl<TVarBase>
{
public:
    CSdvStringValue(CArgumentDefT<TVarBase>& rArgumentDef) : CArgValueImpl<TVarBase>(rArgumentDef)  {}

    void Parse(TVar& rssArgument, const std::string& rssValue) const
    {
        // Assign the string
        // Does the string have a quote �t the beginning and at the end?
        if (rssValue.size() >= 2 && rssValue.front() == '\"' && rssValue.back() == '\"')
            rssArgument = rssValue.substr(1, rssValue.size() - 2);
        else
            rssArgument = rssValue;
    }

    std::string GetArgumentOptionMarkup() const
    {
        return "<string>";
    }

    std::string GetArgumentValueString(const TVar& rssArgument) const
    {
        return rssArgument;
    }
};

template <typename TVarBase = std::filesystem::path>
class CPathArgValue : public CArgValueImpl<TVarBase>
{
public:
    CPathArgValue(CArgumentDefT<TVarBase>& rArgumentDef) : CArgValueImpl<TVarBase>(rArgumentDef){}

    void Parse(std::filesystem::path& rssArgument, const std::string& rssValue) const
    {
        // Assign the string
        // Does the string have a quote �t the beginning and at the end?
        if (rssValue.size() >= 2 && rssValue.front() == '\"' && rssValue.back() == '\"')
            rssArgument = rssValue.substr(1, rssValue.size() - 2);
        else
            rssArgument = rssValue;
    }

    std::string GetArgumentOptionMarkup() const
    {
        return "<path>";
    }

    std::string GetArgumentValueString(const ::std::filesystem::path& rssArgument) const
    {
        return rssArgument.u8string();
    }
};

template <typename TVarBase, typename TVar = TVarBase>
class CNumericArgValue : public CArgValueImpl<TVarBase>
{
public:
    CNumericArgValue(CArgumentDefT<TVarBase>& rArgumentDef) : CArgValueImpl<TVarBase>(rArgumentDef) {}

    void Parse(TVar& rtArgument, const std::string& rssValue) const
    {
        // Assign the number
        std::istringstream sstream;
        sstream.str(rssValue);

        // Skip whitespace
        while (!sstream.str().empty() && std::isspace(sstream.str()[0]))
            sstream.str().erase(0, 1);

        // To support int8_t and uint8_t as well (which are implemented using the char data type), stream into a 64-bit
        // integer and assign separately. Streaming into a character will not interpret the value as number, but as a single
        // character.
        if constexpr (std::is_integral_v<TVar>)
        {
            if constexpr (std::is_signed_v<TVar>)
            {
                int64_t iVal = 0;
                if (!sstream.str().empty() && !std::isdigit(sstream.str()[0]) && sstream.str()[0] != '-')
                    throw SArgumentParseException("Value is not a number!");
                sstream >> iVal;
                if (iVal < std::numeric_limits<TVar>().min())
                    throw SArgumentParseException("Value too small!");
                if (iVal > std::numeric_limits<TVar>().max())
                    throw SArgumentParseException("Value too large!");
                rtArgument = static_cast<TVar>(iVal);
            }
            else
            {
                uint64_t uiVal = 0;
                if (!sstream.str().empty() && !std::isdigit(sstream.str()[0]))
                    throw SArgumentParseException("Value is not a number!");
                sstream >> uiVal;
                if (uiVal > std::numeric_limits<TVar>().max())
                    throw SArgumentParseException("Value too large!");
                rtArgument = static_cast<TVar>(uiVal);
            }
        }
        else
        {
            if (!sstream.str().empty() && !std::isdigit(sstream.str()[0]) && sstream.str()[0] != '-' && sstream.str()[0] != '.')
                throw SArgumentParseException("Value is not a number!");
            sstream >> rtArgument;
        }
    }

    std::string GetArgumentOptionMarkup() const
    {
        return "<number>";
    }

    std::string GetArgumentValueString(const TVar& rtArgument) const
    {
        std::ostringstream sstream;
        sstream << rtArgument;
        return sstream.str();
    }
};

template <typename TVarBase, typename TVar = TVarBase>
class CEnumArgValue : public CArgValueImpl<TVarBase>
{
public:
    CEnumArgValue(CArgumentDefT<TVarBase>& rArgumentDef) : CArgValueImpl<TVarBase>(rArgumentDef)    {}

    void Parse(TVar& rtArgument, const std::string& rssValue) const
    {
        // Find the value
        bool bFound = false;
        for(const typename CArgumentDefT<TVarBase>::SEnumAssociation& rsEnumAssociation :
            CArgValueImpl<TVarBase>::m_rArgumentDef.GetAssociations())
        {
            if (rssValue == rsEnumAssociation.ssValueText)
            {
                rtArgument = rsEnumAssociation.eValue;
                bFound = true;
                break;
            }
        }

        // Error?
        if (!bFound)
            throw SArgumentParseException("Incorrect value!");
    }

    std::string GetArgumentOptionMarkup() const
    {
        return "<...>";
    }

    std::string GetOptionDetails(size_t nMaxStringLen)
    {
        size_t nMaxNameLen = 0;
        for (const typename CArgumentDefT<TVarBase>::SEnumAssociation& rsEnumAssociation :
            CArgValueImpl<TVarBase>::m_rArgumentDef.GetAssociations())
        {
            nMaxNameLen = std::max(rsEnumAssociation.ssValueText.size(), nMaxNameLen);
        }
        std::string ssResult;
        bool bFirst = true;
        for (const typename CArgumentDefT<TVarBase>::SEnumAssociation& rsEnumAssociation :
            CArgValueImpl<TVarBase>::m_rArgumentDef.GetAssociations())
        {
            // Insert a newline starting with the second enum value
            if (!bFirst) ssResult += "\n";
            bFirst = false;

            // A space
            ssResult += " ";

            // The enum value
            ssResult += rsEnumAssociation.ssValueText;

            // Extra space
            ssResult.insert(ssResult.end(), nMaxNameLen - rsEnumAssociation.ssValueText.size(), ' ');

            // Separator
            ssResult += " - ";

            // Start position of text
            size_t nStartTextPos = nMaxNameLen + 4; // Include ' ' and ' - '

            // Create a text block
            std::stringstream sstream;
            PrintBlock(sstream, rsEnumAssociation.ssDescription, nStartTextPos, nStartTextPos, nMaxStringLen);

            // Text
            ssResult += sstream.str();
        }
        return ssResult;
    }

    std::string GetArgumentValueString(const TVar& rtArgument) const
    {
        // Find the value
        for (const typename CArgumentDefT<TVarBase>::SEnumAssociation& rsEnumAssociation :
            CArgValueImpl<TVarBase>::m_rArgumentDef.GetAssociations())
        {
            if (rtArgument == rsEnumAssociation.eValue)
                return rsEnumAssociation.ssValueText;
        }
        return std::string();
    }
};

template <typename TArgument, typename TEnable = void>
class CArgumentProvideImpl;

template <> class CArgumentProvideImpl<std::string, void> :
    public CValueAssignment<CStdStringValue<>, std::string>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::string>& rArgumentDef) :
        CValueAssignment<CStdStringValue<>, std::string>(rArgumentDef)
    {}
};


template <> class CArgumentProvideImpl<sdv::u8string, void> :
    public CValueAssignment<CSdvStringValue<>, sdv::u8string>
{
public:
    CArgumentProvideImpl(CArgumentDefT<sdv::u8string>& rArgumentDef) :
        CValueAssignment<CSdvStringValue<>, sdv::u8string>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<std::filesystem::path, void> :
    public CValueAssignment<CPathArgValue<>, std::filesystem::path>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::filesystem::path>& rArgumentDef) :
        CValueAssignment<CPathArgValue<>, std::filesystem::path>(rArgumentDef)
    {}
};

template <typename TArgument>
class CArgumentProvideImpl<TArgument, typename std::enable_if_t<std::is_arithmetic<TArgument>::value>> :
    public CValueAssignment<CNumericArgValue<TArgument>, TArgument>
{
public:
    CArgumentProvideImpl(CArgumentDefT<TArgument>& rArgumentDef) :
        CValueAssignment<CNumericArgValue<TArgument>, TArgument>(rArgumentDef)
    {}
};

template <typename TArgument>
class CArgumentProvideImpl<std::vector<TArgument>, typename std::enable_if_t<std::is_arithmetic<TArgument>::value>> :
    public CValueAssignment<
        CContainerArgValue<std::vector<TArgument>, CNumericArgValue<std::vector<TArgument>, TArgument>, TArgument>,
        std::vector<TArgument>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::vector<TArgument>>& rArgumentDef) :
        CValueAssignment<
            CContainerArgValue<std::vector<TArgument>, CNumericArgValue<std::vector<TArgument>, TArgument>, TArgument>,
            std::vector<TArgument>>(rArgumentDef)
    {}
};

template <typename TArgument>
class CArgumentProvideImpl<sdv::sequence<TArgument>, typename std::enable_if_t<std::is_arithmetic<TArgument>::value>> :
    public CValueAssignment<
        CContainerArgValue<sdv::sequence<TArgument>, CNumericArgValue<sdv::sequence<TArgument>, TArgument>, TArgument>,
    sdv::sequence<TArgument>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<sdv::sequence<TArgument>>& rArgumentDef) :
        CValueAssignment<
            CContainerArgValue<sdv::sequence<TArgument>, CNumericArgValue<sdv::sequence<TArgument>, TArgument>, TArgument>,
        sdv::sequence<TArgument>>(rArgumentDef)
    {}
};

template <typename TArgument>
class CArgumentProvideImpl<std::list<TArgument>, typename std::enable_if_t<std::is_arithmetic<TArgument>::value>> :
    public CValueAssignment<CContainerArgValue<std::list<TArgument>, CNumericArgValue<std::list<TArgument>, TArgument>, TArgument>,
        std::list<TArgument>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::list<TArgument>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::list<TArgument>, CNumericArgValue<std::list<TArgument>, TArgument>, TArgument>,
            std::list<TArgument>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<std::vector<std::string>, void> :
    public CValueAssignment<CContainerArgValue<std::vector<std::string>, CStdStringValue<std::vector<std::string>>, std::string>,
    std::vector<std::string>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::vector<std::string>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::vector<std::string>, CStdStringValue<std::vector<std::string>>, std::string>,
        std::vector<std::string>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<sdv::sequence<std::string>, void> :
    public CValueAssignment<CContainerArgValue<sdv::sequence<std::string>, CStdStringValue<sdv::sequence<std::string>>, std::string>,
    sdv::sequence<std::string>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<sdv::sequence<std::string>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<sdv::sequence<std::string>, CStdStringValue<sdv::sequence<std::string>>, std::string>,
        sdv::sequence<std::string>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<std::list<std::string>, void> :
    public CValueAssignment<CContainerArgValue<std::list<std::string>, CStdStringValue<std::list<std::string>>, std::string>,
    std::list<std::string>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::list<std::string>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::list<std::string>, CStdStringValue<std::list<std::string>>, std::string>,
        std::list<std::string>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<std::vector<sdv::u8string>, void> :
    public CValueAssignment<CContainerArgValue<std::vector<sdv::u8string>, CSdvStringValue<std::vector<sdv::u8string>>, sdv::u8string>,
    std::vector<sdv::u8string>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::vector<sdv::u8string>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::vector<sdv::u8string>, CSdvStringValue<std::vector<sdv::u8string>>, sdv::u8string>,
        std::vector<sdv::u8string>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<sdv::sequence<sdv::u8string>, void> :
    public CValueAssignment<CContainerArgValue<sdv::sequence<sdv::u8string>, CSdvStringValue<sdv::sequence<sdv::u8string>>, sdv::u8string>,
    sdv::sequence<sdv::u8string>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<sdv::sequence<sdv::u8string>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<sdv::sequence<sdv::u8string>, CSdvStringValue<sdv::sequence<sdv::u8string>>, sdv::u8string>,
        sdv::sequence<sdv::u8string>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<std::list<sdv::u8string>, void> :
    public CValueAssignment<CContainerArgValue<std::list<sdv::u8string>, CSdvStringValue<std::list<sdv::u8string>>, sdv::u8string>,
    std::list<sdv::u8string>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::list<sdv::u8string>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::list<sdv::u8string>, CSdvStringValue<std::list<sdv::u8string>>, sdv::u8string>,
        std::list<sdv::u8string>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<std::vector<std::filesystem::path>, void> :
    public CValueAssignment<CContainerArgValue<std::vector<std::filesystem::path>,
        CPathArgValue<std::vector<std::filesystem::path>>, std::filesystem::path>, std::vector<std::filesystem::path>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::vector<std::filesystem::path>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::vector<std::filesystem::path>,
            CPathArgValue<std::vector<std::filesystem::path>>, std::filesystem::path>,
            std::vector<std::filesystem::path>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<sdv::sequence<std::filesystem::path>, void> :
    public CValueAssignment<CContainerArgValue<sdv::sequence<std::filesystem::path>,
        CPathArgValue<sdv::sequence<std::filesystem::path>>, std::filesystem::path>, sdv::sequence<std::filesystem::path>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<sdv::sequence<std::filesystem::path>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<sdv::sequence<std::filesystem::path>,
            CPathArgValue<sdv::sequence<std::filesystem::path>>, std::filesystem::path>,
        sdv::sequence<std::filesystem::path>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<std::list<std::filesystem::path>, void> :
    public CValueAssignment<CContainerArgValue<std::list<std::filesystem::path>,
        CPathArgValue<std::list<std::filesystem::path>>, std::filesystem::path>, std::list<std::filesystem::path>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::list<std::filesystem::path>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::list<std::filesystem::path>, CPathArgValue<std::list<std::filesystem::path>>,
            std::filesystem::path>, std::list<std::filesystem::path>>(rArgumentDef)
    {}
};

template <typename TEnum>
class CArgumentProvideImpl<TEnum, typename std::enable_if_t<std::is_enum<TEnum>::value>> :
    public CValueAssignment<CEnumArgValue<TEnum>, TEnum>
{
public:
    CArgumentProvideImpl(CArgumentDefT<TEnum>& rArgumentDef) :
        CValueAssignment<CEnumArgValue<TEnum>, TEnum>(rArgumentDef)
    {}
};

template <typename TEnum>
class CArgumentProvideImpl<std::vector<TEnum>, typename std::enable_if_t<std::is_enum<TEnum>::value>> :
    public CValueAssignment<CContainerArgValue<std::vector<TEnum>, CEnumArgValue<std::vector<TEnum>, TEnum>, TEnum>,
        std::vector<TEnum>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::vector<TEnum>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::vector<TEnum>, CEnumArgValue<std::vector<TEnum>, TEnum>, TEnum>,
            std::vector<TEnum>>(rArgumentDef)
    {}
};

template <typename TEnum>
class CArgumentProvideImpl<sdv::sequence<TEnum>, typename std::enable_if_t<std::is_enum<TEnum>::value>> :
    public CValueAssignment<CContainerArgValue<sdv::sequence<TEnum>, CEnumArgValue<sdv::sequence<TEnum>, TEnum>, TEnum>,
    sdv::sequence<TEnum>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<sdv::sequence<TEnum>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<sdv::sequence<TEnum>, CEnumArgValue<sdv::sequence<TEnum>, TEnum>, TEnum>,
        sdv::sequence<TEnum>>(rArgumentDef)
    {}
};

template <typename TEnum>
class CArgumentProvideImpl<std::list<TEnum>, typename std::enable_if_t<std::is_enum<TEnum>::value>> :
    public CValueAssignment<CContainerArgValue<std::list<TEnum>, CEnumArgValue<std::list<TEnum>, TEnum>, TEnum>, std::list<TEnum>>
{
public:
    CArgumentProvideImpl(CArgumentDefT<std::list<TEnum>>& rArgumentDef) :
        CValueAssignment<CContainerArgValue<std::list<TEnum>, CEnumArgValue<std::list<TEnum>, TEnum>, TEnum>,
            std::list<TEnum>>(rArgumentDef)
    {}
};

template <> class CArgumentProvideImpl<bool, void> : public CArgValueImpl<bool>
{
public:
    CArgumentProvideImpl(CArgumentDefT<bool>& rArgumentDef) : CArgValueImpl<bool>(rArgumentDef) {}

    void Parse(bool& rbArgument, const std::string& rssValue) const
    {
        // Differentiate between a flag and a boolean.
        if (m_rArgumentDef.CheckFlag(EArgumentFlags::flag_option))
        {
            // Interpret the value (either + or -).
            if (rssValue == "+")
                rbArgument = true;
            else if (rssValue == "-")
                rbArgument = false;
            else
                throw SArgumentParseException("Incorrect value: + or - expected!");
        }
        else
        {
            // The value can be ignored...
            rbArgument = true;
        }
    }

    std::string GetArgumentOptionMarkup() const
    {
        if (m_rArgumentDef.CheckFlag(EArgumentFlags::flag_option))
            return "<+|->";
        else
            return std::string();
    }

    std::string GetArgumentValueString(const bool& rbArgument) const
    {
        if (m_rArgumentDef.CheckFlag(EArgumentFlags::flag_option))
            return rbArgument ? "signalled (+)" : "not signalled (-)";
        else
            return rbArgument ? "true" : "false";
    }
};

template <typename TArgument>
class CArgumentProvide : public CArgumentProvideImpl<TArgument>, public IArgumentProvide
{
public:
    CArgumentProvide(CArgumentDefT<TArgument>& rArgumentDef, TArgument& rtArgument) :
        CArgumentProvideImpl<TArgument>(rArgumentDef), m_rtArgument(rtArgument), m_bArgumentAssigned(false)
    {};

    virtual void ArgumentAssign(const std::string& rssValue) override
    {
        CArgumentProvideImpl<TArgument>::Parse(m_rtArgument, rssValue);
        m_bArgumentAssigned = true;
    }

    virtual std::string GetArgumentOptionMarkup() override
    {
        return CArgumentProvideImpl<TArgument>::GetArgumentOptionMarkup();
    }

    virtual std::string GetArgumentOptionDetails(size_t nMaxStringLen) override
    {
        return CArgumentProvideImpl<TArgument>::GetOptionDetails(nMaxStringLen);
    }

    virtual std::string GetArgumentValueString() override
    {
        return CArgumentProvideImpl<TArgument>::GetArgumentValueString(m_rtArgument);
    }

    virtual bool IsArgumentAssigned() override
    {
        return m_bArgumentAssigned;
    }

    virtual bool AllowMultiArgumentAssign() override
    {
        return CArgumentProvideImpl<TArgument>::MultiArgument();
    }

private:
    TArgument&  m_rtArgument;
    bool        m_bArgumentAssigned;
};

template <typename TVar, typename... TArgumentGroup>
CArgumentDefBase::CArgumentDefBase(CCommandLine& rCLParser, const std::string& rssArgument,
    const std::shared_ptr<SGroupDef>& rptrGroup,const std::string& rssHelpText, uint32_t uiFlags, TVar& rtVar,
    TArgumentGroup... nArgumentGroup) :
    m_rCLParser(rCLParser), m_ptrGroup(rptrGroup), m_setArgumentGroups({ static_cast<size_t>(nArgumentGroup)... })
{
    // Set the flags, but remove the option/sub-option information.
    m_uiFlags = uiFlags &
        ~static_cast<uint32_t>(EArgumentFlags::option_argument) &
        ~static_cast<uint32_t>(EArgumentFlags::sub_option_argument);
    if (!rssArgument.empty())
    {
        std::string ssArgument = rssArgument;
        size_t nPos = 0;
        do
        {
            size_t nSeparator = ssArgument.find('|', nPos);
            std::string ssArgumentPart = helper::trim(ssArgument.substr(nPos, nSeparator - nPos));
            if (!uiFlags && !ssArgumentPart.empty())
                for (char& rc : ssArgumentPart)
                    rc = static_cast<char>(std::tolower(rc));
            if (ssArgumentPart.size())
            {
                SOptionName sOptionName{ ssArgumentPart, uiFlags };
                m_vecOptionNames.push_back(sOptionName);
            }
            if (nSeparator != std::string::npos)
                nSeparator++;
            nPos = nSeparator;
        } while (nPos < ssArgument.size());
    }
    if (!rssHelpText.empty()) m_ssHelpText = rssHelpText;
    m_ptrArgProvide = std::make_shared<CArgumentProvide<TVar>>(static_cast<CArgumentDefT<TVar>&>(*this), rtVar);
}

template <typename TCharType>
CArgumentIterator::CArgumentIterator(size_t nArgs, const TCharType** rgszArgs)
{
    // Start with the second argument (skipping the app name).
    for (size_t nArg = 1; rgszArgs && nArg < nArgs; nArg++)
    {
        // Create an UTF-8 string from the argument
        std::string ssArg = sdv::MakeUtf8String(rgszArgs[nArg]);
        if (ssArg.empty())
        {
            SArgumentParseException exception("Invalid argument!");
            exception.AddIndex(nArg);
            throw exception;
        }

        // Add to the queue
        m_queueArguments.push(ssArg);
    }
}

inline std::optional<std::string> CArgumentIterator::GetNext()
{
    if (m_queueArguments.empty()) return {};
    std::string ssArg = std::move(m_queueArguments.front());
    m_queueArguments.pop();
    m_nCounter++;
    return ssArg;
}

inline size_t CArgumentIterator::GetIndexOfLastArg() const
{
    return m_nCounter;
}

template <typename TCharType>
inline void CCommandLine::Parse(size_t nArgs, const TCharType** rgszArgs)
{
    switch (m_uiParseFlags)
    {
    case static_cast<uint32_t>(EParseFlags::assignment_character):
    case static_cast<uint32_t>(EParseFlags::no_assignment_character):
    case static_cast<uint32_t>(EParseFlags::assignment_next_arg):
        break;
    default:
        throw (SArgumentParseException("Invalid parse mode!"));
    }

    if (!nArgs || !rgszArgs[0])
    {
        SArgumentParseException exception("Missing arguments!");
        exception.AddIndex(0);
        throw exception;
    }

    CArgumentIterator argit(nArgs, rgszArgs);

    // Iterate through the arguments. Skip the first argument, which points to the name of the application.
    while (true)
    {
        // Get the next argument
        auto optArg = argit.GetNext();
        if (!optArg) return;

        // Create an UTF-8 string from the argument
        std::string ssArg = *optArg;

        if (ssArg.empty())
            throw (SArgumentParseException("Invalid argument!"));

        // Check for the (sub-)option character '-' (or under Windows '/').
        bool bSubOption = false;
        bool bOption = false;
        if (ssArg.size() >= 2 && ssArg[0] == '-' && ssArg[1] == '-')
            bSubOption = true;
        else if (ssArg.size() >= 2 && ssArg[0] == '-')
            bOption = true;
#ifdef _WIN32
        else if (ssArg.size() >= 2 && ssArg[0] == '/')
            bOption = true;
#endif

        // Find argument function
        auto fnFindAndAssign = [&](CArgumentDefBase& rArgument, const std::string& rssOptionName) -> bool
        {
            // Compare the argument name
            try
            {
                bool bRet = rArgument.CompareNameAndAssign(argit, ssArg, rssOptionName,
                    CheckParseFlag(EParseFlags::no_assignment_character) || CheckParseFlag(EParseFlags::assignment_next_arg));
                if (bRet)
                {
                    std::string ssOptionName(rssOptionName);
                    m_lstSupplied.emplace_back(std::ref(rArgument), ssArg);
                }
                return bRet;
            }
            catch (SArgumentParseException& rexception)
            {
                rexception.AddIndex(argit.GetIndexOfLastArg());
                rexception.AddArgument(ssArg);
                throw;
            }
        };

        // Find the argument
        bool bFound = false;
        if (bOption)
        {
            for (auto& rvtOption : m_mapSortedOptions)
            {
                bFound = fnFindAndAssign(rvtOption.second, rvtOption.first);
                if (bFound) break;
            }
        }
        else if (bSubOption)
        {
            for (auto& rvtOption : m_mapSortedSubOptions)
            {
                bFound = fnFindAndAssign(rvtOption.second, rvtOption.first);
                if (bFound) break;
            }
        } else // Default argument
            bFound = m_ptrDefaultArg ? fnFindAndAssign(*m_ptrDefaultArg.get(), {}) : false;

        if (!bFound)
        {
            SArgumentParseException exception("Argument unknown");
            exception.AddIndex(argit.GetIndexOfLastArg());
            exception.AddArgument(ssArg);
            throw exception;
        }
    }
}

template <typename TVar>
inline CArgumentDefT<TVar>& CCommandLine::DefineDefaultArgument(TVar& rtVar, const std::string& rssHelpText)
{
    uint32_t uiFlags = static_cast<uint32_t>(EArgumentFlags::default_argument);
    m_ptrDefaultArg = std::make_shared<CArgumentDefT<TVar>>(*this, "", m_ptrCurrentGroup, rssHelpText, uiFlags, rtVar);
    return static_cast<CArgumentDefT<TVar>&>(*m_ptrDefaultArg.get());
}

template <typename TVar, typename... TArgumentGroup>
inline CArgumentDefT<TVar>& CCommandLine::DefineOption(const std::string& rssArgument, TVar& rtVar, const std::string& rssHelpText,
    bool bCaseSensitive /*= true*/, size_t nArgumentGroup /*= 0*/, TArgumentGroup... nAdditionalGroups)
{
    uint32_t uiFlags = static_cast<uint32_t>(EArgumentFlags::option_argument);
    if (bCaseSensitive) uiFlags |= static_cast<uint32_t>(EArgumentFlags::case_sensitive);
    if constexpr (std::is_same_v<TVar, bool>)
        uiFlags |= static_cast<uint32_t>(EArgumentFlags::bool_option);
    auto ptrOption = std::make_shared<CArgumentDefT<TVar>>(*this, rssArgument, m_ptrCurrentGroup, rssHelpText, uiFlags, rtVar,
        nArgumentGroup, nAdditionalGroups...);
    m_lstOptionArgs.push_back(ptrOption);
    m_mapSortedOptions.emplace(rssArgument, *ptrOption.get());
    CArgumentDefT<TVar>& rArg = static_cast<CArgumentDefT<TVar>&>(*ptrOption.get());
    return rArg;
}

template <typename TVar, typename... TArgumentGroup>
inline CArgumentDefT<TVar>& CCommandLine::DefineSubOption(const std::string& rssArgument, TVar& rtVar, const std::string& rssHelpText,
    bool bCaseSensitive /*= true*/, size_t nArgumentGroup /*= 0*/, TArgumentGroup... nAdditionalGroups)
{
    uint32_t uiFlags = static_cast<uint32_t>(EArgumentFlags::sub_option_argument);
    if (bCaseSensitive) uiFlags |= static_cast<uint32_t>(EArgumentFlags::case_sensitive);
    if constexpr (std::is_same_v<TVar, bool>)
        uiFlags |= static_cast<uint32_t>(EArgumentFlags::bool_option);
    auto ptrOption = std::make_shared<CArgumentDefT<TVar>>(*this, rssArgument, m_ptrCurrentGroup, rssHelpText, uiFlags, rtVar,
        nArgumentGroup, nAdditionalGroups...);
    m_lstOptionArgs.push_back(ptrOption);
    m_mapSortedSubOptions.emplace(rssArgument, *ptrOption.get());
    CArgumentDefT<TVar>& rArg = static_cast<CArgumentDefT<TVar>&>(*ptrOption.get());
    return rArg;
}

template <typename... TArgumentGroup>
inline CArgumentDefT<bool>& CCommandLine::DefineFlagOption(const std::string& rssArgument, bool& rbFlag, const std::string& rssHelpText,
    bool bCaseSensitive /*= true*/, size_t nArgumentGroup /*= 0*/, TArgumentGroup... nAdditionalGroups)
{
    uint32_t uiFlags = static_cast<uint32_t>(EArgumentFlags::option_argument);
    uiFlags |= static_cast<uint32_t>(EArgumentFlags::flag_option);
    if (bCaseSensitive) uiFlags |= static_cast<uint32_t>(EArgumentFlags::case_sensitive);
    auto ptrOption = std::make_shared<CArgumentDefT<bool>>(*this, rssArgument, m_ptrCurrentGroup, rssHelpText, uiFlags, rbFlag,
        nArgumentGroup, nAdditionalGroups...);
    m_lstOptionArgs.push_back(ptrOption);
    m_mapSortedOptions.emplace(rssArgument, *ptrOption.get());
    return static_cast<CArgumentDefT<bool>&>(*ptrOption.get());
}

template <typename... TArgumentGroup>
inline CArgumentDefT<bool>& CCommandLine::DefineFlagSubOption(const std::string& rssArgument, bool& rbFlag, const std::string& rssHelpText,
    bool bCaseSensitive /*= true*/, size_t nArgumentGroup /*= 0*/, TArgumentGroup... nAdditionalGroups)
{
    uint32_t uiFlags = static_cast<uint32_t>(EArgumentFlags::sub_option_argument);
    uiFlags |= static_cast<uint32_t>(EArgumentFlags::flag_option);
    if (bCaseSensitive) uiFlags |= static_cast<uint32_t>(EArgumentFlags::case_sensitive);
    auto ptrOption = std::make_shared<CArgumentDefT<bool>>(*this, rssArgument, m_ptrCurrentGroup, rssHelpText, uiFlags, rbFlag,
        nArgumentGroup, nAdditionalGroups...);
    m_lstOptionArgs.push_back(ptrOption);
    m_mapSortedSubOptions.emplace(rssArgument, *ptrOption.get());
    CArgumentDefT<bool>& rArg = static_cast<CArgumentDefT<bool>&>(*ptrOption.get());
    return rArg;
}

#endif // !defined CMDLN_PARSER_H