summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common-install/common-install.vcxitems19
-rw-r--r--common-install/external/WinReg.hpp2000
-rw-r--r--common-install/utility-install.hpp46
-rw-r--r--common/common.vcxitems22
-rw-r--r--common/external/clipp.h7027
-rw-r--r--common/rawaccel-userspace.hpp113
-rw-r--r--common/rawaccel.hpp176
-rw-r--r--common/vec2.h9
-rw-r--r--common/x64-util.hpp22
-rw-r--r--console/console.cpp53
-rw-r--r--console/console.vcxproj97
-rw-r--r--driver/driver.cpp611
-rw-r--r--driver/driver.h47
-rw-r--r--driver/driver.vcxproj133
-rw-r--r--installer/installer.cpp80
-rw-r--r--installer/installer.vcxproj98
-rw-r--r--rawaccel.sln56
-rw-r--r--uninstaller/uninstaller.cpp29
-rw-r--r--uninstaller/uninstaller.vcxproj91
19 files changed, 10729 insertions, 0 deletions
diff --git a/common-install/common-install.vcxitems b/common-install/common-install.vcxitems
new file mode 100644
index 0000000..fa4e370
--- /dev/null
+++ b/common-install/common-install.vcxitems
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup Label="Globals">
+ <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+ <HasSharedItems>true</HasSharedItems>
+ <ItemsProjectGuid>{058d66c6-d88b-4fdb-b0e4-0a6fe7483b95}</ItemsProjectGuid>
+ </PropertyGroup>
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectCapability Include="SourceItemsFromImports" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="$(MSBuildThisFileDirectory)utility-install.hpp" />
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/common-install/external/WinReg.hpp b/common-install/external/WinReg.hpp
new file mode 100644
index 0000000..9ce2396
--- /dev/null
+++ b/common-install/external/WinReg.hpp
@@ -0,0 +1,2000 @@
+#ifndef GIOVANNI_DICANIO_WINREG_HPP_INCLUDED
+#define GIOVANNI_DICANIO_WINREG_HPP_INCLUDED
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// *** Modern C++ Wrappers Around Windows Registry C API ***
+//
+// Copyright (C) by Giovanni Dicanio
+//
+// First version: 2017, January 22nd
+// Last update: 2020, June 11th
+//
+// E-mail: <first name>.<last name> AT REMOVE_THIS gmail.com
+//
+// Registry key handles are safely and conveniently wrapped
+// in the RegKey resource manager C++ class.
+//
+// Errors are signaled throwing exceptions of class RegException.
+// In addition, there are also some methods named like TryGet...
+// (e.g. TryGetDwordValue), that _try_ to perform the given query,
+// and return a std::optional value.
+// (In particular, on failure, the returned std::optional object
+// doesn't contain any value).
+//
+// Unicode UTF-16 strings are represented using the std::wstring class;
+// ATL's CString is not used, to avoid dependencies from ATL or MFC.
+//
+// Compiler: Visual Studio 2019
+// Code compiles cleanly at /W4 on both 32-bit and 64-bit builds.
+//
+// Requires building in Unicode mode (which is the default since VS2005).
+//
+// ===========================================================================
+//
+// The MIT License(MIT)
+//
+// Copyright(c) 2017-2020 by Giovanni Dicanio
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files(the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions :
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+#include <Windows.h> // Windows Platform SDK
+#include <crtdbg.h> // _ASSERTE
+
+#include <memory> // std::unique_ptr, std::make_unique
+#include <optional> // std::optional
+#include <string> // std::wstring
+#include <system_error> // std::system_error
+#include <utility> // std::swap, std::pair
+#include <vector> // std::vector
+
+
+
+namespace winreg
+{
+
+// Forward class declarations
+class RegException;
+class RegResult;
+
+
+//------------------------------------------------------------------------------
+// Safe, efficient and convenient C++ wrapper around HKEY registry key handles.
+//
+// This class is movable but not copyable.
+//
+// This class is designed to be very *efficient* and low-overhead, for example:
+// non-throwing operations are carefully marked as noexcept, so the C++ compiler
+// can emit optimized code.
+//
+// Moreover, this class just wraps a raw HKEY handle, without any
+// shared-ownership overhead like in std::shared_ptr; you can think of this
+// class kind of like a std::unique_ptr for HKEYs.
+//
+// The class is also swappable (defines a custom non-member swap);
+// relational operators are properly overloaded as well.
+//------------------------------------------------------------------------------
+class RegKey
+{
+public:
+
+ //
+ // Construction/Destruction
+ //
+
+ // Initialize as an empty key handle
+ RegKey() noexcept = default;
+
+ // Take ownership of the input key handle
+ explicit RegKey(HKEY hKey) noexcept;
+
+ // Open the given registry key if it exists, else create a new key.
+ // Uses default KEY_READ|KEY_WRITE access.
+ // For finer grained control, call the Create() method overloads.
+ // Throw RegException on failure.
+ RegKey(HKEY hKeyParent, const std::wstring& subKey);
+
+ // Open the given registry key if it exists, else create a new key.
+ // Allow the caller to specify the desired access to the key (e.g. KEY_READ
+ // for read-only access).
+ // For finer grained control, call the Create() method overloads.
+ // Throw RegException on failure.
+ RegKey(HKEY hKeyParent, const std::wstring& subKey, REGSAM desiredAccess);
+
+
+ // Take ownership of the input key handle.
+ // The input key handle wrapper is reset to an empty state.
+ RegKey(RegKey&& other) noexcept;
+
+ // Move-assign from the input key handle.
+ // Properly check against self-move-assign (which is safe and does nothing).
+ RegKey& operator=(RegKey&& other) noexcept;
+
+ // Ban copy
+ RegKey(const RegKey&) = delete;
+ RegKey& operator=(const RegKey&) = delete;
+
+ // Safely close the wrapped key handle (if any)
+ ~RegKey() noexcept;
+
+
+ //
+ // Properties
+ //
+
+ // Access the wrapped raw HKEY handle
+ [[nodiscard]] HKEY Get() const noexcept;
+
+ // Is the wrapped HKEY handle valid?
+ [[nodiscard]] bool IsValid() const noexcept;
+
+ // Same as IsValid(), but allow a short "if (regKey)" syntax
+ [[nodiscard]] explicit operator bool() const noexcept;
+
+ // Is the wrapped handle a predefined handle (e.g.HKEY_CURRENT_USER) ?
+ [[nodiscard]] bool IsPredefined() const noexcept;
+
+
+ //
+ // Operations
+ //
+
+ // Close current HKEY handle.
+ // If there's no valid handle, do nothing.
+ // This method doesn't close predefined HKEY handles (e.g. HKEY_CURRENT_USER).
+ void Close() noexcept;
+
+ // Transfer ownership of current HKEY to the caller.
+ // Note that the caller is responsible for closing the key handle!
+ [[nodiscard]] HKEY Detach() noexcept;
+
+ // Take ownership of the input HKEY handle.
+ // Safely close any previously open handle.
+ // Input key handle can be nullptr.
+ void Attach(HKEY hKey) noexcept;
+
+ // Non-throwing swap;
+ // Note: There's also a non-member swap overload
+ void SwapWith(RegKey& other) noexcept;
+
+
+ //
+ // Wrappers around Windows Registry APIs.
+ // See the official MSDN documentation for these APIs for detailed explanations
+ // of the wrapper method parameters.
+ //
+
+ // Wrapper around RegCreateKeyEx, that allows you to specify desired access
+ void Create(
+ HKEY hKeyParent,
+ const std::wstring& subKey,
+ REGSAM desiredAccess = KEY_READ | KEY_WRITE
+ );
+
+ // Wrapper around RegCreateKeyEx
+ void Create(
+ HKEY hKeyParent,
+ const std::wstring& subKey,
+ REGSAM desiredAccess,
+ DWORD options,
+ SECURITY_ATTRIBUTES* securityAttributes,
+ DWORD* disposition
+ );
+
+ // Wrapper around RegOpenKeyEx
+ void Open(
+ HKEY hKeyParent,
+ const std::wstring& subKey,
+ REGSAM desiredAccess = KEY_READ | KEY_WRITE
+ );
+
+ // Wrapper around RegCreateKeyEx, that allows you to specify desired access
+ [[nodiscard]] RegResult TryCreate(
+ HKEY hKeyParent,
+ const std::wstring& subKey,
+ REGSAM desiredAccess = KEY_READ | KEY_WRITE
+ ) noexcept;
+
+ // Wrapper around RegCreateKeyEx
+ [[nodiscard]] RegResult TryCreate(
+ HKEY hKeyParent,
+ const std::wstring& subKey,
+ REGSAM desiredAccess,
+ DWORD options,
+ SECURITY_ATTRIBUTES* securityAttributes,
+ DWORD* disposition
+ ) noexcept;
+
+ // Wrapper around RegOpenKeyEx
+ [[nodiscard]] RegResult TryOpen(
+ HKEY hKeyParent,
+ const std::wstring& subKey,
+ REGSAM desiredAccess = KEY_READ | KEY_WRITE
+ ) noexcept;
+
+
+ //
+ // Registry Value Setters
+ //
+
+ void SetDwordValue(const std::wstring& valueName, DWORD data);
+ void SetQwordValue(const std::wstring& valueName, const ULONGLONG& data);
+ void SetStringValue(const std::wstring& valueName, const std::wstring& data);
+ void SetExpandStringValue(const std::wstring& valueName, const std::wstring& data);
+ void SetMultiStringValue(const std::wstring& valueName, const std::vector<std::wstring>& data);
+ void SetBinaryValue(const std::wstring& valueName, const std::vector<BYTE>& data);
+ void SetBinaryValue(const std::wstring& valueName, const void* data, DWORD dataSize);
+
+
+ //
+ // Registry Value Getters
+ //
+
+ [[nodiscard]] DWORD GetDwordValue(const std::wstring& valueName) const;
+ [[nodiscard]] ULONGLONG GetQwordValue(const std::wstring& valueName) const;
+ [[nodiscard]] std::wstring GetStringValue(const std::wstring& valueName) const;
+
+ enum class ExpandStringOption
+ {
+ DontExpand,
+ Expand
+ };
+
+ [[nodiscard]] std::wstring GetExpandStringValue(
+ const std::wstring& valueName,
+ ExpandStringOption expandOption = ExpandStringOption::DontExpand
+ ) const;
+
+ [[nodiscard]] std::vector<std::wstring> GetMultiStringValue(const std::wstring& valueName) const;
+ [[nodiscard]] std::vector<BYTE> GetBinaryValue(const std::wstring& valueName) const;
+
+
+ //
+ // Registry Value Getters Returning std::optional
+ // (instead of throwing RegException on error)
+ //
+
+ [[nodiscard]] std::optional<DWORD> TryGetDwordValue(const std::wstring& valueName) const;
+ [[nodiscard]] std::optional<ULONGLONG> TryGetQwordValue(const std::wstring& valueName) const;
+ [[nodiscard]] std::optional<std::wstring> TryGetStringValue(const std::wstring& valueName) const;
+
+ [[nodiscard]] std::optional<std::wstring> TryGetExpandStringValue(
+ const std::wstring& valueName,
+ ExpandStringOption expandOption = ExpandStringOption::DontExpand
+ ) const;
+
+ [[nodiscard]] std::optional<std::vector<std::wstring>> TryGetMultiStringValue(const std::wstring& valueName) const;
+ [[nodiscard]] std::optional<std::vector<BYTE>> TryGetBinaryValue(const std::wstring& valueName) const;
+
+
+ //
+ // Query Operations
+ //
+
+ void QueryInfoKey(DWORD& subKeys, DWORD &values, FILETIME& lastWriteTime) const;
+
+ // Return the DWORD type ID for the input registry value
+ [[nodiscard]] DWORD QueryValueType(const std::wstring& valueName) const;
+
+ // Enumerate the subkeys of the registry key, using RegEnumKeyEx
+ [[nodiscard]] std::vector<std::wstring> EnumSubKeys() const;
+
+ // Enumerate the values under the registry key, using RegEnumValue.
+ // Returns a vector of pairs: In each pair, the wstring is the value name,
+ // the DWORD is the value type.
+ [[nodiscard]] std::vector<std::pair<std::wstring, DWORD>> EnumValues() const;
+
+
+ //
+ // Misc Registry API Wrappers
+ //
+
+ void DeleteValue(const std::wstring& valueName);
+ void DeleteKey(const std::wstring& subKey, REGSAM desiredAccess);
+ void DeleteTree(const std::wstring& subKey);
+ void CopyTree(const std::wstring& sourceSubKey, const RegKey& destKey);
+ void FlushKey();
+ void LoadKey(const std::wstring& subKey, const std::wstring& filename);
+ void SaveKey(const std::wstring& filename, SECURITY_ATTRIBUTES* securityAttributes) const;
+ void EnableReflectionKey();
+ void DisableReflectionKey();
+ [[nodiscard]] bool QueryReflectionKey() const;
+ void ConnectRegistry(const std::wstring& machineName, HKEY hKeyPredefined);
+
+
+ // Return a string representation of Windows registry types
+ [[nodiscard]] static std::wstring RegTypeToString(DWORD regType);
+
+ //
+ // Relational comparison operators are overloaded as non-members
+ // ==, !=, <, <=, >, >=
+ //
+
+
+private:
+ // The wrapped registry key handle
+ HKEY m_hKey{ nullptr };
+};
+
+
+//------------------------------------------------------------------------------
+// An exception representing an error with the registry operations
+//------------------------------------------------------------------------------
+class RegException
+ : public std::system_error
+{
+public:
+ RegException(LONG errorCode, const char* message);
+ RegException(LONG errorCode, const std::string& message);
+};
+
+
+//------------------------------------------------------------------------------
+// A tiny wrapper around LONG return codes used by the Windows Registry API.
+//------------------------------------------------------------------------------
+class RegResult
+{
+public:
+
+ // Initialize to success code (ERROR_SUCCESS)
+ RegResult() noexcept = default;
+
+ // Conversion constructor, *not* marked "explicit" on purpose,
+ // allows easy and convenient conversion from Win32 API return code type
+ // to this C++ wrapper.
+ RegResult(LONG result) noexcept;
+
+ // Is the wrapped code a success code?
+ [[nodiscard]] bool IsOk() const noexcept;
+
+ // Is the wrapped error code a failure code?
+ [[nodiscard]] bool Failed() const noexcept;
+
+ // Is the wrapped code a success code?
+ [[nodiscard]] explicit operator bool() const noexcept;
+
+ // Get the wrapped Win32 code
+ [[nodiscard]] LONG Code() const noexcept;
+
+ // Return the system error message associated to the current error code
+ [[nodiscard]] std::wstring ErrorMessage() const;
+
+ // Return the system error message associated to the current error code,
+ // using the given input language identifier
+ [[nodiscard]] std::wstring ErrorMessage(DWORD languageId) const;
+
+private:
+ // Error code returned by Windows Registry C API;
+ // default initialized to success code.
+ LONG m_result{ ERROR_SUCCESS };
+};
+
+
+//------------------------------------------------------------------------------
+// Overloads of relational comparison operators for RegKey
+//------------------------------------------------------------------------------
+
+inline bool operator==(const RegKey& a, const RegKey& b) noexcept
+{
+ return a.Get() == b.Get();
+}
+
+inline bool operator!=(const RegKey& a, const RegKey& b) noexcept
+{
+ return a.Get() != b.Get();
+}
+
+inline bool operator<(const RegKey& a, const RegKey& b) noexcept
+{
+ return a.Get() < b.Get();
+}
+
+inline bool operator<=(const RegKey& a, const RegKey& b) noexcept
+{
+ return a.Get() <= b.Get();
+}
+
+inline bool operator>(const RegKey& a, const RegKey& b) noexcept
+{
+ return a.Get() > b.Get();
+}
+
+inline bool operator>=(const RegKey& a, const RegKey& b) noexcept
+{
+ return a.Get() >= b.Get();
+}
+
+
+//------------------------------------------------------------------------------
+// Private Helper Classes and Functions
+//------------------------------------------------------------------------------
+
+namespace detail
+{
+
+//------------------------------------------------------------------------------
+// Simple scoped-based RAII wrapper that *automatically* invokes LocalFree()
+// in its destructor.
+//------------------------------------------------------------------------------
+template <typename T>
+class ScopedLocalFree
+{
+public:
+
+ typedef T Type;
+ typedef T* TypePtr;
+
+
+ // Init wrapped pointer to nullptr
+ ScopedLocalFree() noexcept = default;
+
+ // Automatically and safely invoke ::LocalFree()
+ ~ScopedLocalFree() noexcept
+ {
+ Free();
+ }
+
+ //
+ // Ban copy and move operations
+ //
+ ScopedLocalFree(const ScopedLocalFree&) = delete;
+ ScopedLocalFree(ScopedLocalFree&&) = delete;
+ ScopedLocalFree& operator=(const ScopedLocalFree&) = delete;
+ ScopedLocalFree& operator=(ScopedLocalFree&&) = delete;
+
+
+ // Read-only access to the wrapped pointer
+ [[nodiscard]] T* Get() const noexcept
+ {
+ return m_ptr;
+ }
+
+ // Writable access to the wrapped pointer
+ [[nodiscard]] T** AddressOf() noexcept
+ {
+ return &m_ptr;
+ }
+
+ // Explicit pointer conversion to bool
+ explicit operator bool() const noexcept
+ {
+ return (m_ptr != nullptr);
+ }
+
+ // Safely invoke ::LocalFree() on the wrapped pointer
+ void Free() noexcept
+ {
+ if (m_ptr != nullptr)
+ {
+ LocalFree(m_ptr);
+ m_ptr = nullptr;
+ }
+ }
+
+
+ //
+ // IMPLEMENTATION
+ //
+private:
+ T* m_ptr{ nullptr };
+};
+
+
+//------------------------------------------------------------------------------
+// Helper function to build a multi-string from a vector<wstring>.
+//
+// A multi-string is a sequence of contiguous NUL-terminated strings,
+// that terminates with an additional NUL.
+// Basically, considered as a whole, the sequence is terminated by two NULs.
+// E.g.:
+// Hello\0World\0\0
+//------------------------------------------------------------------------------
+[[nodiscard]] inline std::vector<wchar_t> BuildMultiString(
+ const std::vector<std::wstring>& data
+)
+{
+ // Special case of the empty multi-string
+ if (data.empty())
+ {
+ // Build a vector containing just two NULs
+ return std::vector<wchar_t>(2, L'\0');
+ }
+
+ // Get the total length in wchar_ts of the multi-string
+ size_t totalLen = 0;
+ for (const auto& s : data)
+ {
+ // Add one to current string's length for the terminating NUL
+ totalLen += (s.length() + 1);
+ }
+
+ // Add one for the last NUL terminator (making the whole structure double-NUL terminated)
+ totalLen++;
+
+ // Allocate a buffer to store the multi-string
+ std::vector<wchar_t> multiString;
+
+ // Reserve room in the vector to speed up the following insertion loop
+ multiString.reserve(totalLen);
+
+ // Copy the single strings into the multi-string
+ for (const auto& s : data)
+ {
+ multiString.insert(multiString.end(), s.begin(), s.end());
+
+ // Don't forget to NUL-terminate the current string
+ multiString.emplace_back(L'\0');
+ }
+
+ // Add the last NUL-terminator
+ multiString.emplace_back(L'\0');
+
+ return multiString;
+}
+
+
+} // namespace detail
+
+
+//------------------------------------------------------------------------------
+// RegKey Inline Methods
+//------------------------------------------------------------------------------
+
+inline RegKey::RegKey(const HKEY hKey) noexcept
+ : m_hKey{ hKey }
+{
+}
+
+
+inline RegKey::RegKey(const HKEY hKeyParent, const std::wstring& subKey)
+{
+ Create(hKeyParent, subKey);
+}
+
+
+inline RegKey::RegKey(const HKEY hKeyParent, const std::wstring& subKey, const REGSAM desiredAccess)
+{
+ Create(hKeyParent, subKey, desiredAccess);
+}
+
+
+inline RegKey::RegKey(RegKey&& other) noexcept
+ : m_hKey{ other.m_hKey }
+{
+ // Other doesn't own the handle anymore
+ other.m_hKey = nullptr;
+}
+
+
+inline RegKey& RegKey::operator=(RegKey&& other) noexcept
+{
+ // Prevent self-move-assign
+ if ((this != &other) && (m_hKey != other.m_hKey))
+ {
+ // Close current
+ Close();
+
+ // Move from other (i.e. take ownership of other's raw handle)
+ m_hKey = other.m_hKey;
+ other.m_hKey = nullptr;
+ }
+ return *this;
+}
+
+
+inline RegKey::~RegKey() noexcept
+{
+ // Release the owned handle (if any)
+ Close();
+}
+
+
+inline HKEY RegKey::Get() const noexcept
+{
+ return m_hKey;
+}
+
+
+inline void RegKey::Close() noexcept
+{
+ if (IsValid())
+ {
+ // Do not call RegCloseKey on predefined keys
+ if (! IsPredefined())
+ {
+ RegCloseKey(m_hKey);
+ }
+
+ // Avoid dangling references
+ m_hKey = nullptr;
+ }
+}
+
+
+inline bool RegKey::IsValid() const noexcept
+{
+ return m_hKey != nullptr;
+}
+
+
+inline RegKey::operator bool() const noexcept
+{
+ return IsValid();
+}
+
+
+inline bool RegKey::IsPredefined() const noexcept
+{
+ // Predefined keys
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724836(v=vs.85).aspx
+
+ if ( (m_hKey == HKEY_CURRENT_USER)
+ || (m_hKey == HKEY_LOCAL_MACHINE)
+ || (m_hKey == HKEY_CLASSES_ROOT)
+ || (m_hKey == HKEY_CURRENT_CONFIG)
+ || (m_hKey == HKEY_CURRENT_USER_LOCAL_SETTINGS)
+ || (m_hKey == HKEY_PERFORMANCE_DATA)
+ || (m_hKey == HKEY_PERFORMANCE_NLSTEXT)
+ || (m_hKey == HKEY_PERFORMANCE_TEXT)
+ || (m_hKey == HKEY_USERS))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+
+inline HKEY RegKey::Detach() noexcept
+{
+ HKEY hKey = m_hKey;
+
+ // We don't own the HKEY handle anymore
+ m_hKey = nullptr;
+
+ // Transfer ownership to the caller
+ return hKey;
+}
+
+
+inline void RegKey::Attach(const HKEY hKey) noexcept
+{
+ // Prevent self-attach
+ if (m_hKey != hKey)
+ {
+ // Close any open registry handle
+ Close();
+
+ // Take ownership of the input hKey
+ m_hKey = hKey;
+ }
+}
+
+
+inline void RegKey::SwapWith(RegKey& other) noexcept
+{
+ // Enable ADL (not necessary in this case, but good practice)
+ using std::swap;
+
+ // Swap the raw handle members
+ swap(m_hKey, other.m_hKey);
+}
+
+
+inline void swap(RegKey& a, RegKey& b) noexcept
+{
+ a.SwapWith(b);
+}
+
+
+inline void RegKey::Create(
+ const HKEY hKeyParent,
+ const std::wstring& subKey,
+ const REGSAM desiredAccess
+)
+{
+ constexpr DWORD kDefaultOptions = REG_OPTION_NON_VOLATILE;
+
+ Create(hKeyParent, subKey, desiredAccess, kDefaultOptions,
+ nullptr, // no security attributes,
+ nullptr // no disposition
+ );
+}
+
+
+inline void RegKey::Create(
+ const HKEY hKeyParent,
+ const std::wstring& subKey,
+ const REGSAM desiredAccess,
+ const DWORD options,
+ SECURITY_ATTRIBUTES* const securityAttributes,
+ DWORD* const disposition
+)
+{
+ HKEY hKey = nullptr;
+ LONG retCode = RegCreateKeyEx(
+ hKeyParent,
+ subKey.c_str(),
+ 0, // reserved
+ REG_NONE, // user-defined class type parameter not supported
+ options,
+ desiredAccess,
+ securityAttributes,
+ &hKey,
+ disposition
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegCreateKeyEx failed." };
+ }
+
+ // Safely close any previously opened key
+ Close();
+
+ // Take ownership of the newly created key
+ m_hKey = hKey;
+}
+
+
+inline void RegKey::Open(
+ const HKEY hKeyParent,
+ const std::wstring& subKey,
+ const REGSAM desiredAccess
+)
+{
+ HKEY hKey = nullptr;
+ LONG retCode = RegOpenKeyEx(
+ hKeyParent,
+ subKey.c_str(),
+ REG_NONE, // default options
+ desiredAccess,
+ &hKey
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegOpenKeyEx failed." };
+ }
+
+ // Safely close any previously opened key
+ Close();
+
+ // Take ownership of the newly created key
+ m_hKey = hKey;
+}
+
+
+inline RegResult RegKey::TryCreate(
+ const HKEY hKeyParent,
+ const std::wstring& subKey,
+ const REGSAM desiredAccess
+) noexcept
+{
+ constexpr DWORD kDefaultOptions = REG_OPTION_NON_VOLATILE;
+
+ return TryCreate(hKeyParent, subKey, desiredAccess, kDefaultOptions,
+ nullptr, // no security attributes,
+ nullptr // no disposition
+ );
+}
+
+
+inline RegResult RegKey::TryCreate(
+ const HKEY hKeyParent,
+ const std::wstring& subKey,
+ const REGSAM desiredAccess,
+ const DWORD options,
+ SECURITY_ATTRIBUTES* const securityAttributes,
+ DWORD* const disposition
+) noexcept
+{
+ HKEY hKey = nullptr;
+ RegResult retCode = RegCreateKeyEx(
+ hKeyParent,
+ subKey.c_str(),
+ 0, // reserved
+ REG_NONE, // user-defined class type parameter not supported
+ options,
+ desiredAccess,
+ securityAttributes,
+ &hKey,
+ disposition
+ );
+ if (retCode.Failed())
+ {
+ return retCode;
+ }
+
+ // Safely close any previously opened key
+ Close();
+
+ // Take ownership of the newly created key
+ m_hKey = hKey;
+
+ _ASSERTE(retCode.IsOk());
+ return retCode;
+}
+
+
+inline RegResult RegKey::TryOpen(
+ const HKEY hKeyParent,
+ const std::wstring& subKey,
+ const REGSAM desiredAccess
+) noexcept
+{
+ HKEY hKey = nullptr;
+ RegResult retCode = RegOpenKeyEx(
+ hKeyParent,
+ subKey.c_str(),
+ REG_NONE, // default options
+ desiredAccess,
+ &hKey
+ );
+ if (retCode.Failed())
+ {
+ return retCode;
+ }
+
+ // Safely close any previously opened key
+ Close();
+
+ // Take ownership of the newly created key
+ m_hKey = hKey;
+
+ _ASSERTE(retCode.IsOk());
+ return retCode;
+}
+
+
+inline void RegKey::SetDwordValue(const std::wstring& valueName, const DWORD data)
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegSetValueEx(
+ m_hKey,
+ valueName.c_str(),
+ 0, // reserved
+ REG_DWORD,
+ reinterpret_cast<const BYTE*>(&data),
+ sizeof(data)
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot write DWORD value: RegSetValueEx failed." };
+ }
+}
+
+
+inline void RegKey::SetQwordValue(const std::wstring& valueName, const ULONGLONG& data)
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegSetValueEx(
+ m_hKey,
+ valueName.c_str(),
+ 0, // reserved
+ REG_QWORD,
+ reinterpret_cast<const BYTE*>(&data),
+ sizeof(data)
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot write QWORD value: RegSetValueEx failed." };
+ }
+}
+
+
+inline void RegKey::SetStringValue(const std::wstring& valueName, const std::wstring& data)
+{
+ _ASSERTE(IsValid());
+
+ // String size including the terminating NUL, in bytes
+ const DWORD dataSize = static_cast<DWORD>((data.length() + 1) * sizeof(wchar_t));
+
+ LONG retCode = RegSetValueEx(
+ m_hKey,
+ valueName.c_str(),
+ 0, // reserved
+ REG_SZ,
+ reinterpret_cast<const BYTE*>(data.c_str()),
+ dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot write string value: RegSetValueEx failed." };
+ }
+}
+
+
+inline void RegKey::SetExpandStringValue(const std::wstring& valueName, const std::wstring& data)
+{
+ _ASSERTE(IsValid());
+
+ // String size including the terminating NUL, in bytes
+ const DWORD dataSize = static_cast<DWORD>((data.length() + 1) * sizeof(wchar_t));
+
+ LONG retCode = RegSetValueEx(
+ m_hKey,
+ valueName.c_str(),
+ 0, // reserved
+ REG_EXPAND_SZ,
+ reinterpret_cast<const BYTE*>(data.c_str()),
+ dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot write expand string value: RegSetValueEx failed." };
+ }
+}
+
+
+inline void RegKey::SetMultiStringValue(
+ const std::wstring& valueName,
+ const std::vector<std::wstring>& data
+)
+{
+ _ASSERTE(IsValid());
+
+ // First, we have to build a double-NUL-terminated multi-string from the input data
+ const std::vector<wchar_t> multiString = detail::BuildMultiString(data);
+
+ // Total size, in bytes, of the whole multi-string structure
+ const DWORD dataSize = static_cast<DWORD>(multiString.size() * sizeof(wchar_t));
+
+ LONG retCode = RegSetValueEx(
+ m_hKey,
+ valueName.c_str(),
+ 0, // reserved
+ REG_MULTI_SZ,
+ reinterpret_cast<const BYTE*>(&multiString[0]),
+ dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot write multi-string value: RegSetValueEx failed." };
+ }
+}
+
+
+inline void RegKey::SetBinaryValue(const std::wstring& valueName, const std::vector<BYTE>& data)
+{
+ _ASSERTE(IsValid());
+
+ // Total data size, in bytes
+ const DWORD dataSize = static_cast<DWORD>(data.size());
+
+ LONG retCode = RegSetValueEx(
+ m_hKey,
+ valueName.c_str(),
+ 0, // reserved
+ REG_BINARY,
+ &data[0],
+ dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot write binary data value: RegSetValueEx failed." };
+ }
+}
+
+
+inline void RegKey::SetBinaryValue(
+ const std::wstring& valueName,
+ const void* const data,
+ const DWORD dataSize
+)
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegSetValueEx(
+ m_hKey,
+ valueName.c_str(),
+ 0, // reserved
+ REG_BINARY,
+ static_cast<const BYTE*>(data),
+ dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot write binary data value: RegSetValueEx failed." };
+ }
+}
+
+
+inline DWORD RegKey::GetDwordValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ DWORD data = 0; // to be read from the registry
+ DWORD dataSize = sizeof(data); // size of data, in bytes
+
+ constexpr DWORD flags = RRF_RT_REG_DWORD;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &data,
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get DWORD value: RegGetValue failed." };
+ }
+
+ return data;
+}
+
+
+inline ULONGLONG RegKey::GetQwordValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ ULONGLONG data = 0; // to be read from the registry
+ DWORD dataSize = sizeof(data); // size of data, in bytes
+
+ constexpr DWORD flags = RRF_RT_REG_QWORD;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &data,
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get QWORD value: RegGetValue failed." };
+ }
+
+ return data;
+}
+
+
+inline std::wstring RegKey::GetStringValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ // Get the size of the result string
+ DWORD dataSize = 0; // size of data, in bytes
+ constexpr DWORD flags = RRF_RT_REG_SZ;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ nullptr, // output buffer not needed now
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get size of string value: RegGetValue failed." };
+ }
+
+ // Allocate a string of proper size.
+ // Note that dataSize is in bytes and includes the terminating NUL;
+ // we have to convert the size from bytes to wchar_ts for wstring::resize.
+ std::wstring result(dataSize / sizeof(wchar_t), L' ');
+
+ // Call RegGetValue for the second time to read the string's content
+ retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &result[0], // output buffer
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get string value: RegGetValue failed." };
+ }
+
+ // Remove the NUL terminator scribbled by RegGetValue from the wstring
+ result.resize((dataSize / sizeof(wchar_t)) - 1);
+
+ return result;
+}
+
+
+inline std::wstring RegKey::GetExpandStringValue(
+ const std::wstring& valueName,
+ const ExpandStringOption expandOption
+) const
+{
+ _ASSERTE(IsValid());
+
+ DWORD flags = RRF_RT_REG_EXPAND_SZ;
+
+ // Adjust the flag for RegGetValue considering the expand string option specified by the caller
+ if (expandOption == ExpandStringOption::DontExpand)
+ {
+ flags |= RRF_NOEXPAND;
+ }
+
+ // Get the size of the result string
+ DWORD dataSize = 0; // size of data, in bytes
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ nullptr, // output buffer not needed now
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get size of expand string value: RegGetValue failed." };
+ }
+
+ // Allocate a string of proper size.
+ // Note that dataSize is in bytes and includes the terminating NUL.
+ // We must convert from bytes to wchar_ts for wstring::resize.
+ std::wstring result(dataSize / sizeof(wchar_t), L' ');
+
+ // Call RegGetValue for the second time to read the string's content
+ retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &result[0], // output buffer
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get expand string value: RegGetValue failed." };
+ }
+
+ // Remove the NUL terminator scribbled by RegGetValue from the wstring
+ result.resize((dataSize / sizeof(wchar_t)) - 1);
+
+ return result;
+}
+
+
+inline std::vector<std::wstring> RegKey::GetMultiStringValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ // Request the size of the multi-string, in bytes
+ DWORD dataSize = 0;
+ constexpr DWORD flags = RRF_RT_REG_MULTI_SZ;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ nullptr, // output buffer not needed now
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get size of multi-string value: RegGetValue failed." };
+ }
+
+ // Allocate room for the result multi-string.
+ // Note that dataSize is in bytes, but our vector<wchar_t>::resize method requires size
+ // to be expressed in wchar_ts.
+ std::vector<wchar_t> data(dataSize / sizeof(wchar_t), L' ');
+
+ // Read the multi-string from the registry into the vector object
+ retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // no type required
+ &data[0], // output buffer
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get multi-string value: RegGetValue failed." };
+ }
+
+ // Resize vector to the actual size returned by GetRegValue.
+ // Note that the vector is a vector of wchar_ts, instead the size returned by GetRegValue
+ // is in bytes, so we have to scale from bytes to wchar_t count.
+ data.resize(dataSize / sizeof(wchar_t));
+
+ // Parse the double-NUL-terminated string into a vector<wstring>,
+ // which will be returned to the caller
+ std::vector<std::wstring> result;
+ const wchar_t* currStringPtr = &data[0];
+ while (*currStringPtr != L'\0')
+ {
+ // Current string is NUL-terminated, so get its length calling wcslen
+ const size_t currStringLength = wcslen(currStringPtr);
+
+ // Add current string to the result vector
+ result.emplace_back(currStringPtr, currStringLength);
+
+ // Move to the next string
+ currStringPtr += currStringLength + 1;
+ }
+
+ return result;
+}
+
+
+inline std::vector<BYTE> RegKey::GetBinaryValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ // Get the size of the binary data
+ DWORD dataSize = 0; // size of data, in bytes
+ constexpr DWORD flags = RRF_RT_REG_BINARY;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ nullptr, // output buffer not needed now
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get size of binary data: RegGetValue failed." };
+ }
+
+ // Allocate a buffer of proper size to store the binary data
+ std::vector<BYTE> data(dataSize);
+
+ // Call RegGetValue for the second time to read the data content
+ retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &data[0], // output buffer
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get binary data: RegGetValue failed." };
+ }
+
+ return data;
+}
+
+
+inline std::optional<DWORD> RegKey::TryGetDwordValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ DWORD data = 0; // to be read from the registry
+ DWORD dataSize = sizeof(data); // size of data, in bytes
+
+ constexpr DWORD flags = RRF_RT_REG_DWORD;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &data,
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ return data;
+}
+
+
+inline std::optional<ULONGLONG> RegKey::TryGetQwordValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ ULONGLONG data = 0; // to be read from the registry
+ DWORD dataSize = sizeof(data); // size of data, in bytes
+
+ constexpr DWORD flags = RRF_RT_REG_QWORD;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &data,
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ return data;
+}
+
+
+inline std::optional<std::wstring> RegKey::TryGetStringValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ // Get the size of the result string
+ DWORD dataSize = 0; // size of data, in bytes
+ constexpr DWORD flags = RRF_RT_REG_SZ;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ nullptr, // output buffer not needed now
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ // Allocate a string of proper size.
+ // Note that dataSize is in bytes and includes the terminating NUL;
+ // we have to convert the size from bytes to wchar_ts for wstring::resize.
+ std::wstring result(dataSize / sizeof(wchar_t), L' ');
+
+ // Call RegGetValue for the second time to read the string's content
+ retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &result[0], // output buffer
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ // Remove the NUL terminator scribbled by RegGetValue from the wstring
+ result.resize((dataSize / sizeof(wchar_t)) - 1);
+
+ return result;
+}
+
+
+inline std::optional<std::wstring> RegKey::TryGetExpandStringValue(
+ const std::wstring& valueName,
+ const ExpandStringOption expandOption
+) const
+{
+ _ASSERTE(IsValid());
+
+ DWORD flags = RRF_RT_REG_EXPAND_SZ;
+
+ // Adjust the flag for RegGetValue considering the expand string option specified by the caller
+ if (expandOption == ExpandStringOption::DontExpand)
+ {
+ flags |= RRF_NOEXPAND;
+ }
+
+ // Get the size of the result string
+ DWORD dataSize = 0; // size of data, in bytes
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ nullptr, // output buffer not needed now
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ // Allocate a string of proper size.
+ // Note that dataSize is in bytes and includes the terminating NUL.
+ // We must convert from bytes to wchar_ts for wstring::resize.
+ std::wstring result(dataSize / sizeof(wchar_t), L' ');
+
+ // Call RegGetValue for the second time to read the string's content
+ retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &result[0], // output buffer
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ // Remove the NUL terminator scribbled by RegGetValue from the wstring
+ result.resize((dataSize / sizeof(wchar_t)) - 1);
+
+ return result;
+}
+
+
+inline std::optional<std::vector<std::wstring>> RegKey::TryGetMultiStringValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ // Request the size of the multi-string, in bytes
+ DWORD dataSize = 0;
+ constexpr DWORD flags = RRF_RT_REG_MULTI_SZ;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ nullptr, // output buffer not needed now
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ // Allocate room for the result multi-string.
+ // Note that dataSize is in bytes, but our vector<wchar_t>::resize method requires size
+ // to be expressed in wchar_ts.
+ std::vector<wchar_t> data(dataSize / sizeof(wchar_t), L' ');
+
+ // Read the multi-string from the registry into the vector object
+ retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // no type required
+ &data[0], // output buffer
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ // Resize vector to the actual size returned by GetRegValue.
+ // Note that the vector is a vector of wchar_ts, instead the size returned by GetRegValue
+ // is in bytes, so we have to scale from bytes to wchar_t count.
+ data.resize(dataSize / sizeof(wchar_t));
+
+ // Parse the double-NUL-terminated string into a vector<wstring>,
+ // which will be returned to the caller
+ std::vector<std::wstring> result;
+ const wchar_t* currStringPtr = &data[0];
+ while (*currStringPtr != L'\0')
+ {
+ // Current string is NUL-terminated, so get its length calling wcslen
+ const size_t currStringLength = wcslen(currStringPtr);
+
+ // Add current string to the result vector
+ result.emplace_back(currStringPtr, currStringLength);
+
+ // Move to the next string
+ currStringPtr += currStringLength + 1;
+ }
+
+ return result;
+}
+
+
+inline std::optional<std::vector<BYTE>> RegKey::TryGetBinaryValue(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ // Get the size of the binary data
+ DWORD dataSize = 0; // size of data, in bytes
+ constexpr DWORD flags = RRF_RT_REG_BINARY;
+ LONG retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ nullptr, // output buffer not needed now
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ // Allocate a buffer of proper size to store the binary data
+ std::vector<BYTE> data(dataSize);
+
+ // Call RegGetValue for the second time to read the data content
+ retCode = RegGetValue(
+ m_hKey,
+ nullptr, // no subkey
+ valueName.c_str(),
+ flags,
+ nullptr, // type not required
+ &data[0], // output buffer
+ &dataSize
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ return {};
+ }
+
+ return data;
+}
+
+
+inline std::vector<std::wstring> RegKey::EnumSubKeys() const
+{
+ _ASSERTE(IsValid());
+
+ // Get some useful enumeration info, like the total number of subkeys
+ // and the maximum length of the subkey names
+ DWORD subKeyCount = 0;
+ DWORD maxSubKeyNameLen = 0;
+ LONG retCode = RegQueryInfoKey(
+ m_hKey,
+ nullptr, // no user-defined class
+ nullptr, // no user-defined class size
+ nullptr, // reserved
+ &subKeyCount,
+ &maxSubKeyNameLen,
+ nullptr, // no subkey class length
+ nullptr, // no value count
+ nullptr, // no value name max length
+ nullptr, // no max value length
+ nullptr, // no security descriptor
+ nullptr // no last write time
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{
+ retCode,
+ "RegQueryInfoKey failed while preparing for subkey enumeration."
+ };
+ }
+
+ // NOTE: According to the MSDN documentation, the size returned for subkey name max length
+ // does *not* include the terminating NUL, so let's add +1 to take it into account
+ // when I allocate the buffer for reading subkey names.
+ maxSubKeyNameLen++;
+
+ // Preallocate a buffer for the subkey names
+ auto nameBuffer = std::make_unique<wchar_t[]>(maxSubKeyNameLen);
+
+ // The result subkey names will be stored here
+ std::vector<std::wstring> subkeyNames;
+
+ // Reserve room in the vector to speed up the following insertion loop
+ subkeyNames.reserve(subKeyCount);
+
+ // Enumerate all the subkeys
+ for (DWORD index = 0; index < subKeyCount; index++)
+ {
+ // Get the name of the current subkey
+ DWORD subKeyNameLen = maxSubKeyNameLen;
+ retCode = RegEnumKeyEx(
+ m_hKey,
+ index,
+ nameBuffer.get(),
+ &subKeyNameLen,
+ nullptr, // reserved
+ nullptr, // no class
+ nullptr, // no class
+ nullptr // no last write time
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot enumerate subkeys: RegEnumKeyEx failed." };
+ }
+
+ // On success, the ::RegEnumKeyEx API writes the length of the
+ // subkey name in the subKeyNameLen output parameter
+ // (not including the terminating NUL).
+ // So I can build a wstring based on that length.
+ subkeyNames.emplace_back(nameBuffer.get(), subKeyNameLen);
+ }
+
+ return subkeyNames;
+}
+
+
+inline std::vector<std::pair<std::wstring, DWORD>> RegKey::EnumValues() const
+{
+ _ASSERTE(IsValid());
+
+ // Get useful enumeration info, like the total number of values
+ // and the maximum length of the value names
+ DWORD valueCount = 0;
+ DWORD maxValueNameLen = 0;
+ LONG retCode = RegQueryInfoKey(
+ m_hKey,
+ nullptr, // no user-defined class
+ nullptr, // no user-defined class size
+ nullptr, // reserved
+ nullptr, // no subkey count
+ nullptr, // no subkey max length
+ nullptr, // no subkey class length
+ &valueCount,
+ &maxValueNameLen,
+ nullptr, // no max value length
+ nullptr, // no security descriptor
+ nullptr // no last write time
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{
+ retCode,
+ "RegQueryInfoKey failed while preparing for value enumeration."
+ };
+ }
+
+ // NOTE: According to the MSDN documentation, the size returned for value name max length
+ // does *not* include the terminating NUL, so let's add +1 to take it into account
+ // when I allocate the buffer for reading value names.
+ maxValueNameLen++;
+
+ // Preallocate a buffer for the value names
+ auto nameBuffer = std::make_unique<wchar_t[]>(maxValueNameLen);
+
+ // The value names and types will be stored here
+ std::vector<std::pair<std::wstring, DWORD>> valueInfo;
+
+ // Reserve room in the vector to speed up the following insertion loop
+ valueInfo.reserve(valueCount);
+
+ // Enumerate all the values
+ for (DWORD index = 0; index < valueCount; index++)
+ {
+ // Get the name and the type of the current value
+ DWORD valueNameLen = maxValueNameLen;
+ DWORD valueType = 0;
+ retCode = RegEnumValue(
+ m_hKey,
+ index,
+ nameBuffer.get(),
+ &valueNameLen,
+ nullptr, // reserved
+ &valueType,
+ nullptr, // no data
+ nullptr // no data size
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot enumerate values: RegEnumValue failed." };
+ }
+
+ // On success, the RegEnumValue API writes the length of the
+ // value name in the valueNameLen output parameter
+ // (not including the terminating NUL).
+ // So we can build a wstring based on that.
+ valueInfo.emplace_back(
+ std::wstring{ nameBuffer.get(), valueNameLen },
+ valueType
+ );
+ }
+
+ return valueInfo;
+}
+
+
+inline DWORD RegKey::QueryValueType(const std::wstring& valueName) const
+{
+ _ASSERTE(IsValid());
+
+ DWORD typeId = 0; // will be returned by RegQueryValueEx
+
+ LONG retCode = RegQueryValueEx(
+ m_hKey,
+ valueName.c_str(),
+ nullptr, // reserved
+ &typeId,
+ nullptr, // not interested
+ nullptr // not interested
+ );
+
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "Cannot get the value type: RegQueryValueEx failed." };
+ }
+
+ return typeId;
+}
+
+
+inline void RegKey::QueryInfoKey(DWORD& subKeys, DWORD &values, FILETIME& lastWriteTime) const
+{
+ _ASSERTE(IsValid());
+
+ subKeys = 0;
+ values = 0;
+ lastWriteTime.dwLowDateTime = lastWriteTime.dwHighDateTime = 0;
+
+ LONG retCode = RegQueryInfoKey(
+ m_hKey,
+ nullptr,
+ nullptr,
+ nullptr,
+ &subKeys,
+ nullptr,
+ nullptr,
+ &values,
+ nullptr,
+ nullptr,
+ nullptr,
+ &lastWriteTime
+ );
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegQueryInfoKey failed." };
+ }
+}
+
+
+inline void RegKey::DeleteValue(const std::wstring& valueName)
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegDeleteValue(m_hKey, valueName.c_str());
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegDeleteValue failed." };
+ }
+}
+
+
+inline void RegKey::DeleteKey(const std::wstring& subKey, const REGSAM desiredAccess)
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegDeleteKeyEx(m_hKey, subKey.c_str(), desiredAccess, 0);
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegDeleteKeyEx failed." };
+ }
+}
+
+
+inline void RegKey::DeleteTree(const std::wstring& subKey)
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegDeleteTree(m_hKey, subKey.c_str());
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegDeleteTree failed." };
+ }
+}
+
+
+inline void RegKey::CopyTree(const std::wstring& sourceSubKey, const RegKey& destKey)
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegCopyTree(m_hKey, sourceSubKey.c_str(), destKey.Get());
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegCopyTree failed." };
+ }
+}
+
+
+inline void RegKey::FlushKey()
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegFlushKey(m_hKey);
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegFlushKey failed." };
+ }
+}
+
+
+inline void RegKey::LoadKey(const std::wstring& subKey, const std::wstring& filename)
+{
+ Close();
+
+ LONG retCode = RegLoadKey(m_hKey, subKey.c_str(), filename.c_str());
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegLoadKey failed." };
+ }
+}
+
+
+inline void RegKey::SaveKey(
+ const std::wstring& filename,
+ SECURITY_ATTRIBUTES* const securityAttributes
+) const
+{
+ _ASSERTE(IsValid());
+
+ LONG retCode = RegSaveKey(m_hKey, filename.c_str(), securityAttributes);
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegSaveKey failed." };
+ }
+}
+
+
+inline void RegKey::EnableReflectionKey()
+{
+ LONG retCode = RegEnableReflectionKey(m_hKey);
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegEnableReflectionKey failed." };
+ }
+}
+
+
+inline void RegKey::DisableReflectionKey()
+{
+ LONG retCode = RegDisableReflectionKey(m_hKey);
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegDisableReflectionKey failed." };
+ }
+}
+
+
+inline bool RegKey::QueryReflectionKey() const
+{
+ BOOL isReflectionDisabled = FALSE;
+ LONG retCode = RegQueryReflectionKey(m_hKey, &isReflectionDisabled);
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegQueryReflectionKey failed." };
+ }
+
+ return (isReflectionDisabled ? true : false);
+}
+
+
+inline void RegKey::ConnectRegistry(const std::wstring& machineName, const HKEY hKeyPredefined)
+{
+ // Safely close any previously opened key
+ Close();
+
+ HKEY hKeyResult = nullptr;
+ LONG retCode = RegConnectRegistry(machineName.c_str(), hKeyPredefined, &hKeyResult);
+ if (retCode != ERROR_SUCCESS)
+ {
+ throw RegException{ retCode, "RegConnectRegistry failed." };
+ }
+
+ // Take ownership of the result key
+ m_hKey = hKeyResult;
+}
+
+
+inline std::wstring RegKey::RegTypeToString(const DWORD regType)
+{
+ switch (regType)
+ {
+ case REG_SZ: return L"REG_SZ";
+ case REG_EXPAND_SZ: return L"REG_EXPAND_SZ";
+ case REG_MULTI_SZ: return L"REG_MULTI_SZ";
+ case REG_DWORD: return L"REG_DWORD";
+ case REG_QWORD: return L"REG_QWORD";
+ case REG_BINARY: return L"REG_BINARY";
+
+ default: return L"Unknown/unsupported registry type";
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// RegException Inline Methods
+//------------------------------------------------------------------------------
+
+inline RegException::RegException(const LONG errorCode, const char* const message)
+ : std::system_error{ errorCode, std::system_category(), message }
+{}
+
+
+inline RegException::RegException(const LONG errorCode, const std::string& message)
+ : std::system_error{ errorCode, std::system_category(), message }
+{}
+
+
+//------------------------------------------------------------------------------
+// RegResult Inline Methods
+//------------------------------------------------------------------------------
+
+inline RegResult::RegResult(const LONG result) noexcept
+ : m_result{ result }
+{}
+
+
+inline bool RegResult::IsOk() const noexcept
+{
+ return m_result == ERROR_SUCCESS;
+}
+
+
+inline bool RegResult::Failed() const noexcept
+{
+ return m_result != ERROR_SUCCESS;
+}
+
+
+inline RegResult::operator bool() const noexcept
+{
+ return IsOk();
+}
+
+
+inline LONG RegResult::Code() const noexcept
+{
+ return m_result;
+}
+
+
+inline std::wstring RegResult::ErrorMessage() const
+{
+ return ErrorMessage(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
+}
+
+
+inline std::wstring RegResult::ErrorMessage(const DWORD languageId) const
+{
+ // Invoke FormatMessage() to retrieve the error message from Windows
+ detail::ScopedLocalFree<wchar_t> messagePtr;
+ DWORD retCode = FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr,
+ m_result,
+ languageId,
+ reinterpret_cast<LPWSTR>(messagePtr.AddressOf()),
+ 0,
+ nullptr
+ );
+ if (retCode == 0)
+ {
+ // FormatMessage failed: return an empty string
+ return std::wstring{};
+ }
+
+ // Safely copy the C-string returned by FormatMessage() into a std::wstring object,
+ // and return it back to the caller.
+ return std::wstring{ messagePtr.Get() };
+}
+
+
+} // namespace winreg
+
+
+#endif // GIOVANNI_DICANIO_WINREG_HPP_INCLUDED
diff --git a/common-install/utility-install.hpp b/common-install/utility-install.hpp
new file mode 100644
index 0000000..e1823e4
--- /dev/null
+++ b/common-install/utility-install.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <filesystem>
+#include <Windows.h>
+
+#include "external/WinReg.hpp"
+
+namespace fs = std::filesystem;
+namespace wr = winreg;
+
+inline const std::wstring DRIVER_NAME = L"rawaccel";
+inline const std::wstring DRIVER_FILE_NAME = DRIVER_NAME + L".sys";
+
+fs::path get_sys_path() {
+ std::wstring path;
+ path.resize(MAX_PATH);
+
+ UINT chars_copied = GetSystemDirectoryW(path.data(), MAX_PATH);
+ if (chars_copied == 0) throw std::runtime_error("GetSystemDirectory failed");
+
+ path.resize(chars_copied);
+ return path;
+}
+
+fs::path get_target_path() {
+ return get_sys_path() / L"drivers" / DRIVER_FILE_NAME;
+}
+
+fs::path make_temp_path(const fs::path& p) {
+ auto tmp_path = p;
+ tmp_path.concat(".tmp");
+ return tmp_path;
+}
+
+template<typename Func>
+void modify_upper_filters(Func fn) {
+ const std::wstring FILTERS_NAME = L"UpperFilters";
+ wr::RegKey key(
+ HKEY_LOCAL_MACHINE,
+ L"SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e96f-e325-11ce-bfc1-08002be10318}"
+ );
+
+ std::vector<std::wstring> filters = key.GetMultiStringValue(FILTERS_NAME);
+ fn(filters);
+ key.SetMultiStringValue(FILTERS_NAME, filters);
+}
diff --git a/common/common.vcxitems b/common/common.vcxitems
new file mode 100644
index 0000000..f60f78a
--- /dev/null
+++ b/common/common.vcxitems
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup Label="Globals">
+ <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+ <HasSharedItems>true</HasSharedItems>
+ <ItemsProjectGuid>{24b4226f-1461-408f-a1a4-1371c97153ea}</ItemsProjectGuid>
+ </PropertyGroup>
+ <ItemDefinitionGroup>
+ <ClCompile>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
+ </ClCompile>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ProjectCapability Include="SourceItemsFromImports" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="$(MSBuildThisFileDirectory)rawaccel-userspace.hpp" />
+ <ClInclude Include="$(MSBuildThisFileDirectory)rawaccel.hpp" />
+ <ClInclude Include="$(MSBuildThisFileDirectory)x64-util.hpp" />
+ <ClInclude Include="$(MSBuildThisFileDirectory)vec2.h" />
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/common/external/clipp.h b/common/external/clipp.h
new file mode 100644
index 0000000..cca1554
--- /dev/null
+++ b/common/external/clipp.h
@@ -0,0 +1,7027 @@
+/*****************************************************************************
+ * ___ _ _ ___ ___
+ * | _|| | | | | _ \ _ \ CLIPP - command line interfaces for modern C++
+ * | |_ | |_ | | | _/ _/ version 1.2.3
+ * |___||___||_| |_| |_| https://github.com/muellan/clipp
+ *
+ * Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+ * Copyright (c) 2017-2018 André Müller <[email protected]>
+ *
+ * ---------------------------------------------------------------------------
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *****************************************************************************/
+
+#ifndef AM_CLIPP_H__
+#define AM_CLIPP_H__
+
+#include <cstring>
+#include <string>
+#include <cstdlib>
+#include <cstring>
+#include <cctype>
+#include <memory>
+#include <vector>
+#include <limits>
+#include <stack>
+#include <algorithm>
+#include <sstream>
+#include <utility>
+#include <iterator>
+#include <functional>
+
+
+/*************************************************************************//**
+ *
+ * @brief primary namespace
+ *
+ *****************************************************************************/
+namespace clipp {
+
+
+
+/*****************************************************************************
+ *
+ * basic constants and datatype definitions
+ *
+ *****************************************************************************/
+using arg_index = int;
+
+using arg_string = std::string;
+using doc_string = std::string;
+
+using arg_list = std::vector<arg_string>;
+
+
+
+/*************************************************************************//**
+ *
+ * @brief tristate
+ *
+ *****************************************************************************/
+enum class tri : char { no, yes, either };
+
+inline constexpr bool operator == (tri t, bool b) noexcept {
+ return b ? t != tri::no : t != tri::yes;
+}
+inline constexpr bool operator == (bool b, tri t) noexcept { return (t == b); }
+inline constexpr bool operator != (tri t, bool b) noexcept { return !(t == b); }
+inline constexpr bool operator != (bool b, tri t) noexcept { return !(t == b); }
+
+
+
+/*************************************************************************//**
+ *
+ * @brief (start,size) index range
+ *
+ *****************************************************************************/
+class subrange {
+public:
+ using size_type = arg_string::size_type;
+
+ /** @brief default: no match */
+ explicit constexpr
+ subrange() noexcept :
+ at_{arg_string::npos}, length_{0}
+ {}
+
+ /** @brief match length & position within subject string */
+ explicit constexpr
+ subrange(size_type pos, size_type len) noexcept :
+ at_{pos}, length_{len}
+ {}
+
+ /** @brief position of the match within the subject string */
+ constexpr size_type at() const noexcept { return at_; }
+ /** @brief length of the matching subsequence */
+ constexpr size_type length() const noexcept { return length_; }
+
+ /** @brief returns true, if query string is a prefix of the subject string */
+ constexpr bool prefix() const noexcept {
+ return at_ == 0;
+ }
+
+ /** @brief returns true, if query is a substring of the query string */
+ constexpr explicit operator bool () const noexcept {
+ return at_ != arg_string::npos;
+ }
+
+private:
+ size_type at_;
+ size_type length_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief match predicates
+ *
+ *****************************************************************************/
+using match_predicate = std::function<bool(const arg_string&)>;
+using match_function = std::function<subrange(const arg_string&)>;
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief type traits (NOT FOR DIRECT USE IN CLIENT CODE!)
+ * no interface guarantees; might be changed or removed in the future
+ *
+ *****************************************************************************/
+namespace traits {
+
+/*************************************************************************//**
+ *
+ * @brief function (class) signature type trait
+ *
+ *****************************************************************************/
+template<class Fn, class Ret, class... Args>
+constexpr auto
+check_is_callable(int) -> decltype(
+ std::declval<Fn>()(std::declval<Args>()...),
+ std::integral_constant<bool,
+ std::is_same<Ret, std::invoke_result_t<Fn, Args...>>::value>{} );
+
+template<class,class,class...>
+constexpr auto
+check_is_callable(long) -> std::false_type;
+
+template<class Fn, class Ret>
+constexpr auto
+check_is_callable_without_arg(int) -> decltype(
+ std::declval<Fn>()(),
+ std::integral_constant<bool,
+ std::is_same<Ret, std::invoke_result_t<Fn>>::value>{} );
+
+template<class,class>
+constexpr auto
+check_is_callable_without_arg(long) -> std::false_type;
+
+
+
+template<class Fn, class... Args>
+constexpr auto
+check_is_void_callable(int) -> decltype(
+ std::declval<Fn>()(std::declval<Args>()...), std::true_type{});
+
+template<class,class,class...>
+constexpr auto
+check_is_void_callable(long) -> std::false_type;
+
+template<class Fn>
+constexpr auto
+check_is_void_callable_without_arg(int) -> decltype(
+ std::declval<Fn>()(), std::true_type{});
+
+template<class>
+constexpr auto
+check_is_void_callable_without_arg(long) -> std::false_type;
+
+
+
+template<class Fn, class Ret>
+struct is_callable;
+
+
+template<class Fn, class Ret, class... Args>
+struct is_callable<Fn, Ret(Args...)> :
+ decltype(check_is_callable<Fn,Ret,Args...>(0))
+{};
+
+template<class Fn, class Ret>
+struct is_callable<Fn,Ret()> :
+ decltype(check_is_callable_without_arg<Fn,Ret>(0))
+{};
+
+
+template<class Fn, class... Args>
+struct is_callable<Fn, void(Args...)> :
+ decltype(check_is_void_callable<Fn,Args...>(0))
+{};
+
+template<class Fn>
+struct is_callable<Fn,void()> :
+ decltype(check_is_void_callable_without_arg<Fn>(0))
+{};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief input range type trait
+ *
+ *****************************************************************************/
+template<class T>
+constexpr auto
+check_is_input_range(int) -> decltype(
+ begin(std::declval<T>()), end(std::declval<T>()),
+ std::true_type{});
+
+template<class T>
+constexpr auto
+check_is_input_range(char) -> decltype(
+ std::begin(std::declval<T>()), std::end(std::declval<T>()),
+ std::true_type{});
+
+template<class>
+constexpr auto
+check_is_input_range(long) -> std::false_type;
+
+template<class T>
+struct is_input_range :
+ decltype(check_is_input_range<T>(0))
+{};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief size() member type trait
+ *
+ *****************************************************************************/
+template<class T>
+constexpr auto
+check_has_size_getter(int) ->
+ decltype(std::declval<T>().size(), std::true_type{});
+
+template<class>
+constexpr auto
+check_has_size_getter(long) -> std::false_type;
+
+template<class T>
+struct has_size_getter :
+ decltype(check_has_size_getter<T>(0))
+{};
+
+} // namespace traits
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
+ * no interface guarantees; might be changed or removed in the future
+ *
+ *****************************************************************************/
+namespace detail {
+
+
+/*************************************************************************//**
+ * @brief forwards string to first non-whitespace char;
+ * std string -> unsigned conv yields max value, but we want 0;
+ * also checks for nullptr
+ *****************************************************************************/
+inline bool
+fwd_to_unsigned_int(const char*& s)
+{
+ if(!s) return false;
+ for(; std::isspace(*s); ++s);
+ if(!s[0] || s[0] == '-') return false;
+ if(s[0] == '-') return false;
+ return true;
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief value limits clamping
+ *
+ *****************************************************************************/
+template<class T, class V, bool = (sizeof(V) > sizeof(T))>
+struct limits_clamped {
+ static T from(const V& v) {
+ if(v >= V(std::numeric_limits<T>::max())) {
+ return std::numeric_limits<T>::max();
+ }
+ if(v <= V(std::numeric_limits<T>::lowest())) {
+ return std::numeric_limits<T>::lowest();
+ }
+ return T(v);
+ }
+};
+
+template<class T, class V>
+struct limits_clamped<T,V,false> {
+ static T from(const V& v) { return T(v); }
+};
+
+
+/*************************************************************************//**
+ *
+ * @brief returns value of v as a T, clamped at T's maximum
+ *
+ *****************************************************************************/
+template<class T, class V>
+inline T clamped_on_limits(const V& v) {
+ return limits_clamped<T,V>::from(v);
+}
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief type conversion helpers
+ *
+ *****************************************************************************/
+template<class T>
+struct make {
+ static inline T from(const char* s) {
+ if(!s) return false;
+ //a conversion from const char* to / must exist
+ return static_cast<T>(s);
+ }
+};
+
+template<>
+struct make<bool> {
+ static inline bool from(const char* s) {
+ if(!s) return false;
+ return static_cast<bool>(s);
+ }
+};
+
+template<>
+struct make<unsigned char> {
+ static inline unsigned char from(const char* s) {
+ if(!fwd_to_unsigned_int(s)) return (0);
+ return clamped_on_limits<unsigned char>(std::strtoull(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<unsigned short int> {
+ static inline unsigned short int from(const char* s) {
+ if(!fwd_to_unsigned_int(s)) return (0);
+ return clamped_on_limits<unsigned short int>(std::strtoull(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<unsigned int> {
+ static inline unsigned int from(const char* s) {
+ if(!fwd_to_unsigned_int(s)) return (0);
+ return clamped_on_limits<unsigned int>(std::strtoull(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<unsigned long int> {
+ static inline unsigned long int from(const char* s) {
+ if(!fwd_to_unsigned_int(s)) return (0);
+ return clamped_on_limits<unsigned long int>(std::strtoull(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<unsigned long long int> {
+ static inline unsigned long long int from(const char* s) {
+ if(!fwd_to_unsigned_int(s)) return (0);
+ return clamped_on_limits<unsigned long long int>(std::strtoull(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<char> {
+ static inline char from(const char* s) {
+ //parse as single character?
+ const auto n = std::strlen(s);
+ if(n == 1) return s[0];
+ //parse as integer
+ return clamped_on_limits<char>(std::strtoll(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<short int> {
+ static inline short int from(const char* s) {
+ return clamped_on_limits<short int>(std::strtoll(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<int> {
+ static inline int from(const char* s) {
+ return clamped_on_limits<int>(std::strtoll(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<long int> {
+ static inline long int from(const char* s) {
+ return clamped_on_limits<long int>(std::strtoll(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<long long int> {
+ static inline long long int from(const char* s) {
+ return (std::strtoll(s,nullptr,10));
+ }
+};
+
+template<>
+struct make<float> {
+ static inline float from(const char* s) {
+ return (std::strtof(s,nullptr));
+ }
+};
+
+template<>
+struct make<double> {
+ static inline double from(const char* s) {
+ return (std::strtod(s,nullptr));
+ }
+};
+
+template<>
+struct make<long double> {
+ static inline long double from(const char* s) {
+ return (std::strtold(s,nullptr));
+ }
+};
+
+template<>
+struct make<std::string> {
+ static inline std::string from(const char* s) {
+ return std::string(s);
+ }
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief assigns boolean constant to one or multiple target objects
+ *
+ *****************************************************************************/
+template<class T, class V = T>
+class assign_value
+{
+public:
+ template<class X>
+ explicit constexpr
+ assign_value(T& target, X&& value) noexcept :
+ t_{std::addressof(target)}, v_{std::forward<X>(value)}
+ {}
+
+ void operator () () const {
+ if(t_) *t_ = v_;
+ }
+
+private:
+ T* t_;
+ V v_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief flips bools
+ *
+ *****************************************************************************/
+class flip_bool
+{
+public:
+ explicit constexpr
+ flip_bool(bool& target) noexcept :
+ b_{&target}
+ {}
+
+ void operator () () const {
+ if(b_) *b_ = !*b_;
+ }
+
+private:
+ bool* b_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief increments using operator ++
+ *
+ *****************************************************************************/
+template<class T>
+class increment
+{
+public:
+ explicit constexpr
+ increment(T& target) noexcept : t_{std::addressof(target)} {}
+
+ void operator () () const {
+ if(t_) ++(*t_);
+ }
+
+private:
+ T* t_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief decrements using operator --
+ *
+ *****************************************************************************/
+template<class T>
+class decrement
+{
+public:
+ explicit constexpr
+ decrement(T& target) noexcept : t_{std::addressof(target)} {}
+
+ void operator () () const {
+ if(t_) --(*t_);
+ }
+
+private:
+ T* t_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief increments by a fixed amount using operator +=
+ *
+ *****************************************************************************/
+template<class T>
+class increment_by
+{
+public:
+ explicit constexpr
+ increment_by(T& target, T by) noexcept :
+ t_{std::addressof(target)}, by_{std::move(by)}
+ {}
+
+ void operator () () const {
+ if(t_) (*t_) += by_;
+ }
+
+private:
+ T* t_;
+ T by_;
+};
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a value from a string and assigns it to an object
+ *
+ *****************************************************************************/
+template<class T>
+class map_arg_to
+{
+public:
+ explicit constexpr
+ map_arg_to(T& target) noexcept : t_{std::addressof(target)} {}
+
+ void operator () (const char* s) const {
+ if(t_ && s) *t_ = detail::make<T>::from(s);
+ }
+
+private:
+ T* t_;
+};
+
+
+//-------------------------------------------------------------------
+/**
+ * @brief specialization for vectors: append element
+ */
+template<class T>
+class map_arg_to<std::vector<T>>
+{
+public:
+ map_arg_to(std::vector<T>& target): t_{std::addressof(target)} {}
+
+ void operator () (const char* s) const {
+ if(t_ && s) t_->push_back(detail::make<T>::from(s));
+ }
+
+private:
+ std::vector<T>* t_;
+};
+
+
+//-------------------------------------------------------------------
+/**
+ * @brief specialization for bools:
+ * set to true regardless of string content
+ */
+template<>
+class map_arg_to<bool>
+{
+public:
+ map_arg_to(bool& target): t_{&target} {}
+
+ void operator () (const char* s) const {
+ if(t_ && s) *t_ = true;
+ }
+
+private:
+ bool* t_;
+};
+
+
+} // namespace detail
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief string matching and processing tools
+ *
+ *****************************************************************************/
+
+namespace str {
+
+
+/*************************************************************************//**
+ *
+ * @brief converts string to value of target type 'T'
+ *
+ *****************************************************************************/
+template<class T>
+T make(const arg_string& s)
+{
+ return detail::make<T>::from(s);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief removes trailing whitespace from string
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+inline void
+trimr(std::basic_string<C,T,A>& s)
+{
+ if(s.empty()) return;
+
+ s.erase(
+ std::find_if_not(s.rbegin(), s.rend(),
+ [](char c) { return std::isspace(c);} ).base(),
+ s.end() );
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief removes leading whitespace from string
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+inline void
+triml(std::basic_string<C,T,A>& s)
+{
+ if(s.empty()) return;
+
+ s.erase(
+ s.begin(),
+ std::find_if_not(s.begin(), s.end(),
+ [](char c) { return std::isspace(c);})
+ );
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief removes leading and trailing whitespace from string
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+inline void
+trim(std::basic_string<C,T,A>& s)
+{
+ triml(s);
+ trimr(s);
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief removes all whitespaces from string
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+inline void
+remove_ws(std::basic_string<C,T,A>& s)
+{
+ if(s.empty()) return;
+
+ s.erase(std::remove_if(s.begin(), s.end(),
+ [](char c) { return std::isspace(c); }),
+ s.end() );
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief returns true, if the 'prefix' argument
+ * is a prefix of the 'subject' argument
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+inline bool
+has_prefix(const std::basic_string<C,T,A>& subject,
+ const std::basic_string<C,T,A>& prefix)
+{
+ if(prefix.size() > subject.size()) return false;
+ return subject.find(prefix) == 0;
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief returns true, if the 'postfix' argument
+ * is a postfix of the 'subject' argument
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+inline bool
+has_postfix(const std::basic_string<C,T,A>& subject,
+ const std::basic_string<C,T,A>& postfix)
+{
+ if(postfix.size() > subject.size()) return false;
+ return (subject.size() - postfix.size()) == subject.find(postfix);
+}
+
+
+
+/*************************************************************************//**
+*
+* @brief returns longest common prefix of several
+* sequential random access containers
+*
+* @details InputRange require begin and end (member functions or overloads)
+* the elements of InputRange require a size() member
+*
+*****************************************************************************/
+template<class InputRange>
+auto
+longest_common_prefix(const InputRange& strs)
+ -> typename std::decay<decltype(*begin(strs))>::type
+{
+ static_assert(traits::is_input_range<InputRange>(),
+ "parameter must satisfy the InputRange concept");
+
+ static_assert(traits::has_size_getter<
+ typename std::decay<decltype(*begin(strs))>::type>(),
+ "elements of input range must have a ::size() member function");
+
+ using std::begin;
+ using std::end;
+
+ using item_t = typename std::decay<decltype(*begin(strs))>::type;
+ using str_size_t = typename std::decay<decltype(begin(strs)->size())>::type;
+
+ const auto n = size_t(distance(begin(strs), end(strs)));
+ if(n < 1) return item_t("");
+ if(n == 1) return *begin(strs);
+
+ //length of shortest string
+ auto m = std::min_element(begin(strs), end(strs),
+ [](const item_t& a, const item_t& b) {
+ return a.size() < b.size(); })->size();
+
+ //check each character until we find a mismatch
+ for(str_size_t i = 0; i < m; ++i) {
+ for(str_size_t j = 1; j < n; ++j) {
+ if(strs[j][i] != strs[j-1][i])
+ return strs[0].substr(0, i);
+ }
+ }
+ return strs[0].substr(0, m);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief returns longest substring range that could be found in 'arg'
+ *
+ * @param arg string to be searched in
+ * @param substrings range of candidate substrings
+ *
+ *****************************************************************************/
+template<class C, class T, class A, class InputRange>
+subrange
+longest_substring_match(const std::basic_string<C,T,A>& arg,
+ const InputRange& substrings)
+{
+ using string_t = std::basic_string<C,T,A>;
+
+ static_assert(traits::is_input_range<InputRange>(),
+ "parameter must satisfy the InputRange concept");
+
+ static_assert(std::is_same<string_t,
+ typename std::decay<decltype(*begin(substrings))>::type>(),
+ "substrings must have same type as 'arg'");
+
+ auto i = string_t::npos;
+ auto n = string_t::size_type(0);
+ for(const auto& s : substrings) {
+ auto j = arg.find(s);
+ if(j != string_t::npos && s.size() > n) {
+ i = j;
+ n = s.size();
+ }
+ }
+ return subrange{i,n};
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief returns longest prefix range that could be found in 'arg'
+ *
+ * @param arg string to be searched in
+ * @param prefixes range of candidate prefix strings
+ *
+ *****************************************************************************/
+template<class C, class T, class A, class InputRange>
+subrange
+longest_prefix_match(const std::basic_string<C,T,A>& arg,
+ const InputRange& prefixes)
+{
+ using string_t = std::basic_string<C,T,A>;
+ using s_size_t = typename string_t::size_type;
+
+ static_assert(traits::is_input_range<InputRange>(),
+ "parameter must satisfy the InputRange concept");
+
+ static_assert(std::is_same<string_t,
+ typename std::decay<decltype(*begin(prefixes))>::type>(),
+ "prefixes must have same type as 'arg'");
+
+ auto i = string_t::npos;
+ auto n = s_size_t(0);
+ for(const auto& s : prefixes) {
+ auto j = arg.find(s);
+ if(j == 0 && s.size() > n) {
+ i = 0;
+ n = s.size();
+ }
+ }
+ return subrange{i,n};
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief returns the first occurrence of 'query' within 'subject'
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+inline subrange
+substring_match(const std::basic_string<C,T,A>& subject,
+ const std::basic_string<C,T,A>& query)
+{
+ if(subject.empty() && query.empty()) return subrange(0,0);
+ if(subject.empty() || query.empty()) return subrange{};
+ auto i = subject.find(query);
+ if(i == std::basic_string<C,T,A>::npos) return subrange{};
+ return subrange{i,query.size()};
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief returns first substring match (pos,len) within the input string
+ * that represents a number
+ * (with at maximum one decimal point and digit separators)
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+subrange
+first_number_match(std::basic_string<C,T,A> s,
+ C digitSeparator = C(','),
+ C decimalPoint = C('.'),
+ C exponential = C('e'))
+{
+ using string_t = std::basic_string<C,T,A>;
+ str::trim(s);
+ if(s.empty()) return subrange{};
+
+ //auto i = s.find_first_of("0123456789+-");
+ //if(i == string_t::npos) {
+ // i = s.find(decimalPoint);
+ // if(i == string_t::npos) return subrange{};
+ //}
+ //bool point = false;
+
+ // overwritten to match numbers without leading 0,
+ // also commented out call to sanitize_args in parse
+ auto i = s.find_first_of("0123456789+-.");
+ if (i == string_t::npos) return subrange{};
+ bool point = s[i] == decimalPoint;
+
+ bool sep = false;
+ auto exp = string_t::npos;
+ auto j = i + 1;
+ for(; j < s.size(); ++j) {
+ if(s[j] == digitSeparator) {
+ if(!sep) sep = true; else break;
+ }
+ else {
+ sep = false;
+ if(s[j] == decimalPoint) {
+ //only one decimal point before exponent allowed
+ if(!point && exp == string_t::npos) point = true; else break;
+ }
+ else if(std::tolower(s[j]) == std::tolower(exponential)) {
+ //only one exponent separator allowed
+ if(exp == string_t::npos) exp = j; else break;
+ }
+ else if(exp != string_t::npos && (exp+1) == j) {
+ //only sign or digit after exponent separator
+ if(s[j] != '+' && s[j] != '-' && !std::isdigit(s[j])) break;
+ }
+ else if(!std::isdigit(s[j])) {
+ break;
+ }
+ }
+ }
+
+ //if length == 1 then must be a digit
+ if(j-i == 1 && !std::isdigit(s[i])) return subrange{};
+
+ return subrange{i,j-i};
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief returns first substring match (pos,len)
+ * that represents an integer (with optional digit separators)
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+subrange
+first_integer_match(std::basic_string<C,T,A> s,
+ C digitSeparator = C(','))
+{
+ using string_t = std::basic_string<C,T,A>;
+ str::trim(s);
+ if(s.empty()) return subrange{};
+
+ auto i = s.find_first_of("0123456789+-");
+ if(i == string_t::npos) return subrange{};
+
+ bool sep = false;
+ auto j = i + 1;
+ for(; j < s.size(); ++j) {
+ if(s[j] == digitSeparator) {
+ if(!sep) sep = true; else break;
+ }
+ else {
+ sep = false;
+ if(!std::isdigit(s[j])) break;
+ }
+ }
+
+ //if length == 1 then must be a digit
+ if(j-i == 1 && !std::isdigit(s[i])) return subrange{};
+
+ return subrange{i,j-i};
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief returns true if candidate string represents a number
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+bool represents_number(const std::basic_string<C,T,A>& candidate,
+ C digitSeparator = C(','),
+ C decimalPoint = C('.'),
+ C exponential = C('e'))
+{
+ const auto match = str::first_number_match(candidate, digitSeparator,
+ decimalPoint, exponential);
+
+ return (match && match.length() == candidate.size());
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief returns true if candidate string represents an integer
+ *
+ *****************************************************************************/
+template<class C, class T, class A>
+bool represents_integer(const std::basic_string<C,T,A>& candidate,
+ C digitSeparator = C(','))
+{
+ const auto match = str::first_integer_match(candidate, digitSeparator);
+ return (match && match.length() == candidate.size());
+}
+
+} // namespace str
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes function object with a const char* parameter
+ * that assigns a value to a ref-captured object
+ *
+ *****************************************************************************/
+template<class T, class V>
+inline detail::assign_value<T,V>
+set(T& target, V value) {
+ return detail::assign_value<T>{target, std::move(value)};
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes parameter-less function object
+ * that assigns value(s) to a ref-captured object;
+ * value(s) are obtained by converting the const char* argument to
+ * the captured object types;
+ * bools are always set to true if the argument is not nullptr
+ *
+ *****************************************************************************/
+template<class T>
+inline detail::map_arg_to<T>
+set(T& target) {
+ return detail::map_arg_to<T>{target};
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes function object that sets a bool to true
+ *
+ *****************************************************************************/
+inline detail::assign_value<bool>
+set(bool& target) {
+ return detail::assign_value<bool>{target,true};
+}
+
+/*************************************************************************//**
+ *
+ * @brief makes function object that sets a bool to false
+ *
+ *****************************************************************************/
+inline detail::assign_value<bool>
+unset(bool& target) {
+ return detail::assign_value<bool>{target,false};
+}
+
+/*************************************************************************//**
+ *
+ * @brief makes function object that flips the value of a ref-captured bool
+ *
+ *****************************************************************************/
+inline detail::flip_bool
+flip(bool& b) {
+ return detail::flip_bool(b);
+}
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes function object that increments using operator ++
+ *
+ *****************************************************************************/
+template<class T>
+inline detail::increment<T>
+increment(T& target) {
+ return detail::increment<T>{target};
+}
+
+/*************************************************************************//**
+ *
+ * @brief makes function object that decrements using operator --
+ *
+ *****************************************************************************/
+template<class T>
+inline detail::increment_by<T>
+increment(T& target, T by) {
+ return detail::increment_by<T>{target, std::move(by)};
+}
+
+/*************************************************************************//**
+ *
+ * @brief makes function object that increments by a fixed amount using operator +=
+ *
+ *****************************************************************************/
+template<class T>
+inline detail::decrement<T>
+decrement(T& target) {
+ return detail::decrement<T>{target};
+}
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
+ *
+ *****************************************************************************/
+namespace detail {
+
+
+/*************************************************************************//**
+ *
+ * @brief mixin that provides action definition and execution
+ *
+ *****************************************************************************/
+template<class Derived>
+class action_provider
+{
+private:
+ //---------------------------------------------------------------
+ using simple_action = std::function<void()>;
+ using arg_action = std::function<void(const char*)>;
+ using index_action = std::function<void(int)>;
+
+ //-----------------------------------------------------
+ class simple_action_adapter {
+ public:
+ simple_action_adapter() = default;
+ simple_action_adapter(const simple_action& a): action_(a) {}
+ simple_action_adapter(simple_action&& a): action_(std::move(a)) {}
+ void operator() (const char*) const { action_(); }
+ void operator() (int) const { action_(); }
+ private:
+ simple_action action_;
+ };
+
+
+public:
+ //---------------------------------------------------------------
+ /** @brief adds an action that has an operator() that is callable
+ * with a 'const char*' argument */
+ Derived&
+ call(arg_action a) {
+ argActions_.push_back(std::move(a));
+ return *static_cast<Derived*>(this);
+ }
+
+ /** @brief adds an action that has an operator()() */
+ Derived&
+ call(simple_action a) {
+ argActions_.push_back(simple_action_adapter(std::move(a)));
+ return *static_cast<Derived*>(this);
+ }
+
+ /** @brief adds an action that has an operator() that is callable
+ * with a 'const char*' argument */
+ Derived& operator () (arg_action a) { return call(std::move(a)); }
+
+ /** @brief adds an action that has an operator()() */
+ Derived& operator () (simple_action a) { return call(std::move(a)); }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds an action that will set the value of 't' from
+ * a 'const char*' arg */
+ template<class Target>
+ Derived&
+ set(Target& t) {
+ static_assert(!std::is_pointer<Target>::value,
+ "parameter target type must not be a pointer");
+
+ return call(clipp::set(t));
+ }
+
+ /** @brief adds an action that will set the value of 't' to 'v' */
+ template<class Target, class Value>
+ Derived&
+ set(Target& t, Value&& v) {
+ return call(clipp::set(t, std::forward<Value>(v)));
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds an action that will be called if a parameter
+ * matches an argument for the 2nd, 3rd, 4th, ... time
+ */
+ Derived&
+ if_repeated(simple_action a) {
+ repeatActions_.push_back(simple_action_adapter{std::move(a)});
+ return *static_cast<Derived*>(this);
+ }
+ /** @brief adds an action that will be called with the argument's
+ * index if a parameter matches an argument for
+ * the 2nd, 3rd, 4th, ... time
+ */
+ Derived&
+ if_repeated(index_action a) {
+ repeatActions_.push_back(std::move(a));
+ return *static_cast<Derived*>(this);
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds an action that will be called if a required parameter
+ * is missing
+ */
+ Derived&
+ if_missing(simple_action a) {
+ missingActions_.push_back(simple_action_adapter{std::move(a)});
+ return *static_cast<Derived*>(this);
+ }
+ /** @brief adds an action that will be called if a required parameter
+ * is missing; the action will get called with the index of
+ * the command line argument where the missing event occurred first
+ */
+ Derived&
+ if_missing(index_action a) {
+ missingActions_.push_back(std::move(a));
+ return *static_cast<Derived*>(this);
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds an action that will be called if a parameter
+ * was matched, but was unreachable in the current scope
+ */
+ Derived&
+ if_blocked(simple_action a) {
+ blockedActions_.push_back(simple_action_adapter{std::move(a)});
+ return *static_cast<Derived*>(this);
+ }
+ /** @brief adds an action that will be called if a parameter
+ * was matched, but was unreachable in the current scope;
+ * the action will be called with the index of
+ * the command line argument where the problem occurred
+ */
+ Derived&
+ if_blocked(index_action a) {
+ blockedActions_.push_back(std::move(a));
+ return *static_cast<Derived*>(this);
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds an action that will be called if a parameter match
+ * was in conflict with a different alternative parameter
+ */
+ Derived&
+ if_conflicted(simple_action a) {
+ conflictActions_.push_back(simple_action_adapter{std::move(a)});
+ return *static_cast<Derived*>(this);
+ }
+ /** @brief adds an action that will be called if a parameter match
+ * was in conflict with a different alternative parameter;
+ * the action will be called with the index of
+ * the command line argument where the problem occurred
+ */
+ Derived&
+ if_conflicted(index_action a) {
+ conflictActions_.push_back(std::move(a));
+ return *static_cast<Derived*>(this);
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds targets = either objects whose values should be
+ * set by command line arguments or actions that should
+ * be called in case of a match */
+ template<class T, class... Ts>
+ Derived&
+ target(T&& t, Ts&&... ts) {
+ target(std::forward<T>(t));
+ target(std::forward<Ts>(ts)...);
+ return *static_cast<Derived*>(this);
+ }
+
+ /** @brief adds action that should be called in case of a match */
+ template<class T, class = typename std::enable_if<
+ !std::is_fundamental<typename std::decay<T>::type>() &&
+ (traits::is_callable<T,void()>() ||
+ traits::is_callable<T,void(const char*)>() )
+ >::type>
+ Derived&
+ target(T&& t) {
+ call(std::forward<T>(t));
+ return *static_cast<Derived*>(this);
+ }
+
+ /** @brief adds object whose value should be set by command line arguments
+ */
+ template<class T, class = typename std::enable_if<
+ std::is_fundamental<typename std::decay<T>::type>() ||
+ (!traits::is_callable<T,void()>() &&
+ !traits::is_callable<T,void(const char*)>() )
+ >::type>
+ Derived&
+ target(T& t) {
+ set(t);
+ return *static_cast<Derived*>(this);
+ }
+
+ //TODO remove ugly empty param list overload
+ Derived&
+ target() {
+ return *static_cast<Derived*>(this);
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds target, see member function 'target' */
+ template<class Target>
+ inline friend Derived&
+ operator << (Target&& t, Derived& p) {
+ p.target(std::forward<Target>(t));
+ return p;
+ }
+ /** @brief adds target, see member function 'target' */
+ template<class Target>
+ inline friend Derived&&
+ operator << (Target&& t, Derived&& p) {
+ p.target(std::forward<Target>(t));
+ return std::move(p);
+ }
+
+ //-----------------------------------------------------
+ /** @brief adds target, see member function 'target' */
+ template<class Target>
+ inline friend Derived&
+ operator >> (Derived& p, Target&& t) {
+ p.target(std::forward<Target>(t));
+ return p;
+ }
+ /** @brief adds target, see member function 'target' */
+ template<class Target>
+ inline friend Derived&&
+ operator >> (Derived&& p, Target&& t) {
+ p.target(std::forward<Target>(t));
+ return std::move(p);
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief executes all argument actions */
+ void execute_actions(const arg_string& arg) const {
+ int i = 0;
+ for(const auto& a : argActions_) {
+ ++i;
+ a(arg.c_str());
+ }
+ }
+
+ /** @brief executes repeat actions */
+ void notify_repeated(arg_index idx) const {
+ for(const auto& a : repeatActions_) a(idx);
+ }
+ /** @brief executes missing error actions */
+ void notify_missing(arg_index idx) const {
+ for(const auto& a : missingActions_) a(idx);
+ }
+ /** @brief executes blocked error actions */
+ void notify_blocked(arg_index idx) const {
+ for(const auto& a : blockedActions_) a(idx);
+ }
+ /** @brief executes conflict error actions */
+ void notify_conflict(arg_index idx) const {
+ for(const auto& a : conflictActions_) a(idx);
+ }
+
+private:
+ //---------------------------------------------------------------
+ std::vector<arg_action> argActions_;
+ std::vector<index_action> repeatActions_;
+ std::vector<index_action> missingActions_;
+ std::vector<index_action> blockedActions_;
+ std::vector<index_action> conflictActions_;
+};
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief mixin that provides basic common settings of parameters and groups
+ *
+ *****************************************************************************/
+template<class Derived>
+class token
+{
+public:
+ //---------------------------------------------------------------
+ using doc_string = clipp::doc_string;
+
+
+ //---------------------------------------------------------------
+ /** @brief returns documentation string */
+ const doc_string& doc() const noexcept {
+ return doc_;
+ }
+
+ /** @brief sets documentations string */
+ Derived& doc(const doc_string& txt) {
+ doc_ = txt;
+ return *static_cast<Derived*>(this);
+ }
+
+ /** @brief sets documentations string */
+ Derived& doc(doc_string&& txt) {
+ doc_ = std::move(txt);
+ return *static_cast<Derived*>(this);
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns if a group/parameter is repeatable */
+ bool repeatable() const noexcept {
+ return repeatable_;
+ }
+
+ /** @brief sets repeatability of group/parameter */
+ Derived& repeatable(bool yes) noexcept {
+ repeatable_ = yes;
+ return *static_cast<Derived*>(this);
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns if a group/parameter is blocking/positional */
+ bool blocking() const noexcept {
+ return blocking_;
+ }
+
+ /** @brief determines, if a group/parameter is blocking/positional */
+ Derived& blocking(bool yes) noexcept {
+ blocking_ = yes;
+ return *static_cast<Derived*>(this);
+ }
+
+
+private:
+ //---------------------------------------------------------------
+ doc_string doc_;
+ bool repeatable_ = false;
+ bool blocking_ = false;
+};
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief sets documentation strings on a token
+ *
+ *****************************************************************************/
+template<class T>
+inline T&
+operator % (doc_string docstr, token<T>& p)
+{
+ return p.doc(std::move(docstr));
+}
+//---------------------------------------------------------
+template<class T>
+inline T&&
+operator % (doc_string docstr, token<T>&& p)
+{
+ return std::move(p.doc(std::move(docstr)));
+}
+
+//---------------------------------------------------------
+template<class T>
+inline T&
+operator % (token<T>& p, doc_string docstr)
+{
+ return p.doc(std::move(docstr));
+}
+//---------------------------------------------------------
+template<class T>
+inline T&&
+operator % (token<T>&& p, doc_string docstr)
+{
+ return std::move(p.doc(std::move(docstr)));
+}
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief sets documentation strings on a token
+ *
+ *****************************************************************************/
+template<class T>
+inline T&
+doc(doc_string docstr, token<T>& p)
+{
+ return p.doc(std::move(docstr));
+}
+//---------------------------------------------------------
+template<class T>
+inline T&&
+doc(doc_string docstr, token<T>&& p)
+{
+ return std::move(p.doc(std::move(docstr)));
+}
+
+
+
+} // namespace detail
+
+
+
+/*************************************************************************//**
+ *
+ * @brief contains parameter matching functions and function classes
+ *
+ *****************************************************************************/
+namespace match {
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that is always true
+ *
+ *****************************************************************************/
+inline bool
+any(const arg_string&) { return true; }
+
+/*************************************************************************//**
+ *
+ * @brief predicate that is always false
+ *
+ *****************************************************************************/
+inline bool
+none(const arg_string&) { return false; }
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the argument string is non-empty string
+ *
+ *****************************************************************************/
+inline bool
+nonempty(const arg_string& s) {
+ return !s.empty();
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the argument is a non-empty
+ * string that consists only of alphanumeric characters
+ *
+ *****************************************************************************/
+inline bool
+alphanumeric(const arg_string& s) {
+ if(s.empty()) return false;
+ return std::all_of(s.begin(), s.end(), [](char c) {return std::isalnum(c); });
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the argument is a non-empty
+ * string that consists only of alphabetic characters
+ *
+ *****************************************************************************/
+inline bool
+alphabetic(const arg_string& s) {
+ return std::all_of(s.begin(), s.end(), [](char c) {return std::isalpha(c); });
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns false if the argument string is
+ * equal to any string from the exclusion list
+ *
+ *****************************************************************************/
+class none_of
+{
+public:
+ none_of(arg_list strs):
+ excluded_{std::move(strs)}
+ {}
+
+ template<class... Strings>
+ none_of(arg_string str, Strings&&... strs):
+ excluded_{std::move(str), std::forward<Strings>(strs)...}
+ {}
+
+ template<class... Strings>
+ none_of(const char* str, Strings&&... strs):
+ excluded_{arg_string(str), std::forward<Strings>(strs)...}
+ {}
+
+ bool operator () (const arg_string& arg) const {
+ return (std::find(begin(excluded_), end(excluded_), arg)
+ == end(excluded_));
+ }
+
+private:
+ arg_list excluded_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns the first substring match within the input
+ * string that rmeepresents a number
+ * (with at maximum one decimal point and digit separators)
+ *
+ *****************************************************************************/
+class numbers
+{
+public:
+ explicit
+ numbers(char decimalPoint = '.',
+ char digitSeparator = ' ',
+ char exponentSeparator = 'e')
+ :
+ decpoint_{decimalPoint}, separator_{digitSeparator},
+ exp_{exponentSeparator}
+ {}
+
+ subrange operator () (const arg_string& s) const {
+ return str::first_number_match(s, separator_, decpoint_, exp_);
+ }
+
+private:
+ char decpoint_;
+ char separator_;
+ char exp_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the input string represents an integer
+ * (with optional digit separators)
+ *
+ *****************************************************************************/
+class integers {
+public:
+ explicit
+ integers(char digitSeparator = ' '): separator_{digitSeparator} {}
+
+ subrange operator () (const arg_string& s) const {
+ return str::first_integer_match(s, separator_);
+ }
+
+private:
+ char separator_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the input string represents
+ * a non-negative integer (with optional digit separators)
+ *
+ *****************************************************************************/
+class positive_integers {
+public:
+ explicit
+ positive_integers(char digitSeparator = ' ') : separator_{ digitSeparator } {}
+ subrange operator () (const arg_string& s) const {
+ auto match = str::first_integer_match(s, separator_);
+ if(!match) return subrange{};
+ if(s[match.at()] == '-') return subrange{};
+ return match;
+ }
+
+private:
+ char separator_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the input string
+ * contains a given substring
+ *
+ *****************************************************************************/
+class substring
+{
+public:
+ explicit
+ substring(arg_string str): str_{std::move(str)} {}
+
+ subrange operator () (const arg_string& s) const {
+ return str::substring_match(s, str_);
+ }
+
+private:
+ arg_string str_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the input string starts
+ * with a given prefix
+ *
+ *****************************************************************************/
+class prefix {
+public:
+ explicit
+ prefix(arg_string p): prefix_{std::move(p)} {}
+
+ bool operator () (const arg_string& s) const {
+ return s.find(prefix_) == 0;
+ }
+
+private:
+ arg_string prefix_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the input string does not start
+ * with a given prefix
+ *
+ *****************************************************************************/
+class prefix_not {
+public:
+ explicit
+ prefix_not(arg_string p): prefix_{std::move(p)} {}
+
+ bool operator () (const arg_string& s) const {
+ return s.find(prefix_) != 0;
+ }
+
+private:
+ arg_string prefix_;
+};
+
+
+/** @brief alias for prefix_not */
+using noprefix = prefix_not;
+
+
+
+/*************************************************************************//**
+ *
+ * @brief predicate that returns true if the length of the input string
+ * is wihtin a given interval
+ *
+ *****************************************************************************/
+class length {
+public:
+ explicit
+ length(std::size_t exact):
+ min_{exact}, max_{exact}
+ {}
+
+ explicit
+ length(std::size_t min, std::size_t max):
+ min_{min}, max_{max}
+ {}
+
+ bool operator () (const arg_string& s) const {
+ return s.size() >= min_ && s.size() <= max_;
+ }
+
+private:
+ std::size_t min_;
+ std::size_t max_;
+};
+
+
+/*************************************************************************//**
+ *
+ * @brief makes function object that returns true if the input string has a
+ * given minimum length
+ *
+ *****************************************************************************/
+inline length min_length(std::size_t min)
+{
+ return length{min, arg_string::npos-1};
+}
+
+/*************************************************************************//**
+ *
+ * @brief makes function object that returns true if the input string is
+ * not longer than a given maximum length
+ *
+ *****************************************************************************/
+inline length max_length(std::size_t max)
+{
+ return length{0, max};
+}
+
+
+} // namespace match
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief command line parameter that can match one or many arguments.
+ *
+ *****************************************************************************/
+class parameter :
+ public detail::token<parameter>,
+ public detail::action_provider<parameter>
+{
+ /** @brief adapts a 'match_predicate' to the 'match_function' interface */
+ class predicate_adapter {
+ public:
+ explicit
+ predicate_adapter(match_predicate pred): match_{std::move(pred)} {}
+
+ subrange operator () (const arg_string& arg) const {
+ return match_(arg) ? subrange{0,arg.size()} : subrange{};
+ }
+
+ private:
+ match_predicate match_;
+ };
+
+public:
+ //---------------------------------------------------------------
+ /** @brief makes default parameter, that will match nothing */
+ parameter():
+ flags_{},
+ matcher_{predicate_adapter{match::none}},
+ label_{}, required_{false}, greedy_{false}
+ {}
+
+ /** @brief makes "flag" parameter */
+ template<class... Strings>
+ explicit
+ parameter(arg_string str, Strings&&... strs):
+ flags_{},
+ matcher_{predicate_adapter{match::none}},
+ label_{}, required_{false}, greedy_{false}
+ {
+ add_flags(std::move(str), std::forward<Strings>(strs)...);
+ }
+
+ /** @brief makes "flag" parameter from range of strings */
+ explicit
+ parameter(const arg_list& flaglist):
+ flags_{},
+ matcher_{predicate_adapter{match::none}},
+ label_{}, required_{false}, greedy_{false}
+ {
+ add_flags(flaglist);
+ }
+
+ //-----------------------------------------------------
+ /** @brief makes "value" parameter with custom match predicate
+ * (= yes/no matcher)
+ */
+ explicit
+ parameter(match_predicate filter):
+ flags_{},
+ matcher_{predicate_adapter{std::move(filter)}},
+ label_{}, required_{false}, greedy_{false}
+ {}
+
+ /** @brief makes "value" parameter with custom match function
+ * (= partial matcher)
+ */
+ explicit
+ parameter(match_function filter):
+ flags_{},
+ matcher_{std::move(filter)},
+ label_{}, required_{false}, greedy_{false}
+ {}
+
+
+ //---------------------------------------------------------------
+ /** @brief returns if a parameter is required */
+ bool
+ required() const noexcept {
+ return required_;
+ }
+
+ /** @brief determines if a parameter is required */
+ parameter&
+ required(bool yes) noexcept {
+ required_ = yes;
+ return *this;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns if a parameter should match greedily */
+ bool
+ greedy() const noexcept {
+ return greedy_;
+ }
+
+ /** @brief determines if a parameter should match greedily */
+ parameter&
+ greedy(bool yes) noexcept {
+ greedy_ = yes;
+ return *this;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns parameter label;
+ * will be used for documentation, if flags are empty
+ */
+ const doc_string&
+ label() const {
+ return label_;
+ }
+
+ /** @brief sets parameter label;
+ * will be used for documentation, if flags are empty
+ */
+ parameter&
+ label(const doc_string& lbl) {
+ label_ = lbl;
+ return *this;
+ }
+
+ /** @brief sets parameter label;
+ * will be used for documentation, if flags are empty
+ */
+ parameter&
+ label(doc_string&& lbl) {
+ label_ = lbl;
+ return *this;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns either longest matching prefix of 'arg' in any
+ * of the flags or the result of the custom match operation
+ */
+ subrange
+ match(const arg_string& arg) const
+ {
+ if(flags_.empty()) {
+ return matcher_(arg);
+ }
+ else {
+ //empty flags are not allowed
+ if(arg.empty()) return subrange{};
+
+ if(std::find(flags_.begin(), flags_.end(), arg) != flags_.end()) {
+ return subrange{0,arg.size()};
+ }
+ return str::longest_prefix_match(arg, flags_);
+ }
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief access range of flag strings */
+ const arg_list&
+ flags() const noexcept {
+ return flags_;
+ }
+
+ /** @brief access custom match operation */
+ const match_function&
+ matcher() const noexcept {
+ return matcher_;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief prepend prefix to each flag */
+ inline friend parameter&
+ with_prefix(const arg_string& prefix, parameter& p)
+ {
+ if(prefix.empty() || p.flags().empty()) return p;
+
+ for(auto& f : p.flags_) {
+ if(f.find(prefix) != 0) f.insert(0, prefix);
+ }
+ return p;
+ }
+
+
+ /** @brief prepend prefix to each flag
+ */
+ inline friend parameter&
+ with_prefixes_short_long(
+ const arg_string& shortpfx, const arg_string& longpfx,
+ parameter& p)
+ {
+ if(shortpfx.empty() && longpfx.empty()) return p;
+ if(p.flags().empty()) return p;
+
+ for(auto& f : p.flags_) {
+ if(f.size() == 1) {
+ if(f.find(shortpfx) != 0) f.insert(0, shortpfx);
+ } else {
+ if(f.find(longpfx) != 0) f.insert(0, longpfx);
+ }
+ }
+ return p;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief prepend suffix to each flag */
+ inline friend parameter&
+ with_suffix(const arg_string& suffix, parameter& p)
+ {
+ if(suffix.empty() || p.flags().empty()) return p;
+
+ for(auto& f : p.flags_) {
+ if(f.find(suffix) + suffix.size() != f.size()) {
+ f.insert(f.end(), suffix.begin(), suffix.end());
+ }
+ }
+ return p;
+ }
+
+
+ /** @brief prepend suffix to each flag
+ */
+ inline friend parameter&
+ with_suffixes_short_long(
+ const arg_string& shortsfx, const arg_string& longsfx,
+ parameter& p)
+ {
+ if(shortsfx.empty() && longsfx.empty()) return p;
+ if(p.flags().empty()) return p;
+
+ for(auto& f : p.flags_) {
+ if(f.size() == 1) {
+ if(f.find(shortsfx) + shortsfx.size() != f.size()) {
+ f.insert(f.end(), shortsfx.begin(), shortsfx.end());
+ }
+ } else {
+ if(f.find(longsfx) + longsfx.size() != f.size()) {
+ f.insert(f.end(), longsfx.begin(), longsfx.end());
+ }
+ }
+ }
+ return p;
+ }
+
+private:
+ //---------------------------------------------------------------
+ void add_flags(arg_string str) {
+ //empty flags are not allowed
+ str::remove_ws(str);
+ if(!str.empty()) flags_.push_back(std::move(str));
+ }
+
+ //---------------------------------------------------------------
+ void add_flags(const arg_list& strs) {
+ if(strs.empty()) return;
+ flags_.reserve(flags_.size() + strs.size());
+ for(const auto& s : strs) add_flags(s);
+ }
+
+ template<class String1, class String2, class... Strings>
+ void
+ add_flags(String1&& s1, String2&& s2, Strings&&... ss) {
+ flags_.reserve(2 + sizeof...(ss));
+ add_flags(std::forward<String1>(s1));
+ add_flags(std::forward<String2>(s2), std::forward<Strings>(ss)...);
+ }
+
+ arg_list flags_;
+ match_function matcher_;
+ doc_string label_;
+ bool required_ = false;
+ bool greedy_ = false;
+};
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required non-blocking exact match parameter
+ *
+ *****************************************************************************/
+template<class String, class... Strings>
+inline parameter
+command(String&& flag, Strings&&... flags)
+{
+ return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
+ .required(true).blocking(true).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required non-blocking exact match parameter
+ *
+ *****************************************************************************/
+template<class String, class... Strings>
+inline parameter
+required(String&& flag, Strings&&... flags)
+{
+ return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
+ .required(true).blocking(false).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, non-blocking exact match parameter
+ *
+ *****************************************************************************/
+template<class String, class... Strings>
+inline parameter
+option(String&& flag, Strings&&... flags)
+{
+ return parameter{std::forward<String>(flag), std::forward<Strings>(flags)...}
+ .required(false).blocking(false).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required, blocking, repeatable value parameter;
+ * matches any non-empty string
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+value(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::nonempty}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(false);
+}
+
+template<class Filter, class... Targets, class = typename std::enable_if<
+ traits::is_callable<Filter,bool(const char*)>::value ||
+ traits::is_callable<Filter,subrange(const char*)>::value>::type>
+inline parameter
+value(Filter&& filter, doc_string label, Targets&&... tgts)
+{
+ return parameter{std::forward<Filter>(filter)}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required, blocking, repeatable value parameter;
+ * matches any non-empty string
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+values(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::nonempty}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(true);
+}
+
+template<class Filter, class... Targets, class = typename std::enable_if<
+ traits::is_callable<Filter,bool(const char*)>::value ||
+ traits::is_callable<Filter,subrange(const char*)>::value>::type>
+inline parameter
+values(Filter&& filter, doc_string label, Targets&&... tgts)
+{
+ return parameter{std::forward<Filter>(filter)}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, blocking value parameter;
+ * matches any non-empty string
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+opt_value(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::nonempty}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(false);
+}
+
+template<class Filter, class... Targets, class = typename std::enable_if<
+ traits::is_callable<Filter,bool(const char*)>::value ||
+ traits::is_callable<Filter,subrange(const char*)>::value>::type>
+inline parameter
+opt_value(Filter&& filter, doc_string label, Targets&&... tgts)
+{
+ return parameter{std::forward<Filter>(filter)}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, blocking, repeatable value parameter;
+ * matches any non-empty string
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+opt_values(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::nonempty}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(true);
+}
+
+template<class Filter, class... Targets, class = typename std::enable_if<
+ traits::is_callable<Filter,bool(const char*)>::value ||
+ traits::is_callable<Filter,subrange(const char*)>::value>::type>
+inline parameter
+opt_values(Filter&& filter, doc_string label, Targets&&... tgts)
+{
+ return parameter{std::forward<Filter>(filter)}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required, blocking value parameter;
+ * matches any string consisting of alphanumeric characters
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+word(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::alphanumeric}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required, blocking, repeatable value parameter;
+ * matches any string consisting of alphanumeric characters
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+words(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::alphanumeric}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, blocking value parameter;
+ * matches any string consisting of alphanumeric characters
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+opt_word(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::alphanumeric}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, blocking, repeatable value parameter;
+ * matches any string consisting of alphanumeric characters
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+opt_words(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::alphanumeric}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required, blocking value parameter;
+ * matches any string that represents a number
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+number(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::numbers{}}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required, blocking, repeatable value parameter;
+ * matches any string that represents a number
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+numbers(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::numbers{}}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, blocking value parameter;
+ * matches any string that represents a number
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+opt_number(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::numbers{}}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, blocking, repeatable value parameter;
+ * matches any string that represents a number
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+opt_numbers(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::numbers{}}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required, blocking value parameter;
+ * matches any string that represents an integer
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+integer(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::integers{}}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes required, blocking, repeatable value parameter;
+ * matches any string that represents an integer
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+integers(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::integers{}}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(true).blocking(true).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, blocking value parameter;
+ * matches any string that represents an integer
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+opt_integer(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::integers{}}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes optional, blocking, repeatable value parameter;
+ * matches any string that represents an integer
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+opt_integers(const doc_string& label, Targets&&... tgts)
+{
+ return parameter{match::integers{}}
+ .label(label)
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes catch-all value parameter
+ *
+ *****************************************************************************/
+template<class... Targets>
+inline parameter
+any_other(Targets&&... tgts)
+{
+ return parameter{match::any}
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes catch-all value parameter with custom filter
+ *
+ *****************************************************************************/
+template<class Filter, class... Targets, class = typename std::enable_if<
+ traits::is_callable<Filter,bool(const char*)>::value ||
+ traits::is_callable<Filter,subrange(const char*)>::value>::type>
+inline parameter
+any(Filter&& filter, Targets&&... tgts)
+{
+ return parameter{std::forward<Filter>(filter)}
+ .target(std::forward<Targets>(tgts)...)
+ .required(false).blocking(false).repeatable(true);
+}
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief group of parameters and/or other groups;
+ * can be configured to act as a group of alternatives (exclusive match)
+ *
+ *****************************************************************************/
+class group :
+ public detail::token<group>
+{
+ //---------------------------------------------------------------
+ /**
+ * @brief tagged union type that either stores a parameter or a group
+ * and provides a common interface to them
+ * could be replaced by std::variant in the future
+ *
+ * Note to future self: do NOT try again to do this with
+ * dynamic polymorphism; there are a couple of
+ * nasty problems associated with it and the implementation
+ * becomes bloated and needlessly complicated.
+ */
+ template<class Param, class Group>
+ struct child_t {
+ enum class type : char {param, group};
+ public:
+
+ explicit
+ child_t(const Param& v) : m_{v}, type_{type::param} {}
+ child_t( Param&& v) noexcept : m_{std::move(v)}, type_{type::param} {}
+
+ explicit
+ child_t(const Group& g) : m_{g}, type_{type::group} {}
+ child_t( Group&& g) noexcept : m_{std::move(g)}, type_{type::group} {}
+
+ child_t(const child_t& src): type_{src.type_} {
+ switch(type_) {
+ default:
+ case type::param: new(&m_)data{src.m_.param}; break;
+ case type::group: new(&m_)data{src.m_.group}; break;
+ }
+ }
+
+ child_t(child_t&& src) noexcept : type_{src.type_} {
+ switch(type_) {
+ default:
+ case type::param: new(&m_)data{std::move(src.m_.param)}; break;
+ case type::group: new(&m_)data{std::move(src.m_.group)}; break;
+ }
+ }
+
+ child_t& operator = (const child_t& src) {
+ destroy_content();
+ type_ = src.type_;
+ switch(type_) {
+ default:
+ case type::param: new(&m_)data{src.m_.param}; break;
+ case type::group: new(&m_)data{src.m_.group}; break;
+ }
+ return *this;
+ }
+
+ child_t& operator = (child_t&& src) noexcept {
+ destroy_content();
+ type_ = src.type_;
+ switch(type_) {
+ default:
+ case type::param: new(&m_)data{std::move(src.m_.param)}; break;
+ case type::group: new(&m_)data{std::move(src.m_.group)}; break;
+ }
+ return *this;
+ }
+
+ ~child_t() {
+ destroy_content();
+ }
+
+ const doc_string&
+ doc() const noexcept {
+ switch(type_) {
+ default:
+ case type::param: return m_.param.doc();
+ case type::group: return m_.group.doc();
+ }
+ }
+
+ bool blocking() const noexcept {
+ switch(type_) {
+ case type::param: return m_.param.blocking();
+ case type::group: return m_.group.blocking();
+ default: return false;
+ }
+ }
+ bool repeatable() const noexcept {
+ switch(type_) {
+ case type::param: return m_.param.repeatable();
+ case type::group: return m_.group.repeatable();
+ default: return false;
+ }
+ }
+ bool required() const noexcept {
+ switch(type_) {
+ case type::param: return m_.param.required();
+ case type::group:
+ return (m_.group.exclusive() && m_.group.all_required() ) ||
+ (!m_.group.exclusive() && m_.group.any_required() );
+ default: return false;
+ }
+ }
+ bool exclusive() const noexcept {
+ switch(type_) {
+ case type::group: return m_.group.exclusive();
+ case type::param:
+ default: return false;
+ }
+ }
+ std::size_t param_count() const noexcept {
+ switch(type_) {
+ case type::group: return m_.group.param_count();
+ case type::param:
+ default: return std::size_t(1);
+ }
+ }
+ std::size_t depth() const noexcept {
+ switch(type_) {
+ case type::group: return m_.group.depth();
+ case type::param:
+ default: return std::size_t(0);
+ }
+ }
+
+ void execute_actions(const arg_string& arg) const {
+ switch(type_) {
+ default:
+ case type::group: return;
+ case type::param: m_.param.execute_actions(arg); break;
+ }
+
+ }
+
+ void notify_repeated(arg_index idx) const {
+ switch(type_) {
+ default:
+ case type::group: return;
+ case type::param: m_.param.notify_repeated(idx); break;
+ }
+ }
+ void notify_missing(arg_index idx) const {
+ switch(type_) {
+ default:
+ case type::group: return;
+ case type::param: m_.param.notify_missing(idx); break;
+ }
+ }
+ void notify_blocked(arg_index idx) const {
+ switch(type_) {
+ default:
+ case type::group: return;
+ case type::param: m_.param.notify_blocked(idx); break;
+ }
+ }
+ void notify_conflict(arg_index idx) const {
+ switch(type_) {
+ default:
+ case type::group: return;
+ case type::param: m_.param.notify_conflict(idx); break;
+ }
+ }
+
+ bool is_param() const noexcept { return type_ == type::param; }
+ bool is_group() const noexcept { return type_ == type::group; }
+
+ Param& as_param() noexcept { return m_.param; }
+ Group& as_group() noexcept { return m_.group; }
+
+ const Param& as_param() const noexcept { return m_.param; }
+ const Group& as_group() const noexcept { return m_.group; }
+
+ private:
+ void destroy_content() {
+ switch(type_) {
+ default:
+ case type::param: m_.param.~Param(); break;
+ case type::group: m_.group.~Group(); break;
+ }
+ }
+
+ union data {
+ data() {}
+
+ data(const Param& v) : param{v} {}
+ data( Param&& v) noexcept : param{std::move(v)} {}
+
+ data(const Group& g) : group{g} {}
+ data( Group&& g) noexcept : group{std::move(g)} {}
+ ~data() {}
+
+ Param param;
+ Group group;
+ };
+
+ data m_;
+ type type_;
+ };
+
+
+public:
+ //---------------------------------------------------------------
+ using child = child_t<parameter,group>;
+ using value_type = child;
+
+private:
+ using children_store = std::vector<child>;
+
+public:
+ using const_iterator = children_store::const_iterator;
+ using iterator = children_store::iterator;
+ using size_type = children_store::size_type;
+
+
+ //---------------------------------------------------------------
+ /**
+ * @brief recursively iterates over all nodes
+ */
+ class depth_first_traverser
+ {
+ public:
+ //-----------------------------------------------------
+ struct context {
+ context() = default;
+ context(const group& p):
+ parent{&p}, cur{p.begin()}, end{p.end()}
+ {}
+ const group* parent = nullptr;
+ const_iterator cur;
+ const_iterator end;
+ };
+ using context_list = std::vector<context>;
+
+ //-----------------------------------------------------
+ class memento {
+ friend class depth_first_traverser;
+ int level_;
+ context context_;
+ public:
+ int level() const noexcept { return level_; }
+ const child* param() const noexcept { return &(*context_.cur); }
+ };
+
+ depth_first_traverser() = default;
+
+ explicit
+ depth_first_traverser(const group& cur): stack_{} {
+ if(!cur.empty()) stack_.emplace_back(cur);
+ }
+
+ explicit operator bool() const noexcept {
+ return !stack_.empty();
+ }
+
+ int level() const noexcept {
+ return int(stack_.size());
+ }
+
+ bool is_first_in_parent() const noexcept {
+ if(stack_.empty()) return false;
+ return (stack_.back().cur == stack_.back().parent->begin());
+ }
+
+ bool is_last_in_parent() const noexcept {
+ if(stack_.empty()) return false;
+ return (stack_.back().cur+1 == stack_.back().end);
+ }
+
+ bool is_last_in_path() const noexcept {
+ if(stack_.empty()) return false;
+ for(const auto& t : stack_) {
+ if(t.cur+1 != t.end) return false;
+ }
+ const auto& top = stack_.back();
+ //if we have to descend into group on next ++ => not last in path
+ if(top.cur->is_group()) return false;
+ return true;
+ }
+
+ /** @brief inside a group of alternatives >= minlevel */
+ bool is_alternative(int minlevel = 0) const noexcept {
+ if(stack_.empty()) return false;
+ if(minlevel > 0) minlevel -= 1;
+ if(minlevel >= int(stack_.size())) return false;
+ return std::any_of(stack_.begin() + minlevel, stack_.end(),
+ [](const context& c) { return c.parent->exclusive(); });
+ }
+
+ /** @brief repeatable or inside a repeatable group >= minlevel */
+ bool is_repeatable(int minlevel = 0) const noexcept {
+ if(stack_.empty()) return false;
+ if(stack_.back().cur->repeatable()) return true;
+ if(minlevel > 0) minlevel -= 1;
+ if(minlevel >= int(stack_.size())) return false;
+ return std::any_of(stack_.begin() + minlevel, stack_.end(),
+ [](const context& c) { return c.parent->repeatable(); });
+ }
+
+ /** @brief inside a particular group */
+ bool is_inside(const group* g) const noexcept {
+ if(!g) return false;
+ return std::any_of(stack_.begin(), stack_.end(),
+ [g](const context& c) { return c.parent == g; });
+ }
+
+ /** @brief inside group with joinable flags */
+ bool joinable() const noexcept {
+ if(stack_.empty()) return false;
+ return std::any_of(stack_.begin(), stack_.end(),
+ [](const context& c) { return c.parent->joinable(); });
+ }
+
+ const context_list&
+ stack() const {
+ return stack_;
+ }
+
+ /** @brief innermost repeat group */
+ const group*
+ innermost_repeat_group() const noexcept {
+ auto i = std::find_if(stack_.rbegin(), stack_.rend(),
+ [](const context& c) { return c.parent->repeatable(); });
+ return i != stack_.rend() ? i->parent : nullptr;
+ }
+
+ /** @brief innermost exclusive (alternatives) group */
+ const group*
+ innermost_exclusive_group() const noexcept {
+ auto i = std::find_if(stack_.rbegin(), stack_.rend(),
+ [](const context& c) { return c.parent->exclusive(); });
+ return i != stack_.rend() ? i->parent : nullptr;
+ }
+
+ /** @brief innermost blocking group */
+ const group*
+ innermost_blocking_group() const noexcept {
+ auto i = std::find_if(stack_.rbegin(), stack_.rend(),
+ [](const context& c) { return c.parent->blocking(); });
+ return i != stack_.rend() ? i->parent : nullptr;
+ }
+
+ /** @brief returns the outermost group that will be left on next ++*/
+ const group*
+ outermost_blocking_group_fully_explored() const noexcept {
+ if(stack_.empty()) return nullptr;
+
+ const group* g = nullptr;
+ for(auto i = stack_.rbegin(); i != stack_.rend(); ++i) {
+ if(i->cur+1 == i->end) {
+ if(i->parent->blocking()) g = i->parent;
+ } else {
+ return g;
+ }
+ }
+ return g;
+ }
+
+ /** @brief outermost join group */
+ const group*
+ outermost_join_group() const noexcept {
+ auto i = std::find_if(stack_.begin(), stack_.end(),
+ [](const context& c) { return c.parent->joinable(); });
+ return i != stack_.end() ? i->parent : nullptr;
+ }
+
+ const group* root() const noexcept {
+ return stack_.empty() ? nullptr : stack_.front().parent;
+ }
+
+ /** @brief common flag prefix of all flags in current group */
+ arg_string common_flag_prefix() const noexcept {
+ if(stack_.empty()) return "";
+ auto g = outermost_join_group();
+ return g ? g->common_flag_prefix() : arg_string("");
+ }
+
+ const child&
+ operator * () const noexcept {
+ return *stack_.back().cur;
+ }
+
+ const child*
+ operator -> () const noexcept {
+ return &(*stack_.back().cur);
+ }
+
+ const group&
+ parent() const noexcept {
+ return *(stack_.back().parent);
+ }
+
+
+ /** @brief go to next element of depth first search */
+ depth_first_traverser&
+ operator ++ () {
+ if(stack_.empty()) return *this;
+ //at group -> decend into group
+ if(stack_.back().cur->is_group()) {
+ stack_.emplace_back(stack_.back().cur->as_group());
+ }
+ else {
+ next_sibling();
+ }
+ return *this;
+ }
+
+ /** @brief go to next sibling of current */
+ depth_first_traverser&
+ next_sibling() {
+ if(stack_.empty()) return *this;
+ ++stack_.back().cur;
+ //at the end of current group?
+ while(stack_.back().cur == stack_.back().end) {
+ //go to parent
+ stack_.pop_back();
+ if(stack_.empty()) return *this;
+ //go to next sibling in parent
+ ++stack_.back().cur;
+ }
+ return *this;
+ }
+
+ /** @brief go to next position after siblings of current */
+ depth_first_traverser&
+ next_after_siblings() {
+ if(stack_.empty()) return *this;
+ stack_.back().cur = stack_.back().end-1;
+ next_sibling();
+ return *this;
+ }
+
+ /**
+ * @brief
+ */
+ depth_first_traverser&
+ back_to_ancestor(const group* g) {
+ if(!g) return *this;
+ while(!stack_.empty()) {
+ const auto& top = stack_.back().cur;
+ if(top->is_group() && &(top->as_group()) == g) return *this;
+ stack_.pop_back();
+ }
+ return *this;
+ }
+
+ /** @brief don't visit next siblings, go back to parent on next ++
+ * note: renders siblings unreachable for *this
+ **/
+ depth_first_traverser&
+ skip_siblings() {
+ if(stack_.empty()) return *this;
+ //future increments won't visit subsequent siblings:
+ stack_.back().end = stack_.back().cur+1;
+ return *this;
+ }
+
+ /** @brief skips all other alternatives in surrounding exclusive groups
+ * on next ++
+ * note: renders alternatives unreachable for *this
+ */
+ depth_first_traverser&
+ skip_alternatives() {
+ if(stack_.empty()) return *this;
+
+ //exclude all other alternatives in surrounding groups
+ //by making their current position the last one
+ for(auto& c : stack_) {
+ if(c.parent && c.parent->exclusive() && c.cur < c.end)
+ c.end = c.cur+1;
+ }
+
+ return *this;
+ }
+
+ void invalidate() {
+ stack_.clear();
+ }
+
+ inline friend bool operator == (const depth_first_traverser& a,
+ const depth_first_traverser& b)
+ {
+ if(a.stack_.empty() || b.stack_.empty()) return false;
+
+ //parents not the same -> different position
+ if(a.stack_.back().parent != b.stack_.back().parent) return false;
+
+ bool aEnd = a.stack_.back().cur == a.stack_.back().end;
+ bool bEnd = b.stack_.back().cur == b.stack_.back().end;
+ //either both at the end of the same parent => same position
+ if(aEnd && bEnd) return true;
+ //or only one at the end => not at the same position
+ if(aEnd || bEnd) return false;
+ return std::addressof(*a.stack_.back().cur) ==
+ std::addressof(*b.stack_.back().cur);
+ }
+ inline friend bool operator != (const depth_first_traverser& a,
+ const depth_first_traverser& b)
+ {
+ return !(a == b);
+ }
+
+ memento
+ undo_point() const {
+ memento m;
+ m.level_ = int(stack_.size());
+ if(!stack_.empty()) m.context_ = stack_.back();
+ return m;
+ }
+
+ void undo(const memento& m) {
+ if(m.level_ < 1) return;
+ if(m.level_ <= int(stack_.size())) {
+ stack_.erase(stack_.begin() + m.level_, stack_.end());
+ stack_.back() = m.context_;
+ }
+ else if(stack_.empty() && m.level_ == 1) {
+ stack_.push_back(m.context_);
+ }
+ }
+
+ private:
+ context_list stack_;
+ };
+
+
+ //---------------------------------------------------------------
+ group() = default;
+
+ template<class Param, class... Params>
+ explicit
+ group(doc_string docstr, Param param, Params... params):
+ children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
+ {
+ doc(std::move(docstr));
+ push_back(std::move(param), std::move(params)...);
+ }
+
+ template<class... Params>
+ explicit
+ group(parameter param, Params... params):
+ children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
+ {
+ push_back(std::move(param), std::move(params)...);
+ }
+
+ template<class P2, class... Ps>
+ explicit
+ group(group p1, P2 p2, Ps... ps):
+ children_{}, exclusive_{false}, joinable_{false}, scoped_{true}
+ {
+ push_back(std::move(p1), std::move(p2), std::move(ps)...);
+ }
+
+
+ //-----------------------------------------------------
+ group(const group&) = default;
+ group(group&&) = default;
+
+
+ //---------------------------------------------------------------
+ group& operator = (const group&) = default;
+ group& operator = (group&&) = default;
+
+
+ //---------------------------------------------------------------
+ /** @brief determines if a command line argument can be matched by a
+ * combination of (partial) matches through any number of children
+ */
+ group& joinable(bool yes) {
+ joinable_ = yes;
+ return *this;
+ }
+
+ /** @brief returns if a command line argument can be matched by a
+ * combination of (partial) matches through any number of children
+ */
+ bool joinable() const noexcept {
+ return joinable_;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief turns explicit scoping on or off
+ * operators , & | and other combinating functions will
+ * not merge groups that are marked as scoped
+ */
+ group& scoped(bool yes) {
+ scoped_ = yes;
+ return *this;
+ }
+
+ /** @brief returns true if operators , & | and other combinating functions
+ * will merge groups and false otherwise
+ */
+ bool scoped() const noexcept
+ {
+ return scoped_;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief determines if children are mutually exclusive alternatives */
+ group& exclusive(bool yes) {
+ exclusive_ = yes;
+ return *this;
+ }
+ /** @brief returns if children are mutually exclusive alternatives */
+ bool exclusive() const noexcept {
+ return exclusive_;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns true, if any child is required to match */
+ bool any_required() const
+ {
+ return std::any_of(children_.begin(), children_.end(),
+ [](const child& n){ return n.required(); });
+ }
+ /** @brief returns true, if all children are required to match */
+ bool all_required() const
+ {
+ return std::all_of(children_.begin(), children_.end(),
+ [](const child& n){ return n.required(); });
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns true if any child is optional (=non-required) */
+ bool any_optional() const {
+ return !all_required();
+ }
+ /** @brief returns true if all children are optional (=non-required) */
+ bool all_optional() const {
+ return !any_required();
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns if the entire group is blocking / positional */
+ bool blocking() const noexcept {
+ return token<group>::blocking() || (exclusive() && all_blocking());
+ }
+ //-----------------------------------------------------
+ /** @brief determines if the entire group is blocking / positional */
+ group& blocking(bool yes) {
+ return token<group>::blocking(yes);
+ }
+
+ //---------------------------------------------------------------
+ /** @brief returns true if any child is blocking */
+ bool any_blocking() const
+ {
+ return std::any_of(children_.begin(), children_.end(),
+ [](const child& n){ return n.blocking(); });
+ }
+ //---------------------------------------------------------------
+ /** @brief returns true if all children is blocking */
+ bool all_blocking() const
+ {
+ return std::all_of(children_.begin(), children_.end(),
+ [](const child& n){ return n.blocking(); });
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns if any child is a value parameter (recursive) */
+ bool any_flagless() const
+ {
+ return std::any_of(children_.begin(), children_.end(),
+ [](const child& p){
+ return p.is_param() && p.as_param().flags().empty();
+ });
+ }
+ /** @brief returns if all children are value parameters (recursive) */
+ bool all_flagless() const
+ {
+ return std::all_of(children_.begin(), children_.end(),
+ [](const child& p){
+ return p.is_param() && p.as_param().flags().empty();
+ });
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds child parameter at the end */
+ group&
+ push_back(const parameter& v) {
+ children_.emplace_back(v);
+ return *this;
+ }
+ //-----------------------------------------------------
+ /** @brief adds child parameter at the end */
+ group&
+ push_back(parameter&& v) {
+ children_.emplace_back(std::move(v));
+ return *this;
+ }
+ //-----------------------------------------------------
+ /** @brief adds child group at the end */
+ group&
+ push_back(const group& g) {
+ children_.emplace_back(g);
+ return *this;
+ }
+ //-----------------------------------------------------
+ /** @brief adds child group at the end */
+ group&
+ push_back(group&& g) {
+ children_.emplace_back(std::move(g));
+ return *this;
+ }
+
+
+ //-----------------------------------------------------
+ /** @brief adds children (groups and/or parameters) */
+ template<class Param1, class Param2, class... Params>
+ group&
+ push_back(Param1&& param1, Param2&& param2, Params&&... params)
+ {
+ children_.reserve(children_.size() + 2 + sizeof...(params));
+ push_back(std::forward<Param1>(param1));
+ push_back(std::forward<Param2>(param2), std::forward<Params>(params)...);
+ return *this;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds child parameter at the beginning */
+ group&
+ push_front(const parameter& v) {
+ children_.emplace(children_.begin(), v);
+ return *this;
+ }
+ //-----------------------------------------------------
+ /** @brief adds child parameter at the beginning */
+ group&
+ push_front(parameter&& v) {
+ children_.emplace(children_.begin(), std::move(v));
+ return *this;
+ }
+ //-----------------------------------------------------
+ /** @brief adds child group at the beginning */
+ group&
+ push_front(const group& g) {
+ children_.emplace(children_.begin(), g);
+ return *this;
+ }
+ //-----------------------------------------------------
+ /** @brief adds child group at the beginning */
+ group&
+ push_front(group&& g) {
+ children_.emplace(children_.begin(), std::move(g));
+ return *this;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief adds all children of other group at the end */
+ group&
+ merge(group&& g)
+ {
+ children_.insert(children_.end(),
+ std::make_move_iterator(g.begin()),
+ std::make_move_iterator(g.end()));
+ return *this;
+ }
+ //-----------------------------------------------------
+ /** @brief adds all children of several other groups at the end */
+ template<class... Groups>
+ group&
+ merge(group&& g1, group&& g2, Groups&&... gs)
+ {
+ merge(std::move(g1));
+ merge(std::move(g2), std::forward<Groups>(gs)...);
+ return *this;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief indexed, nutable access to child */
+ child& operator [] (size_type index) noexcept {
+ return children_[index];
+ }
+ /** @brief indexed, non-nutable access to child */
+ const child& operator [] (size_type index) const noexcept {
+ return children_[index];
+ }
+
+ //---------------------------------------------------------------
+ /** @brief mutable access to first child */
+ child& front() noexcept { return children_.front(); }
+ /** @brief non-mutable access to first child */
+ const child& front() const noexcept { return children_.front(); }
+ //-----------------------------------------------------
+ /** @brief mutable access to last child */
+ child& back() noexcept { return children_.back(); }
+ /** @brief non-mutable access to last child */
+ const child& back() const noexcept { return children_.back(); }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns true, if group has no children, false otherwise */
+ bool empty() const noexcept { return children_.empty(); }
+
+ /** @brief returns number of children */
+ size_type size() const noexcept { return children_.size(); }
+
+ /** @brief returns number of nested levels; 1 for a flat group */
+ size_type depth() const {
+ size_type n = 0;
+ for(const auto& c : children_) {
+ auto l = 1 + c.depth();
+ if(l > n) n = l;
+ }
+ return n;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns mutating iterator to position of first element */
+ iterator begin() noexcept { return children_.begin(); }
+ /** @brief returns non-mutating iterator to position of first element */
+ const_iterator begin() const noexcept { return children_.begin(); }
+ /** @brief returns non-mutating iterator to position of first element */
+ const_iterator cbegin() const noexcept { return children_.begin(); }
+
+ /** @brief returns mutating iterator to position one past the last element */
+ iterator end() noexcept { return children_.end(); }
+ /** @brief returns non-mutating iterator to position one past the last element */
+ const_iterator end() const noexcept { return children_.end(); }
+ /** @brief returns non-mutating iterator to position one past the last element */
+ const_iterator cend() const noexcept { return children_.end(); }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns augmented iterator for depth first searches
+ * @details traverser knows end of iteration and can skip over children
+ */
+ depth_first_traverser
+ begin_dfs() const noexcept {
+ return depth_first_traverser{*this};
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns recursive parameter count */
+ size_type param_count() const {
+ size_type c = 0;
+ for(const auto& n : children_) {
+ c += n.param_count();
+ }
+ return c;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns range of all flags (recursive) */
+ arg_list all_flags() const
+ {
+ std::vector<arg_string> all;
+ gather_flags(children_, all);
+ return all;
+ }
+
+ /** @brief returns true, if no flag occurs as true
+ * prefix of any other flag (identical flags will be ignored) */
+ bool flags_are_prefix_free() const
+ {
+ const auto fs = all_flags();
+
+ using std::begin; using std::end;
+ for(auto i = begin(fs), e = end(fs); i != e; ++i) {
+ if(!i->empty()) {
+ for(auto j = i+1; j != e; ++j) {
+ if(!j->empty() && *i != *j) {
+ if(i->find(*j) == 0) return false;
+ if(j->find(*i) == 0) return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns longest common prefix of all flags */
+ arg_string common_flag_prefix() const
+ {
+ arg_list prefixes;
+ gather_prefixes(children_, prefixes);
+ return str::longest_common_prefix(prefixes);
+ }
+
+
+private:
+ //---------------------------------------------------------------
+ static void
+ gather_flags(const children_store& nodes, arg_list& all)
+ {
+ for(const auto& p : nodes) {
+ if(p.is_group()) {
+ gather_flags(p.as_group().children_, all);
+ }
+ else {
+ const auto& pf = p.as_param().flags();
+ using std::begin;
+ using std::end;
+ if(!pf.empty()) all.insert(end(all), begin(pf), end(pf));
+ }
+ }
+ }
+ //---------------------------------------------------------------
+ static void
+ gather_prefixes(const children_store& nodes, arg_list& all)
+ {
+ for(const auto& p : nodes) {
+ if(p.is_group()) {
+ gather_prefixes(p.as_group().children_, all);
+ }
+ else if(!p.as_param().flags().empty()) {
+ auto pfx = str::longest_common_prefix(p.as_param().flags());
+ if(!pfx.empty()) all.push_back(std::move(pfx));
+ }
+ }
+ }
+
+ //---------------------------------------------------------------
+ children_store children_;
+ bool exclusive_ = false;
+ bool joinable_ = false;
+ bool scoped_ = false;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief group or parameter
+ *
+ *****************************************************************************/
+using pattern = group::child;
+
+
+
+/*************************************************************************//**
+ *
+ * @brief apply an action to all parameters in a group
+ *
+ *****************************************************************************/
+template<class Action>
+void for_all_params(group& g, Action&& action)
+{
+ for(auto& p : g) {
+ if(p.is_group()) {
+ for_all_params(p.as_group(), action);
+ }
+ else {
+ action(p.as_param());
+ }
+ }
+}
+
+template<class Action>
+void for_all_params(const group& g, Action&& action)
+{
+ for(auto& p : g) {
+ if(p.is_group()) {
+ for_all_params(p.as_group(), action);
+ }
+ else {
+ action(p.as_param());
+ }
+ }
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a group of parameters and/or groups
+ *
+ *****************************************************************************/
+inline group
+operator , (parameter a, parameter b)
+{
+ return group{std::move(a), std::move(b)}.scoped(false);
+}
+
+//---------------------------------------------------------
+inline group
+operator , (parameter a, group b)
+{
+ return !b.scoped() && !b.blocking() && !b.exclusive() && !b.repeatable()
+ && !b.joinable() && (b.doc().empty() || b.doc() == a.doc())
+ ? b.push_front(std::move(a))
+ : group{std::move(a), std::move(b)}.scoped(false);
+}
+
+//---------------------------------------------------------
+inline group
+operator , (group a, parameter b)
+{
+ return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
+ && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
+ ? a.push_back(std::move(b))
+ : group{std::move(a), std::move(b)}.scoped(false);
+}
+
+//---------------------------------------------------------
+inline group
+operator , (group a, group b)
+{
+ return !a.scoped() && !a.blocking() && !a.exclusive() && !a.repeatable()
+ && !a.joinable() && (a.doc().empty() || a.doc() == b.doc())
+ ? a.push_back(std::move(b))
+ : group{std::move(a), std::move(b)}.scoped(false);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a group of alternative parameters or groups
+ *
+ *****************************************************************************/
+template<class Param, class... Params>
+inline group
+one_of(Param param, Params... params)
+{
+ return group{std::move(param), std::move(params)...}.exclusive(true);
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a group of alternative parameters or groups
+ *
+ *****************************************************************************/
+inline group
+operator | (parameter a, parameter b)
+{
+ return group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
+}
+
+//-------------------------------------------------------------------
+inline group
+operator | (parameter a, group b)
+{
+ return !b.scoped() && !b.blocking() && b.exclusive() && !b.repeatable()
+ && !b.joinable()
+ && (b.doc().empty() || b.doc() == a.doc())
+ ? b.push_front(std::move(a))
+ : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
+}
+
+//-------------------------------------------------------------------
+inline group
+operator | (group a, parameter b)
+{
+ return !a.scoped() && a.exclusive() && !a.repeatable() && !a.joinable()
+ && a.blocking() == b.blocking()
+ && (a.doc().empty() || a.doc() == b.doc())
+ ? a.push_back(std::move(b))
+ : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
+}
+
+inline group
+operator | (group a, group b)
+{
+ return !a.scoped() && a.exclusive() &&!a.repeatable() && !a.joinable()
+ && a.blocking() == b.blocking()
+ && (a.doc().empty() || a.doc() == b.doc())
+ ? a.push_back(std::move(b))
+ : group{std::move(a), std::move(b)}.scoped(false).exclusive(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief helpers (NOT FOR DIRECT USE IN CLIENT CODE!)
+ * no interface guarantees; might be changed or removed in the future
+ *
+ *****************************************************************************/
+namespace detail {
+
+inline void set_blocking(bool) {}
+
+template<class P, class... Ps>
+void set_blocking(bool yes, P& p, Ps&... ps) {
+ p.blocking(yes);
+ set_blocking(yes, ps...);
+}
+
+} // namespace detail
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a parameter/group sequence by making all input objects blocking
+ *
+ *****************************************************************************/
+template<class Param, class... Params>
+inline group
+in_sequence(Param param, Params... params)
+{
+ detail::set_blocking(true, param, params...);
+ return group{std::move(param), std::move(params)...}.scoped(true);
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a parameter/group sequence by making all input objects blocking
+ *
+ *****************************************************************************/
+inline group
+operator & (parameter a, parameter b)
+{
+ a.blocking(true);
+ b.blocking(true);
+ return group{std::move(a), std::move(b)}.scoped(true);
+}
+
+//---------------------------------------------------------
+inline group
+operator & (parameter a, group b)
+{
+ a.blocking(true);
+ return group{std::move(a), std::move(b)}.scoped(true);
+}
+
+//---------------------------------------------------------
+inline group
+operator & (group a, parameter b)
+{
+ b.blocking(true);
+ if(a.all_blocking() && !a.exclusive() && !a.repeatable() && !a.joinable()
+ && (a.doc().empty() || a.doc() == b.doc()))
+ {
+ return a.push_back(std::move(b));
+ }
+ else {
+ if(!a.all_blocking()) a.blocking(true);
+ return group{std::move(a), std::move(b)}.scoped(true);
+ }
+}
+
+inline group
+operator & (group a, group b)
+{
+ if(!b.all_blocking()) b.blocking(true);
+ if(a.all_blocking() && !a.exclusive() && !a.repeatable()
+ && !a.joinable() && (a.doc().empty() || a.doc() == b.doc()))
+ {
+ return a.push_back(std::move(b));
+ }
+ else {
+ if(!a.all_blocking()) a.blocking(true);
+ return group{std::move(a), std::move(b)}.scoped(true);
+ }
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a group of parameters and/or groups
+ * where all single char flag params ("-a", "b", ...) are joinable
+ *
+ *****************************************************************************/
+inline group
+joinable(group g) {
+ return g.joinable(true);
+}
+
+//-------------------------------------------------------------------
+template<class... Params>
+inline group
+joinable(parameter param, Params... params)
+{
+ return group{std::move(param), std::move(params)...}.joinable(true);
+}
+
+template<class P2, class... Ps>
+inline group
+joinable(group p1, P2 p2, Ps... ps)
+{
+ return group{std::move(p1), std::move(p2), std::move(ps)...}.joinable(true);
+}
+
+template<class Param, class... Params>
+inline group
+joinable(doc_string docstr, Param param, Params... params)
+{
+ return group{std::move(param), std::move(params)...}
+ .joinable(true).doc(std::move(docstr));
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a repeatable copy of a parameter
+ *
+ *****************************************************************************/
+inline parameter
+repeatable(parameter p) {
+ return p.repeatable(true);
+}
+
+/*************************************************************************//**
+ *
+ * @brief makes a repeatable copy of a group
+ *
+ *****************************************************************************/
+inline group
+repeatable(group g) {
+ return g.repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a group of parameters and/or groups
+ * that is repeatable as a whole
+ * Note that a repeatable group consisting entirely of non-blocking
+ * children is equivalent to a non-repeatable group of
+ * repeatable children.
+ *
+ *****************************************************************************/
+template<class P2, class... Ps>
+inline group
+repeatable(parameter p1, P2 p2, Ps... ps)
+{
+ return group{std::move(p1), std::move(p2),
+ std::move(ps)...}.repeatable(true);
+}
+
+template<class P2, class... Ps>
+inline group
+repeatable(group p1, P2 p2, Ps... ps)
+{
+ return group{std::move(p1), std::move(p2),
+ std::move(ps)...}.repeatable(true);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief makes a parameter greedy (match with top priority)
+ *
+ *****************************************************************************/
+inline parameter
+greedy(parameter p) {
+ return p.greedy(true);
+}
+
+inline parameter
+operator ! (parameter p) {
+ return greedy(p);
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief recursively prepends a prefix to all flags
+ *
+ *****************************************************************************/
+inline parameter&&
+with_prefix(const arg_string& prefix, parameter&& p) {
+ return std::move(with_prefix(prefix, p));
+}
+
+
+//-------------------------------------------------------------------
+inline group&
+with_prefix(const arg_string& prefix, group& g)
+{
+ for(auto& p : g) {
+ if(p.is_group()) {
+ with_prefix(prefix, p.as_group());
+ } else {
+ with_prefix(prefix, p.as_param());
+ }
+ }
+ return g;
+}
+
+
+inline group&&
+with_prefix(const arg_string& prefix, group&& params)
+{
+ return std::move(with_prefix(prefix, params));
+}
+
+
+template<class Param, class... Params>
+inline group
+with_prefix(arg_string prefix, Param&& param, Params&&... params)
+{
+ return with_prefix(prefix, group{std::forward<Param>(param),
+ std::forward<Params>(params)...});
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief recursively prepends a prefix to all flags
+ *
+ * @param shortpfx : used for single-letter flags
+ * @param longpfx : used for flags with length > 1
+ *
+ *****************************************************************************/
+inline parameter&&
+with_prefixes_short_long(const arg_string& shortpfx, const arg_string& longpfx,
+ parameter&& p)
+{
+ return std::move(with_prefixes_short_long(shortpfx, longpfx, p));
+}
+
+
+//-------------------------------------------------------------------
+inline group&
+with_prefixes_short_long(const arg_string& shortFlagPrefix,
+ const arg_string& longFlagPrefix,
+ group& g)
+{
+ for(auto& p : g) {
+ if(p.is_group()) {
+ with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_group());
+ } else {
+ with_prefixes_short_long(shortFlagPrefix, longFlagPrefix, p.as_param());
+ }
+ }
+ return g;
+}
+
+
+inline group&&
+with_prefixes_short_long(const arg_string& shortFlagPrefix,
+ const arg_string& longFlagPrefix,
+ group&& params)
+{
+ return std::move(with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
+ params));
+}
+
+
+template<class Param, class... Params>
+inline group
+with_prefixes_short_long(const arg_string& shortFlagPrefix,
+ const arg_string& longFlagPrefix,
+ Param&& param, Params&&... params)
+{
+ return with_prefixes_short_long(shortFlagPrefix, longFlagPrefix,
+ group{std::forward<Param>(param),
+ std::forward<Params>(params)...});
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief recursively prepends a suffix to all flags
+ *
+ *****************************************************************************/
+inline parameter&&
+with_suffix(const arg_string& suffix, parameter&& p) {
+ return std::move(with_suffix(suffix, p));
+}
+
+
+//-------------------------------------------------------------------
+inline group&
+with_suffix(const arg_string& suffix, group& g)
+{
+ for(auto& p : g) {
+ if(p.is_group()) {
+ with_suffix(suffix, p.as_group());
+ } else {
+ with_suffix(suffix, p.as_param());
+ }
+ }
+ return g;
+}
+
+
+inline group&&
+with_suffix(const arg_string& suffix, group&& params)
+{
+ return std::move(with_suffix(suffix, params));
+}
+
+
+template<class Param, class... Params>
+inline group
+with_suffix(arg_string suffix, Param&& param, Params&&... params)
+{
+ return with_suffix(suffix, group{std::forward<Param>(param),
+ std::forward<Params>(params)...});
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief recursively prepends a suffix to all flags
+ *
+ * @param shortsfx : used for single-letter flags
+ * @param longsfx : used for flags with length > 1
+ *
+ *****************************************************************************/
+inline parameter&&
+with_suffixes_short_long(const arg_string& shortsfx, const arg_string& longsfx,
+ parameter&& p)
+{
+ return std::move(with_suffixes_short_long(shortsfx, longsfx, p));
+}
+
+
+//-------------------------------------------------------------------
+inline group&
+with_suffixes_short_long(const arg_string& shortFlagSuffix,
+ const arg_string& longFlagSuffix,
+ group& g)
+{
+ for(auto& p : g) {
+ if(p.is_group()) {
+ with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_group());
+ } else {
+ with_suffixes_short_long(shortFlagSuffix, longFlagSuffix, p.as_param());
+ }
+ }
+ return g;
+}
+
+
+inline group&&
+with_suffixes_short_long(const arg_string& shortFlagSuffix,
+ const arg_string& longFlagSuffix,
+ group&& params)
+{
+ return std::move(with_suffixes_short_long(shortFlagSuffix, longFlagSuffix,
+ params));
+}
+
+
+template<class Param, class... Params>
+inline group
+with_suffixes_short_long(const arg_string& shortFlagSuffix,
+ const arg_string& longFlagSuffix,
+ Param&& param, Params&&... params)
+{
+ return with_suffixes_short_long(shortFlagSuffix, longFlagSuffix,
+ group{std::forward<Param>(param),
+ std::forward<Params>(params)...});
+}
+
+
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief parsing implementation details
+ *
+ *****************************************************************************/
+
+namespace detail {
+
+
+/*************************************************************************//**
+ *
+ * @brief DFS traverser that keeps track of 'scopes'
+ * scope = all parameters that are either bounded by
+ * two blocking parameters on the same depth level
+ * or the beginning/end of the outermost group
+ *
+ *****************************************************************************/
+class scoped_dfs_traverser
+{
+public:
+ using dfs_traverser = group::depth_first_traverser;
+
+ scoped_dfs_traverser() = default;
+
+ explicit
+ scoped_dfs_traverser(const group& g):
+ pos_{g}, lastMatch_{}, posAfterLastMatch_{}, scopes_{},
+ ignoreBlocks_{false},
+ repeatGroupStarted_{false}, repeatGroupContinues_{false}
+ {}
+
+ const dfs_traverser& base() const noexcept { return pos_; }
+ const dfs_traverser& last_match() const noexcept { return lastMatch_; }
+
+ const group& parent() const noexcept { return pos_.parent(); }
+
+ const group* innermost_repeat_group() const noexcept {
+ return pos_.innermost_repeat_group();
+ }
+ const group* outermost_join_group() const noexcept {
+ return pos_.outermost_join_group();
+ }
+ const group* innermost_blocking_group() const noexcept {
+ return pos_.innermost_blocking_group();
+ }
+ const group* innermost_exclusive_group() const noexcept {
+ return pos_.innermost_exclusive_group();
+ }
+
+ const pattern* operator ->() const noexcept { return pos_.operator->(); }
+ const pattern& operator *() const noexcept { return *pos_; }
+
+ const pattern* ptr() const noexcept { return pos_.operator->(); }
+
+ explicit operator bool() const noexcept { return bool(pos_); }
+
+ bool joinable() const noexcept { return pos_.joinable(); }
+ arg_string common_flag_prefix() const { return pos_.common_flag_prefix(); }
+
+ void ignore_blocking(bool yes) { ignoreBlocks_ = yes; }
+
+ void invalidate() {
+ pos_.invalidate();
+ }
+
+ bool matched() const noexcept {
+ return (pos_ == lastMatch_);
+ }
+
+ bool start_of_repeat_group() const noexcept { return repeatGroupStarted_; }
+
+ //-----------------------------------------------------
+ scoped_dfs_traverser&
+ next_sibling() { pos_.next_sibling(); return *this; }
+
+ scoped_dfs_traverser&
+ next_after_siblings() { pos_.next_after_siblings(); return *this; }
+
+
+ //-----------------------------------------------------
+ scoped_dfs_traverser&
+ operator ++ ()
+ {
+ if(!pos_) return *this;
+
+ if(pos_.is_last_in_path()) {
+ return_to_outermost_scope();
+ return *this;
+ }
+
+ //current pattern can block if it didn't match already
+ if(ignoreBlocks_ || matched()) {
+ ++pos_;
+ }
+ else if(!pos_->is_group()) {
+ //current group can block if we didn't have any match in it
+ const group* g = pos_.outermost_blocking_group_fully_explored();
+ //no match in 'g' before -> skip to after its siblings
+ if(g && !lastMatch_.is_inside(g)) {
+ pos_.back_to_ancestor(g).next_after_siblings();
+ if(!pos_) return_to_outermost_scope();
+ }
+ else if(pos_->blocking()) {
+ if(pos_.parent().exclusive()) {
+ pos_.next_sibling();
+ } else {
+ //no match => skip siblings of blocking param
+ pos_.next_after_siblings();
+ }
+ if(!pos_) return_to_outermost_scope();
+ } else {
+ ++pos_;
+ }
+ } else {
+ ++pos_;
+ }
+ check_if_left_scope();
+ return *this;
+ }
+
+ //-----------------------------------------------------
+ void next_after_match(scoped_dfs_traverser match)
+ {
+ if(!match || ignoreBlocks_) return;
+
+ check_repeat_group_start(match);
+
+ lastMatch_ = match.base();
+
+ // if there is a blocking ancestor -> go back to it
+ if(!match->blocking()) {
+ match.pos_.back_to_ancestor(match.innermost_blocking_group());
+ }
+
+ //if match is not in current position & current position is blocking
+ //=> current position has to be advanced by one so that it is
+ //no longer reachable within current scope
+ //(can happen for repeatable, blocking parameters)
+ if(match.base() != pos_ && pos_->blocking()) pos_.next_sibling();
+
+ if(match->blocking()) {
+ if(match.pos_.is_alternative()) {
+ //discard other alternatives
+ match.pos_.skip_alternatives();
+ }
+
+ if(is_last_in_current_scope(match.pos_)) {
+ //if current param is not repeatable -> back to previous scope
+ if(!match->repeatable() && !match->is_group()) {
+ pos_ = std::move(match.pos_);
+ if(!scopes_.empty()) pos_.undo(scopes_.top());
+ }
+ else { //stay at match position
+ pos_ = std::move(match.pos_);
+ }
+ }
+ else { //not last in current group
+ //if current param is not repeatable, go directly to next
+ if(!match->repeatable() && !match->is_group()) {
+ ++match.pos_;
+ }
+
+ if(match.pos_.level() > pos_.level()) {
+ scopes_.push(pos_.undo_point());
+ pos_ = std::move(match.pos_);
+ }
+ else if(match.pos_.level() < pos_.level()) {
+ return_to_level(match.pos_.level());
+ }
+ else {
+ pos_ = std::move(match.pos_);
+ }
+ }
+ posAfterLastMatch_ = pos_;
+ }
+ else {
+ if(match.pos_.level() < pos_.level()) {
+ return_to_level(match.pos_.level());
+ }
+ posAfterLastMatch_ = pos_;
+ }
+ repeatGroupContinues_ = repeat_group_continues();
+ }
+
+private:
+ //-----------------------------------------------------
+ bool is_last_in_current_scope(const dfs_traverser& pos) const
+ {
+ if(scopes_.empty()) return pos.is_last_in_path();
+ //check if we would leave the current scope on ++
+ auto p = pos;
+ ++p;
+ return p.level() < scopes_.top().level();
+ }
+
+ //-----------------------------------------------------
+ void check_repeat_group_start(const scoped_dfs_traverser& newMatch)
+ {
+ const auto newrg = newMatch.innermost_repeat_group();
+ if(!newrg) {
+ repeatGroupStarted_ = false;
+ }
+ else if(lastMatch_.innermost_repeat_group() != newrg) {
+ repeatGroupStarted_ = true;
+ }
+ else if(!repeatGroupContinues_ || !newMatch.repeatGroupContinues_) {
+ repeatGroupStarted_ = true;
+ }
+ else {
+ //special case: repeat group is outermost group
+ //=> we can never really 'leave' and 'reenter' it
+ //but if the current scope is the first element, then we are
+ //conceptually at a position 'before' the group
+ repeatGroupStarted_ = scopes_.empty() || (
+ newrg == pos_.root() &&
+ scopes_.top().param() == &(*pos_.root()->begin()) );
+ }
+ repeatGroupContinues_ = repeatGroupStarted_;
+ }
+
+ //-----------------------------------------------------
+ bool repeat_group_continues() const
+ {
+ if(!repeatGroupContinues_) return false;
+ const auto curRepGroup = pos_.innermost_repeat_group();
+ if(!curRepGroup) return false;
+ if(curRepGroup != lastMatch_.innermost_repeat_group()) return false;
+ if(!posAfterLastMatch_) return false;
+ return true;
+ }
+
+ //-----------------------------------------------------
+ void check_if_left_scope()
+ {
+ if(posAfterLastMatch_) {
+ if(pos_.level() < posAfterLastMatch_.level()) {
+ while(!scopes_.empty() && scopes_.top().level() >= pos_.level()) {
+ pos_.undo(scopes_.top());
+ scopes_.pop();
+ }
+ posAfterLastMatch_.invalidate();
+ }
+ }
+ while(!scopes_.empty() && scopes_.top().level() > pos_.level()) {
+ pos_.undo(scopes_.top());
+ scopes_.pop();
+ }
+ repeatGroupContinues_ = repeat_group_continues();
+ }
+
+ //-----------------------------------------------------
+ void return_to_outermost_scope()
+ {
+ posAfterLastMatch_.invalidate();
+
+ if(scopes_.empty()) {
+ pos_.invalidate();
+ repeatGroupContinues_ = false;
+ return;
+ }
+
+ while(!scopes_.empty() && (!pos_ || pos_.level() >= 1)) {
+ pos_.undo(scopes_.top());
+ scopes_.pop();
+ }
+ while(!scopes_.empty()) scopes_.pop();
+
+ repeatGroupContinues_ = repeat_group_continues();
+ }
+
+ //-----------------------------------------------------
+ void return_to_level(int level)
+ {
+ if(pos_.level() <= level) return;
+ while(!scopes_.empty() && pos_.level() > level) {
+ pos_.undo(scopes_.top());
+ scopes_.pop();
+ }
+ };
+
+ dfs_traverser pos_;
+ dfs_traverser lastMatch_;
+ dfs_traverser posAfterLastMatch_;
+ std::stack<dfs_traverser::memento> scopes_;
+ bool ignoreBlocks_ = false;
+ bool repeatGroupStarted_ = false;
+ bool repeatGroupContinues_ = false;
+};
+
+
+
+
+/*****************************************************************************
+ *
+ * some parameter property predicates
+ *
+ *****************************************************************************/
+struct select_all {
+ bool operator () (const parameter&) const noexcept { return true; }
+};
+
+struct select_flags {
+ bool operator () (const parameter& p) const noexcept {
+ return !p.flags().empty();
+ }
+};
+
+struct select_values {
+ bool operator () (const parameter& p) const noexcept {
+ return p.flags().empty();
+ }
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief result of a matching operation
+ *
+ *****************************************************************************/
+class match_t {
+public:
+ using size_type = arg_string::size_type;
+
+ match_t() = default;
+
+ match_t(arg_string s, scoped_dfs_traverser p):
+ str_{std::move(s)}, pos_{std::move(p)}
+ {}
+
+ size_type length() const noexcept { return str_.size(); }
+
+ const arg_string& str() const noexcept { return str_; }
+ const scoped_dfs_traverser& pos() const noexcept { return pos_; }
+
+ explicit operator bool() const noexcept { return bool(pos_); }
+
+private:
+ arg_string str_;
+ scoped_dfs_traverser pos_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief finds the first parameter that matches a given string;
+ * candidate parameters are traversed using a scoped DFS traverser
+ *
+ *****************************************************************************/
+template<class ParamSelector>
+match_t
+full_match(scoped_dfs_traverser pos, const arg_string& arg,
+ const ParamSelector& select)
+{
+ while(pos) {
+ if(pos->is_param()) {
+ const auto& param = pos->as_param();
+ if(select(param)) {
+ const auto match = param.match(arg);
+ if(match && match.length() == arg.size()) {
+ return match_t{arg, std::move(pos)};
+ }
+ }
+ }
+ ++pos;
+ }
+ return match_t{};
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief finds the first parameter that matches any (non-empty) prefix
+ * of a given string;
+ * candidate parameters are traversed using a scoped DFS traverser
+ *
+ *****************************************************************************/
+template<class ParamSelector>
+match_t
+longest_prefix_match(scoped_dfs_traverser pos, const arg_string& arg,
+ const ParamSelector& select)
+{
+ match_t longest;
+
+ while(pos) {
+ if(pos->is_param()) {
+ const auto& param = pos->as_param();
+ if(select(param)) {
+ auto match = param.match(arg);
+ if(match.prefix()) {
+ if(match.length() == arg.size()) {
+ return match_t{arg, std::move(pos)};
+ }
+ else if(match.length() > longest.length()) {
+ longest = match_t{arg.substr(match.at(), match.length()),
+ pos};
+ }
+ }
+ }
+ }
+ ++pos;
+ }
+ return longest;
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief finds the first parameter that partially matches a given string;
+ * candidate parameters are traversed using a scoped DFS traverser
+ *
+ *****************************************************************************/
+template<class ParamSelector>
+match_t
+partial_match(scoped_dfs_traverser pos, const arg_string& arg,
+ const ParamSelector& select)
+{
+ while(pos) {
+ if(pos->is_param()) {
+ const auto& param = pos->as_param();
+ if(select(param)) {
+ const auto match = param.match(arg);
+ if(match) {
+ return match_t{arg.substr(match.at(), match.length()),
+ std::move(pos)};
+ }
+ }
+ }
+ ++pos;
+ }
+ return match_t{};
+}
+
+} //namespace detail
+
+
+
+
+
+
+/***************************************************************//**
+ *
+ * @brief default command line arguments parser
+ *
+ *******************************************************************/
+class parser
+{
+public:
+ using dfs_traverser = group::depth_first_traverser;
+ using scoped_dfs_traverser = detail::scoped_dfs_traverser;
+
+
+ /*****************************************************//**
+ * @brief arg -> parameter mapping
+ *********************************************************/
+ class arg_mapping {
+ public:
+ friend class parser;
+
+ explicit
+ arg_mapping(arg_index idx, arg_string s,
+ const dfs_traverser& match)
+ :
+ index_{idx}, arg_{std::move(s)}, match_{match},
+ repeat_{0}, startsRepeatGroup_{false},
+ blocked_{false}, conflict_{false}
+ {}
+
+ explicit
+ arg_mapping(arg_index idx, arg_string s) :
+ index_{idx}, arg_{std::move(s)}, match_{},
+ repeat_{0}, startsRepeatGroup_{false},
+ blocked_{false}, conflict_{false}
+ {}
+
+ arg_index index() const noexcept { return index_; }
+ const arg_string& arg() const noexcept { return arg_; }
+
+ const parameter* param() const noexcept {
+ return match_ && match_->is_param()
+ ? &(match_->as_param()) : nullptr;
+ }
+
+ std::size_t repeat() const noexcept { return repeat_; }
+
+ bool blocked() const noexcept { return blocked_; }
+ bool conflict() const noexcept { return conflict_; }
+
+ bool bad_repeat() const noexcept {
+ if(!param()) return false;
+ return repeat_ > 0 && !param()->repeatable()
+ && !match_.innermost_repeat_group();
+ }
+
+ bool any_error() const noexcept {
+ return !match_ || blocked() || conflict() || bad_repeat();
+ }
+
+ private:
+ arg_index index_;
+ arg_string arg_;
+ dfs_traverser match_;
+ std::size_t repeat_;
+ bool startsRepeatGroup_;
+ bool blocked_;
+ bool conflict_;
+ };
+
+ /*****************************************************//**
+ * @brief references a non-matched, required parameter
+ *********************************************************/
+ class missing_event {
+ public:
+ explicit
+ missing_event(const parameter* p, arg_index after):
+ param_{p}, aftIndex_{after}
+ {}
+
+ const parameter* param() const noexcept { return param_; }
+
+ arg_index after_index() const noexcept { return aftIndex_; }
+
+ private:
+ const parameter* param_;
+ arg_index aftIndex_;
+ };
+
+ //-----------------------------------------------------
+ using missing_events = std::vector<missing_event>;
+ using arg_mappings = std::vector<arg_mapping>;
+
+
+private:
+ struct miss_candidate {
+ miss_candidate(dfs_traverser p, arg_index idx,
+ bool firstInRepeatGroup = false):
+ pos{std::move(p)}, index{idx},
+ startsRepeatGroup{firstInRepeatGroup}
+ {}
+
+ dfs_traverser pos;
+ arg_index index;
+ bool startsRepeatGroup;
+ };
+ using miss_candidates = std::vector<miss_candidate>;
+
+
+public:
+ //---------------------------------------------------------------
+ /** @brief initializes parser with a command line interface
+ * @param offset = argument index offset used for reports
+ * */
+ explicit
+ parser(const group& root, arg_index offset = 0):
+ root_{&root}, pos_{root},
+ index_{offset-1}, eaten_{0},
+ args_{}, missCand_{}, blocked_{false}
+ {
+ for_each_potential_miss(dfs_traverser{root},
+ [this](const dfs_traverser& p){
+ missCand_.emplace_back(p, index_);
+ });
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief processes one command line argument */
+ bool operator() (const arg_string& arg)
+ {
+ ++eaten_;
+ ++index_;
+
+ if(!valid()) return false;
+
+ if(!blocked_ && try_match(arg)) return true;
+
+ if(try_match_blocked(arg)) return false;
+
+ //skipping of blocking & required patterns is not allowed
+ if(!blocked_ && !pos_.matched() && pos_->required() && pos_->blocking()) {
+ blocked_ = true;
+ }
+
+ add_nomatch(arg);
+ return false;
+ }
+
+
+ //---------------------------------------------------------------
+ /** @brief returns range of argument -> parameter mappings */
+ const arg_mappings& args() const {
+ return args_;
+ }
+
+ /** @brief returns list of missing events */
+ missing_events missed() const {
+ missing_events misses;
+ misses.reserve(missCand_.size());
+ for(auto i = missCand_.begin(); i != missCand_.end(); ++i) {
+ misses.emplace_back(&(i->pos->as_param()), i->index);
+ }
+ return misses;
+ }
+
+ /** @brief returns number of processed command line arguments */
+ arg_index parse_count() const noexcept { return eaten_; }
+
+ /** @brief returns false if previously processed command line arguments
+ * lead to an invalid / inconsistent parsing result
+ */
+ bool valid() const noexcept { return bool(pos_); }
+
+ /** @brief returns false if previously processed command line arguments
+ * lead to an invalid / inconsistent parsing result
+ */
+ explicit operator bool() const noexcept { return valid(); }
+
+
+private:
+ //---------------------------------------------------------------
+ using match_t = detail::match_t;
+
+
+ //---------------------------------------------------------------
+ /** @brief try to match argument with unreachable parameter */
+ bool try_match_blocked(const arg_string& arg)
+ {
+ //try to match ahead (using temporary parser)
+ if(pos_) {
+ auto ahead = *this;
+ if(try_match_blocked(std::move(ahead), arg)) return true;
+ }
+
+ //try to match from the beginning (using temporary parser)
+ if(root_) {
+ parser all{*root_, index_+1};
+ if(try_match_blocked(std::move(all), arg)) return true;
+ }
+
+ return false;
+ }
+
+ //---------------------------------------------------------------
+ bool try_match_blocked(parser&& parse, const arg_string& arg)
+ {
+ const auto nold = int(parse.args_.size());
+
+ parse.pos_.ignore_blocking(true);
+
+ if(!parse.try_match(arg)) return false;
+
+ for(auto i = parse.args_.begin() + nold; i != parse.args_.end(); ++i) {
+ args_.push_back(*i);
+ args_.back().blocked_ = true;
+ }
+ return true;
+ }
+
+ //---------------------------------------------------------------
+ /** @brief try to find a parameter/pattern that matches 'arg' */
+ bool try_match(const arg_string& arg)
+ {
+ //match greedy parameters before everything else
+ if(pos_->is_param() && pos_->blocking() && pos_->as_param().greedy()) {
+ const auto match = pos_->as_param().match(arg);
+ if(match && match.length() == arg.size()) {
+ add_match(detail::match_t{arg,pos_});
+ return true;
+ }
+ }
+
+ //try flags first (alone, joinable or strict sequence)
+ if(try_match_full(arg, detail::select_flags{})) return true;
+ if(try_match_joined_flags(arg)) return true;
+ if(try_match_joined_sequence(arg, detail::select_flags{})) return true;
+ //try value params (alone or strict sequence)
+ if(try_match_full(arg, detail::select_values{})) return true;
+ if(try_match_joined_sequence(arg, detail::select_all{})) return true;
+ //try joinable params + values in any order
+ if(try_match_joined_params(arg)) return true;
+ return false;
+ }
+
+ //---------------------------------------------------------------
+ /**
+ * @brief try to match full argument
+ * @param select : predicate that candidate parameters must satisfy
+ */
+ template<class ParamSelector>
+ bool try_match_full(const arg_string& arg, const ParamSelector& select)
+ {
+ auto match = detail::full_match(pos_, arg, select);
+ if(!match) return false;
+ add_match(match);
+ return true;
+ }
+
+ //---------------------------------------------------------------
+ /**
+ * @brief try to match argument as blocking sequence of parameters
+ * @param select : predicate that a parameter matching the prefix of
+ * 'arg' must satisfy
+ */
+ template<class ParamSelector>
+ bool try_match_joined_sequence(arg_string arg,
+ const ParamSelector& acceptFirst)
+ {
+ auto fstMatch = detail::longest_prefix_match(pos_, arg, acceptFirst);
+
+ if(!fstMatch) return false;
+
+ if(fstMatch.str().size() == arg.size()) {
+ add_match(fstMatch);
+ return true;
+ }
+
+ if(!fstMatch.pos()->blocking()) return false;
+
+ auto pos = fstMatch.pos();
+ pos.ignore_blocking(true);
+ const auto parent = &pos.parent();
+ if(!pos->repeatable()) ++pos;
+
+ arg.erase(0, fstMatch.str().size());
+ std::vector<match_t> matches { std::move(fstMatch) };
+
+ while(!arg.empty() && pos &&
+ pos->blocking() && pos->is_param() &&
+ (&pos.parent() == parent))
+ {
+ auto match = pos->as_param().match(arg);
+
+ if(match.prefix()) {
+ matches.emplace_back(arg.substr(0,match.length()), pos);
+ arg.erase(0, match.length());
+ if(!pos->repeatable()) ++pos;
+ }
+ else {
+ if(!pos->repeatable()) return false;
+ ++pos;
+ }
+
+ }
+ //if arg not fully covered => discard temporary matches
+ if(!arg.empty() || matches.empty()) return false;
+
+ for(const auto& m : matches) add_match(m);
+ return true;
+ }
+
+ //-----------------------------------------------------
+ /** @brief try to match 'arg' as a concatenation of joinable flags */
+ bool try_match_joined_flags(const arg_string& arg)
+ {
+ return find_join_group(pos_, [&](const group& g) {
+ return try_match_joined(g, arg, detail::select_flags{},
+ g.common_flag_prefix());
+ });
+ }
+
+ //---------------------------------------------------------------
+ /** @brief try to match 'arg' as a concatenation of joinable parameters */
+ bool try_match_joined_params(const arg_string& arg)
+ {
+ return find_join_group(pos_, [&](const group& g) {
+ return try_match_joined(g, arg, detail::select_all{});
+ });
+ }
+
+ //-----------------------------------------------------
+ /** @brief try to match 'arg' as concatenation of joinable parameters
+ * that are all contained within one group
+ */
+ template<class ParamSelector>
+ bool try_match_joined(const group& joinGroup, arg_string arg,
+ const ParamSelector& select,
+ const arg_string& prefix = "")
+ {
+ //temporary parser with 'joinGroup' as top-level group
+ parser parse {joinGroup};
+ //records temporary matches
+ std::vector<match_t> matches;
+
+ while(!arg.empty()) {
+ auto match = detail::longest_prefix_match(parse.pos_, arg, select);
+
+ if(!match) return false;
+
+ arg.erase(0, match.str().size());
+ //make sure prefix is always present after the first match
+ //so that, e.g., flags "-a" and "-b" will be found in "-ab"
+ if(!arg.empty() && !prefix.empty() && arg.find(prefix) != 0 &&
+ prefix != match.str())
+ {
+ arg.insert(0,prefix);
+ }
+
+ parse.add_match(match);
+ matches.push_back(std::move(match));
+ }
+
+ if(!arg.empty() || matches.empty()) return false;
+
+ if(!parse.missCand_.empty()) return false;
+ for(const auto& a : parse.args_) if(a.any_error()) return false;
+
+ //replay matches onto *this
+ for(const auto& m : matches) add_match(m);
+ return true;
+ }
+
+ //-----------------------------------------------------
+ template<class GroupSelector>
+ bool find_join_group(const scoped_dfs_traverser& start,
+ const GroupSelector& accept) const
+ {
+ if(start && start.parent().joinable()) {
+ const auto& g = start.parent();
+ if(accept(g)) return true;
+ return false;
+ }
+
+ auto pos = start;
+ while(pos) {
+ if(pos->is_group() && pos->as_group().joinable()) {
+ const auto& g = pos->as_group();
+ if(accept(g)) return true;
+ pos.next_sibling();
+ }
+ else {
+ ++pos;
+ }
+ }
+ return false;
+ }
+
+
+ //---------------------------------------------------------------
+ void add_nomatch(const arg_string& arg) {
+ args_.emplace_back(index_, arg);
+ }
+
+
+ //---------------------------------------------------------------
+ void add_match(const match_t& match)
+ {
+ const auto& pos = match.pos();
+ if(!pos || !pos->is_param()) return;
+
+ pos_.next_after_match(pos);
+
+ arg_mapping newArg{index_, match.str(), pos.base()};
+ newArg.repeat_ = occurrences_of(&pos->as_param());
+ newArg.conflict_ = check_conflicts(pos.base());
+ newArg.startsRepeatGroup_ = pos_.start_of_repeat_group();
+ args_.push_back(std::move(newArg));
+
+ add_miss_candidates_after(pos);
+ clean_miss_candidates_for(pos.base());
+ discard_alternative_miss_candidates(pos.base());
+
+ }
+
+ //-----------------------------------------------------
+ bool check_conflicts(const dfs_traverser& match)
+ {
+ if(pos_.start_of_repeat_group()) return false;
+ bool conflict = false;
+ for(const auto& m : match.stack()) {
+ if(m.parent->exclusive()) {
+ for(auto i = args_.rbegin(); i != args_.rend(); ++i) {
+ if(!i->blocked()) {
+ for(const auto& c : i->match_.stack()) {
+ //sibling within same exclusive group => conflict
+ if(c.parent == m.parent && c.cur != m.cur) {
+ conflict = true;
+ i->conflict_ = true;
+ }
+ }
+ }
+ //check for conflicts only within current repeat cycle
+ if(i->startsRepeatGroup_) break;
+ }
+ }
+ }
+ return conflict;
+ }
+
+ //-----------------------------------------------------
+ void clean_miss_candidates_for(const dfs_traverser& match)
+ {
+ auto i = std::find_if(missCand_.rbegin(), missCand_.rend(),
+ [&](const miss_candidate& m) {
+ return &(*m.pos) == &(*match);
+ });
+
+ if(i != missCand_.rend()) {
+ missCand_.erase(prev(i.base()));
+ }
+ }
+
+ //-----------------------------------------------------
+ void discard_alternative_miss_candidates(const dfs_traverser& match)
+ {
+ if(missCand_.empty()) return;
+ //find out, if miss candidate is sibling of one of the same
+ //alternative groups that the current match is a member of
+ //if so, we can discard the miss
+
+ //go through all exclusive groups of matching pattern
+ for(const auto& m : match.stack()) {
+ if(m.parent->exclusive()) {
+ for(auto i = int(missCand_.size())-1; i >= 0; --i) {
+ bool removed = false;
+ for(const auto& c : missCand_[i].pos.stack()) {
+ //sibling within same exclusive group => discard
+ if(c.parent == m.parent && c.cur != m.cur) {
+ missCand_.erase(missCand_.begin() + i);
+ if(missCand_.empty()) return;
+ removed = true;
+ break;
+ }
+ }
+ //remove miss candidates only within current repeat cycle
+ if(i > 0 && removed) {
+ if(missCand_[i-1].startsRepeatGroup) break;
+ } else {
+ if(missCand_[i].startsRepeatGroup) break;
+ }
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------
+ void add_miss_candidates_after(const scoped_dfs_traverser& match)
+ {
+ auto npos = match.base();
+ if(npos.is_alternative()) npos.skip_alternatives();
+ ++npos;
+ //need to add potential misses if:
+ //either new repeat group was started
+ const auto newRepGroup = match.innermost_repeat_group();
+ if(newRepGroup) {
+ if(pos_.start_of_repeat_group()) {
+ for_each_potential_miss(std::move(npos),
+ [&,this](const dfs_traverser& pos) {
+ //only add candidates within repeat group
+ if(newRepGroup == pos.innermost_repeat_group()) {
+ missCand_.emplace_back(pos, index_, true);
+ }
+ });
+ }
+ }
+ //... or an optional blocking param was hit
+ else if(match->blocking() && !match->required() &&
+ npos.level() >= match.base().level())
+ {
+ for_each_potential_miss(std::move(npos),
+ [&,this](const dfs_traverser& pos) {
+ //only add new candidates
+ if(std::find_if(missCand_.begin(), missCand_.end(),
+ [&](const miss_candidate& c){
+ return &(*c.pos) == &(*pos);
+ }) == missCand_.end())
+ {
+ missCand_.emplace_back(pos, index_);
+ }
+ });
+ }
+
+ }
+
+ //-----------------------------------------------------
+ template<class Action>
+ static void
+ for_each_potential_miss(dfs_traverser pos, Action&& action)
+ {
+ const auto level = pos.level();
+ while(pos && pos.level() >= level) {
+ if(pos->is_group() ) {
+ const auto& g = pos->as_group();
+ if(g.all_optional() || (g.exclusive() && g.any_optional())) {
+ pos.next_sibling();
+ } else {
+ ++pos;
+ }
+ } else { //param
+ if(pos->required()) {
+ action(pos);
+ ++pos;
+ } else if(pos->blocking()) { //optional + blocking
+ pos.next_after_siblings();
+ } else {
+ ++pos;
+ }
+ }
+ }
+ }
+
+
+ //---------------------------------------------------------------
+ std::size_t occurrences_of(const parameter* p) const
+ {
+ if(!p) return 0;
+
+ auto i = std::find_if(args_.rbegin(), args_.rend(),
+ [p](const arg_mapping& a){ return a.param() == p; });
+
+ if(i != args_.rend()) return i->repeat() + 1;
+ return 0;
+ }
+
+
+ //---------------------------------------------------------------
+ const group* root_;
+ scoped_dfs_traverser pos_;
+ arg_index index_;
+ arg_index eaten_;
+ arg_mappings args_;
+ miss_candidates missCand_;
+ bool blocked_;
+};
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief contains argument -> parameter mappings
+ * and missing parameters
+ *
+ *****************************************************************************/
+class parsing_result
+{
+public:
+ using arg_mapping = parser::arg_mapping;
+ using arg_mappings = parser::arg_mappings;
+ using missing_event = parser::missing_event;
+ using missing_events = parser::missing_events;
+ using iterator = arg_mappings::const_iterator;
+
+ //-----------------------------------------------------
+ /** @brief default: empty result */
+ parsing_result() = default;
+
+ parsing_result(arg_mappings arg2param, missing_events misses):
+ arg2param_{std::move(arg2param)}, missing_{std::move(misses)}
+ {}
+
+ //-----------------------------------------------------
+ /** @brief returns number of arguments that could not be mapped to
+ * a parameter
+ */
+ arg_mappings::size_type
+ unmapped_args_count() const noexcept {
+ return std::count_if(arg2param_.begin(), arg2param_.end(),
+ [](const arg_mapping& a){ return !a.param(); });
+ }
+
+ /** @brief returns if any argument could only be matched by an
+ * unreachable parameter
+ */
+ bool any_blocked() const noexcept {
+ return std::any_of(arg2param_.begin(), arg2param_.end(),
+ [](const arg_mapping& a){ return a.blocked(); });
+ }
+
+ /** @brief returns if any argument matched more than one parameter
+ * that were mutually exclusive */
+ bool any_conflict() const noexcept {
+ return std::any_of(arg2param_.begin(), arg2param_.end(),
+ [](const arg_mapping& a){ return a.conflict(); });
+ }
+
+ /** @brief returns if any parameter matched repeatedly although
+ * it was not allowed to */
+ bool any_bad_repeat() const noexcept {
+ return std::any_of(arg2param_.begin(), arg2param_.end(),
+ [](const arg_mapping& a){ return a.bad_repeat(); });
+ }
+
+ /** @brief returns true if any parsing error / violation of the
+ * command line interface definition occurred */
+ bool any_error() const noexcept {
+ return unmapped_args_count() > 0 || !missing().empty() ||
+ any_blocked() || any_conflict() || any_bad_repeat();
+ }
+
+ /** @brief returns true if no parsing error / violation of the
+ * command line interface definition occurred */
+ explicit operator bool() const noexcept { return !any_error(); }
+
+ /** @brief access to range of missing parameter match events */
+ const missing_events& missing() const noexcept { return missing_; }
+
+ /** @brief returns non-mutating iterator to position of
+ * first argument -> parameter mapping */
+ iterator begin() const noexcept { return arg2param_.begin(); }
+ /** @brief returns non-mutating iterator to position one past the
+ * last argument -> parameter mapping */
+ iterator end() const noexcept { return arg2param_.end(); }
+
+private:
+ //-----------------------------------------------------
+ arg_mappings arg2param_;
+ missing_events missing_;
+};
+
+
+
+
+namespace detail {
+namespace {
+
+/*************************************************************************//**
+ *
+ * @brief correct some common problems
+ * does not - and MUST NOT - change the number of arguments
+ * (no insertions or deletions allowed)
+ *
+ *****************************************************************************/
+void sanitize_args(arg_list& args)
+{
+ //e.g. {"-o12", ".34"} -> {"-o", "12.34"}
+
+ if(args.empty()) return;
+
+ for(auto i = begin(args)+1; i != end(args); ++i) {
+ if(i != begin(args) && i->size() > 1 &&
+ i->find('.') == 0 && std::isdigit((*i)[1]) )
+ {
+ //find trailing digits in previous arg
+ using std::prev;
+ auto& prv = *prev(i);
+ auto fstDigit = std::find_if_not(prv.rbegin(), prv.rend(),
+ [](arg_string::value_type c){
+ return std::isdigit(c);
+ }).base();
+
+ //handle leading sign
+ if(fstDigit > prv.begin() &&
+ (*prev(fstDigit) == '+' || *prev(fstDigit) == '-'))
+ {
+ --fstDigit;
+ }
+
+ //prepend digits from previous arg
+ i->insert(begin(*i), fstDigit, end(prv));
+
+ //erase digits in previous arg
+ prv.erase(fstDigit, end(prv));
+ }
+ }
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief executes actions based on a parsing result
+ *
+ *****************************************************************************/
+void execute_actions(const parsing_result& res)
+{
+ for(const auto& m : res) {
+ if(m.param()) {
+ const auto& param = *(m.param());
+
+ if(m.repeat() > 0) param.notify_repeated(m.index());
+ if(m.blocked()) param.notify_blocked(m.index());
+ if(m.conflict()) param.notify_conflict(m.index());
+ //main action
+ if(!m.any_error()) param.execute_actions(m.arg());
+ }
+ }
+
+ for(auto m : res.missing()) {
+ if(m.param()) m.param()->notify_missing(m.after_index());
+ }
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief parses input args
+ *
+ *****************************************************************************/
+static parsing_result
+parse_args(const arg_list& args, const group& cli,
+ arg_index offset = 0)
+{
+ //parse args and store unrecognized arg indices
+ parser parse{cli, offset};
+ for(const auto& arg : args) {
+ parse(arg);
+ if(!parse.valid()) break;
+ }
+
+ return parsing_result{parse.args(), parse.missed()};
+}
+
+/*************************************************************************//**
+ *
+ * @brief parses input args & executes actions
+ *
+ *****************************************************************************/
+static parsing_result
+parse_and_execute(const arg_list& args, const group& cli,
+ arg_index offset = 0)
+{
+ auto result = parse_args(args, cli, offset);
+
+ execute_actions(result);
+
+ return result;
+}
+
+} //anonymous namespace
+} // namespace detail
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief parses vector of arg strings and executes actions
+ *
+ *****************************************************************************/
+inline parsing_result
+parse(arg_list args, const group& cli)
+{
+ //detail::sanitize_args(args);
+ return detail::parse_and_execute(args, cli);
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief parses initializer_list of C-style arg strings and executes actions
+ *
+ *****************************************************************************/
+inline parsing_result
+parse(std::initializer_list<const char*> arglist, const group& cli)
+{
+ arg_list args;
+ args.reserve(arglist.size());
+ for(auto a : arglist) {
+ args.push_back(a);
+ }
+
+ return parse(std::move(args), cli);
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief parses range of arg strings and executes actions
+ *
+ *****************************************************************************/
+template<class InputIterator>
+inline parsing_result
+parse(InputIterator first, InputIterator last, const group& cli)
+{
+ return parse(arg_list(first,last), cli);
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief parses the standard array of command line arguments; omits argv[0]
+ *
+ *****************************************************************************/
+inline parsing_result
+parse(const int argc, char* argv[], const group& cli, arg_index offset = 1)
+{
+ arg_list args;
+ if(offset < argc) args.assign(argv+offset, argv+argc);
+ //detail::sanitize_args(args);
+ return detail::parse_and_execute(args, cli, offset);
+}
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief filter predicate for parameters and groups;
+ * Can be used to limit documentation generation to parameter subsets.
+ *
+ *****************************************************************************/
+class param_filter
+{
+public:
+ /** @brief only allow parameters with given prefix */
+ param_filter& prefix(const arg_string& p) noexcept {
+ prefix_ = p; return *this;
+ }
+ /** @brief only allow parameters with given prefix */
+ param_filter& prefix(arg_string&& p) noexcept {
+ prefix_ = std::move(p); return *this;
+ }
+ const arg_string& prefix() const noexcept { return prefix_; }
+
+ /** @brief only allow parameters with given requirement status */
+ param_filter& required(tri t) noexcept { required_ = t; return *this; }
+ tri required() const noexcept { return required_; }
+
+ /** @brief only allow parameters with given blocking status */
+ param_filter& blocking(tri t) noexcept { blocking_ = t; return *this; }
+ tri blocking() const noexcept { return blocking_; }
+
+ /** @brief only allow parameters with given repeatable status */
+ param_filter& repeatable(tri t) noexcept { repeatable_ = t; return *this; }
+ tri repeatable() const noexcept { return repeatable_; }
+
+ /** @brief only allow parameters with given docstring status */
+ param_filter& has_doc(tri t) noexcept { hasDoc_ = t; return *this; }
+ tri has_doc() const noexcept { return hasDoc_; }
+
+
+ /** @brief returns true, if parameter satisfies all filters */
+ bool operator() (const parameter& p) const noexcept {
+ if(!prefix_.empty()) {
+ if(!std::any_of(p.flags().begin(), p.flags().end(),
+ [&](const arg_string& flag){
+ return str::has_prefix(flag, prefix_);
+ })) return false;
+ }
+ if(required() != p.required()) return false;
+ if(blocking() != p.blocking()) return false;
+ if(repeatable() != p.repeatable()) return false;
+ if(has_doc() != !p.doc().empty()) return false;
+ return true;
+ }
+
+private:
+ arg_string prefix_;
+ tri required_ = tri::either;
+ tri blocking_ = tri::either;
+ tri repeatable_ = tri::either;
+ tri hasDoc_ = tri::yes;
+};
+
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief documentation formatting options
+ *
+ *****************************************************************************/
+class doc_formatting
+{
+public:
+ using string = doc_string;
+
+ /** @brief same as 'first_column' */
+#if __cplusplus >= 201402L
+ [[deprecated]]
+#endif
+ doc_formatting& start_column(int col) { return first_column(col); }
+#if __cplusplus >= 201402L
+ [[deprecated]]
+#endif
+ int start_column() const noexcept { return first_column(); }
+
+ /** @brief determines column where documentation printing starts */
+ doc_formatting&
+ first_column(int col) {
+ //limit to [0,last_column] but push doc_column to the right if necessary
+ if(col < 0) col = 0;
+ else if(col > last_column()) col = last_column();
+ if(col > doc_column()) doc_column(first_column());
+ firstCol_ = col;
+ return *this;
+ }
+ int first_column() const noexcept {
+ return firstCol_;
+ }
+
+ /** @brief determines column where docstrings start */
+ doc_formatting&
+ doc_column(int col) {
+ //limit to [first_column,last_column]
+ if(col < 0) col = 0;
+ else if(col < first_column()) col = first_column();
+ else if(col > last_column()) col = last_column();
+ docCol_ = col;
+ return *this;
+ }
+ int doc_column() const noexcept {
+ return docCol_;
+ }
+
+ /** @brief determines column that no documentation text must exceed;
+ * (text should be wrapped appropriately after this column)
+ */
+ doc_formatting&
+ last_column(int col) {
+ //limit to [first_column,oo] but push doc_column to the left if necessary
+ if(col < first_column()) col = first_column();
+ if(col < doc_column()) doc_column(col);
+ lastCol_ = col;
+ return *this;
+ }
+
+ int last_column() const noexcept {
+ return lastCol_;
+ }
+
+ /** @brief determines indent of documentation lines
+ * for children of a documented group */
+ doc_formatting& indent_size(int indent) { indentSize_ = indent; return *this; }
+ int indent_size() const noexcept { return indentSize_; }
+
+ /** @brief determines string to be used
+ * if a parameter has no flags and no label */
+ doc_formatting& empty_label(const string& label) {
+ emptyLabel_ = label;
+ return *this;
+ }
+ const string& empty_label() const noexcept { return emptyLabel_; }
+
+ /** @brief determines string for separating parameters */
+ doc_formatting& param_separator(const string& sep) {
+ paramSep_ = sep;
+ return *this;
+ }
+ const string& param_separator() const noexcept { return paramSep_; }
+
+ /** @brief determines string for separating groups (in usage lines) */
+ doc_formatting& group_separator(const string& sep) {
+ groupSep_ = sep;
+ return *this;
+ }
+ const string& group_separator() const noexcept { return groupSep_; }
+
+ /** @brief determines string for separating alternative parameters */
+ doc_formatting& alternative_param_separator(const string& sep) {
+ altParamSep_ = sep;
+ return *this;
+ }
+ const string& alternative_param_separator() const noexcept { return altParamSep_; }
+
+ /** @brief determines string for separating alternative groups */
+ doc_formatting& alternative_group_separator(const string& sep) {
+ altGroupSep_ = sep;
+ return *this;
+ }
+ const string& alternative_group_separator() const noexcept { return altGroupSep_; }
+
+ /** @brief determines string for separating flags of the same parameter */
+ doc_formatting& flag_separator(const string& sep) {
+ flagSep_ = sep;
+ return *this;
+ }
+ const string& flag_separator() const noexcept { return flagSep_; }
+
+ /** @brief determines strings surrounding parameter labels */
+ doc_formatting&
+ surround_labels(const string& prefix, const string& postfix) {
+ labelPre_ = prefix;
+ labelPst_ = postfix;
+ return *this;
+ }
+ const string& label_prefix() const noexcept { return labelPre_; }
+ const string& label_postfix() const noexcept { return labelPst_; }
+
+ /** @brief determines strings surrounding optional parameters/groups */
+ doc_formatting&
+ surround_optional(const string& prefix, const string& postfix) {
+ optionPre_ = prefix;
+ optionPst_ = postfix;
+ return *this;
+ }
+ const string& optional_prefix() const noexcept { return optionPre_; }
+ const string& optional_postfix() const noexcept { return optionPst_; }
+
+ /** @brief determines strings surrounding repeatable parameters/groups */
+ doc_formatting&
+ surround_repeat(const string& prefix, const string& postfix) {
+ repeatPre_ = prefix;
+ repeatPst_ = postfix;
+ return *this;
+ }
+ const string& repeat_prefix() const noexcept { return repeatPre_; }
+ const string& repeat_postfix() const noexcept { return repeatPst_; }
+
+ /** @brief determines strings surrounding exclusive groups */
+ doc_formatting&
+ surround_alternatives(const string& prefix, const string& postfix) {
+ alternPre_ = prefix;
+ alternPst_ = postfix;
+ return *this;
+ }
+ const string& alternatives_prefix() const noexcept { return alternPre_; }
+ const string& alternatives_postfix() const noexcept { return alternPst_; }
+
+ /** @brief determines strings surrounding alternative flags */
+ doc_formatting&
+ surround_alternative_flags(const string& prefix, const string& postfix) {
+ alternFlagPre_ = prefix;
+ alternFlagPst_ = postfix;
+ return *this;
+ }
+ const string& alternative_flags_prefix() const noexcept { return alternFlagPre_; }
+ const string& alternative_flags_postfix() const noexcept { return alternFlagPst_; }
+
+ /** @brief determines strings surrounding non-exclusive groups */
+ doc_formatting&
+ surround_group(const string& prefix, const string& postfix) {
+ groupPre_ = prefix;
+ groupPst_ = postfix;
+ return *this;
+ }
+ const string& group_prefix() const noexcept { return groupPre_; }
+ const string& group_postfix() const noexcept { return groupPst_; }
+
+ /** @brief determines strings surrounding joinable groups */
+ doc_formatting&
+ surround_joinable(const string& prefix, const string& postfix) {
+ joinablePre_ = prefix;
+ joinablePst_ = postfix;
+ return *this;
+ }
+ const string& joinable_prefix() const noexcept { return joinablePre_; }
+ const string& joinable_postfix() const noexcept { return joinablePst_; }
+
+ /** @brief determines maximum number of flags per parameter to be printed
+ * in detailed parameter documentation lines */
+ doc_formatting& max_flags_per_param_in_doc(int max) {
+ maxAltInDocs_ = max > 0 ? max : 0;
+ return *this;
+ }
+ int max_flags_per_param_in_doc() const noexcept { return maxAltInDocs_; }
+
+ /** @brief determines maximum number of flags per parameter to be printed
+ * in usage lines */
+ doc_formatting& max_flags_per_param_in_usage(int max) {
+ maxAltInUsage_ = max > 0 ? max : 0;
+ return *this;
+ }
+ int max_flags_per_param_in_usage() const noexcept { return maxAltInUsage_; }
+
+ /** @brief determines number of empty rows after one single-line
+ * documentation entry */
+ doc_formatting& line_spacing(int lines) {
+ lineSpc_ = lines > 0 ? lines : 0;
+ return *this;
+ }
+ int line_spacing() const noexcept { return lineSpc_; }
+
+ /** @brief determines number of empty rows before and after a paragraph;
+ * a paragraph is defined by a documented group or if
+ * a parameter documentation entry used more than one line */
+ doc_formatting& paragraph_spacing(int lines) {
+ paragraphSpc_ = lines > 0 ? lines : 0;
+ return *this;
+ }
+ int paragraph_spacing() const noexcept { return paragraphSpc_; }
+
+ /** @brief determines if alternative flags with a common prefix should
+ * be printed in a merged fashion */
+ doc_formatting& merge_alternative_flags_with_common_prefix(bool yes = true) {
+ mergeAltCommonPfx_ = yes;
+ return *this;
+ }
+ bool merge_alternative_flags_with_common_prefix() const noexcept {
+ return mergeAltCommonPfx_;
+ }
+
+ /** @brief determines if joinable flags with a common prefix should
+ * be printed in a merged fashion */
+ doc_formatting& merge_joinable_with_common_prefix(bool yes = true) {
+ mergeJoinableCommonPfx_ = yes;
+ return *this;
+ }
+ bool merge_joinable_with_common_prefix() const noexcept {
+ return mergeJoinableCommonPfx_;
+ }
+
+ /** @brief determines if children of exclusive groups should be printed
+ * on individual lines if the exceed 'alternatives_min_split_size'
+ */
+ doc_formatting& split_alternatives(bool yes = true) {
+ splitTopAlt_ = yes;
+ return *this;
+ }
+ bool split_alternatives() const noexcept {
+ return splitTopAlt_;
+ }
+
+ /** @brief determines how many children exclusive groups can have before
+ * their children are printed on individual usage lines */
+ doc_formatting& alternatives_min_split_size(int size) {
+ groupSplitSize_ = size > 0 ? size : 0;
+ return *this;
+ }
+ int alternatives_min_split_size() const noexcept { return groupSplitSize_; }
+
+ /** @brief determines whether to ignore new line characters in docstrings
+ */
+ doc_formatting& ignore_newline_chars(bool yes = true) {
+ ignoreNewlines_ = yes;
+ return *this;
+ }
+ bool ignore_newline_chars() const noexcept {
+ return ignoreNewlines_;
+ }
+
+private:
+ string paramSep_ = string(" ");
+ string groupSep_ = string(" ");
+ string altParamSep_ = string("|");
+ string altGroupSep_ = string(" | ");
+ string flagSep_ = string(", ");
+ string labelPre_ = string("<");
+ string labelPst_ = string(">");
+ string optionPre_ = string("[");
+ string optionPst_ = string("]");
+ string repeatPre_ = string("");
+ string repeatPst_ = string("...");
+ string groupPre_ = string("(");
+ string groupPst_ = string(")");
+ string alternPre_ = string("(");
+ string alternPst_ = string(")");
+ string alternFlagPre_ = string("");
+ string alternFlagPst_ = string("");
+ string joinablePre_ = string("(");
+ string joinablePst_ = string(")");
+ string emptyLabel_ = string("");
+ int firstCol_ = 8;
+ int docCol_ = 20;
+ int lastCol_ = 100;
+ int indentSize_ = 4;
+ int maxAltInUsage_ = 1;
+ int maxAltInDocs_ = 32;
+ int lineSpc_ = 0;
+ int paragraphSpc_ = 1;
+ int groupSplitSize_ = 3;
+ bool splitTopAlt_ = true;
+ bool mergeAltCommonPfx_ = false;
+ bool mergeJoinableCommonPfx_ = true;
+ bool ignoreNewlines_ = false;
+};
+
+
+
+namespace detail {
+
+/*************************************************************************//**
+ *
+ * @brief stream decorator
+ * that applies formatting like line wrapping
+ *
+ *****************************************************************************/
+template<class OStream = std::ostream, class StringT = doc_string>
+class formatting_ostream
+{
+public:
+ using string_type = StringT;
+ using size_type = typename string_type::size_type;
+ using char_type = typename string_type::value_type;
+
+ formatting_ostream(OStream& os):
+ os_(os),
+ curCol_{0}, firstCol_{0}, lastCol_{100},
+ hangingIndent_{0}, paragraphSpacing_{0}, paragraphSpacingThreshold_{2},
+ curBlankLines_{0}, curParagraphLines_{1},
+ totalNonBlankLines_{0},
+ ignoreInputNls_{false}
+ {}
+
+
+ //---------------------------------------------------------------
+ const OStream& base() const noexcept { return os_; }
+ OStream& base() noexcept { return os_; }
+
+ bool good() const { return os_.good(); }
+
+
+ //---------------------------------------------------------------
+ /** @brief determines the leftmost border of the text body */
+ formatting_ostream& first_column(int c) {
+ firstCol_ = c < 0 ? 0 : c;
+ return *this;
+ }
+ int first_column() const noexcept { return firstCol_; }
+
+ /** @brief determines the rightmost border of the text body */
+ formatting_ostream& last_column(int c) {
+ lastCol_ = c < 0 ? 0 : c;
+ return *this;
+ }
+
+ int last_column() const noexcept { return lastCol_; }
+
+ int text_width() const noexcept {
+ return lastCol_ - firstCol_;
+ }
+
+ /** @brief additional indentation for the 2nd, 3rd, ... line of
+ a paragraph (sequence of soft-wrapped lines) */
+ formatting_ostream& hanging_indent(int amount) {
+ hangingIndent_ = amount;
+ return *this;
+ }
+ int hanging_indent() const noexcept {
+ return hangingIndent_;
+ }
+
+ /** @brief amount of blank lines between paragraphs */
+ formatting_ostream& paragraph_spacing(int lines) {
+ paragraphSpacing_ = lines;
+ return *this;
+ }
+ int paragraph_spacing() const noexcept {
+ return paragraphSpacing_;
+ }
+
+ /** @brief insert paragraph spacing
+ if paragraph is at least 'lines' lines long */
+ formatting_ostream& min_paragraph_lines_for_spacing(int lines) {
+ paragraphSpacingThreshold_ = lines;
+ return *this;
+ }
+ int min_paragraph_lines_for_spacing() const noexcept {
+ return paragraphSpacingThreshold_;
+ }
+
+ /** @brief if set to true, newline characters will be ignored */
+ formatting_ostream& ignore_newline_chars(bool yes) {
+ ignoreInputNls_ = yes;
+ return *this;
+ }
+
+ bool ignore_newline_chars() const noexcept {
+ return ignoreInputNls_;
+ }
+
+
+ //---------------------------------------------------------------
+ /* @brief insert 'n' spaces */
+ void write_spaces(int n) {
+ if(n < 1) return;
+ os_ << string_type(size_type(n), ' ');
+ curCol_ += n;
+ }
+
+ /* @brief go to new line, but continue current paragraph */
+ void wrap_soft(int times = 1) {
+ if(times < 1) return;
+ if(times > 1) {
+ os_ << string_type(size_type(times), '\n');
+ } else {
+ os_ << '\n';
+ }
+ curCol_ = 0;
+ ++curParagraphLines_;
+ }
+
+ /* @brief go to new line, and start a new paragraph */
+ void wrap_hard(int times = 1) {
+ if(times < 1) return;
+
+ if(paragraph_spacing() > 0 &&
+ paragraph_lines() >= min_paragraph_lines_for_spacing())
+ {
+ times = paragraph_spacing() + 1;
+ }
+
+ if(times > 1) {
+ os_ << string_type(size_type(times), '\n');
+ curBlankLines_ += times - 1;
+ } else {
+ os_ << '\n';
+ }
+ if(at_begin_of_line()) {
+ ++curBlankLines_;
+ }
+ curCol_ = 0;
+ curParagraphLines_ = 1;
+ }
+
+
+ //---------------------------------------------------------------
+ bool at_begin_of_line() const noexcept {
+ return curCol_ <= current_line_begin();
+ }
+ int current_line_begin() const noexcept {
+ return in_hanging_part_of_paragraph()
+ ? firstCol_ + hangingIndent_
+ : firstCol_;
+ }
+
+ int current_column() const noexcept {
+ return curCol_;
+ }
+
+ int total_non_blank_lines() const noexcept {
+ return totalNonBlankLines_;
+ }
+ int paragraph_lines() const noexcept {
+ return curParagraphLines_;
+ }
+ int blank_lines_before_paragraph() const noexcept {
+ return curBlankLines_;
+ }
+
+
+ //---------------------------------------------------------------
+ template<class T>
+ friend formatting_ostream&
+ operator << (formatting_ostream& os, const T& x) {
+ os.write(x);
+ return os;
+ }
+
+ void flush() {
+ os_.flush();
+ }
+
+
+private:
+ bool in_hanging_part_of_paragraph() const noexcept {
+ return hanging_indent() > 0 && paragraph_lines() > 1;
+ }
+ bool current_line_empty() const noexcept {
+ return curCol_ < 1;
+ }
+ bool left_of_text_area() const noexcept {
+ return curCol_ < current_line_begin();
+ }
+ bool right_of_text_area() const noexcept {
+ return curCol_ > lastCol_;
+ }
+ int columns_left_in_line() const noexcept {
+ return lastCol_ - std::max(current_line_begin(), curCol_);
+ }
+
+ void fix_indent() {
+ if(left_of_text_area()) {
+ const auto fst = current_line_begin();
+ write_spaces(fst - curCol_);
+ curCol_ = fst;
+ }
+ }
+
+ template<class Iter>
+ bool only_whitespace(Iter first, Iter last) const {
+ return last == std::find_if_not(first, last,
+ [](char_type c) { return std::isspace(c); });
+ }
+
+ /** @brief write any object */
+ template<class T>
+ void write(const T& x) {
+ std::ostringstream ss;
+ ss << x;
+ write(std::move(ss).str());
+ }
+
+ /** @brief write a stringstream */
+ void write(const std::ostringstream& s) {
+ write(s.str());
+ }
+
+ /** @brief write a string */
+ void write(const string_type& s) {
+ write(s.begin(), s.end());
+ }
+
+ /** @brief partition output into lines */
+ template<class Iter>
+ void write(Iter first, Iter last)
+ {
+ if(first == last) return;
+ if(*first == '\n') {
+ if(!ignore_newline_chars()) wrap_hard();
+ ++first;
+ if(first == last) return;
+ }
+ auto i = std::find(first, last, '\n');
+ if(i != last) {
+ if(ignore_newline_chars()) ++i;
+ if(i != last) {
+ write_line(first, i);
+ write(i, last);
+ }
+ }
+ else {
+ write_line(first, last);
+ }
+ }
+
+ /** @brief handle line wrapping due to column constraints */
+ template<class Iter>
+ void write_line(Iter first, Iter last)
+ {
+ if(first == last) return;
+ if(only_whitespace(first, last)) return;
+
+ if(right_of_text_area()) wrap_soft();
+
+ if(at_begin_of_line()) {
+ //discard whitespace, it we start a new line
+ first = std::find_if(first, last,
+ [](char_type c) { return !std::isspace(c); });
+ if(first == last) return;
+ }
+
+ const auto n = int(std::distance(first,last));
+ const auto m = columns_left_in_line();
+ //if text to be printed is too long for one line -> wrap
+ if(n > m) {
+ //break before word, if break is mid-word
+ auto breakat = first + m;
+ while(breakat > first && !std::isspace(*breakat)) --breakat;
+ //could not find whitespace before word -> try after the word
+ if(!std::isspace(*breakat) && breakat == first) {
+ breakat = std::find_if(first+m, last,
+ [](char_type c) { return std::isspace(c); });
+ }
+ if(breakat > first) {
+ if(curCol_ < 1) ++totalNonBlankLines_;
+ fix_indent();
+ std::copy(first, breakat, std::ostream_iterator<char_type>(os_));
+ curBlankLines_ = 0;
+ }
+ if(breakat < last) {
+ wrap_soft();
+ write_line(breakat, last);
+ }
+ }
+ else {
+ if(curCol_ < 1) ++totalNonBlankLines_;
+ fix_indent();
+ std::copy(first, last, std::ostream_iterator<char_type>(os_));
+ curCol_ += n;
+ curBlankLines_ = 0;
+ }
+ }
+
+ /** @brief write a single character */
+ void write(char_type c)
+ {
+ if(c == '\n') {
+ if(!ignore_newline_chars()) wrap_hard();
+ }
+ else {
+ if(at_begin_of_line()) ++totalNonBlankLines_;
+ fix_indent();
+ os_ << c;
+ ++curCol_;
+ }
+ }
+
+ OStream& os_;
+ int curCol_;
+ int firstCol_;
+ int lastCol_;
+ int hangingIndent_;
+ int paragraphSpacing_;
+ int paragraphSpacingThreshold_;
+ int curBlankLines_;
+ int curParagraphLines_;
+ int totalNonBlankLines_;
+ bool ignoreInputNls_;
+};
+
+
+}
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief generates usage lines
+ *
+ * @details lazily evaluated
+ *
+ *****************************************************************************/
+class usage_lines
+{
+public:
+ using string = doc_string;
+
+ usage_lines(const group& cli, string prefix = "",
+ const doc_formatting& fmt = doc_formatting{})
+ :
+ cli_(cli), fmt_(fmt), prefix_(std::move(prefix))
+ {
+ if(!prefix_.empty()) prefix_ += ' ';
+ }
+
+ usage_lines(const group& cli, const doc_formatting& fmt):
+ usage_lines(cli, "", fmt)
+ {}
+
+ usage_lines& ommit_outermost_group_surrounders(bool yes) {
+ ommitOutermostSurrounders_ = yes;
+ return *this;
+ }
+ bool ommit_outermost_group_surrounders() const {
+ return ommitOutermostSurrounders_;
+ }
+
+ template<class OStream>
+ inline friend OStream& operator << (OStream& os, const usage_lines& p) {
+ p.write(os);
+ return os;
+ }
+
+ string str() const {
+ std::ostringstream os; os << *this; return os.str();
+ }
+
+
+private:
+ using stream_t = detail::formatting_ostream<>;
+ const group& cli_;
+ doc_formatting fmt_;
+ string prefix_;
+ bool ommitOutermostSurrounders_ = false;
+
+
+ //-----------------------------------------------------
+ struct context {
+ group::depth_first_traverser pos;
+ std::stack<string> separators;
+ std::stack<string> postfixes;
+ int level = 0;
+ const group* outermost = nullptr;
+ bool linestart = false;
+ bool useOutermost = true;
+ int line = 0;
+
+ bool is_singleton() const noexcept {
+ return linestart && pos.is_last_in_path();
+ }
+ bool is_alternative() const noexcept {
+ return pos.parent().exclusive();
+ }
+ };
+
+
+ /***************************************************************//**
+ *
+ * @brief writes usage text for command line parameters
+ *
+ *******************************************************************/
+ template<class OStream>
+ void write(OStream& os) const
+ {
+ detail::formatting_ostream<OStream> fos(os);
+ fos.first_column(fmt_.first_column());
+ fos.last_column(fmt_.last_column());
+
+ auto hindent = int(prefix_.size());
+ if(fos.first_column() + hindent >= int(0.4 * fos.text_width())) {
+ hindent = fmt_.indent_size();
+ }
+ fos.hanging_indent(hindent);
+
+ fos.paragraph_spacing(fmt_.paragraph_spacing());
+ fos.min_paragraph_lines_for_spacing(2);
+ fos.ignore_newline_chars(fmt_.ignore_newline_chars());
+
+ context cur;
+ cur.pos = cli_.begin_dfs();
+ cur.linestart = true;
+ cur.level = cur.pos.level();
+ cur.outermost = &cli_;
+
+ write(fos, cur, prefix_);
+ }
+
+
+ /***************************************************************//**
+ *
+ * @brief writes usage text for command line parameters
+ *
+ * @param prefix all that goes in front of current things to print
+ *
+ *******************************************************************/
+ template<class OStream>
+ void write(OStream& os, context cur, string prefix) const
+ {
+ if(!cur.pos) return;
+
+ std::ostringstream buf;
+ if(cur.linestart) buf << prefix;
+ const auto initPos = buf.tellp();
+
+ cur.level = cur.pos.level();
+
+ if(cur.useOutermost) {
+ //we cannot start outside of the outermost group
+ //so we have to treat it separately
+ start_group(buf, cur.pos.parent(), cur);
+ if(!cur.pos) {
+ os << buf.str();
+ return;
+ }
+ }
+ else {
+ //don't visit siblings of starter node
+ cur.pos.skip_siblings();
+ }
+ check_end_group(buf, cur);
+
+ do {
+ if(buf.tellp() > initPos) cur.linestart = false;
+ if(!cur.linestart && !cur.pos.is_first_in_parent()) {
+ buf << cur.separators.top();
+ }
+ if(cur.pos->is_group()) {
+ start_group(buf, cur.pos->as_group(), cur);
+ if(!cur.pos) {
+ os << buf.str();
+ return;
+ }
+ }
+ else {
+ buf << param_label(cur.pos->as_param(), cur);
+ ++cur.pos;
+ }
+ check_end_group(buf, cur);
+ } while(cur.pos);
+
+ os << buf.str();
+ }
+
+
+ /***************************************************************//**
+ *
+ * @brief handles pattern group surrounders and separators
+ * and alternative splitting
+ *
+ *******************************************************************/
+ void start_group(std::ostringstream& os,
+ const group& group, context& cur) const
+ {
+ //does cur.pos already point to a member or to group itself?
+ //needed for special treatment of outermost group
+ const bool alreadyInside = &(cur.pos.parent()) == &group;
+
+ auto lbl = joined_label(group, cur);
+ if(!lbl.empty()) {
+ os << lbl;
+ cur.linestart = false;
+ //skip over entire group as its label has already been created
+ if(alreadyInside) {
+ cur.pos.next_after_siblings();
+ } else {
+ cur.pos.next_sibling();
+ }
+ }
+ else {
+ const bool splitAlternatives = group.exclusive() &&
+ fmt_.split_alternatives() &&
+ std::any_of(group.begin(), group.end(),
+ [this](const pattern& p) {
+ return int(p.param_count()) >= fmt_.alternatives_min_split_size();
+ });
+
+ if(splitAlternatives) {
+ cur.postfixes.push("");
+ cur.separators.push("");
+ //recursively print alternative paths in decision-DAG
+ //enter group?
+ if(!alreadyInside) ++cur.pos;
+ cur.linestart = true;
+ cur.useOutermost = false;
+ auto pfx = os.str();
+ os.str("");
+ //print paths in DAG starting at each group member
+ for(std::size_t i = 0; i < group.size(); ++i) {
+ std::stringstream buf;
+ cur.outermost = cur.pos->is_group() ? &(cur.pos->as_group()) : nullptr;
+ write(buf, cur, pfx);
+ if(buf.tellp() > int(pfx.size())) {
+ os << buf.str();
+ if(i < group.size()-1) {
+ if(cur.line > 0) {
+ os << string(fmt_.line_spacing(), '\n');
+ }
+ ++cur.line;
+ os << '\n';
+ }
+ }
+ cur.pos.next_sibling(); //do not descend into members
+ }
+ cur.pos.invalidate(); //signal end-of-path
+ return;
+ }
+ else {
+ //pre & postfixes, separators
+ auto surround = group_surrounders(group, cur);
+ os << surround.first;
+ cur.postfixes.push(std::move(surround.second));
+ cur.separators.push(group_separator(group, fmt_));
+ //descend into group?
+ if(!alreadyInside) ++cur.pos;
+ }
+ }
+ cur.level = cur.pos.level();
+ }
+
+
+ /***************************************************************//**
+ *
+ *******************************************************************/
+ void check_end_group(std::ostringstream& os, context& cur) const
+ {
+ for(; cur.level > cur.pos.level(); --cur.level) {
+ os << cur.postfixes.top();
+ cur.postfixes.pop();
+ cur.separators.pop();
+ }
+ cur.level = cur.pos.level();
+ }
+
+
+ /***************************************************************//**
+ *
+ * @brief makes usage label for one command line parameter
+ *
+ *******************************************************************/
+ string param_label(const parameter& p, const context& cur) const
+ {
+ const auto& parent = cur.pos.parent();
+
+ const bool startsOptionalSequence =
+ parent.size() > 1 && p.blocking() && cur.pos.is_first_in_parent();
+
+ const bool outermost =
+ ommitOutermostSurrounders_ && cur.outermost == &parent;
+
+ const bool showopt = !cur.is_alternative() && !p.required()
+ && !startsOptionalSequence && !outermost;
+
+ const bool showrep = p.repeatable() && !outermost;
+
+ string lbl;
+
+ if(showrep) lbl += fmt_.repeat_prefix();
+ if(showopt) lbl += fmt_.optional_prefix();
+
+ const auto& flags = p.flags();
+ if(!flags.empty()) {
+ const int n = std::min(fmt_.max_flags_per_param_in_usage(),
+ int(flags.size()));
+
+ const bool surrAlt = n > 1 && !showopt && !cur.is_singleton();
+
+ if(surrAlt) lbl += fmt_.alternative_flags_prefix();
+ bool sep = false;
+ for(int i = 0; i < n; ++i) {
+ if(sep) {
+ if(cur.is_singleton())
+ lbl += fmt_.alternative_group_separator();
+ else
+ lbl += fmt_.flag_separator();
+ }
+ lbl += flags[i];
+ sep = true;
+ }
+ if(surrAlt) lbl += fmt_.alternative_flags_postfix();
+ }
+ else {
+ if(!p.label().empty()) {
+ lbl += fmt_.label_prefix()
+ + p.label()
+ + fmt_.label_postfix();
+ } else if(!fmt_.empty_label().empty()) {
+ lbl += fmt_.label_prefix()
+ + fmt_.empty_label()
+ + fmt_.label_postfix();
+ } else {
+ return "";
+ }
+ }
+
+ if(showopt) lbl += fmt_.optional_postfix();
+ if(showrep) lbl += fmt_.repeat_postfix();
+
+ return lbl;
+ }
+
+
+ /***************************************************************//**
+ *
+ * @brief prints flags in one group in a merged fashion
+ *
+ *******************************************************************/
+ string joined_label(const group& g, const context& cur) const
+ {
+ if(!fmt_.merge_alternative_flags_with_common_prefix() &&
+ !fmt_.merge_joinable_with_common_prefix()) return "";
+
+ const bool flagsonly = std::all_of(g.begin(), g.end(),
+ [](const pattern& p){
+ return p.is_param() && !p.as_param().flags().empty();
+ });
+
+ if(!flagsonly) return "";
+
+ const bool showOpt = g.all_optional() &&
+ !(ommitOutermostSurrounders_ && cur.outermost == &g);
+
+ auto pfx = g.common_flag_prefix();
+ if(pfx.empty()) return "";
+
+ const auto n = pfx.size();
+ if(g.exclusive() &&
+ fmt_.merge_alternative_flags_with_common_prefix())
+ {
+ string lbl;
+ if(showOpt) lbl += fmt_.optional_prefix();
+ lbl += pfx + fmt_.alternatives_prefix();
+ bool first = true;
+ for(const auto& p : g) {
+ if(p.is_param()) {
+ if(first)
+ first = false;
+ else
+ lbl += fmt_.alternative_param_separator();
+ lbl += p.as_param().flags().front().substr(n);
+ }
+ }
+ lbl += fmt_.alternatives_postfix();
+ if(showOpt) lbl += fmt_.optional_postfix();
+ return lbl;
+ }
+ //no alternatives, but joinable flags
+ else if(g.joinable() &&
+ fmt_.merge_joinable_with_common_prefix())
+ {
+ const bool allSingleChar = std::all_of(g.begin(), g.end(),
+ [&](const pattern& p){
+ return p.is_param() &&
+ p.as_param().flags().front().substr(n).size() == 1;
+ });
+
+ if(allSingleChar) {
+ string lbl;
+ if(showOpt) lbl += fmt_.optional_prefix();
+ lbl += pfx;
+ for(const auto& p : g) {
+ if(p.is_param())
+ lbl += p.as_param().flags().front().substr(n);
+ }
+ if(showOpt) lbl += fmt_.optional_postfix();
+ return lbl;
+ }
+ }
+
+ return "";
+ }
+
+
+ /***************************************************************//**
+ *
+ * @return symbols with which to surround a group
+ *
+ *******************************************************************/
+ std::pair<string,string>
+ group_surrounders(const group& group, const context& cur) const
+ {
+ string prefix;
+ string postfix;
+
+ const bool isOutermost = &group == cur.outermost;
+ if(isOutermost && ommitOutermostSurrounders_)
+ return {string{}, string{}};
+
+ if(group.exclusive()) {
+ if(group.all_optional()) {
+ prefix = fmt_.optional_prefix();
+ postfix = fmt_.optional_postfix();
+ if(group.all_flagless()) {
+ prefix += fmt_.label_prefix();
+ postfix = fmt_.label_prefix() + postfix;
+ }
+ } else if(group.all_flagless()) {
+ prefix = fmt_.label_prefix();
+ postfix = fmt_.label_postfix();
+ } else if(!cur.is_singleton() || !isOutermost) {
+ prefix = fmt_.alternatives_prefix();
+ postfix = fmt_.alternatives_postfix();
+ }
+ }
+ else if(group.size() > 1 &&
+ group.front().blocking() && !group.front().required())
+ {
+ prefix = fmt_.optional_prefix();
+ postfix = fmt_.optional_postfix();
+ }
+ else if(group.size() > 1 && cur.is_alternative() &&
+ &group != cur.outermost)
+ {
+ prefix = fmt_.group_prefix();
+ postfix = fmt_.group_postfix();
+ }
+ else if(!group.exclusive() &&
+ group.joinable() && !cur.linestart)
+ {
+ prefix = fmt_.joinable_prefix();
+ postfix = fmt_.joinable_postfix();
+ }
+
+ if(group.repeatable()) {
+ if(prefix.empty()) prefix = fmt_.group_prefix();
+ prefix = fmt_.repeat_prefix() + prefix;
+ if(postfix.empty()) postfix = fmt_.group_postfix();
+ postfix += fmt_.repeat_postfix();
+ }
+
+ return {std::move(prefix), std::move(postfix)};
+ }
+
+
+ /***************************************************************//**
+ *
+ * @return symbol that separates members of a group
+ *
+ *******************************************************************/
+ static string
+ group_separator(const group& group, const doc_formatting& fmt)
+ {
+ const bool only1ParamPerMember = std::all_of(group.begin(), group.end(),
+ [](const pattern& p) { return p.param_count() < 2; });
+
+ if(only1ParamPerMember) {
+ if(group.exclusive()) {
+ return fmt.alternative_param_separator();
+ } else {
+ return fmt.param_separator();
+ }
+ }
+ else { //there is at least one large group inside
+ if(group.exclusive()) {
+ return fmt.alternative_group_separator();
+ } else {
+ return fmt.group_separator();
+ }
+ }
+ }
+};
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief generates parameter and group documentation from docstrings
+ *
+ * @details lazily evaluated
+ *
+ *****************************************************************************/
+class documentation
+{
+public:
+ using string = doc_string;
+ using filter_function = std::function<bool(const parameter&)>;
+
+ documentation(const group& cli,
+ const doc_formatting& fmt = doc_formatting{},
+ filter_function filter = param_filter{})
+ :
+ cli_(cli), fmt_{fmt}, usgFmt_{fmt}, filter_{std::move(filter)}
+ {
+ //necessary, because we re-use "usage_lines" to generate
+ //labels for documented groups
+ usgFmt_.max_flags_per_param_in_usage(
+ usgFmt_.max_flags_per_param_in_doc());
+ }
+
+ documentation(const group& cli, filter_function filter) :
+ documentation{cli, doc_formatting{}, std::move(filter)}
+ {}
+
+ documentation(const group& cli, const param_filter& filter) :
+ documentation{cli, doc_formatting{},
+ [filter](const parameter& p) { return filter(p); }}
+ {}
+
+ template<class OStream>
+ inline friend OStream& operator << (OStream& os, const documentation& p) {
+ p.write(os);
+ return os;
+ }
+
+ string str() const {
+ std::ostringstream os;
+ write(os);
+ return os.str();
+ }
+
+
+private:
+ using dfs_traverser = group::depth_first_traverser;
+
+ const group& cli_;
+ doc_formatting fmt_;
+ doc_formatting usgFmt_;
+ filter_function filter_;
+ enum class paragraph { param, group };
+
+
+ /***************************************************************//**
+ *
+ * @brief writes documentation to output stream
+ *
+ *******************************************************************/
+ template<class OStream>
+ void write(OStream& os) const {
+ detail::formatting_ostream<OStream> fos(os);
+ fos.first_column(fmt_.first_column());
+ fos.last_column(fmt_.last_column());
+ fos.hanging_indent(0);
+ fos.paragraph_spacing(0);
+ fos.ignore_newline_chars(fmt_.ignore_newline_chars());
+ print_doc(fos, cli_);
+ }
+
+
+ /***************************************************************//**
+ *
+ * @brief writes full documentation text for command line parameters
+ *
+ *******************************************************************/
+ template<class OStream>
+ void print_doc(detail::formatting_ostream<OStream>& os,
+ const group& cli, int indentLvl = 0) const
+ {
+ if(cli.empty()) return;
+
+ //if group itself doesn't have docstring
+ if(cli.doc().empty()) {
+ for(const auto& p : cli) {
+ print_doc(os, p, indentLvl);
+ }
+ }
+ else { //group itself does have docstring
+ bool anyDocInside = std::any_of(cli.begin(), cli.end(),
+ [](const pattern& p){ return !p.doc().empty(); });
+
+ if(anyDocInside) { //group docstring as title, then child entries
+ handle_spacing(os, paragraph::group, indentLvl);
+ os << cli.doc();
+ for(const auto& p : cli) {
+ print_doc(os, p, indentLvl + 1);
+ }
+ }
+ else { //group label first then group docstring
+ auto lbl = usage_lines(cli, usgFmt_)
+ .ommit_outermost_group_surrounders(true).str();
+
+ str::trim(lbl);
+ handle_spacing(os, paragraph::param, indentLvl);
+ print_entry(os, lbl, cli.doc());
+ }
+ }
+ }
+
+
+ /***************************************************************//**
+ *
+ * @brief writes documentation text for one group or parameter
+ *
+ *******************************************************************/
+ template<class OStream>
+ void print_doc(detail::formatting_ostream<OStream>& os,
+ const pattern& ptrn, int indentLvl) const
+ {
+ if(ptrn.is_group()) {
+ print_doc(os, ptrn.as_group(), indentLvl);
+ }
+ else {
+ const auto& p = ptrn.as_param();
+ if(!filter_(p)) return;
+
+ handle_spacing(os, paragraph::param, indentLvl);
+ print_entry(os, param_label(p, fmt_), p.doc());
+ }
+ }
+
+ /***************************************************************//**
+ *
+ * @brief handles line and paragraph spacings
+ *
+ *******************************************************************/
+ template<class OStream>
+ void handle_spacing(detail::formatting_ostream<OStream>& os,
+ paragraph p, int indentLvl) const
+ {
+ const auto oldIndent = os.first_column();
+ const auto indent = fmt_.first_column() + indentLvl * fmt_.indent_size();
+
+ if(os.total_non_blank_lines() < 1) {
+ os.first_column(indent);
+ return;
+ }
+
+ if(os.paragraph_lines() > 1 || indent < oldIndent) {
+ os.wrap_hard(fmt_.paragraph_spacing() + 1);
+ } else {
+ os.wrap_hard();
+ }
+
+ if(p == paragraph::group) {
+ if(os.blank_lines_before_paragraph() < fmt_.paragraph_spacing()) {
+ os.wrap_hard(fmt_.paragraph_spacing() - os.blank_lines_before_paragraph());
+ }
+ }
+ else if(os.blank_lines_before_paragraph() < fmt_.line_spacing()) {
+ os.wrap_hard(fmt_.line_spacing() - os.blank_lines_before_paragraph());
+ }
+ os.first_column(indent);
+ }
+
+ /*********************************************************************//**
+ *
+ * @brief prints one entry = label + docstring
+ *
+ ************************************************************************/
+ template<class OStream>
+ void print_entry(detail::formatting_ostream<OStream>& os,
+ const string& label, const string& docstr) const
+ {
+ if(label.empty()) return;
+
+ os << label;
+
+ if(!docstr.empty()) {
+ if(os.current_column() >= fmt_.doc_column()) os.wrap_soft();
+ const auto oldcol = os.first_column();
+ os.first_column(fmt_.doc_column());
+ os << docstr;
+ os.first_column(oldcol);
+ }
+ }
+
+
+ /*********************************************************************//**
+ *
+ * @brief makes label for one parameter
+ *
+ ************************************************************************/
+ static doc_string
+ param_label(const parameter& param, const doc_formatting& fmt)
+ {
+ doc_string lbl;
+
+ if(param.repeatable()) lbl += fmt.repeat_prefix();
+
+ const auto& flags = param.flags();
+ if(!flags.empty()) {
+ lbl += flags[0];
+ const int n = std::min(fmt.max_flags_per_param_in_doc(),
+ int(flags.size()));
+ for(int i = 1; i < n; ++i) {
+ lbl += fmt.flag_separator() + flags[i];
+ }
+ }
+ else if(!param.label().empty() || !fmt.empty_label().empty()) {
+ lbl += fmt.label_prefix();
+ if(!param.label().empty()) {
+ lbl += param.label();
+ } else {
+ lbl += fmt.empty_label();
+ }
+ lbl += fmt.label_postfix();
+ }
+
+ if(param.repeatable()) lbl += fmt.repeat_postfix();
+
+ return lbl;
+ }
+
+};
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief stores strings for man page sections
+ *
+ *****************************************************************************/
+class man_page
+{
+public:
+ //---------------------------------------------------------------
+ using string = doc_string;
+
+ //---------------------------------------------------------------
+ /** @brief man page section */
+ class section {
+ public:
+ using string = doc_string;
+
+ section(string stitle, string scontent):
+ title_{std::move(stitle)}, content_{std::move(scontent)}
+ {}
+
+ const string& title() const noexcept { return title_; }
+ const string& content() const noexcept { return content_; }
+
+ private:
+ string title_;
+ string content_;
+ };
+
+private:
+ using section_store = std::vector<section>;
+
+public:
+ //---------------------------------------------------------------
+ using value_type = section;
+ using const_iterator = section_store::const_iterator;
+ using size_type = section_store::size_type;
+
+
+ //---------------------------------------------------------------
+ man_page&
+ append_section(string title, string content)
+ {
+ sections_.emplace_back(std::move(title), std::move(content));
+ return *this;
+ }
+ //-----------------------------------------------------
+ man_page&
+ prepend_section(string title, string content)
+ {
+ sections_.emplace(sections_.begin(),
+ std::move(title), std::move(content));
+ return *this;
+ }
+
+
+ //---------------------------------------------------------------
+ const section& operator [] (size_type index) const noexcept {
+ return sections_[index];
+ }
+
+ //---------------------------------------------------------------
+ size_type size() const noexcept { return sections_.size(); }
+
+ bool empty() const noexcept { return sections_.empty(); }
+
+
+ //---------------------------------------------------------------
+ const_iterator begin() const noexcept { return sections_.begin(); }
+ const_iterator end() const noexcept { return sections_.end(); }
+
+
+ //---------------------------------------------------------------
+ man_page& program_name(const string& n) {
+ progName_ = n;
+ return *this;
+ }
+ man_page& program_name(string&& n) {
+ progName_ = std::move(n);
+ return *this;
+ }
+ const string& program_name() const noexcept {
+ return progName_;
+ }
+
+
+ //---------------------------------------------------------------
+ man_page& section_row_spacing(int rows) {
+ sectionSpc_ = rows > 0 ? rows : 0;
+ return *this;
+ }
+ int section_row_spacing() const noexcept { return sectionSpc_; }
+
+
+private:
+ int sectionSpc_ = 1;
+ section_store sections_;
+ string progName_;
+};
+
+
+
+/*************************************************************************//**
+ *
+ * @brief generates man sections from command line parameters
+ * with sections "synopsis" and "options"
+ *
+ *****************************************************************************/
+inline man_page
+make_man_page(const group& cli,
+ doc_string progname = "",
+ const doc_formatting& fmt = doc_formatting{})
+{
+ man_page man;
+ man.append_section("SYNOPSIS", usage_lines(cli,progname,fmt).str());
+ man.append_section("OPTIONS", documentation(cli,fmt).str());
+ return man;
+}
+
+
+
+/*************************************************************************//**
+ *
+ * @brief generates man page based on command line parameters
+ *
+ *****************************************************************************/
+template<class OStream>
+OStream&
+operator << (OStream& os, const man_page& man)
+{
+ bool first = true;
+ const auto secSpc = doc_string(man.section_row_spacing() + 1, '\n');
+ for(const auto& section : man) {
+ if(!section.content().empty()) {
+ if(first) first = false; else os << secSpc;
+ if(!section.title().empty()) os << section.title() << '\n';
+ os << section.content();
+ }
+ }
+ os << '\n';
+ return os;
+}
+
+
+
+
+
+/*************************************************************************//**
+ *
+ * @brief printing methods for debugging command line interfaces
+ *
+ *****************************************************************************/
+namespace debug {
+
+
+/*************************************************************************//**
+ *
+ * @brief prints first flag or value label of a parameter
+ *
+ *****************************************************************************/
+inline doc_string doc_label(const parameter& p)
+{
+ if(!p.flags().empty()) return p.flags().front();
+ if(!p.label().empty()) return p.label();
+ return doc_string{"<?>"};
+}
+
+inline doc_string doc_label(const group&)
+{
+ return "<group>";
+}
+
+inline doc_string doc_label(const pattern& p)
+{
+ return p.is_group() ? doc_label(p.as_group()) : doc_label(p.as_param());
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief prints parsing result
+ *
+ *****************************************************************************/
+template<class OStream>
+void print(OStream& os, const parsing_result& result)
+{
+ for(const auto& m : result) {
+ os << "#" << m.index() << " " << m.arg() << " -> ";
+ auto p = m.param();
+ if(p) {
+ os << doc_label(*p) << " \t";
+ if(m.repeat() > 0) {
+ os << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
+ << m.repeat() << "]";
+ }
+ if(m.blocked()) os << " [blocked]";
+ if(m.conflict()) os << " [conflict]";
+ os << '\n';
+ }
+ else {
+ os << " [unmapped]\n";
+ }
+ }
+
+ for(const auto& m : result.missing()) {
+ auto p = m.param();
+ if(p) {
+ os << doc_label(*p) << " \t";
+ os << " [missing after " << m.after_index() << "]\n";
+ }
+ }
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief prints parameter label and some properties
+ *
+ *****************************************************************************/
+template<class OStream>
+void print(OStream& os, const parameter& p)
+{
+ if(p.greedy()) os << '!';
+ if(p.blocking()) os << '~';
+ if(!p.required()) os << '[';
+ os << doc_label(p);
+ if(p.repeatable()) os << "...";
+ if(!p.required()) os << "]";
+}
+
+
+//-------------------------------------------------------------------
+template<class OStream>
+void print(OStream& os, const group& g, int level = 0);
+
+
+/*************************************************************************//**
+ *
+ * @brief prints group or parameter; uses indentation
+ *
+ *****************************************************************************/
+template<class OStream>
+void print(OStream& os, const pattern& param, int level = 0)
+{
+ if(param.is_group()) {
+ print(os, param.as_group(), level);
+ }
+ else {
+ os << doc_string(4*level, ' ');
+ print(os, param.as_param());
+ }
+}
+
+
+/*************************************************************************//**
+ *
+ * @brief prints group and its contents; uses indentation
+ *
+ *****************************************************************************/
+template<class OStream>
+void print(OStream& os, const group& g, int level)
+{
+ auto indent = doc_string(4*level, ' ');
+ os << indent;
+ if(g.blocking()) os << '~';
+ if(g.joinable()) os << 'J';
+ os << (g.exclusive() ? "(|\n" : "(\n");
+ for(const auto& p : g) {
+ print(os, p, level+1);
+ }
+ os << '\n' << indent << (g.exclusive() ? "|)" : ")");
+ if(g.repeatable()) os << "...";
+ os << '\n';
+}
+
+
+} // namespace debug
+} //namespace clipp
+
+#endif
+
diff --git a/common/rawaccel-userspace.hpp b/common/rawaccel-userspace.hpp
new file mode 100644
index 0000000..80fcecc
--- /dev/null
+++ b/common/rawaccel-userspace.hpp
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <iostream>
+
+#include "external/clipp.h"
+
+#include "rawaccel.hpp"
+
+namespace rawaccel {
+
+inline constexpr int SYSTEM_ERROR = -1;
+inline constexpr int PARSE_ERROR = 1;
+inline constexpr int INVALID_ARGUMENT = 2;
+
+void error(const char* s) {
+ throw std::domain_error(s);
+}
+
+variables parse(int argc, char** argv) {
+ double degrees = 0;
+ vec2d sens = { 1, 1 };
+ accel_function::args_t accel_args{};
+
+ // default options
+ auto opt_sens = "sensitivity, <y> defaults to <x> (default = 1)" % (
+ clipp::option("sens") &
+ clipp::number("x", sens.x, sens.y) &
+ clipp::opt_number("y", sens.y)
+ );
+ auto opt_rot = "counter-clockwise rotation (default = 0)" % (
+ clipp::option("rotate") &
+ clipp::number("degrees", degrees)
+ );
+
+ // mode-independent accel options
+ auto opt_weight = "accel multiplier, <y> defaults to <x> (default = 1)" % (
+ clipp::option("weight") &
+ clipp::number("x", accel_args.weight.x, accel_args.weight.y) &
+ clipp::opt_number("y", accel_args.weight.y)
+ );
+ auto opt_offset = "speed (dots/ms) where accel kicks in (default = 0)" % (
+ clipp::option("offset") & clipp::number("speed", accel_args.offset)
+ );
+ auto opt_cap = "accel scale cap, <y> defaults to <x> (default = 9)" % (
+ clipp::option("cap") &
+ clipp::number("x", accel_args.cap.x, accel_args.cap.y) &
+ clipp::opt_number("y", accel_args.cap.y)
+ );
+ auto opt_tmin = "minimum time between polls (default = 0.4)" % (
+ clipp::option("tmin") &
+ clipp::number("ms", accel_args.time_min)
+ );
+
+ auto accel_var = (clipp::required("accel") & clipp::number("num", accel_args.accel)) % "ramp rate";
+ auto limit_var = (clipp::required("limit") & clipp::number("scale", accel_args.lim_exp)) % "limit";
+
+ // modes
+ auto noaccel_mode = "no-accel mode" % (
+ clipp::command("off", "noaccel").set(accel_args.accel_mode, mode::noaccel)
+ );
+ auto lin_mode = "linear accel mode:" % (
+ clipp::command("linear").set(accel_args.accel_mode, mode::linear),
+ accel_var
+ );
+ auto classic_mode = "classic accel mode:" % (
+ clipp::command("classic").set(accel_args.accel_mode, mode::classic),
+ accel_var,
+ (clipp::required("exponent") & clipp::number("num", accel_args.lim_exp)) % "exponent"
+ );
+ auto nat_mode = "natural accel mode:" % (
+ clipp::command("natural").set(accel_args.accel_mode, mode::natural),
+ accel_var,
+ limit_var
+ );
+ auto log_mode = "logarithmic accel mode:" % (
+ clipp::command("logarithmic").set(accel_args.accel_mode, mode::logarithmic),
+ accel_var
+ );
+ auto sig_mode = "sigmoid accel mode:" % (
+ clipp::command("sigmoid").set(accel_args.accel_mode, mode::sigmoid),
+ accel_var,
+ limit_var,
+ (clipp::required("midpoint") & clipp::number("speed", accel_args.midpoint)) % "midpoint"
+ );
+
+ auto accel_mode_exclusive = (lin_mode | classic_mode | nat_mode | log_mode | sig_mode);
+ auto accel_opts = "mode-independent accel options:" % (opt_offset, opt_cap, opt_weight, opt_tmin);
+
+ bool help = false;
+
+ auto cli = clipp::group(clipp::command("help").set(help)) | (
+ noaccel_mode | (accel_mode_exclusive, accel_opts),
+ opt_sens,
+ opt_rot
+ );
+
+ if (!clipp::parse(argc, argv, cli)) {
+ std::cout << clipp::usage_lines(cli, "rawaccel");
+ std::exit(PARSE_ERROR);
+ }
+
+ if (help) {
+ auto fmt = clipp::doc_formatting{}.first_column(4)
+ .doc_column(28)
+ .last_column(80);
+ std::cout << clipp::make_man_page(cli, "rawaccel", fmt);
+ std::exit(0);
+ }
+
+ return variables(-degrees, sens, accel_args);
+}
+
+} // rawaccel
diff --git a/common/rawaccel.hpp b/common/rawaccel.hpp
new file mode 100644
index 0000000..1483e67
--- /dev/null
+++ b/common/rawaccel.hpp
@@ -0,0 +1,176 @@
+#pragma once
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#include "vec2.h"
+#include "x64-util.hpp"
+
+namespace rawaccel {
+
+enum class mode { noaccel, linear, classic, natural, logarithmic, sigmoid };
+
+struct rotator {
+ vec2d rot_vec = { 1, 0 };
+
+ inline vec2d operator()(const vec2d& input) const {
+ return {
+ input.x * rot_vec.x - input.y * rot_vec.y,
+ input.x * rot_vec.y + input.y * rot_vec.x
+ };
+ }
+
+ rotator(double degrees) {
+ double rads = degrees * M_PI / 180;
+ rot_vec = { cos(rads), sin(rads) };
+ }
+
+ rotator() = default;
+};
+
+struct accel_scale_clamp {
+ double lo = 0;
+ double hi = 9;
+
+ inline double operator()(double scale) const {
+ return clampsd(scale, lo, hi);
+ }
+
+ accel_scale_clamp(double cap) : accel_scale_clamp() {
+ if (cap <= 0) {
+ // use default, effectively uncapped accel
+ return;
+ }
+
+ if (cap < 1) {
+ // assume negative accel
+ lo = cap;
+ hi = 1;
+ }
+ else hi = cap;
+ }
+
+ accel_scale_clamp() = default;
+};
+
+void error(const char*);
+
+struct accel_function {
+ using milliseconds = double;
+
+ /*
+ This value is ideally a few microseconds lower than
+ the user's mouse polling interval, though it should
+ not matter if the system is stable.
+ */
+ milliseconds time_min = 0.4;
+
+ double speed_offset = 0;
+
+ // speed midpoint in sigmoid mode
+ double m = 0;
+
+ // accel ramp rate
+ double b = 0;
+
+ // the limit for natural and sigmoid modes,
+ // or the exponent for classic mode
+ double k = 1;
+
+ vec2d weight = { 1, 1 };
+ vec2<accel_scale_clamp> clamp;
+
+ inline vec2d operator()(const vec2d& input, milliseconds time, mode accel_mode) const {
+ double mag = sqrtsd(input.x * input.x + input.y * input.y);
+ double time_clamped = clampsd(time, time_min, 100);
+ double speed = maxsd(mag / time_clamped - speed_offset, 0);
+
+ double accel_val = 0;
+
+ switch (accel_mode) {
+ case mode::linear: accel_val = b * speed;
+ break;
+ case mode::classic: accel_val = pow(b * speed, k);
+ break;
+ case mode::natural: accel_val = k - (k * exp(-b * speed));
+ break;
+ case mode::logarithmic: accel_val = log(speed * b + 1);
+ break;
+ case mode::sigmoid: accel_val = k / (exp(-b * (speed - m)) + 1);
+ break;
+ default:
+ break;
+ }
+
+ double scale_x = weight.x * accel_val + 1;
+ double scale_y = weight.y * accel_val + 1;
+
+ return {
+ input.x * clamp.x(scale_x),
+ input.y * clamp.y(scale_y)
+ };
+ }
+
+ struct args_t {
+ mode accel_mode = mode::noaccel;
+ milliseconds time_min = 0.4;
+ double offset = 0;
+ double accel = 0;
+ double lim_exp = 2;
+ double midpoint = 0;
+ vec2d weight = { 1, 1 };
+ vec2d cap = { 0, 0 };
+ };
+
+ accel_function(args_t args) {
+ // Preconditions to guard against division by zero and
+ // ensure the C math functions can not return NaN or -Inf.
+ if (args.accel < 0) error("accel can not be negative, use a negative weight to compensate");
+ if (args.time_min <= 0) error("min time must be positive");
+ if (args.lim_exp <= 1) {
+ if (args.accel_mode == mode::classic) error("exponent must be greater than 1");
+ else error("limit must be greater than 1");
+ }
+
+ time_min = args.time_min;
+ m = args.midpoint;
+ b = args.accel;
+ k = args.lim_exp - 1;
+ if (args.accel_mode == mode::natural) b /= k;
+
+ speed_offset = args.offset;
+ weight = args.weight;
+ clamp.x = accel_scale_clamp(args.cap.x);
+ clamp.y = accel_scale_clamp(args.cap.y);
+ }
+
+ accel_function() = default;
+};
+
+struct variables {
+ bool apply_rotate = false;
+ bool apply_accel = false;
+ mode accel_mode = mode::noaccel;
+ rotator rotate;
+ accel_function accel_fn;
+ vec2d sensitivity = { 1, 1 };
+
+ variables(double degrees, vec2d sens, accel_function::args_t accel_args)
+ : accel_fn(accel_args)
+ {
+ apply_rotate = degrees != 0;
+ if (apply_rotate) rotate = rotator(degrees);
+ else rotate = rotator();
+
+ apply_accel = accel_args.accel_mode != mode::noaccel;
+ accel_mode = accel_args.accel_mode;
+
+ if (sens.x == 0) sens.x = 1;
+ if (sens.y == 0) sens.y = 1;
+ sensitivity = sens;
+ }
+
+ variables() = default;
+};
+
+} // rawaccel
diff --git a/common/vec2.h b/common/vec2.h
new file mode 100644
index 0000000..6484e69
--- /dev/null
+++ b/common/vec2.h
@@ -0,0 +1,9 @@
+#pragma once
+
+template <typename T>
+struct vec2 {
+ T x;
+ T y;
+};
+
+using vec2d = vec2<double>;
diff --git a/common/x64-util.hpp b/common/x64-util.hpp
new file mode 100644
index 0000000..2fb61bb
--- /dev/null
+++ b/common/x64-util.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <emmintrin.h>
+
+inline double sqrtsd(double val) {
+ __m128d src = _mm_load_sd(&val);
+ __m128d dst = _mm_sqrt_sd(src, src);
+ _mm_store_sd(&val, dst);
+ return val;
+}
+
+inline constexpr double minsd(double a, double b) {
+ return (a < b) ? a : b;
+}
+
+inline constexpr double maxsd(double a, double b) {
+ return (b < a) ? a : b;
+}
+
+inline constexpr double clampsd(double v, double lo, double hi) {
+ return minsd(maxsd(v, lo), hi);
+}
diff --git a/console/console.cpp b/console/console.cpp
new file mode 100644
index 0000000..6606cac
--- /dev/null
+++ b/console/console.cpp
@@ -0,0 +1,53 @@
+#include <iostream>
+
+#define NOMINMAX
+#include <Windows.h>
+
+#include <rawaccel-userspace.hpp>
+
+#define RA_WRITE CTL_CODE(0x8888, 0x888, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+namespace ra = rawaccel;
+
+void write(ra::variables vars) {
+ HANDLE ra_handle = INVALID_HANDLE_VALUE;
+
+ ra_handle = CreateFileW(L"\\\\.\\rawaccel", 0, 0, 0, OPEN_EXISTING, 0, 0);
+
+ if (ra_handle == INVALID_HANDLE_VALUE) {
+ throw std::system_error(GetLastError(), std::system_category(), "CreateFile failed");
+ }
+
+ DWORD dummy;
+
+ BOOL success = DeviceIoControl(
+ ra_handle,
+ RA_WRITE,
+ &vars,
+ sizeof(ra::variables),
+ NULL, // output buffer
+ 0, // output buffer size
+ &dummy, // bytes returned
+ NULL // overlapped structure
+ );
+
+ CloseHandle(ra_handle);
+
+ if (!success) {
+ throw std::system_error(GetLastError(), std::system_category(), "DeviceIoControl failed");
+ }
+}
+
+int main(int argc, char** argv) {
+ try {
+ write(ra::parse(argc, argv));
+ }
+ catch (std::domain_error e) {
+ std::cerr << e.what() << '\n';
+ return ra::INVALID_ARGUMENT;
+ }
+ catch (std::system_error e) {
+ std::cerr << "Error: " << e.what() << " (" << e.code() << ")\n";
+ return ra::SYSTEM_ERROR;
+ }
+}
diff --git a/console/console.vcxproj b/console/console.vcxproj
new file mode 100644
index 0000000..d0ad292
--- /dev/null
+++ b/console/console.vcxproj
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{AB7B3759-B85F-4067-8935-FB4539B41869}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>console</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ <Import Project="..\common\common.vcxitems" Label="Shared" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <TargetName>rawaccel</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <TargetName>rawaccel</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory);$(SolutionDir)\external</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory);$(SolutionDir)\external</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="console.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/driver/driver.cpp b/driver/driver.cpp
new file mode 100644
index 0000000..e77ac5f
--- /dev/null
+++ b/driver/driver.cpp
@@ -0,0 +1,611 @@
+#include <rawaccel.hpp>
+
+#include "driver.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (INIT, DriverEntry)
+#pragma alloc_text (PAGE, EvtDeviceAdd)
+#pragma alloc_text (PAGE, EvtIoInternalDeviceControl)
+#pragma alloc_text (PAGE, RawaccelControl)
+#endif
+
+namespace ra = rawaccel;
+
+using milliseconds = double;
+
+struct {
+ milliseconds tick_interval = 0; // set in DriverEntry
+ ra::variables vars;
+} global;
+
+VOID
+RawaccelCallback(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PMOUSE_INPUT_DATA InputDataStart,
+ IN PMOUSE_INPUT_DATA InputDataEnd,
+ IN OUT PULONG InputDataConsumed
+)
+/*++
+
+Routine Description:
+
+ Called when there are mouse packets to report to the RIT.
+
+Arguments:
+
+ DeviceObject - Context passed during the connect IOCTL
+
+ InputDataStart - First packet to be reported
+
+ InputDataEnd - One past the last packet to be reported. Total number of
+ packets is equal to InputDataEnd - InputDataStart
+
+ InputDataConsumed - Set to the total number of packets consumed by the RIT
+ (via the function pointer we replaced in the connect
+ IOCTL)
+
+--*/
+{
+ WDFDEVICE hDevice = WdfWdmDeviceGetWdfDeviceHandle(DeviceObject);
+ PDEVICE_EXTENSION devExt = FilterGetData(hDevice);
+
+ if (!(InputDataStart->Flags & MOUSE_MOVE_ABSOLUTE)) {
+ auto num_packets = InputDataEnd - InputDataStart;
+
+ // if IO is backed up to the point where we get more than 1 packet here
+ // then applying accel is pointless as we can't get an accurate timing
+
+ bool local_apply_accel = num_packets == 1;
+ if (num_packets != 1) {
+ DebugPrint(("RA received %d packets\n", num_packets));
+ }
+
+ vec2d local_carry = devExt->carry;
+
+ auto it = InputDataStart;
+ do {
+ vec2d input = {
+ static_cast<double>(it->LastX),
+ static_cast<double>(it->LastY)
+ };
+
+ if (global.vars.apply_rotate) {
+ input = global.vars.rotate(input);
+ }
+
+ if (global.vars.apply_accel && local_apply_accel) {
+ auto now = KeQueryPerformanceCounter(NULL).QuadPart;
+ auto ticks = now - devExt->counter.QuadPart;
+ devExt->counter.QuadPart = now;
+
+ milliseconds time = ticks * global.tick_interval;
+ if (time < global.vars.accel_fn.time_min) {
+ DebugPrint(("RA time < min with %d ticks\n", ticks));
+ }
+
+ input = global.vars.accel_fn(input, time, global.vars.accel_mode);
+ }
+
+ double result_x = input.x * global.vars.sensitivity.x + local_carry.x;
+ double result_y = input.y * global.vars.sensitivity.y + local_carry.y;
+
+ LONG out_x = static_cast<LONG>(result_x);
+ LONG out_y = static_cast<LONG>(result_y);
+
+ local_carry.x = result_x - out_x;
+ local_carry.y = result_y - out_y;
+
+ it->LastX = out_x;
+ it->LastY = out_y;
+
+ ++it;
+ } while (it < InputDataEnd);
+
+ devExt->carry = local_carry;
+ }
+
+ (*(PSERVICE_CALLBACK_ROUTINE)devExt->UpperConnectData.ClassService)(
+ devExt->UpperConnectData.ClassDeviceObject,
+ InputDataStart,
+ InputDataEnd,
+ InputDataConsumed
+ );
+}
+
+
+#pragma warning(push)
+#pragma warning(disable:28118) // this callback will run at IRQL=PASSIVE_LEVEL
+_Use_decl_annotations_
+VOID
+RawaccelControl(
+ WDFQUEUE Queue,
+ WDFREQUEST Request,
+ size_t OutputBufferLength,
+ size_t InputBufferLength,
+ ULONG IoControlCode
+)
+/*++
+Routine Description:
+ This event is called when the framework receives IRP_MJ_DEVICE_CONTROL
+ requests from the system.
+Arguments:
+ Queue - Handle to the framework queue object that is associated
+ with the I/O request.
+ Request - Handle to a framework request object.
+ OutputBufferLength - length of the request's output buffer,
+ if an output buffer is available.
+ InputBufferLength - length of the request's input buffer,
+ if an input buffer is available.
+ IoControlCode - the driver-defined or system-defined I/O control code
+ (IOCTL) that is associated with the request.
+Return Value:
+ VOID
+--*/
+{
+ NTSTATUS status;
+ void* input_buffer;
+ size_t input_size;
+
+ UNREFERENCED_PARAMETER(Queue);
+ UNREFERENCED_PARAMETER(OutputBufferLength);
+ UNREFERENCED_PARAMETER(IoControlCode);
+
+ PAGED_CODE();
+
+ DebugPrint(("Ioctl received into filter control object.\n"));
+
+ if (InputBufferLength != sizeof(ra::variables)) {
+ DebugPrint(("Received unknown request of %u bytes\n", InputBufferLength));
+ // status maps to win32 error code 1784: ERROR_INVALID_USER_BUFFER
+ WdfRequestComplete(Request, STATUS_INVALID_BUFFER_SIZE);
+ return;
+ }
+
+ status = WdfRequestRetrieveInputBuffer(
+ Request,
+ sizeof(ra::variables),
+ &input_buffer,
+ &input_size
+ );
+
+ if (!NT_SUCCESS(status)) {
+ DebugPrint(("RetrieveInputBuffer failed: 0x%x\n", status));
+ // status maps to win32 error code 1359: ERROR_INTERNAL_ERROR
+ WdfRequestComplete(Request, STATUS_MESSAGE_LOST);
+ return;
+ }
+
+ global.vars = *reinterpret_cast<ra::variables*>(input_buffer);
+
+ WdfRequestComplete(Request, STATUS_SUCCESS);
+}
+#pragma warning(pop) // enable 28118 again
+
+
+NTSTATUS
+DriverEntry(
+ IN PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING RegistryPath
+)
+/*++
+Routine Description:
+
+ Installable driver initialization entry point.
+ This entry point is called directly by the I/O system.
+
+--*/
+{
+
+ WDF_DRIVER_CONFIG config;
+ NTSTATUS status;
+ WDFDRIVER driver;
+
+ DebugPrint(("km accel filter.\n"));
+ DebugPrint(("Built %s %s\n", __DATE__, __TIME__));
+
+ // Initialize driver config to control the attributes that
+ // are global to the driver. Note that framework by default
+ // provides a driver unload routine. If you create any resources
+ // in the DriverEntry and want to be cleaned in driver unload,
+ // you can override that by manually setting the EvtDriverUnload in the
+ // config structure. In general xxx_CONFIG_INIT macros are provided to
+ // initialize most commonly used members.
+
+ WDF_DRIVER_CONFIG_INIT(
+ &config,
+ EvtDeviceAdd
+ );
+
+ //
+ // Create a framework driver object to represent our driver.
+ //
+ status = WdfDriverCreate(DriverObject,
+ RegistryPath,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &config,
+ &driver);
+
+
+ if (NT_SUCCESS(status)) {
+ LARGE_INTEGER freq;
+ KeQueryPerformanceCounter(&freq);
+ global.tick_interval = 1e3 / freq.QuadPart;
+
+ CreateControlDevice(driver);
+ }
+ else {
+ DebugPrint(("WdfDriverCreate failed with status 0x%x\n", status));
+ }
+
+ return status;
+}
+
+
+inline
+VOID
+CreateControlDevice(WDFDRIVER Driver)
+/*++
+Routine Description:
+ This routine is called to create a control device object so that application
+ can talk to the filter driver directly instead of going through the entire
+ device stack. This kind of control device object is useful if the filter
+ driver is underneath another driver which prevents ioctls not known to it
+ or if the driver's dispatch routine is owned by some other (port/class)
+ driver and it doesn't allow any custom ioctls.
+Arguments:
+ Driver - Handle to wdf driver object.
+Return Value:
+ WDF status code
+--*/
+{
+ PWDFDEVICE_INIT pInit = NULL;
+ WDFDEVICE controlDevice = NULL;
+ WDF_IO_QUEUE_CONFIG ioQueueConfig;
+ NTSTATUS status;
+ WDFQUEUE queue;
+ DECLARE_CONST_UNICODE_STRING(ntDeviceName, NTDEVICE_NAME);
+ DECLARE_CONST_UNICODE_STRING(symbolicLinkName, SYMBOLIC_NAME_STRING);
+
+ DebugPrint(("Creating Control Device\n"));
+
+ //
+ //
+ // In order to create a control device, we first need to allocate a
+ // WDFDEVICE_INIT structure and set all properties.
+ //
+ pInit = WdfControlDeviceInitAllocate(
+ Driver,
+ &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RW_RES_R
+ );
+
+ if (pInit == NULL) {
+ status = STATUS_INSUFFICIENT_RESOURCES;
+ goto Error;
+ }
+
+ //
+ // Set exclusive to false so that more than one app can talk to the
+ // control device simultaneously.
+ //
+ WdfDeviceInitSetExclusive(pInit, FALSE);
+
+ status = WdfDeviceInitAssignName(pInit, &ntDeviceName);
+
+ if (!NT_SUCCESS(status)) {
+ goto Error;
+ }
+
+ status = WdfDeviceCreate(&pInit,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &controlDevice);
+
+ if (!NT_SUCCESS(status)) {
+ goto Error;
+ }
+
+ //
+ // Create a symbolic link for the control object so that usermode can open
+ // the device.
+ //
+
+ status = WdfDeviceCreateSymbolicLink(controlDevice, &symbolicLinkName);
+
+ if (!NT_SUCCESS(status)) {
+ goto Error;
+ }
+
+ //
+ // Configure the default queue associated with the control device object
+ // to be Serial so that request passed to RawaccelControl are serialized.
+ //
+
+ WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig,
+ WdfIoQueueDispatchSequential);
+
+ ioQueueConfig.EvtIoDeviceControl = RawaccelControl;
+
+ //
+ // Framework by default creates non-power managed queues for
+ // filter drivers.
+ //
+ status = WdfIoQueueCreate(controlDevice,
+ &ioQueueConfig,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &queue // pointer to default queue
+ );
+ if (!NT_SUCCESS(status)) {
+ goto Error;
+ }
+
+ //
+ // Control devices must notify WDF when they are done initializing. I/O is
+ // rejected until this call is made.
+ //
+ WdfControlFinishInitializing(controlDevice);
+
+ return;
+
+Error:
+
+ if (pInit != NULL) WdfDeviceInitFree(pInit);
+
+ if (controlDevice != NULL) {
+ //
+ // Release the reference on the newly created object, since
+ // we couldn't initialize it.
+ //
+ WdfObjectDelete(controlDevice);
+ }
+
+ DebugPrint(("CreateControlDevice failed\n", status));
+}
+
+
+NTSTATUS
+EvtDeviceAdd(
+ IN WDFDRIVER Driver,
+ IN PWDFDEVICE_INIT DeviceInit
+ )
+/*++
+Routine Description:
+
+ EvtDeviceAdd is called by the framework in response to AddDevice
+ call from the PnP manager. Here you can query the device properties
+ using WdfFdoInitWdmGetPhysicalDevice/IoGetDeviceProperty and based
+ on that, decide to create a filter device object and attach to the
+ function stack.
+
+ If you are not interested in filtering this particular instance of the
+ device, you can just return STATUS_SUCCESS without creating a framework
+ device.
+
+Arguments:
+
+ Driver - Handle to a framework driver object created in DriverEntry
+
+ DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure.
+
+Return Value:
+
+ NTSTATUS
+
+--*/
+{
+ WDF_OBJECT_ATTRIBUTES deviceAttributes;
+ NTSTATUS status;
+ WDFDEVICE hDevice;
+ WDF_IO_QUEUE_CONFIG ioQueueConfig;
+
+ UNREFERENCED_PARAMETER(Driver);
+
+ PAGED_CODE();
+
+ DebugPrint(("Enter FilterEvtDeviceAdd \n"));
+
+ //
+ // Tell the framework that you are filter driver. Framework
+ // takes care of inherting all the device flags & characterstics
+ // from the lower device you are attaching to.
+ //
+ WdfFdoInitSetFilter(DeviceInit);
+
+ WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_MOUSE);
+
+ WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes,
+ DEVICE_EXTENSION);
+
+
+ //
+ // Create a framework device object. This call will in turn create
+ // a WDM deviceobject, attach to the lower stack and set the
+ // appropriate flags and attributes.
+ //
+ status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &hDevice);
+ if (!NT_SUCCESS(status)) {
+ DebugPrint(("WdfDeviceCreate failed with status code 0x%x\n", status));
+ return status;
+ }
+
+
+ //
+ // Configure the default queue to be Parallel. Do not use sequential queue
+ // if this driver is going to be filtering PS2 ports because it can lead to
+ // deadlock. The PS2 port driver sends a request to the top of the stack when it
+ // receives an ioctl request and waits for it to be completed. If you use a
+ // a sequential queue, this request will be stuck in the queue because of the
+ // outstanding ioctl request sent earlier to the port driver.
+ //
+ WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig,
+ WdfIoQueueDispatchParallel);
+
+ //
+ // Framework by default creates non-power managed queues for
+ // filter drivers.
+ //
+ ioQueueConfig.EvtIoInternalDeviceControl = EvtIoInternalDeviceControl;
+
+ status = WdfIoQueueCreate(hDevice,
+ &ioQueueConfig,
+ WDF_NO_OBJECT_ATTRIBUTES,
+ WDF_NO_HANDLE // pointer to default queue
+ );
+ if (!NT_SUCCESS(status)) {
+ DebugPrint( ("WdfIoQueueCreate failed 0x%x\n", status));
+ return status;
+ }
+
+ return status;
+}
+
+
+VOID
+EvtIoInternalDeviceControl(
+ IN WDFQUEUE Queue,
+ IN WDFREQUEST Request,
+ IN size_t OutputBufferLength,
+ IN size_t InputBufferLength,
+ IN ULONG IoControlCode
+ )
+/*++
+
+Routine Description:
+
+ This routine is the dispatch routine for internal device control requests.
+ There are two specific control codes that are of interest:
+
+ IOCTL_INTERNAL_MOUSE_CONNECT:
+ Store the old context and function pointer and replace it with our own.
+ This makes life much simpler than intercepting IRPs sent by the RIT and
+ modifying them on the way back up.
+
+ IOCTL_INTERNAL_I8042_HOOK_MOUSE:
+ Add in the necessary function pointers and context values so that we can
+ alter how the ps/2 mouse is initialized.
+
+ NOTE: Handling IOCTL_INTERNAL_I8042_HOOK_MOUSE is *NOT* necessary if
+ all you want to do is filter MOUSE_INPUT_DATAs. You can remove
+ the handling code and all related device extension fields and
+ functions to conserve space.
+
+
+--*/
+{
+
+ PDEVICE_EXTENSION devExt;
+ PCONNECT_DATA connectData;
+ NTSTATUS status = STATUS_SUCCESS;
+ WDFDEVICE hDevice;
+ size_t length;
+
+ UNREFERENCED_PARAMETER(OutputBufferLength);
+ UNREFERENCED_PARAMETER(InputBufferLength);
+
+ PAGED_CODE();
+
+ hDevice = WdfIoQueueGetDevice(Queue);
+ devExt = FilterGetData(hDevice);
+
+ switch (IoControlCode) {
+
+ //
+ // Connect a mouse class device driver to the port driver.
+ //
+ case IOCTL_INTERNAL_MOUSE_CONNECT:
+ //
+ // Only allow one connection.
+ //
+ if (devExt->UpperConnectData.ClassService != NULL) {
+ status = STATUS_SHARING_VIOLATION;
+ break;
+ }
+
+ //
+ // Copy the connection parameters to the device extension.
+ //
+ status = WdfRequestRetrieveInputBuffer(Request,
+ sizeof(CONNECT_DATA),
+ reinterpret_cast<PVOID*>(&connectData),
+ &length);
+ if(!NT_SUCCESS(status)){
+ DebugPrint(("WdfRequestRetrieveInputBuffer failed %x\n", status));
+ break;
+ }
+
+ devExt->counter = KeQueryPerformanceCounter(NULL);
+ devExt->carry = {};
+ devExt->UpperConnectData = *connectData;
+
+ //
+ // Hook into the report chain. Everytime a mouse packet is reported to
+ // the system, RawaccelCallback will be called
+ //
+
+ connectData->ClassDeviceObject = WdfDeviceWdmGetDeviceObject(hDevice);
+ connectData->ClassService = RawaccelCallback;
+
+ break;
+
+ //
+ // Disconnect a mouse class device driver from the port driver.
+ //
+ case IOCTL_INTERNAL_MOUSE_DISCONNECT:
+ //
+ // Clear the connection parameters in the device extension.
+ //
+ // devExt->UpperConnectData.ClassDeviceObject = NULL;
+ // devExt->UpperConnectData.ClassService = NULL;
+ status = STATUS_NOT_IMPLEMENTED;
+ break;
+ case IOCTL_MOUSE_QUERY_ATTRIBUTES:
+ default:
+ break;
+ }
+
+ if (!NT_SUCCESS(status)) {
+ WdfRequestComplete(Request, status);
+ return;
+ }
+
+ DispatchPassThrough(Request, WdfDeviceGetIoTarget(hDevice));
+}
+
+
+inline
+VOID
+DispatchPassThrough(
+ _In_ WDFREQUEST Request,
+ _In_ WDFIOTARGET Target
+)
+/*++
+Routine Description:
+
+ Passes a request on to the lower driver.
+
+
+--*/
+{
+ //
+ // Pass the IRP to the target
+ //
+
+ WDF_REQUEST_SEND_OPTIONS options;
+ BOOLEAN ret;
+ NTSTATUS status = STATUS_SUCCESS;
+
+ //
+ // We are not interested in post processing the IRP so
+ // fire and forget.
+ //
+ WDF_REQUEST_SEND_OPTIONS_INIT(&options,
+ WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET);
+
+ ret = WdfRequestSend(Request, Target, &options);
+
+ if (ret == FALSE) {
+ status = WdfRequestGetStatus(Request);
+ DebugPrint(("WdfRequestSend failed: 0x%x\n", status));
+ WdfRequestComplete(Request, status);
+ }
+
+ return;
+}
diff --git a/driver/driver.h b/driver/driver.h
new file mode 100644
index 0000000..2a400de
--- /dev/null
+++ b/driver/driver.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <ntddk.h>
+#include <kbdmou.h>
+#include <wdf.h>
+
+#include "vec2.h"
+
+#if DBG
+#define DebugPrint(_x_) DbgPrint _x_
+#else
+#define DebugPrint(_x_)
+#endif
+
+#define NTDEVICE_NAME L"\\Device\\rawaccel"
+#define SYMBOLIC_NAME_STRING L"\\DosDevices\\rawaccel"
+
+typedef struct _DEVICE_EXTENSION {
+ LARGE_INTEGER counter;
+ vec2d carry;
+ CONNECT_DATA UpperConnectData;
+} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_EXTENSION, FilterGetData)
+
+EXTERN_C_START
+
+DRIVER_INITIALIZE DriverEntry;
+EVT_WDF_DRIVER_DEVICE_ADD EvtDeviceAdd;
+EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL EvtIoInternalDeviceControl;
+EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL RawaccelControl;
+
+VOID RawaccelCallback(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PMOUSE_INPUT_DATA InputDataStart,
+ IN PMOUSE_INPUT_DATA InputDataEnd,
+ IN OUT PULONG InputDataConsumed
+);
+
+EXTERN_C_END
+
+VOID CreateControlDevice(WDFDRIVER Driver);
+
+VOID DispatchPassThrough(
+ _In_ WDFREQUEST Request,
+ _In_ WDFIOTARGET Target
+);
diff --git a/driver/driver.vcxproj b/driver/driver.vcxproj
new file mode 100644
index 0000000..9ebcf31
--- /dev/null
+++ b/driver/driver.vcxproj
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{60D6C942-AC20-4C05-A2BE-54B5C966534D}</ProjectGuid>
+ <RootNamespace>$(MSBuildProjectName)</RootNamespace>
+ <KMDF_VERSION_MAJOR>1</KMDF_VERSION_MAJOR>
+ <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
+ <Platform Condition="'$(Platform)' == ''">Win32</Platform>
+ <SampleGuid>{C3E0B8D8-9BDC-4A02-9E56-773CB59546DB}</SampleGuid>
+ <WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
+ <ProjectName>driver</ProjectName>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>False</UseDebugLibraries>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ <DriverType>KMDF</DriverType>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ <OverrideDefaultRuntimeLibrary>true</OverrideDefaultRuntimeLibrary>
+ <SpectreMitigation>Spectre</SpectreMitigation>
+ </PropertyGroup>
+ <PropertyGroup Label="Configuration" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <TargetVersion>Windows10</TargetVersion>
+ <UseDebugLibraries>True</UseDebugLibraries>
+ <DriverTargetPlatform>Universal</DriverTargetPlatform>
+ <DriverType>KMDF</DriverType>
+ <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+ <ConfigurationType>Driver</ConfigurationType>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <PropertyGroup>
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <ImportGroup Label="Shared">
+ <Import Project="..\common\common.vcxitems" Label="Shared" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" />
+ </ImportGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <TargetName>rawaccel</TargetName>
+ <EnableInf2cat>false</EnableInf2cat>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <TargetName>rawaccel</TargetName>
+ <EnableInf2cat>false</EnableInf2cat>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <WarningLevel>Level4</WarningLevel>
+ <ExceptionHandling>
+ </ExceptionHandling>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ <AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories);$(SolutionDir)\external;$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>
+ </RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <CallingConvention>StdCall</CallingConvention>
+ <AssemblerOutput>AssemblyAndSourceCode</AssemblerOutput>
+ <ExpandAttributedSource>false</ExpandAttributedSource>
+ <UseUnicodeForAssemblerListing>false</UseUnicodeForAssemblerListing>
+ <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
+ <Optimization>MaxSpeed</Optimization>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
+ <WholeProgramOptimization>false</WholeProgramOptimization>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ControlFlowGuard>Guard</ControlFlowGuard>
+ <FloatingPointModel>Precise</FloatingPointModel>
+ </ClCompile>
+ <Link>
+ <LinkTimeCodeGeneration>
+ </LinkTimeCodeGeneration>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <IgnoreAllDefaultLibraries>
+ </IgnoreAllDefaultLibraries>
+ <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)wdmsec.lib;$(DDK_LIB_PATH)libcntpr.lib</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <TreatWarningAsError>false</TreatWarningAsError>
+ <WarningLevel>Level4</WarningLevel>
+ <ExceptionHandling>
+ </ExceptionHandling>
+ <AdditionalOptions>/Kernel %(AdditionalOptions)</AdditionalOptions>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ <AdditionalIncludeDirectories>$(IntDir);%(AdditionalIncludeDirectories);$(SolutionDir)\external;$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>
+ </RuntimeLibrary>
+ <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
+ <FloatingPointModel>Precise</FloatingPointModel>
+ <CallingConvention>StdCall</CallingConvention>
+ </ClCompile>
+ <Link>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ <IgnoreAllDefaultLibraries>
+ </IgnoreAllDefaultLibraries>
+ <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)wdmsec.lib;$(DDK_LIB_PATH)libcntpr.lib</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="driver.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <FilesToPackage Include="$(TargetPath)" Condition="'$(ConfigurationType)'=='Driver' or '$(ConfigurationType)'=='DynamicLibrary'" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Exclude="@(None)" Include="*.ico;*.cur;*.bmp;*.dlg;*.rct;*.gif;*.jpg;*.jpeg;*.wav;*.jpe;*.tiff;*.tif;*.png;*.rc2" />
+ <None Exclude="@(None)" Include="*.def;*.bat;*.hpj;*.asmx" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="driver.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+</Project> \ No newline at end of file
diff --git a/installer/installer.cpp b/installer/installer.cpp
new file mode 100644
index 0000000..ac426af
--- /dev/null
+++ b/installer/installer.cpp
@@ -0,0 +1,80 @@
+#include <iostream>
+
+#include <utility-install.hpp>
+
+void add_service(const fs::path& target) {
+ SC_HANDLE schSCManager = OpenSCManager(
+ NULL, // local computer
+ NULL, // ServicesActive database
+ SC_MANAGER_ALL_ACCESS // full access rights
+ );
+
+ if (schSCManager == NULL) throw std::runtime_error("OpenSCManager failed");
+
+ SC_HANDLE schService = CreateService(
+ schSCManager, // SCM database
+ DRIVER_NAME.c_str(), // name of service
+ DRIVER_NAME.c_str(), // service name to display
+ SERVICE_ALL_ACCESS, // desired access
+ SERVICE_KERNEL_DRIVER, // service type
+ SERVICE_DEMAND_START, // start type
+ SERVICE_ERROR_NORMAL, // error control type
+ target.c_str(), // path to service's binary
+ NULL, // no load ordering group
+ NULL, // no tag identifier
+ NULL, // no dependencies
+ NULL, // LocalSystem account
+ NULL // no password
+ );
+
+ if (schService) {
+ CloseServiceHandle(schService);
+ CloseServiceHandle(schSCManager);
+ return;
+ }
+
+ if (auto err = GetLastError(); err != ERROR_SERVICE_EXISTS) {
+ CloseServiceHandle(schSCManager);
+ throw std::runtime_error("CreateService failed");
+ }
+}
+
+int main() {
+ try {
+ fs::path source = fs::path(L"driver") / DRIVER_FILE_NAME;
+
+ if (!fs::exists(source)) {
+ throw std::runtime_error(source.generic_string() + " does not exist");
+ }
+
+ fs::path target = get_target_path();
+
+ add_service(target);
+
+ fs::path tmp = make_temp_path(target);
+
+ // schedule tmp to be deleted if rename target -> tmp is successful
+ if (MoveFileExW(target.c_str(), tmp.c_str(), MOVEFILE_REPLACE_EXISTING)) {
+ MoveFileExW(tmp.c_str(), NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
+ }
+
+ fs::copy_file(source, target, fs::copy_options::overwrite_existing);
+
+ modify_upper_filters([](std::vector<std::wstring>& filters) {
+ auto driver_pos = std::find(filters.begin(), filters.end(), DRIVER_NAME);
+
+ if (driver_pos != filters.end()) return;
+
+ auto mouclass_pos = std::find(filters.begin(), filters.end(), L"mouclass");
+ filters.insert(mouclass_pos, DRIVER_NAME);
+ });
+
+ std::cout << "Install complete, change will take effect after restart.\n";
+ }
+ catch (std::exception e) {
+ std::cerr << "Error: " << e.what() << '\n';
+ }
+
+ std::cout << "Press any key to close this window . . .\n";
+ _getwch();
+}
diff --git a/installer/installer.vcxproj b/installer/installer.vcxproj
new file mode 100644
index 0000000..45ef0c8
--- /dev/null
+++ b/installer/installer.vcxproj
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <ProjectGuid>{896950D1-520A-420A-B6B1-73014B92A68C}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>installer</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ <Import Project="..\common-install\common-install.vcxitems" Label="Shared" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <UACExecutionLevel>RequireAdministrator</UACExecutionLevel>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level4</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ <InlineFunctionExpansion>Default</InlineFunctionExpansion>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <UACExecutionLevel>RequireAdministrator</UACExecutionLevel>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="installer.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/rawaccel.sln b/rawaccel.sln
new file mode 100644
index 0000000..5be7e63
--- /dev/null
+++ b/rawaccel.sln
@@ -0,0 +1,56 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30104.148
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "driver", "driver\driver.vcxproj", "{60D6C942-AC20-4C05-A2BE-54B5C966534D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common\common.vcxitems", "{24B4226F-1461-408F-A1A4-1371C97153EA}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "installer", "installer\installer.vcxproj", "{896950D1-520A-420A-B6B1-73014B92A68C}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "console", "console\console.vcxproj", "{AB7B3759-B85F-4067-8935-FB4539B41869}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common-install", "common-install\common-install.vcxitems", "{058D66C6-D88B-4FDB-B0E4-0A6FE7483B95}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "uninstaller", "uninstaller\uninstaller.vcxproj", "{A4097FF6-A6F0-44E8-B8D0-538D0FB75936}"
+EndProject
+Global
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ common-install\common-install.vcxitems*{058d66c6-d88b-4fdb-b0e4-0a6fe7483b95}*SharedItemsImports = 9
+ common\common.vcxitems*{24b4226f-1461-408f-a1a4-1371c97153ea}*SharedItemsImports = 9
+ common\common.vcxitems*{60d6c942-ac20-4c05-a2be-54b5c966534d}*SharedItemsImports = 4
+ common-install\common-install.vcxitems*{896950d1-520a-420a-b6b1-73014b92a68c}*SharedItemsImports = 4
+ common-install\common-install.vcxitems*{a4097ff6-a6f0-44e8-b8d0-538d0fb75936}*SharedItemsImports = 4
+ common\common.vcxitems*{ab7b3759-b85f-4067-8935-fb4539b41869}*SharedItemsImports = 4
+ EndGlobalSection
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {60D6C942-AC20-4C05-A2BE-54B5C966534D}.Debug|x64.ActiveCfg = Debug|x64
+ {60D6C942-AC20-4C05-A2BE-54B5C966534D}.Debug|x64.Build.0 = Debug|x64
+ {60D6C942-AC20-4C05-A2BE-54B5C966534D}.Debug|x64.Deploy.0 = Debug|x64
+ {60D6C942-AC20-4C05-A2BE-54B5C966534D}.Release|x64.ActiveCfg = Release|x64
+ {60D6C942-AC20-4C05-A2BE-54B5C966534D}.Release|x64.Build.0 = Release|x64
+ {896950D1-520A-420A-B6B1-73014B92A68C}.Debug|x64.ActiveCfg = Debug|x64
+ {896950D1-520A-420A-B6B1-73014B92A68C}.Debug|x64.Build.0 = Debug|x64
+ {896950D1-520A-420A-B6B1-73014B92A68C}.Release|x64.ActiveCfg = Release|x64
+ {896950D1-520A-420A-B6B1-73014B92A68C}.Release|x64.Build.0 = Release|x64
+ {AB7B3759-B85F-4067-8935-FB4539B41869}.Debug|x64.ActiveCfg = Debug|x64
+ {AB7B3759-B85F-4067-8935-FB4539B41869}.Debug|x64.Build.0 = Debug|x64
+ {AB7B3759-B85F-4067-8935-FB4539B41869}.Release|x64.ActiveCfg = Release|x64
+ {AB7B3759-B85F-4067-8935-FB4539B41869}.Release|x64.Build.0 = Release|x64
+ {A4097FF6-A6F0-44E8-B8D0-538D0FB75936}.Debug|x64.ActiveCfg = Debug|x64
+ {A4097FF6-A6F0-44E8-B8D0-538D0FB75936}.Debug|x64.Build.0 = Debug|x64
+ {A4097FF6-A6F0-44E8-B8D0-538D0FB75936}.Release|x64.ActiveCfg = Release|x64
+ {A4097FF6-A6F0-44E8-B8D0-538D0FB75936}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {A3B5EC34-5340-4301-8BE9-33096EFEF77E}
+ EndGlobalSection
+EndGlobal
diff --git a/uninstaller/uninstaller.cpp b/uninstaller/uninstaller.cpp
new file mode 100644
index 0000000..4d02715
--- /dev/null
+++ b/uninstaller/uninstaller.cpp
@@ -0,0 +1,29 @@
+#include <iostream>
+
+#include <utility-install.hpp>
+
+int main() {
+ try {
+ modify_upper_filters([](std::vector<std::wstring>& filters) {
+ std::erase(filters, DRIVER_NAME);
+ });
+
+ fs::path target = get_target_path();
+ fs::path tmp = make_temp_path(target);
+
+ // schedule tmp to be deleted if rename target -> tmp is successful
+ if (MoveFileExW(target.c_str(), tmp.c_str(), MOVEFILE_REPLACE_EXISTING)) {
+ MoveFileExW(tmp.c_str(), NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
+ }
+ else { // tmp is in use and delete is already scheduled
+ if (fs::exists(target)) fs::remove(target);
+ }
+ std::cout << "Removal complete, change will take effect after restart.\n";
+ }
+ catch (std::exception e) {
+ std::cerr << "Error: " << e.what() << '\n';
+ }
+
+ std::cout << "Press any key to close this window . . .\n";
+ _getwch();
+}
diff --git a/uninstaller/uninstaller.vcxproj b/uninstaller/uninstaller.vcxproj
new file mode 100644
index 0000000..95a9dc8
--- /dev/null
+++ b/uninstaller/uninstaller.vcxproj
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>16.0</VCProjectVersion>
+ <Keyword>Win32Proj</Keyword>
+ <ProjectGuid>{a4097ff6-a6f0-44e8-b8d0-538d0fb75936}</ProjectGuid>
+ <RootNamespace>uninstaller</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v142</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ <Import Project="..\common-install\common-install.vcxitems" Label="Shared" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level4</WarningLevel>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <UACExecutionLevel>RequireAdministrator</UACExecutionLevel>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <UACExecutionLevel>RequireAdministrator</UACExecutionLevel>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="uninstaller.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file