diff --git a/CHANGELOG.md b/CHANGELOG.md index 8766c4b80..64d4a8fe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - Added 200 Bus Synthetic Illinois Case - Added node objects to `PowerElectronics` module & updated all examples to make use of them. - Separated internal and external residuals of `PowerElectronics` models. +- Added `CliArgs` class for better management of command-line options. ## v0.1 diff --git a/GridKit/Utilities/CMakeLists.txt b/GridKit/Utilities/CMakeLists.txt index afc0badf2..d9f52f088 100644 --- a/GridKit/Utilities/CMakeLists.txt +++ b/GridKit/Utilities/CMakeLists.txt @@ -6,6 +6,7 @@ target_include_directories(Utilities INTERFACE add_library(GridKit::Utilities ALIAS Utilities) add_subdirectory(Logger) +add_subdirectory(CliArgs) add_subdirectory(CliOptions) install(TARGETS Utilities EXPORT gridkit-targets) diff --git a/GridKit/Utilities/CliArgs/ArgValue.hpp b/GridKit/Utilities/CliArgs/ArgValue.hpp new file mode 100644 index 000000000..cd3274e3c --- /dev/null +++ b/GridKit/Utilities/CliArgs/ArgValue.hpp @@ -0,0 +1,127 @@ +/** + * @file ArgValue.hpp + * @author Philip Fackler (facklerpw@ornl.gov) + */ + +#pragma once + +#include +#include +#include + +#include + +namespace GridKit +{ + namespace Utilities + { + + /** + * @brief Represents a single value of arbitrary type + * + * The value is internally represented as a std::string that is parsed when + * requested as a specific type + */ + class ArgValue + { + /** + * @brief Check that T != ArgValue + * + */ + template + static constexpr bool notAnArgValue = + !std::is_same_v, ArgValue>; + + /** + * @brief Check that T != std::initializer_list + */ + template + static constexpr bool notAnArgValueList = + !std::is_same_v, std::initializer_list>; + + /** + * @brief Check that T is not an ArgValue or list of ArgValues + */ + template + static constexpr bool notArgValue = notAnArgValue && notAnArgValueList; + + public: + /** + * @brief Default construction results in empty value + * + * @note the SFINAE parameter is used to work around an issue with libc++ + */ + ArgValue() = default; + + /** + * @brief Copy constructor + */ + ArgValue(const ArgValue&) = default; + + /** + * @brief Move constructor + */ + ArgValue(ArgValue&&) = default; + + /** + * @brief Construct from any value + */ + template + ArgValue(T&& val, std::enable_if_t, int> = 0) + : value_((std::stringstream() << val).str()) + { + } + + ArgValue& operator=(const ArgValue&) = default; + ArgValue& operator=(ArgValue&&) = default; + + /** + * @brief Assign any value + */ + template + ArgValue& operator=(T&& val) + { + value_ = (std::stringstream() << val).str(); + return *this; + } + + /** + * @brief Check if no value is contained + */ + bool empty() const + { + return value_.empty(); + } + + /** + * @brief Get string representation + */ + const std::string& get() const + { + return value_; + } + + /** + * @brief Get string representation + */ + const std::string& operator()() const + { + return get(); + } + + /** + * @brief Get value of specific expected type + */ + template + T as() const + { + return GridKit::Utilities::parse(value_); + } + + private: + /// Internal representation + std::string value_{}; + }; + + } // namespace Utilities +} // namespace GridKit diff --git a/GridKit/Utilities/CliArgs/ArgVector.hpp b/GridKit/Utilities/CliArgs/ArgVector.hpp new file mode 100644 index 000000000..ef2617c81 --- /dev/null +++ b/GridKit/Utilities/CliArgs/ArgVector.hpp @@ -0,0 +1,135 @@ +/** + * @file ArgVector.hpp + * @author Philip Fackler (facklerpw@ornl.gov) + */ + +#pragma once + +#include +#include +#include + +#include + +namespace GridKit +{ + namespace Utilities + { + + // Forward declaration + struct CliArgsImpl; + + /** + * @brief Represents a set of 0 or more values associated with a given + * command-line option + * + * This class allows the internal data to be interpreted as a discrete set + * of values (std::array) of a given type or as a single value, hopefully + * making it more intuitive for users. For example... + * + * If an ArgVector v is expected to hold one value of type `double`, that + * value can be extracted with `auto r = v.as();`. This means `r` + * will be a `double` value assigned with the contents of the first element + * of `v`. + * + * If an ArgVector v is expected to hold two values of type `int`, on the + * other hand, the values can be extracted as a `std::array` using + * `as()`. Using `std::array` allows structured bindings for the + * user: `auto [a, b] = v.as();` + */ + class ArgVector + { + public: + /** + * @brief Default is empty (no values) + */ + ArgVector() = default; + + /** + * @brief Construct with a single value + */ + template + ArgVector(T&& val) + : vec_{val} + { + } + + ArgVector(std::initializer_list vals) + : vec_{vals} + { + } + + /** + * @brief Interpret as `N` values of type `T` + */ + template + std::array as() const + { + assert(vec_.size() == N); + std::array ret; + for (std::size_t i = 0; i < N; ++i) + { + ret[i] = vec_[i].as(); + } + return ret; + } + + /** + * @brief Interpret as `N` values of type `std::string` + */ + template + decltype(auto) as() const + { + return as(); + } + + /** + * @brief Interpret as single value of type `T` + */ + template + decltype(auto) as() const + { + return vec_[0].as(); + } + + /** + * @brief Interpret as single `std::string` value + */ + const std::string& operator()() const + { + return vec_[0].get(); + } + + /** + * @brief Get one of the contained `ArgValue` objects + */ + const ArgValue& operator[](std::size_t i) const + { + return vec_[i]; + } + + /** + * @brief Check for existence of values + */ + bool empty() const + { + return vec_.empty(); + } + + /** + * @brief Get number of `ArgValue` objects + */ + std::size_t size() const + { + return vec_.size(); + } + + private: + /// Internal set of values + std::vector vec_; + + friend struct CliArgsImpl; + }; + + } // namespace Utilities +} // namespace GridKit diff --git a/GridKit/Utilities/CliArgs/CMakeLists.txt b/GridKit/Utilities/CliArgs/CMakeLists.txt new file mode 100644 index 000000000..5e4d70d49 --- /dev/null +++ b/GridKit/Utilities/CliArgs/CMakeLists.txt @@ -0,0 +1,13 @@ +gridkit_add_library(utilities_cli_args + SOURCES + CliArgs.cpp + HEADERS + CliArgs.hpp + ArgValue.hpp + ArgVector.hpp + Option.hpp + LINK_LIBRARIES + PUBLIC GridKit::utilities_logger + ) + +target_link_libraries(Utilities INTERFACE GridKit::utilities_cli_args) diff --git a/GridKit/Utilities/CliArgs/CliArgs.cpp b/GridKit/Utilities/CliArgs/CliArgs.cpp new file mode 100644 index 000000000..77a109e8f --- /dev/null +++ b/GridKit/Utilities/CliArgs/CliArgs.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "CliArgsImpl.hpp" + +namespace GridKit +{ + namespace Utilities + { + CliArgs::CliArgs(std::initializer_list