From b2e6958bb475f98cb39c738a37c4671010c60aed Mon Sep 17 00:00:00 2001 From: "matthias@arch" Date: Tue, 1 Nov 2022 18:17:25 +0100 Subject: [PATCH] split into multiple files, moved to string dir --- src/string/conversion.cpp | 119 +++++++++++ src/string/conversion.hpp | 377 +++++++++++++++++++++++++++++++++ src/string/from_string.hpp | 123 +++++++++++ src/string/to_string.hpp | 418 +++++++++++++++++++++++++++++++++++++ src/string/utility.cpp | 59 ++++++ src/string/utility.hpp | 77 +++++++ 6 files changed, 1173 insertions(+) create mode 100644 src/string/conversion.cpp create mode 100644 src/string/conversion.hpp create mode 100644 src/string/from_string.hpp create mode 100644 src/string/to_string.hpp create mode 100644 src/string/utility.cpp create mode 100644 src/string/utility.hpp diff --git a/src/string/conversion.cpp b/src/string/conversion.cpp new file mode 100644 index 0000000..7cfc560 --- /dev/null +++ b/src/string/conversion.cpp @@ -0,0 +1,119 @@ +#include "conversion.hpp" +#include "../exceptions.hpp" +#include "../regex.hpp" + + +namespace gz { + +bool isInt(const std::string& s) { + return std::regex_match(s, re::types::intT); +} +bool isInt(const std::string_view& s) { + return re::regex_match(s, re::types::intT); +} +bool isUInt(const std::string& s) { + return std::regex_match(s, re::types::uintT); +} +bool isUInt(const std::string_view& s) { + return re::regex_match(s, re::types::uintT); +} +bool isFloal(const std::string& s) { + return std::regex_match(s, re::types::floatT); +} +bool isFloat(const std::string_view& s) { + return re::regex_match(s, re::types::floatT); +} + + +int getIntOr(const std::string& s, int fallback) noexcept { + try { + fallback = std::stoi(s); + } + catch (...) {} + return fallback; +} + + +unsigned int getUnsignedIntOr(const std::string& s, unsigned int fallback) noexcept { + try { + fallback = std::stoul(s); + } + catch (...) {} + return fallback; +} + + +double getDoubleOr(const std::string& s, double fallback) noexcept { + try { + fallback = std::stod(s); + } + catch (...) {} + return fallback; +} + + +float getFloatOr(const std::string& s, float fallback) noexcept { + try { + fallback = std::stof(s); + } + catch (...) {} + return fallback; +} + + +bool getBoolOr(const std::string& s, bool fallback) noexcept { + if (s == "true" or s == "True" or s == "1") { + fallback = true; + } + else if (s == "false" or s == "False" or s == "0") { + fallback = false; + } + return fallback; +} + + +std::string getStringOr(const std::string& s, const std::string& fallback) noexcept { + if (s == "") { return fallback; } + else { return s; } +} + + +// TODO: remove? +/* // int = 0, double = 1, string = 2 */ +/* std::variant getVariant(std::string value, GetVariantType type, bool bFallback, int iFallback, double dFallback, const char* sFallback) { */ +/* std::variant val = value; */ +/* /1* cout << "id-attr" << id << attr << "val: " << std::get(val); *1/ */ +/* switch (type) { */ +/* // to integer */ +/* case INT: */ +/* val = getIntOr(value, iFallback); */ +/* break; */ +/* case DOUBLE: */ +/* val = getDoubleOr(value, dFallback); */ +/* break; */ +/* case BOOL: */ +/* val = getBoolOr(value, bFallback); */ +/* break; */ +/* case STRING: */ +/* val = getStringOr(value, sFallback); */ +/* break; */ +/* } */ +/* return val; */ +/* } */ + +// +// CONVERT FROM STRING +// +template<> +bool fromString(const std::string& s) { + if (s == "true" or s == "True" or s == "1") { + return true; + } + else if (s == "false" or s == "False" or s == "0") { + return false; + } + throw InvalidArgument("s is not a bool: '" + s + "'", "fromString"); +} + + +} // namespace gz diff --git a/src/string/conversion.hpp b/src/string/conversion.hpp new file mode 100644 index 0000000..cc6a97a --- /dev/null +++ b/src/string/conversion.hpp @@ -0,0 +1,377 @@ +#pragma once + +#include "to_string.hpp" +#include "from_string.hpp" + +#include +#include + +#ifdef FORMAT +#include +#else +#include +#include +#endif + +namespace gz { + /** + * @name Functions that determine if s is a string representation of a certain type, using regex. + * @{ + */ + bool isInt(const std::string& s); + bool isInt(const std::string_view& s); + bool isUInt(const std::string& s); + bool isUInt(const std::string_view& s); + bool isFloat(const std::string& s); + bool isFloat(const std::string_view& s); + /** + * @} + */ + + + /** + * @name Convert to type or return fallback + * @todo Use string_views without constructing a std::string + * @{ + */ + int getIntOr(const std::string& s, int fallback=0) noexcept; + inline int getIntOr(const std::string_view& s, int fallback=0) noexcept { return getIntOr(std::string(s), fallback); } + + unsigned int getUnsignedIntOr(const std::string& s, unsigned int fallback=0) noexcept; + /* inline unsigned int getUnsignedIntOr(const std::string&& s, unsigned int fallback=0) { return getUnsignedIntOr(s, fallback); } */ + inline unsigned int getUnsignedIntOr(const std::string_view& s, unsigned int fallback=0) noexcept { return getUnsignedIntOr(std::string(s), fallback); } + + double getDoubleOr(const std::string& s, double fallback=0) noexcept; + /* inline double getDoubleOr(const std::string&& s, double fallback=0) { return getDoubleOr(s, fallback); } */ + inline double getDoubleOr(const std::string_view& s, double fallback=0) noexcept { return getDoubleOr(std::string(s), fallback); } + + float getFloatOr(const std::string& s, float fallback=0) noexcept; + /* inline float getFloatOr(const std::string&& s, float fallback=0) { return getDoubleOr(s, fallback); } */ + inline float getFloatOr(const std::string_view& s, float fallback=0) noexcept { return getDoubleOr(std::string(s), fallback); } + + bool getBoolOr(const std::string& s, bool fallback=false) noexcept; + /* inline bool getBoolOr(const std::string&& s, bool fallback=false) { return getBoolOr(s, fallback); } */ + inline bool getBoolOr(const std::string_view& s, bool fallback=false) noexcept { return getBoolOr(std::string(s), fallback); } + + /** + * @brief Returns the string or fallback if string is empty. + */ + std::string getStringOr(const std::string& s, const std::string& fallback="none") noexcept; + /// @} + + // + // HEX/OCT + // + /** + * @name Converting an integer to/from a hex/oct/bin string + * @todo Move to std::format, when P0645R10 is implemented in gcc libstd + * @note + * The fromHexString() and fromOctString() functions use `std::hex`, while fromBinString() uses `std::bitset`. Both can throw `std::invalid_argument` and the latter can also throw `std::out_of_range`. + * See https://en.cppreference.com/w/cpp/utility/bitset/bitset and https://en.cppreference.com/w/cpp/io/manip/hex + */ + /// @{ + /** + * @brief Convert an integer to hexdecimal string (prefixed with 0x) + * @param digits + * Minimum number of digits. Defaults to the amount of digits required to represent the largest possible number of type T. + * If digits is smaller than the needed amount to represent t, the needed amount is used. + */ + template + std::string toHexString(const T& t, char digits=sizeof(T)*2) { +#ifdef FORMAT + return std::format("{:#0" + std::to_string(digits) + "x}", t); +#endif + std::stringstream ss; + ss << "0x" << std::setfill('0') << std::setw(digits) << std::hex << t; + return std::string(ss.str()); + } + + /** + * @brief Convert a hexadecimal string (may be prefixed with 0x) to integer + */ + template + T fromHexString(const std::string& s) { + T t; + std::stringstream ss; + ss << std::hex << s; + ss >> t; + return t; + } + + /** + * @brief Convert an integer to octal string (prefixed with 0) + * @param digits + * Minimum number of digits. Defaults to the amount of digits required to represent the largest possible number of type T. + * If digits is smaller than the needed amount to represent t, the needed amount is used. + */ + template + std::string toOctString(const T& t, char digits=sizeof(T)*4) { +#ifdef FORMAT + // TODO test when possible + return std::format("{:#0" + std::to_string(digits) + "o}", t); +#endif + std::stringstream ss; + ss << "0" << std::setfill('0') << std::setw(digits) << std::oct << t; + return std::string(ss.str()); + } + + /** + * @brief Convert an octal string (may be prefixed with 0) to integer + */ + template + T fromOctString(const std::string& s) { + T t; + std::stringstream ss; + ss << std::oct << s; + ss >> t; + return t; + } + + /** + * @brief Convert an integer to binary string (prefixed with 0b) + */ + template + std::string toBinString(const T& t) { +#ifdef FORMAT + // TODO test when possible + return std::format("{:#0" + std::to_string(digits) + "b}", t); +#endif + return std::string("0b") + std::bitset(t).to_string(); + } + + /** + * @brief Convert binary string (may be prefixed with 0b) to integer + */ + template + T fromBinString(const std::string& s) { + if (s.starts_with("0b")) { + return static_cast(std::bitset(s, 2).to_ullong()); + } + else { + return static_cast(std::bitset(s).to_ullong()); + } + } + + + /** + * @overload + * @brief Construct a string where the elements from a forward range are in hexadecimal format + * @returns [ 0x0001, 0x0002, ... ] + */ + template + std::string toHexString(const T& t) { + std::string s = "[ "; + for (auto it = t.begin(); it != t.end(); it++) { + s += toHexString(*it) + ", "; + } + if (s.size() > 2) { + s.erase(s.size() - 2); + } + s += " ]"; + return s; + } + /** + * @overload + * @brief Construct a string where the elements from a forward range are in octal format + * @returns [ 00001, 00002, ... ] + */ + template + std::string toOctString(const T& t) { + std::string s = "[ "; + for (auto it = t.begin(); it != t.end(); it++) { + s += toOctString(*it) + ", "; + } + if (s.size() > 2) { + s.erase(s.size() - 2); + } + s += " ]"; + return s; + } + /** + * @overload + * @brief Construct a string where the elements from a forward range are in octal format + * @returns [ 0b0001, 0b0010, ... ] + */ + template + std::string toBinString(const T& t) { + std::string s = "[ "; + for (auto it = t.begin(); it != t.end(); it++) { + s += toBinString(*it) + ", "; + } + if (s.size() > 2) { + s.erase(s.size() - 2); + } + s += " ]"; + return s; + } + /// @} + + /// gz::toString and gz::fromString overloads exist + template + concept StringConvertible = ConvertibleToString and ConstructibleFromString; + +} // namespace gz + +/** + * @file + * @brief Contains utilities for type conversions to and from string + */ + +namespace gz { +/** + * @page string_conversion String conversion + * @section sc_about About + * This library provides utility for string conversion, from and to `std::string`. + * The string utility is split into 3 files: + * - @ref to_string.hpp "``" for converting types to strings + * - @ref from_string.hpp "``" for converting strings to other types + * - @ref conversion.hpp "``" for other string conversion features like from/to bin/oct/hex numbers, also includes the two previous files + * + * There also three important concepts, ConvertibleToString, ConstructibleFromString and @ref StringConvertible which are + * used in the @ref Log "logger" and the @ref SettingsManager "settings manager" to log and store types. + * + * If you want to use your own types with one of these, + * you can easily @ref sc_overloads "write your own string conversion" function for your custom types. + * + * @section sc_toString Converting to string + * You can use the `gz::toString()` function to turn certain types into strings. + * Concepts are used to determine the correct overload of the function. + * + * The concept ConvertibleToString is satisfied for types that can be converted to string + * using `gz::toString()`. + * @subsection sc_toStringImplemented Types convertible to string + * The toString() function is implemented for these types (non-exhaustive list of examples in braces) + * -# Any @ref util::Stringy "string-like type": eg. std::string, std::string_view { std::string, std::string_view } + * -# Any @ref util::CanConstructString "type that is accepted by the constructor of std::string()" { char, const char* } + * -# Any @ref util::WorksWithStdToString "type that works with std::to_string()" { int, double, bool ... } + * -# Any @ref util::HasToStringMember "type that has a `toString() const` or `to_string() const` member that returns a string" + * -# Any @ref util::ConvertibleToStringGlobal "type where a `std::string toString(const T&)` overload exists in global namespace + * -# Any of the following (the mentioned members have to satisfy one of 1-5) + * - Any @ref util::Vector2ConvertibleToString "type with t.x and t.y" + * - Any @ref util::Vector3ConvertibleToString "type with t.x, t.y, t.z" + * - Any @ref util::Vector4ConvertibleToString "type with t.x, t.y, t.z and t.w" + * - Any @ref util::Extent2DConvertibleToString "type with t.width, t.height" + * - Any @ref util::Extent3DConvertibleToString "type with t.width, t.height, t.depth" + * -# Any of 1-6, but "behind" a pointer + * -# Any @ref util::ForwardRangeConvertibleToString "type that has a forward_iterator" which references any one of 1-7 { std::vector, std::list } + * -# Any @ref util::PairConvertibleToString "type with t.first and t.second" members which satisfy one of 1-8 { std::pair } + * -# Any @ref util::MapConvertibleToString "type that has a forward_iterator" which references 9 { std::map } + * + * @section sc_fromString Construct from string + * You can use the `gz::fromString()` function to create certain types from a string. + * + * The concept ConstructibleFromString is satisfied for types that can be constructed from a string with `gz::fromString()`. + * + * When constructing something from a string, there is of course the problem that errors occur at runtime when + * the string is unsuitable for construction. + * In this case, the fromString functions from this library throw an exception (either InvalidArgument, or std::invalid_argument / std::out_of_range). + * + * @section sc_overloads Overloading the string conversion functions + * @subsection sc_ov_toString Overload for toString + * If you want your custom type to be convertible to string, you have different options. + * You can either add an `toString() const` member to your class + * or overload `std::string toString(const T&)` in global or gz namespace. + * Example for a class called `Custom`: + * @code + * std::string toString(const Custom& c) { + * std::string s; + * ... + * return s; + * } + * @endcode + * + * @subsection sc_ov_fromString Overload for fromString + * Writing an overload for fromString needs a little bit more boiler plate. + * Since fromString is a templated function, you need to declare it as such. + * The function declaration in this library uses a concept so that it is only + * declared for the types that are actully implemented. + * Example for a class called `Custom`: + * @code + * // declare as template, but only for Custom + * template T> + * Custom fromString(const std::string& s); + * + * // instantiation/definition, but only for Custom + * template<> + * Custom fromString(const std::string& s) { + * ... + * return Custom(...); + * }; + * @endcode + * + * @subsection sc_notes Notes + * You should write your function so that the output of one can be used as input of the other. + * In other words: + * @code + * Custom c1; + * Custom c2 = fromString(toString(c1)); + * assert(c1 == c2); + * @endcode + * + * SettingsManager expects the strings from toString() to be a single line. + * If it has multiple lines, it will not load it correctly when loading from a file. + * + * @subsection sc_troubleshooting Troubleshoting + * @subsubsection sc_t_1 My custom overloads are not recognized + * To satisfy the concept(s) ConvertibleToString/ConstructibleFromString/StringConvertible, any overload must be visible to the compiler when the concept is processed, + * so you have to declare it before including `string/to_string.hpp`/`string/from_string.hpp` (which may also be included by `string/conversion.hpp`, `settings_manager.hpp` and `log.hpp`). + * To ease troubleshooting the include order, `string/to_string.hpp`/`string_from_string.hpp` each define the macro `GZ_UTIL_STRING_CONCEPTS`, + * so you can place an assertion like this before declaring your overloads: + * @code + * #ifdef GZ_UTIL_STRING_CONCEPTS + * static_assert(false, "gz-util/string/conversion.hpp must not be included before this file!"); + * #endif + * @endcode + * @subsubsection sc_t_2 I want to use the toString/fromString functions from this library in my custom overloads + * Separate the declaration from the definition: + * @code + * // file.hpp + * // do not include any file from + * // declare your overloads: + * class Custom { + * std::string toString() const; + * } + * // or + * std::string toString(const Custom& c); + * + * template T> + * T fromString(const std::string&) + * + * // file.cpp + * #include "file.hpp" + * #include + * + * // define your functions + * std::string Custom::toString() const { + * ... + * } + * // or + * std::string toString(const Custom& c) { + * ... + * } + * + * template<> + * Custom fromString(const std::string& s) { + * ... + * } + * @endcode + * + * @subsubsection sc_t_3 I want to write the toString() for my Vector types myself + * Define the macro `GZ_TO_STRING_NO_VECTORS` before including ``. + * This disables all overloads for Vector and Extent classes. + * + * @todo Implement fromString for ranges that hold ConstructibleFromString types + * + * @section sc_int_types Converting integers to/from strings with different base + * The functions toHexString(), toOctString() and toBinString() can be used to get a + * string of an integers representation in 16 / 8 / 2 basis. + * + * The functions fromHexString(), fromOctString() and fromBinString() can be used to get an integer + * from a string representation of an integer in 16 / 8 / 2 basis. + * These function can throw std::invalid_argument and std::out_of_range. + * + * @note toHexString() and toOctString() do not work well with `(unsigned) char` (`(u)int8_t`). + * + * + */ +} // namespace gz diff --git a/src/string/from_string.hpp b/src/string/from_string.hpp new file mode 100644 index 0000000..b6b2515 --- /dev/null +++ b/src/string/from_string.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include "../concepts.hpp" +#include + +#define GZ_UTIL_STRING_CONCEPTS + +/** + * @brief Declaration of fromString in global namespace, so that concepts can use it + * @details + * This declaration only exists so that ::fromString can be used in concepts. + */ +template +T fromString(const std::string& s); + +namespace gz::util { + template + concept ConstructibleFromStringGlobal = requires(const std::string& s) { + { ::fromString(s) } -> std::same_as; + }; + + /** + * @brief toString is implemented for these types + */ + template + concept GetTypeFromStringImplemented = + std::same_as or + std::same_as or + std::same_as or + std::same_as or + std::same_as or + std::same_as or + std::same_as or + std::same_as or + std::same_as or + std::same_as; +} // namespace gz::util + +namespace gz { + /** + * @name Construct a type from a string + * @note + * The fromString() functions (except for the bool one) simply return std::stoXX. These can throw std::invalid_argument and std::out_of_range. + * See https://en.cppreference.com/w/cpp/string/basic_string/stol + */ + /// @{ + /** + * @brief Declaration of fromString, but only for the types for which it is implemented + */ + template + T fromString(const std::string& s); + + /// @returns std::stoi(s) + template<> inline int fromString(const std::string& s) { + return std::stoi(s); + } + /// @returns std::stol(s) + template<> inline long fromString(const std::string& s) { + return std::stol(s); + } + /// @returns std::stoll(s) + template<> inline long long fromString(const std::string& s) { + return std::stoll(s); + } + /// @returns std::stoul(s) + template<> inline unsigned int fromString(const std::string& s) { + return std::stoul(s); + } + /// @returns std::stoul(s) + template<> inline unsigned long fromString(const std::string& s) { + return std::stoul(s); + } + /// @returns std::stoull(s) + template<> inline unsigned long long fromString(const std::string& s) { + return std::stoull(s); + } + /// @returns std::stof(s) + template<> inline float fromString(const std::string& s) { + return std::stof(s); + } + /// @returns std::stod(s) + template<> inline double fromString(const std::string& s) { + return std::stod(s); + } + /// @returns std::stold(s) + template<> inline long double fromString(const std::string& s) { + return std::stold(s); + } + /** + * @brief Convert a string to bool + * @details + * - returns true if s = "true" or "True" or "1" + * - returns false if s = "false" or "False" or "0" + * - throws InvalidArgument otherwise + */ + template<> bool fromString(const std::string& s); + + /** + * @overload + * @brief Construct T from a string using a fromString that is declared in global namespace + */ + template + inline T fromString(const std::string& s) { + return ::fromString(s); + } + /// @} + + + /** + * @brief Any type where fromString(string) exists and returns T + * @note The function only has to exist, it does not have to be noexcept! + */ + template + concept ConstructibleFromString = requires(const std::string& s) { + { fromString(s) } -> std::same_as; + }; +} // namespace gz + +/** + * @file + * @brief Contains functions to construct types from string + * @todo Make these functions usable with std::string_view + */ diff --git a/src/string/to_string.hpp b/src/string/to_string.hpp new file mode 100644 index 0000000..cd8020e --- /dev/null +++ b/src/string/to_string.hpp @@ -0,0 +1,418 @@ +#pragma once + +#include "../concepts.hpp" + +#include +#include +#include + +#define GZ_UTIL_STRING_CONCEPTS + +/** + * @brief Declaration of toString in global namespace, so that concepts can use it. + */ +template +std::string toString(const T& s); + +namespace gz::util { + /** + * @name 1) concepts for elementary types and custom toStrings() + * @details + * Definition order matters, the earlier the concept, the "stronger" because later concepts must not also satisfy the previous ones. + * @{ + */ + /// same as std::string, std::string_view or const char* + template + concept Stringy = util::IsInPack; + + /// can construct std::string from T + template + concept CanConstructString = + !Stringy && + requires(const T& t) { + { std::string(t) } -> std::same_as; + }; + + /// has .toString() const member + template + concept HasToStringMember = + !Stringy && + !CanConstructString && + requires(const T& t) { { t.toString() }-> Stringy; }; + + /// has .to_string() const member + template + concept HasToStringMember2 = + !Stringy && + !CanConstructString && + !HasToStringMember && + requires(const T& t) { { t.to_string() }-> Stringy; }; + + /// works with std::to_string() + template + concept WorksWithStdToString = + !Stringy && + !CanConstructString && + !HasToStringMember && + !HasToStringMember2 && + requires(const T& t) { { std::to_string(t) } -> Stringy; }; + + /// toString function overload exists in global namespace and returns std::string + template + concept ConvertibleToStringGlobal = + !Stringy && + !CanConstructString && + !HasToStringMember && + !HasToStringMember2 && + !WorksWithStdToString && + requires(const T& t) { + { ::toString(t) } -> std::same_as; + }; + + template + concept _ToStringBasicNoPtr = + Stringy || + CanConstructString || + HasToStringMember || + HasToStringMember2 || + WorksWithStdToString || + ConvertibleToStringGlobal; + +#ifndef GZ_TO_STRING_NO_VECTORS + // VECTOR + /// Type having string-convertible x, y members and sizeof(T) == 2 * sizeof(x) + template + concept Vector2ConvertibleToString = !_ToStringBasicNoPtr && + requires(T t) { + { t.x } -> _ToStringBasicNoPtr; + { t.y } -> _ToStringBasicNoPtr; + requires sizeof(t.x) * 2 == sizeof(T); + }; + /// Type having string-convertible x, y, z members and sizeof(T) == 3 * sizeof(x) + template + concept Vector3ConvertibleToString = !_ToStringBasicNoPtr && + requires(T t) { + { t.x } -> _ToStringBasicNoPtr; + { t.y } -> _ToStringBasicNoPtr; + { t.z } -> _ToStringBasicNoPtr; + requires sizeof(t.x) * 3 == sizeof(T); + }; + /// Type having string-convertible x, y, z, w members and sizeof(T) == 4 * sizeof(x) + template + concept Vector4ConvertibleToString = !_ToStringBasicNoPtr && + requires(T t) { + { t.x } -> _ToStringBasicNoPtr; + { t.y } -> _ToStringBasicNoPtr; + { t.z } -> _ToStringBasicNoPtr; + { t.w } -> _ToStringBasicNoPtr; + requires sizeof(t.x) * 4 == sizeof(T); + }; + + /// Type having string-convertible width, height members and sizeof(T) == 2 * sizeof(width) + template + concept Extent2DConvertibleToString = !_ToStringBasicNoPtr && + requires(T t) { + { t.width } -> _ToStringBasicNoPtr; + { t.height } -> _ToStringBasicNoPtr; + requires sizeof(t.width) * 2 == sizeof(T); + }; + /// Type having string-convertible width, height, depth members and sizeof(T) == 3 * sizeof(width) + template + concept Extent3DConvertibleToString = !_ToStringBasicNoPtr && + requires(T t) { + { t.width } -> _ToStringBasicNoPtr; + { t.height } -> _ToStringBasicNoPtr; + { t.depth } -> _ToStringBasicNoPtr; + requires sizeof(t.width) * 3 == sizeof(T); + }; +#endif + + template + concept _ToStringBasicOrVectorNoPtr = + _ToStringBasicNoPtr +#ifndef GZ_TO_STRING_NO_VECTORS + || Vector2ConvertibleToString || + Vector3ConvertibleToString || + Vector4ConvertibleToString || + Extent2DConvertibleToString || + Extent3DConvertibleToString +#endif + ; + + template + concept PtrToToStringBasicOrVector = + !_ToStringBasicOrVectorNoPtr && + _ToStringBasicOrVectorNoPtr>; + /// @} +} // namespace gz::util + + +namespace gz { + /** + * @name 1) Converting a type to string + * @details + * All toString functions for types that satisfy util::ToStringBasicNoPtr or util::PtrToToStringBasicType + * @{ + */ + /** + * @brief Return the string + * @returns static_cast(t) + */ + template + inline std::string toString(const T& t) { + return static_cast(t); + } + + /** + * @brief Construct a string from a string like-type + * @returns std::string(t) + */ + template + inline std::string toString(const T& t) { + return std::string(t); + } + + /** + * @overload + * @brief Construct a string from a type having a toString() const member function + * @returns t.toString() + */ + template + inline std::string toString(const T& t) { + return t.toString(); + } + + /** + * @overload + * @brief Construct a string from a type having a to_string() const member function + * @returns t.to_string() + */ + template + inline std::string toString(const T& t) { + return t.to_string(); + } + + /** + * @overload + * @brief Construct a string from a number + * @returns std::to_string(t) + */ + template + inline std::string toString(const T& t) { + return std::to_string(t); + } + + /** + * @brief Construct a string from a boolean + * @details + * Unlike std::to_string(bool), which returns 0 or 1, this function returns "true" or "false" + * @returns "true" or "false" + */ + template T> + inline std::string toString(const T& b) { + return b ? "true" : "false"; + } + + /** + * @overload + * @brief Construct a string from a type that has toString declared in global namespace + */ + template + inline std::string toString(const T& t) { + return ::toString(t); + } + +#ifndef GZ_TO_STRING_NO_VECTORS + /** + * @overload + * @brief Construct a string from a vector with x, y members + * @returns ( x, y ) + */ + template + inline std::string toString(const T& t) { + std::string s = "( "; + s += toString(t.x) + ", "; + s += toString(t.y) + " )"; + return s; + } + + /** + * @overload + * @brief Construct a string from a vector with x, y members + * @returns ( x, y, z ) + */ + template + inline std::string toString(const T& t) { + std::string s = "( "; + s += toString(t.x) + ", "; + s += toString(t.y) + ", "; + s += toString(t.z) + " )"; + return s; + } + + /** + * @overload + * @brief Construct a string from a vector with x, y, z, w members + * @returns ( x, y, z, w ) + */ + template + inline std::string toString(const T& t) { + std::string s = "( "; + s += toString(t.x) + ", "; + s += toString(t.y) + ", "; + s += toString(t.z) + ", "; + s += toString(t.w) + " )"; + return s; + } + + /** + * @overload + * @brief Construct a string from a type having width and height members + * @returns ( width, height ) + */ + template + inline std::string toString(const T& t) { + std::string s = "( "; + s += toString(t.width) + ", "; + s += toString(t.height) + " ) "; + return s; + } + + /** + * @overload + * @brief Construct a string from a type having width and height members + * @returns ( width, height, depth ) + */ + template + inline std::string toString(const T& t) { + std::string s = "( "; + s += toString(t.width) + ", "; + s += toString(t.height) + ", "; + s += toString(t.depth) + " ) "; + return s; + } +#endif + + /** + * @overload + * @brief Construct a string from the element the pointer points at + * @returns *t + */ + template + inline std::string toString(const T& t) { + return toString(*t); + } +} // namespace gz + + + +namespace gz::util { + /** + * @name 2) concepts for containers that hold toString() compatible types + * @{ + */ + /// Everything that is convertible to string with toString at this point + template + concept _ConvertibleToString = + requires (const T& t) { + { gz::toString(t) } -> std::same_as; + }; + + + // CONTAINER + /// Forward range having string-convertible elements + template + concept ForwardRangeConvertibleToString = !_ConvertibleToString and std::ranges::forward_range and _ConvertibleToString>; + + template + concept _TypeOrForwardRangeConvertibleToString = _ConvertibleToString || ForwardRangeConvertibleToString; + + /// Pair having string-convertible elements + template + concept PairConvertibleToString = !ForwardRangeConvertibleToString and requires(const T& p) { + requires _TypeOrForwardRangeConvertibleToString; + requires _TypeOrForwardRangeConvertibleToString; + }; + + /// Container having string-convertible pairs + template + concept MapConvertibleToString = !PairConvertibleToString and !_TypeOrForwardRangeConvertibleToString and + std::ranges::forward_range and PairConvertibleToString>; + + template + concept _ContainerTypeConvertibleToString = PairConvertibleToString || ForwardRangeConvertibleToString || MapConvertibleToString; + /// @} + +} // namespace gz::util + + + +namespace gz { + /** + * @name 2) Converting a container to string + * @details + * All toString functions for types that satisfy util::_ContainerTypeConvertibleToString + * @{ + */ + /** + * @overload + * @brief Construct a string from a forward range + * @returns [ x1, x2, ... ] + */ + template + std::string toString(const T& t) { + std::string s = "[ "; + for (auto it = t.begin(); it != t.end(); it++) { + s += toString(*it) + ", "; + } + if (s.size() > 2) { + s.erase(s.size() - 2); + } + s += " ]"; + return s; + } + + /** + * @overload + * @brief Construct a string from a pair + * @returns ( first, second ) + */ + template + inline std::string toString(const T& t) { + return "( " + toString(t.first) + ", " + toString(t.second) + " )"; + } + + /** + * @overload + * @brief Construct a string from a forward range holding a pair, eg a map + * @returns { first: second, first: second, ... } + */ + template + std::string toString(const T& t) { + std::string s = "{ "; + for (const auto& [k, v] : t) { + s += toString(k) + ": "; + s += toString(v) + ", "; + } + if (s.size() > 2) { + s.erase(s.size() - 2); + } + s += " }"; + return s; + } + /// @} + + /** + * @brief Any type where gz::toString(t) exists + */ + template + concept ConvertibleToString = + requires (const T& t) { + { gz::toString(t) } -> std::same_as; + }; +} // namespace gz + +/** + * @file + * @brief Contains functions to convert types to string + */ diff --git a/src/string/utility.cpp b/src/string/utility.cpp new file mode 100644 index 0000000..e387fe2 --- /dev/null +++ b/src/string/utility.cpp @@ -0,0 +1,59 @@ +#include "utility.hpp" + +#include +#include + + +namespace gz::util { + +std::vector splitStringInVector(std::string& s, char separator) { + // remove linebreaks from the end + if (*(s.end()) == '\n') { s.erase(s.end()); } + + /* std::unique_ptr> params (new std::unordered_map); */ + std::vector v; + std::stringstream ss(s); + std::string temp; + + while (std::getline(ss, temp, separator)) { + // if has "=": store latter part in vector + if (temp.find("=") != std::string::npos) { + int eqPos = temp.find("="); + v.emplace_back(temp.substr(eqPos + 1, temp.length())); + } + else { + v.emplace_back(temp); + } + } + return v; +} + + +template +std::vector splitStringInVector(const std::string_view& s, const std::string& separator, bool skipEmptyStrings) { + std::vector v; + + std::string::size_type posStart = 0; + std::string::size_type posEnd = s.find(separator, posStart); + while (posEnd != std::string::npos) { + if (!(skipEmptyStrings and posStart == posEnd)) { + v.emplace_back(T(s.begin() + posStart, s.begin() + posEnd)); + } + posStart = posEnd + 1; + posEnd = s.find(separator, posStart); + } + // last element + if (posStart < s.size()) { + v.emplace_back(T(s.begin() + posStart, s.end())); + } + // if last char is separator, append empty string + else if (!skipEmptyStrings and posStart == s.size()) { + v.emplace_back(T(s.begin(), s.begin())); + } + return v; +} + +template std::vector splitStringInVector(const std::string_view&, const std::string&, bool); +template std::vector splitStringInVector(const std::string_view&, const std::string&, bool); + +} // namespace gz::util diff --git a/src/string/utility.hpp b/src/string/utility.hpp new file mode 100644 index 0000000..296c5d9 --- /dev/null +++ b/src/string/utility.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include + +namespace gz::util { + template + concept SplitStringInVectorImplemented = std::same_as || std::same_as; + + /** + * @brief Split a string at a separator into a vector + * @details + * Splits a string at a separator.\n + * Two templates exist, one where the vector will hold std::string_views into s + * and one where it will hold std::strings. + * Behavior: + * - the elements of the returned vector will not contain the separator. + * - if the separator is not in s, the vector will contain s as only element. + * - empty elements will occur if: + * - separator is the first char + * - separator is the last char + * - two or more separator appear after each other\n + * You can turn this behaviour off by setting skipEmptyStrings to true + * @note + * The string_view variant has the advantage of being faster, however:\n + * The string_views will reference the original string, so it must not be changed or destroyed + * as long as the string_views are used! + */ + template + std::vector splitStringInVector(const std::string_view& s, const std::string& separator, bool skipEmptyStrings=false); + + /** + * @name Map with string type as key, works with strings, string_view and char* + * @{ + */ + struct string_hash + { + using hash_type = std::hash; + using is_transparent = void; + + size_t operator()(const char* str) const { return hash_type{}(str); } + size_t operator()(std::string_view str) const { return hash_type{}(str); } + size_t operator()(std::string const& str) const { return hash_type{}(str); } + }; + /** + * @brief A unordered_map where you can use string_views to access elements + * @details + * To retrieve an element using a string view, you need to do this: + * @code + * std::string_view sv = ...; + * gz::util::unordered_string_map smap = { ... }; + * ... + * if (smap.contains(sv)) { + * return smap.find(sv)->second; + * } + * @endcode + * The at() member and [] operator do not work with the string_view. + */ + template + using unordered_string_map = std::unordered_map>; + /** + * @brief same as unordered_string_map, but using std::map instead of std::unordered_map + */ + template + using string_map = std::map>; + /** + * @} + */ + +} // namespace gz::util + +/** + * @file + * @brief Contains utility for strings + */