refactor: start to refactor project
This commit is contained in:
348
src/YYCCLegacy/ArgParser.cpp
Normal file
348
src/YYCCLegacy/ArgParser.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
#include "ArgParser.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "ConsoleHelper.hpp"
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <processenv.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::ArgParser {
|
||||
|
||||
#pragma region Arguments List
|
||||
|
||||
ArgumentList ArgumentList::CreateFromStd(int argc, char* argv[]) {
|
||||
std::vector<yycc_u8string> args;
|
||||
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
|
||||
if (argv[i] != nullptr)
|
||||
args.emplace_back(yycc_u8string(YYCC::EncodingHelper::ToUTF8(argv[i])));
|
||||
}
|
||||
return ArgumentList(std::move(args));
|
||||
}
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
ArgumentList ArgumentList::CreateFromWin32() {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw
|
||||
|
||||
// prepare list
|
||||
std::vector<yycc_u8string> args;
|
||||
|
||||
// try fetching from Win32 functions
|
||||
int argc;
|
||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (argv != NULL) {
|
||||
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
|
||||
if (argv[i] != nullptr) {
|
||||
yycc_u8string u8_argv;
|
||||
if (YYCC::EncodingHelper::WcharToUTF8(argv[i], u8_argv))
|
||||
args.emplace_back(std::move(u8_argv));
|
||||
}
|
||||
}
|
||||
}
|
||||
LocalFree(argv);
|
||||
|
||||
// return result
|
||||
return ArgumentList(std::move(args));
|
||||
}
|
||||
#endif
|
||||
|
||||
ArgumentList::ArgumentList(std::vector<yycc_u8string>&& arguments) :
|
||||
m_Arguments(arguments), m_ArgumentsCursor(0u) {}
|
||||
|
||||
void ArgumentList::Prev() {
|
||||
if (m_ArgumentsCursor == 0u)
|
||||
throw std::runtime_error("attempt to move on the head of iterator.");
|
||||
--m_ArgumentsCursor;
|
||||
}
|
||||
|
||||
void ArgumentList::Next() {
|
||||
if (IsEOF()) throw std::runtime_error("attempt to move on the tail of iterator.");
|
||||
++m_ArgumentsCursor;
|
||||
}
|
||||
|
||||
const yycc_u8string& ArgumentList::Argument() const {
|
||||
if (IsEOF()) throw std::runtime_error("attempt to get data on the tail of iterator.");
|
||||
return m_Arguments[m_ArgumentsCursor];
|
||||
}
|
||||
|
||||
bool ArgumentList::IsSwitch(bool* is_long_name, yycc_u8string* long_name, yycc_char8_t* short_name) const {
|
||||
// check eof first
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
// check long name first, then check short name
|
||||
if (IsLongNameSwitch(long_name)) {
|
||||
if (is_long_name != nullptr) *is_long_name = true;
|
||||
return true;
|
||||
}
|
||||
if (IsShortNameSwitch(short_name)) {
|
||||
if (is_long_name != nullptr) *is_long_name = false;
|
||||
return true;
|
||||
}
|
||||
// not matched
|
||||
return false;
|
||||
}
|
||||
bool ArgumentList::IsLongNameSwitch(yycc_u8string* name_part) const {
|
||||
// fetch current parameter
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
|
||||
// find double slash
|
||||
if (param.find(AbstractArgument::DOUBLE_DASH) != 0u) return false;
|
||||
// check gotten long name
|
||||
yycc_u8string_view long_name = yycc_u8string_view(param).substr(2u);
|
||||
if (!AbstractArgument::IsLegalLongName(long_name)) return false;
|
||||
// set checked long name if possible and return
|
||||
if (name_part != nullptr)
|
||||
*name_part = long_name;
|
||||
return true;
|
||||
}
|
||||
bool ArgumentList::IsShortNameSwitch(yycc_char8_t* name_part) const {
|
||||
// fetch current parameter
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
|
||||
// if the length is not exactly equal to 2,
|
||||
// or it not starts with dash,
|
||||
// it is impossible a short name
|
||||
if (param.size() != 2u || param[0] != AbstractArgument::DASH) return false;
|
||||
// check gotten short name
|
||||
yycc_char8_t short_name = param[1];
|
||||
if (!AbstractArgument::IsLegalShortName(short_name)) return false;
|
||||
// set checked short name if possible and return
|
||||
if (name_part != nullptr)
|
||||
*name_part = short_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArgumentList::IsValue(yycc_u8string* val) const {
|
||||
bool is_value = !IsSwitch();
|
||||
if (is_value && val != nullptr)
|
||||
*val = m_Arguments[m_ArgumentsCursor];
|
||||
return is_value;
|
||||
}
|
||||
|
||||
bool ArgumentList::IsEOF() const { return m_ArgumentsCursor >= m_Arguments.size(); }
|
||||
|
||||
void ArgumentList::Reset() { m_ArgumentsCursor = 0u; }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Abstract Argument
|
||||
|
||||
const yycc_u8string AbstractArgument::DOUBLE_DASH = YYCC_U8("--");
|
||||
const yycc_char8_t AbstractArgument::DASH = YYCC_U8_CHAR('-');
|
||||
const yycc_char8_t AbstractArgument::NO_SHORT_NAME = YYCC_U8_CHAR(0);
|
||||
const yycc_char8_t AbstractArgument::MIN_SHORT_NAME = YYCC_U8_CHAR('!');
|
||||
const yycc_char8_t AbstractArgument::MAX_SHORT_NAME = YYCC_U8_CHAR('~');
|
||||
|
||||
bool AbstractArgument::IsLegalShortName(yycc_char8_t short_name) {
|
||||
if (short_name == AbstractArgument::DASH || // dash is not allowed
|
||||
short_name < AbstractArgument::MIN_SHORT_NAME || short_name > AbstractArgument::MAX_SHORT_NAME) { // non-display ASCII chars are not allowed
|
||||
return false;
|
||||
}
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
bool AbstractArgument::IsLegalLongName(const yycc_u8string_view& long_name) {
|
||||
// empty is not allowed
|
||||
if (long_name.empty()) return false;
|
||||
// non-display ASCII chars are not allowed
|
||||
for (const auto& val : long_name) {
|
||||
if (val < AbstractArgument::MIN_SHORT_NAME || val > AbstractArgument::MAX_SHORT_NAME)
|
||||
return false;
|
||||
}
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
|
||||
AbstractArgument::AbstractArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description, const yycc_char8_t* argument_example,
|
||||
bool is_optional) :
|
||||
m_LongName(), m_ShortName(AbstractArgument::NO_SHORT_NAME), m_Description(), m_ArgumentExample(),
|
||||
m_IsOptional(is_optional), m_IsCaptured(false) {
|
||||
|
||||
// try to assign long name and check it
|
||||
if (long_name != nullptr) {
|
||||
m_LongName = long_name;
|
||||
if (!AbstractArgument::IsLegalLongName(m_LongName))
|
||||
throw std::invalid_argument("Given long name is invalid.");
|
||||
}
|
||||
// try to assign short name and check it
|
||||
if (short_name != AbstractArgument::NO_SHORT_NAME) {
|
||||
m_ShortName = short_name;
|
||||
if (!AbstractArgument::IsLegalShortName(m_ShortName))
|
||||
throw std::invalid_argument("Given short name is invalid.");
|
||||
}
|
||||
// check short name and long name existence
|
||||
if (!HasShortName() && !HasLongName())
|
||||
throw std::invalid_argument("you must specify an one of long name or short name.");
|
||||
|
||||
// try to assign other string values
|
||||
if (description != nullptr) m_Description = description;
|
||||
if (argument_example != nullptr) m_ArgumentExample = argument_example;
|
||||
}
|
||||
|
||||
AbstractArgument::~AbstractArgument() {}
|
||||
|
||||
bool AbstractArgument::HasLongName() const { return !m_LongName.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetLongName() const { return m_LongName; }
|
||||
bool AbstractArgument::HasShortName() const { return m_ShortName != NO_SHORT_NAME; }
|
||||
yycc_char8_t AbstractArgument::GetShortName() const { return m_ShortName; }
|
||||
bool AbstractArgument::HasDescription() const { return !m_Description.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetDescription() const { return m_Description; }
|
||||
bool AbstractArgument::HasArgumentExample() const { return !m_ArgumentExample.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetArgumentExample() const { return m_ArgumentExample; }
|
||||
bool AbstractArgument::IsOptional() const { return m_IsOptional; }
|
||||
|
||||
bool AbstractArgument::IsCaptured() const { return m_IsCaptured; }
|
||||
void AbstractArgument::SetCaptured(bool is_captured) { m_IsCaptured = is_captured; }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Option Context
|
||||
|
||||
OptionContext::OptionContext(
|
||||
const yycc_char8_t* summary, const yycc_char8_t* description,
|
||||
std::initializer_list<AbstractArgument*> arguments) :
|
||||
m_Summary(), m_Description() {
|
||||
// assign summary and description
|
||||
if (summary != nullptr) m_Summary = summary;
|
||||
if (description != nullptr) m_Description = description;
|
||||
|
||||
// insert argument list and check them
|
||||
for (auto* arg : arguments) {
|
||||
// insert into long name map if necessary
|
||||
if (arg->HasLongName()) {
|
||||
auto result = m_LongNameMap.try_emplace(arg->GetLongName(), arg);
|
||||
if (!result.second) throw std::invalid_argument("duplicated long name!");
|
||||
}
|
||||
// insert into short name map if necessary
|
||||
if (arg->HasShortName()) {
|
||||
auto result = m_ShortNameMap.try_emplace(arg->GetShortName(), arg);
|
||||
if (!result.second) throw std::invalid_argument("duplicated short name!");
|
||||
}
|
||||
// insert into argument list
|
||||
m_Arguments.emplace_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
OptionContext::~OptionContext() {}
|
||||
|
||||
bool OptionContext::Parse(ArgumentList& al) {
|
||||
// reset argument list first
|
||||
al.Reset();
|
||||
|
||||
// prepare variables and start loop
|
||||
yycc_u8string long_name;
|
||||
yycc_char8_t short_name;
|
||||
bool is_long_name;
|
||||
while (!al.IsEOF()) {
|
||||
// if we can not find any switches, return with error
|
||||
if (!al.IsSwitch(&is_long_name, &long_name, &short_name)) return false;
|
||||
|
||||
// find corresponding argument by long name or short name.
|
||||
// if we can not find it, return with error.
|
||||
AbstractArgument* arg;
|
||||
if (is_long_name) {
|
||||
auto finder = m_LongNameMap.find(long_name);
|
||||
if (finder == m_LongNameMap.end()) return false;
|
||||
arg = finder->second;
|
||||
} else {
|
||||
auto finder = m_ShortNameMap.find(short_name);
|
||||
if (finder == m_ShortNameMap.end()) return false;
|
||||
arg = finder->second;
|
||||
}
|
||||
|
||||
// if this argument has been captured, raise error
|
||||
if (arg->IsCaptured()) return false;
|
||||
// call user parse function of found argument
|
||||
if (arg->Parse(al)) {
|
||||
// success. mark it is captured
|
||||
arg->SetCaptured(true);
|
||||
} else {
|
||||
// failed, return error
|
||||
return false;
|
||||
}
|
||||
|
||||
// move to next argument
|
||||
al.Next();
|
||||
}
|
||||
|
||||
// after processing all argument,
|
||||
// we should check whether all non-optional argument are captured.
|
||||
for (const auto* arg : m_Arguments) {
|
||||
if (!arg->IsOptional() && !arg->IsCaptured())
|
||||
return false;
|
||||
}
|
||||
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
|
||||
void OptionContext::Reset() {
|
||||
for (auto* arg : m_Arguments) {
|
||||
// clear user data and unset captured
|
||||
arg->Reset();
|
||||
arg->SetCaptured(false);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionContext::Help() const {
|
||||
// print summary and description if necessary
|
||||
if (!m_Summary.empty())
|
||||
YYCC::ConsoleHelper::WriteLine(m_Summary.c_str());
|
||||
if (!m_Description.empty())
|
||||
YYCC::ConsoleHelper::WriteLine(m_Description.c_str());
|
||||
|
||||
// blank line
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(""));
|
||||
|
||||
// print argument list
|
||||
for (const auto* arg : m_Arguments) {
|
||||
yycc_u8string argstr;
|
||||
|
||||
// print indent
|
||||
argstr += YYCC_U8("\t");
|
||||
// print optional head
|
||||
bool is_optional = arg->IsOptional();
|
||||
if (is_optional) argstr += YYCC_U8("[");
|
||||
|
||||
// switch name
|
||||
bool short_name = arg->HasShortName(), long_name = arg->HasLongName();
|
||||
if (short_name) {
|
||||
argstr += YYCC_U8("-");
|
||||
argstr += arg->GetShortName();
|
||||
}
|
||||
if (long_name) {
|
||||
if (short_name) argstr += YYCC_U8(", ");
|
||||
argstr += YYCC_U8("--");
|
||||
argstr += arg->GetLongName();
|
||||
}
|
||||
|
||||
// argument example
|
||||
if (arg->HasArgumentExample()) {
|
||||
argstr += YYCC_U8(" ");
|
||||
argstr += arg->GetArgumentExample();
|
||||
}
|
||||
|
||||
// optional tail
|
||||
if (is_optional) argstr += YYCC_U8("]");
|
||||
|
||||
// argument description
|
||||
if (arg->HasDescription()) {
|
||||
// eol and double indent
|
||||
argstr += YYCC_U8("\n\t\t");
|
||||
// description
|
||||
argstr += arg->GetDescription();
|
||||
}
|
||||
|
||||
// write into console
|
||||
YYCC::ConsoleHelper::WriteLine(argstr.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
465
src/YYCCLegacy/ArgParser.hpp
Normal file
465
src/YYCCLegacy/ArgParser.hpp
Normal file
@ -0,0 +1,465 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "Constraints.hpp"
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "ParserHelper.hpp"
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
/**
|
||||
* @brief Universal argument parser.
|
||||
* @details
|
||||
* For how to use this namespace, please see \ref arg_parser.
|
||||
*/
|
||||
namespace YYCC::ArgParser {
|
||||
|
||||
/**
|
||||
* @brief The advanced wrapper of the list containing command line arguments.
|
||||
* @details
|
||||
* This class is used by OptionContext and argument class internally for convenience.
|
||||
* It should not be constrcuted directly.
|
||||
* Programmer should choose proper static creation function to create instance of this class.
|
||||
*/
|
||||
class ArgumentList {
|
||||
public:
|
||||
/**
|
||||
* @brief Create argument list from the parameters of standard C main function.
|
||||
* @param[in] argc The argument count passed to standard C main function.
|
||||
* @param[in] argv The argument value passed to standard C main function.
|
||||
* @return Extracted argument list instance.
|
||||
* @remarks
|
||||
* First item in command line will be stripped,
|
||||
* because in most cases it points to executable self
|
||||
* and should not be seen as a part of arguments.
|
||||
*/
|
||||
static ArgumentList CreateFromStd(int argc, char* argv[]);
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
/**
|
||||
* @brief Create argument list from Win32 function.
|
||||
* @details
|
||||
* @return Extracted argument list instance.
|
||||
* @remarks
|
||||
* First item in command line will be stripped,
|
||||
* because in most cases it points to executable self
|
||||
* and should not be seen as a part of arguments.
|
||||
* \par
|
||||
* Programmer should use this function instead of CreateFromStd(),
|
||||
* because that function involve encoding issue on Windows, especially command line including non-ASCII chars.
|
||||
* Only this function guaranteen that return correct argument list on Windows.
|
||||
*/
|
||||
static ArgumentList CreateFromWin32();
|
||||
#endif
|
||||
private:
|
||||
/**
|
||||
* @brief Constructor of ArgumentList used internally.
|
||||
* @param[in] arguments
|
||||
* Underlying argument list.
|
||||
* This argument list should remove first executable name before passing it to there.
|
||||
*/
|
||||
ArgumentList(std::vector<yycc_u8string>&& arguments);
|
||||
public:
|
||||
YYCC_DEF_CLS_COPY_MOVE(ArgumentList);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Move to previous argument.
|
||||
* @exception std::runtime_error Try moving at the head of argument list.
|
||||
*/
|
||||
void Prev();
|
||||
/**
|
||||
* @brief Move to next argument.
|
||||
* @exception std::runtime_error Try moving at the tail of argument list.
|
||||
*/
|
||||
void Next();
|
||||
/**
|
||||
* @brief Get the string of current argument.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return The constant reference to the string of current argument.
|
||||
*/
|
||||
const yycc_u8string& Argument() const;
|
||||
/**
|
||||
* @brief Check whether current argument is a option / switch.
|
||||
* @param[out] is_long_name
|
||||
* It will be set true if this argument is long name, otherwise short name.
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @param[out] long_name
|
||||
* The container holding matched long name if it is (double dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @param[out] short_name
|
||||
* The variable holding matched short name if it is (dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return
|
||||
* True if it is, otherwise false.
|
||||
* If this function return false, all given parameters are in undefined status.
|
||||
*/
|
||||
bool IsSwitch(
|
||||
bool* is_long_name = nullptr,
|
||||
yycc_u8string* long_name = nullptr,
|
||||
yycc_char8_t* short_name = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether current argument is a value.
|
||||
* @param[out] val
|
||||
* The variable holding value if it is.
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsValue(yycc_u8string* val = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether we are at the tail of argument list.
|
||||
* @details
|
||||
* Please note EOF is a special state that you can not fetch data from it.
|
||||
* EOF is the next element of the last element of argument list.
|
||||
* It more like \c end() in most C++ container.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsEOF() const;
|
||||
/**
|
||||
* @brief Reset cursor to the head of argument list for reuse.
|
||||
*/
|
||||
void Reset();
|
||||
private:
|
||||
/**
|
||||
* @brief Check whether current argument is long name option / switch.
|
||||
* @details This function is used by IsSwitch() internally.
|
||||
* @param[out] name_part
|
||||
* The container holding matched long name if it is (double dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsLongNameSwitch(yycc_u8string* name_part = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether current argument is short name option / switch.
|
||||
* @details This function is used by IsSwitch() internally.
|
||||
* @param[out] name_part
|
||||
* The variable holding matched short name if it is (dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsShortNameSwitch(yycc_char8_t* name_part = nullptr) const;
|
||||
|
||||
private:
|
||||
std::vector<yycc_u8string> m_Arguments;
|
||||
size_t m_ArgumentsCursor;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The base class of every argument.
|
||||
* @details Programmer can inherit this class and implement essential functions to create custom argument.
|
||||
*/
|
||||
class AbstractArgument {
|
||||
friend class OptionContext;
|
||||
|
||||
// Long name and short name constants and checker.
|
||||
public:
|
||||
static const yycc_u8string DOUBLE_DASH; ///< The constant value representing double dash (\c --)
|
||||
static const yycc_char8_t DASH; ///< The constant value representing dash (\c -)
|
||||
static const yycc_char8_t NO_SHORT_NAME; ///< The constant value representing that there is not short value.
|
||||
static const yycc_char8_t MIN_SHORT_NAME; ///< The constant value representing the minimum value of valid ASCII chars in short and long name.
|
||||
static const yycc_char8_t MAX_SHORT_NAME; ///< The constant value representing the maximum value of valid ASCII chars in short and long name.
|
||||
/**
|
||||
* @brief Check whether given short name is valid.
|
||||
* @details
|
||||
* An ASCII code of valid short name
|
||||
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
|
||||
* It also can not be #DASH.
|
||||
* @param[in] short_name Short name for checking.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
static bool IsLegalShortName(yycc_char8_t short_name);
|
||||
/**
|
||||
* @brief Check whether given long name is valid.
|
||||
* @details
|
||||
* An ASCII code of every item in valid long name
|
||||
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
|
||||
* However it can be #DASH. This is different with short name.
|
||||
* @param[in] long_name Long name for checking.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
static bool IsLegalLongName(const yycc_u8string_view& long_name);
|
||||
|
||||
// Constructor & destructor
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional
|
||||
* True if this argument is optional argument.
|
||||
* Optional argument can be absent in argument list.
|
||||
* Non-optional argument must be presented in argument list,
|
||||
* otherwise parser will fail.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
AbstractArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name = AbstractArgument::NO_SHORT_NAME,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false);
|
||||
virtual ~AbstractArgument();
|
||||
YYCC_DEL_CLS_COPY_MOVE(AbstractArgument);
|
||||
|
||||
// ===== User Implementation =====
|
||||
protected:
|
||||
/**
|
||||
* @brief User implemented custom parse function
|
||||
* @param[in] al The argument list for parsing.
|
||||
* @return True if parse is success, otherwise false.
|
||||
* @remarks
|
||||
* When enter this function, argument list points to switch self.
|
||||
* After success parsing, you should point it to the argument this function last accepted.
|
||||
* For exmaple, for command line "-i 114514",
|
||||
* when enter this function, this argument list point to "-i",
|
||||
* and you should set it to "114514" when exiting this function.
|
||||
*/
|
||||
virtual bool Parse(ArgumentList& al) = 0;
|
||||
/**
|
||||
* @brief User implemented custom reset function
|
||||
* @remarks
|
||||
* In this function, user should claer its stored value if is has.
|
||||
* You don't need clar capture state. That is done by library self.
|
||||
*/
|
||||
virtual void Reset() = 0;
|
||||
|
||||
// ===== Basic Infos =====
|
||||
public:
|
||||
/// @brief Check whether this argument specify long name.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasLongName() const;
|
||||
/// @brief Get specified long name.
|
||||
/// @return Specified long name.
|
||||
const yycc_u8string& GetLongName() const;
|
||||
/// @brief Check whether this argument specify short name.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasShortName() const;
|
||||
/// @brief Get specified short name.
|
||||
/// @return Specified short name.
|
||||
yycc_char8_t GetShortName() const;
|
||||
/// @brief Check whether this argument specify description.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasDescription() const;
|
||||
/// @brief Get specified description.
|
||||
/// @return Specified description.
|
||||
const yycc_u8string& GetDescription() const;
|
||||
/// @brief Check whether this argument specify example.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasArgumentExample() const;
|
||||
/// @brief Get specified example.
|
||||
/// @return Specified example.
|
||||
const yycc_u8string& GetArgumentExample() const;
|
||||
/// @brief Check whether this argument is optional.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool IsOptional() const;
|
||||
private:
|
||||
yycc_u8string m_LongName;
|
||||
yycc_char8_t m_ShortName;
|
||||
yycc_u8string m_Description;
|
||||
yycc_u8string m_ArgumentExample;
|
||||
bool m_IsOptional;
|
||||
|
||||
// ===== Capture State =====
|
||||
public:
|
||||
/// @brief Check whether this argument has been captured.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool IsCaptured() const;
|
||||
private:
|
||||
/**
|
||||
* @brief Set capture state of this argument.
|
||||
* @details This function is used internally by OptionContext.
|
||||
* @param[in] is_captured New states of captured.
|
||||
*/
|
||||
void SetCaptured(bool is_captured);
|
||||
bool m_IsCaptured;
|
||||
};
|
||||
|
||||
/// @brief The core of argument parser, also manage all arguments.
|
||||
class OptionContext {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct option context.
|
||||
* @param[in] summary The summary of this application which will be printed in help text.
|
||||
* @param[in] description The description of this application which will be printed in help text.
|
||||
* @param[in] arguments The initializer list including pointers to all arguments.
|
||||
*/
|
||||
OptionContext(
|
||||
const yycc_char8_t* summary, const yycc_char8_t* description,
|
||||
std::initializer_list<AbstractArgument*> arguments);
|
||||
~OptionContext();
|
||||
YYCC_DEL_CLS_COPY_MOVE(OptionContext);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Start a parse.
|
||||
* @param[in] al The reference to ArgumentList for parsing.
|
||||
* @return
|
||||
* True if success, otherwise false.
|
||||
* If this function return false, you should not visit any arguments it managed.
|
||||
*/
|
||||
bool Parse(ArgumentList& al);
|
||||
/**
|
||||
* @brief Reset all managed argument to default state thus you can start another parsing.
|
||||
*/
|
||||
void Reset();
|
||||
/**
|
||||
* @brief Print help text in \c stdout.
|
||||
*/
|
||||
void Help() const;
|
||||
|
||||
private:
|
||||
yycc_u8string m_Summary;
|
||||
yycc_u8string m_Description;
|
||||
|
||||
std::vector<AbstractArgument*> m_Arguments;
|
||||
std::map<yycc_u8string, AbstractArgument*> m_LongNameMap;
|
||||
std::map<yycc_char8_t, AbstractArgument*> m_ShortNameMap;
|
||||
};
|
||||
|
||||
#pragma region Argument Presets
|
||||
|
||||
/**
|
||||
* @brief Arithmetic (integral, floating point. except bool) type argument
|
||||
* @tparam _Ty The internal stored type belongs to arithmetic type.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
class NumberArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an arithmetic argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional True if this argument is optional argument.
|
||||
* @param[in] constraint The constraint applied to this argument.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
NumberArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false,
|
||||
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
|
||||
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
|
||||
virtual ~NumberArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(NumberArgument);
|
||||
|
||||
public:
|
||||
/// @brief Get stored data in argument.
|
||||
_Ty Get() const {
|
||||
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
|
||||
return m_Data;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override {
|
||||
// try get corresponding value
|
||||
yycc_u8string strval;
|
||||
al.Next();
|
||||
if (al.IsEOF() || !al.IsValue(&strval)) {
|
||||
al.Prev();
|
||||
return false;
|
||||
}
|
||||
// try parsing value
|
||||
if (!YYCC::ParserHelper::TryParse<_Ty>(strval, m_Data)) return false;
|
||||
// check constraint
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
virtual void Reset() override {
|
||||
std::memset(&m_Data, 0, sizeof(m_Data));
|
||||
}
|
||||
|
||||
protected:
|
||||
_Ty m_Data;
|
||||
Constraints::Constraint<_Ty> m_Constraint;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A simple switch type argument which do not store any value.
|
||||
*/
|
||||
class SwitchArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an switch argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
SwitchArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr) :
|
||||
// bool switch must be optional, because it is false if no given switch.
|
||||
// bool switch doesn't have argument, so it doesn't have example property.
|
||||
AbstractArgument(long_name, short_name, description, nullptr, true) {}
|
||||
virtual ~SwitchArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(SwitchArgument);
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override { return true; } // simply return true because no value to store.
|
||||
virtual void Reset() override {} // nothing need to be reset.
|
||||
};
|
||||
|
||||
/// @brief String type argument
|
||||
class StringArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor a string argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional True if this argument is optional argument.
|
||||
* @param[in] constraint The constraint applied to this argument.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
StringArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false,
|
||||
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
|
||||
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
|
||||
virtual ~StringArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(StringArgument);
|
||||
|
||||
public:
|
||||
/// @brief Get stored data in argument.
|
||||
const yycc_u8string& Get() const {
|
||||
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
|
||||
return m_Data;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override {
|
||||
// try get corresponding value
|
||||
al.Next();
|
||||
if (al.IsEOF() || !al.IsValue(&m_Data)) {
|
||||
al.Prev();
|
||||
return false;
|
||||
}
|
||||
// check constraint
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
virtual void Reset() override {
|
||||
m_Data.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
yycc_u8string m_Data;
|
||||
Constraints::Constraint<yycc_u8string> m_Constraint;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
}
|
48
src/YYCCLegacy/COMHelper.cpp
Normal file
48
src/YYCCLegacy/COMHelper.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "COMHelper.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
namespace YYCC::COMHelper {
|
||||
|
||||
/**
|
||||
* @brief The guard for initialize COM environment.
|
||||
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
|
||||
* and it also will try uninitializing COM environment when destructing.
|
||||
* If initialization failed, uninitialization will not be executed.
|
||||
*/
|
||||
class ComGuard {
|
||||
public:
|
||||
ComGuard() : m_HasInit(false) {
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (SUCCEEDED(hr)) m_HasInit = true;
|
||||
}
|
||||
~ComGuard() {
|
||||
if (m_HasInit) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_HasInit;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_HasInit;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The instance of COM environment guard.
|
||||
* @details Dialog related function need COM environment,
|
||||
* so we need initializing COM environment when loading this module,
|
||||
* and uninitializing COM environment when we no longer use this module.
|
||||
* So we use a static instance in here.
|
||||
* And make it be const so no one can change it.
|
||||
*/
|
||||
static const ComGuard c_ComGuard {};
|
||||
|
||||
bool IsInitialized() {
|
||||
return c_ComGuard.IsInitialized();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
71
src/YYCCLegacy/COMHelper.hpp
Normal file
71
src/YYCCLegacy/COMHelper.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief Windows COM related types and checker.
|
||||
* @details
|
||||
* This namespace is Windows specific.
|
||||
* In other platforms, this whole namespace will be unavailable.
|
||||
*
|
||||
* See also \ref com_helper.
|
||||
*/
|
||||
namespace YYCC::COMHelper {
|
||||
|
||||
/// @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
|
||||
class ComPtrDeleter {
|
||||
public:
|
||||
ComPtrDeleter() {}
|
||||
void operator() (IUnknown* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
com_ptr->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of \c IFileDialog
|
||||
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IFileOpenDialog
|
||||
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItem
|
||||
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItemArray
|
||||
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellFolder
|
||||
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
|
||||
|
||||
/// @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
|
||||
class CoTaskMemDeleter {
|
||||
public:
|
||||
CoTaskMemDeleter() {}
|
||||
void operator() (void* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
CoTaskMemFree(com_ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of COM created \c WCHAR sequence.
|
||||
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
|
||||
|
||||
/**
|
||||
* @brief Check whether COM environment has been initialized.
|
||||
* @return True if it is, otherwise false.
|
||||
* @remarks
|
||||
* This function will call corresponding function of COM Guard.
|
||||
* Do not remove this function and you must preserve at least one reference to this function in final program.
|
||||
* Some compiler will try to drop COM Guard in final program if no reference to it and it will cause the initialization of COM environment failed.
|
||||
* This is the reason why I order you do the things said above.
|
||||
*/
|
||||
bool IsInitialized();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
186
src/YYCCLegacy/ConfigManager.cpp
Normal file
186
src/YYCCLegacy/ConfigManager.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include "ConfigManager.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "IOHelper.hpp"
|
||||
#include "EnumHelper.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace YYCC::ConfigManager {
|
||||
|
||||
#pragma region Abstract Setting
|
||||
|
||||
AbstractSetting::AbstractSetting(const yycc_u8string_view& name) : m_Name(name), m_RawData() {
|
||||
if (m_Name.empty())
|
||||
throw std::invalid_argument("the name of setting should not be empty");
|
||||
}
|
||||
|
||||
AbstractSetting::~AbstractSetting() {}
|
||||
|
||||
const yycc_u8string& AbstractSetting::GetName() const { return m_Name; }
|
||||
|
||||
void AbstractSetting::ResizeData(size_t new_size) { m_RawData.resize(new_size); }
|
||||
const void* AbstractSetting::GetDataPtr() const { return m_RawData.data(); }
|
||||
void* AbstractSetting::GetDataPtr() { return m_RawData.data(); }
|
||||
size_t AbstractSetting::GetDataSize() const { return m_RawData.size(); }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Core Manager
|
||||
|
||||
CoreManager::CoreManager(
|
||||
const yycc_u8string_view& cfg_file_path,
|
||||
uint64_t version_identifier,
|
||||
std::initializer_list<AbstractSetting*> settings) :
|
||||
m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
|
||||
// Mark: no need to check cfg file path
|
||||
// it will be checked at creating file handle
|
||||
|
||||
// assign settings
|
||||
for (auto* setting : settings) {
|
||||
auto result = m_Settings.try_emplace(setting->GetName(), setting);
|
||||
if (!result.second) {
|
||||
// if not inserted because duplicated, raise exception
|
||||
throw std::invalid_argument("Duplicated setting name");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigLoadResult CoreManager::Load() {
|
||||
// prepare result variables
|
||||
ConfigLoadResult ret = ConfigLoadResult::OK;
|
||||
|
||||
// reset all settings first
|
||||
Reset();
|
||||
|
||||
// get file handle
|
||||
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("rb")));
|
||||
if (fs.get() == nullptr) {
|
||||
// if we fail to get, it means that we do not have corresponding cfg file.
|
||||
// all settings should be reset to default value.
|
||||
ret = ConfigLoadResult::Created;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// fetch version info
|
||||
uint64_t version_info;
|
||||
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
|
||||
ret = ConfigLoadResult::Created;
|
||||
return ret;
|
||||
}
|
||||
// check version
|
||||
// if read version is greater than we expected,
|
||||
// it means that this cfg file is created by the program higer than this.
|
||||
// we should not read anything from it.
|
||||
// however, for compaitibility reason, we allow read old cfg data.
|
||||
if (version_info > m_VersionIdentifier) {
|
||||
ret = ConfigLoadResult::ForwardNew;
|
||||
return ret;
|
||||
} else if (version_info < m_VersionIdentifier) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::Migrated);
|
||||
}
|
||||
|
||||
// fetch setting item from file
|
||||
yycc_u8string name_cache;
|
||||
while (true) {
|
||||
// try fetch setting name
|
||||
// fetch name length
|
||||
size_t name_length;
|
||||
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
|
||||
// we also check whether reach EOF at there.
|
||||
if (std::feof(fs.get())) break;
|
||||
else {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
// fetch name body
|
||||
name_cache.resize(name_length);
|
||||
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// get setting data length
|
||||
size_t data_length;
|
||||
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// get matched setting first
|
||||
const auto& found = m_Settings.find(name_cache);
|
||||
if (found != m_Settings.end()) {
|
||||
// found. read data for it
|
||||
found->second->ResizeData(data_length);
|
||||
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
// call user defined load function
|
||||
// if fail to parse, reset to default value
|
||||
if (!found->second->UserLoad()) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::ItemError);
|
||||
found->second->UserReset();
|
||||
}
|
||||
} else {
|
||||
// fail to find. skip this unknown setting
|
||||
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CoreManager::Save() {
|
||||
// get file handle
|
||||
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("wb")));
|
||||
// if we fail to get, return false.
|
||||
if (fs == nullptr) return false;
|
||||
|
||||
// write config data
|
||||
uint64_t version_info = m_VersionIdentifier;
|
||||
if (std::fwrite(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info))
|
||||
return false;
|
||||
|
||||
// iterate all data for writing
|
||||
for (const auto& pair : m_Settings) {
|
||||
// do user defined save
|
||||
// if failed, skip this setting
|
||||
if (!pair.second->UserSave())
|
||||
continue;
|
||||
|
||||
// write setting name
|
||||
// write name length
|
||||
size_t name_length = pair.first.size();
|
||||
if (std::fwrite(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length))
|
||||
return false;
|
||||
// write name body
|
||||
if (std::fwrite(pair.first.c_str(), 1u, name_length, fs.get()) != name_length)
|
||||
return false;
|
||||
|
||||
// write setting daat
|
||||
// write data length
|
||||
size_t data_length = pair.second->GetDataSize();
|
||||
if (std::fwrite(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length))
|
||||
return false;
|
||||
// write data body
|
||||
if (std::fwrite(pair.second->GetDataPtr(), 1u, data_length, fs.get()) != data_length)
|
||||
return false;
|
||||
}
|
||||
|
||||
// all settings done, return true
|
||||
return true;
|
||||
}
|
||||
|
||||
void CoreManager::Reset() {
|
||||
for (const auto& pair : m_Settings) {
|
||||
pair.second->UserReset();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
269
src/YYCCLegacy/ConfigManager.hpp
Normal file
269
src/YYCCLegacy/ConfigManager.hpp
Normal file
@ -0,0 +1,269 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "Constraints.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
/**
|
||||
* @brief Universal configuration manager
|
||||
* @details For how to use this namespace, please see \ref config_manager.
|
||||
*/
|
||||
namespace YYCC::ConfigManager {
|
||||
|
||||
/**
|
||||
* @brief The load result of loading config.
|
||||
*/
|
||||
enum class ConfigLoadResult {
|
||||
OK = 0, ///< Success load configs.
|
||||
Created = 1 << 0, ///< Given file is not existing, we create all configs in default values.
|
||||
ForwardNew = 1 << 1, ///< Detect the config file created by higher version. We create all configs in default values.
|
||||
Migrated = 1 << 2, ///< Detect the config file created by lower version. We try migrate configs written in it.
|
||||
BrokenFile = 1 << 3, ///< Given file has bad format. Thus some configs are kept as its default values.
|
||||
ItemError = 1 << 4 ///< Some config can not be recognized from the data read from file so they are reset to default value.
|
||||
};
|
||||
using UnderlyingConfigLoadResult_t = std::underlying_type_t<ConfigLoadResult>;
|
||||
|
||||
/// @brief The base class of every setting.
|
||||
/// @details Programmer can inherit this class and implement essential functions to create custom setting.
|
||||
class AbstractSetting {
|
||||
friend class CoreManager;
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a setting
|
||||
* @param[in] name The name of this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
AbstractSetting(const yycc_u8string_view& name);
|
||||
virtual ~AbstractSetting();
|
||||
YYCC_DEL_CLS_COPY_MOVE(AbstractSetting);
|
||||
|
||||
// Name interface
|
||||
public:
|
||||
/// @brief Get name of this setting.
|
||||
/// @details Name was used in storing setting in file.
|
||||
const yycc_u8string& GetName() const;
|
||||
private:
|
||||
yycc_u8string m_Name;
|
||||
|
||||
// User Implementations
|
||||
protected:
|
||||
/// @brief User implemented custom load function
|
||||
/// @remarks
|
||||
/// In this function, programmer should read data from internal buffer
|
||||
/// and store it to its own another internal variables.
|
||||
/// @return True if success, otherwise false.
|
||||
virtual bool UserLoad() = 0;
|
||||
/// @brief User implemented custom save function
|
||||
/// @remarks
|
||||
/// In this function, programmer should write data,
|
||||
/// which is stored in another variavle by it own, to internal buffer.
|
||||
/// @return True if success, otherwise false.
|
||||
virtual bool UserSave() = 0;
|
||||
/// @brief User implemented custom reset function
|
||||
/// @remarks In this function, programmer should reset its internal variable to default value.
|
||||
virtual void UserReset() = 0;
|
||||
|
||||
// Buffer related functions
|
||||
protected:
|
||||
/// @brief Resize internal buffer to given size.
|
||||
/// @remarks It is usually used in UserSave.
|
||||
/// @param[in] new_size The new size of internal buffer.
|
||||
void ResizeData(size_t new_size);
|
||||
/// @brief Get data pointer to internal buffer.
|
||||
/// @remarks It is usually used in UserLoad.
|
||||
const void* GetDataPtr() const;
|
||||
/// @brief Get mutable data pointer to internal buffer.
|
||||
/// @remarks It is usually used in UserSave.
|
||||
void* GetDataPtr();
|
||||
/// @brief Get the length of internal buffer.
|
||||
size_t GetDataSize() const;
|
||||
private:
|
||||
std::vector<uint8_t> m_RawData;
|
||||
};
|
||||
|
||||
/// @brief Settings manager and config file reader writer.
|
||||
class CoreManager {
|
||||
public:
|
||||
/**
|
||||
* @brief Build core manager.
|
||||
* @param[in] cfg_file_path The path to config file.
|
||||
* @param[in] version_identifier The identifier of version. Higher is newer. Lower config will try doing migration.
|
||||
* @param[in] settings An initializer list containing pointers to all managed settings.
|
||||
*/
|
||||
CoreManager(
|
||||
const yycc_u8string_view& cfg_file_path,
|
||||
uint64_t version_identifier,
|
||||
std::initializer_list<AbstractSetting*> settings);
|
||||
~CoreManager() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(CoreManager);
|
||||
|
||||
// Core functions
|
||||
public:
|
||||
/// @brief Load settings from file.
|
||||
/// @details Before loading, all settings will be reset to default value first.
|
||||
/// @return What happend when loading config. This function always success.
|
||||
ConfigLoadResult Load();
|
||||
/// @brief Save settings to file.
|
||||
/// @return True if success, otherwise false.
|
||||
bool Save();
|
||||
/// @brief Reset all settings to default value.
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
yycc_u8string m_CfgFilePath;
|
||||
uint64_t m_VersionIdentifier;
|
||||
std::map<yycc_u8string, AbstractSetting*> m_Settings;
|
||||
};
|
||||
|
||||
#pragma region Setting Presets
|
||||
|
||||
/**
|
||||
* @brief Arithmetic (integral, floating point, bool) and enum type setting
|
||||
* @tparam _Ty The internal stored type belongs to arithmetic type.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> || std::is_enum_v<_Ty>, int> = 0>
|
||||
class NumberSetting : public AbstractSetting {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct arithmetic type setting.
|
||||
* @param[in] name The name of this setting.
|
||||
* @param[in] default_value The default value of this setting.
|
||||
* @param[in] constraint The constraint applied to this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
NumberSetting(
|
||||
const yycc_u8string_view& name, _Ty default_value,
|
||||
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
|
||||
AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {}
|
||||
virtual ~NumberSetting() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(NumberSetting);
|
||||
|
||||
/// @brief Get stored data in setting.
|
||||
_Ty Get() const { return m_Data; }
|
||||
/**
|
||||
* @brief Set data to setting.
|
||||
* @param[in] new_data The new data.
|
||||
* @return True if success, otherwise false (given value is invalid)
|
||||
*/
|
||||
bool Set(_Ty new_data) {
|
||||
// validate data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data))
|
||||
return false;
|
||||
// assign data
|
||||
m_Data = new_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool UserLoad() override {
|
||||
// read data
|
||||
if (sizeof(m_Data) != GetDataSize())
|
||||
return false;
|
||||
m_Data = *reinterpret_cast<const _Ty*>(GetDataPtr());
|
||||
// check data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
virtual bool UserSave() override {
|
||||
// write data
|
||||
ResizeData(sizeof(m_Data));
|
||||
*reinterpret_cast<_Ty*>(GetDataPtr()) = m_Data;
|
||||
return true;
|
||||
}
|
||||
virtual void UserReset() override {
|
||||
m_Data = m_DefaultData;
|
||||
}
|
||||
|
||||
_Ty m_Data, m_DefaultData;
|
||||
Constraints::Constraint<_Ty> m_Constraint;
|
||||
};
|
||||
|
||||
/// @brief String type setting
|
||||
class StringSetting : public AbstractSetting {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct string setting
|
||||
* @param[in] name The name of this setting.
|
||||
* @param[in] default_value The default value of this setting.
|
||||
* @param[in] constraint The constraint applied to this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
StringSetting(
|
||||
const yycc_u8string_view& name, const yycc_u8string_view& default_value,
|
||||
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
|
||||
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) {
|
||||
m_Data = default_value;
|
||||
m_DefaultData = default_value;
|
||||
}
|
||||
virtual ~StringSetting() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(StringSetting);
|
||||
|
||||
/// @brief Get reference to stored string.
|
||||
const yycc_u8string& Get() const { return m_Data; }
|
||||
/**
|
||||
* @brief Set string data to setting.
|
||||
* @param[in] new_data The new string data.
|
||||
* @return True if success, otherwise false (given value is invalid)
|
||||
*/
|
||||
bool Set(const yycc_u8string_view& new_data) {
|
||||
// check data validation
|
||||
yycc_u8string new_data_cache(new_data);
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data_cache))
|
||||
return false;
|
||||
// assign data
|
||||
m_Data = std::move(new_data_cache);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool UserLoad() override {
|
||||
// read string length
|
||||
size_t string_length;
|
||||
if (GetDataSize() < sizeof(string_length))
|
||||
return false;
|
||||
string_length = *reinterpret_cast<const size_t*>(GetDataPtr());
|
||||
// read string body
|
||||
if (GetDataSize() != sizeof(string_length) + string_length)
|
||||
return false;
|
||||
m_Data.assign(
|
||||
reinterpret_cast<const yycc_char8_t*>(static_cast<const uint8_t*>(GetDataPtr()) + sizeof(string_length)),
|
||||
string_length
|
||||
);
|
||||
// check data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
virtual bool UserSave() override {
|
||||
// allocate result buffer
|
||||
size_t string_length = m_Data.size();
|
||||
ResizeData(sizeof(string_length) + string_length);
|
||||
// get pointer
|
||||
uint8_t* ptr = static_cast<uint8_t*>(GetDataPtr());
|
||||
// assign string length
|
||||
*reinterpret_cast<size_t*>(ptr) = string_length;
|
||||
// assign string body
|
||||
std::memcpy(ptr + sizeof(string_length), m_Data.data(), string_length);
|
||||
return true;
|
||||
}
|
||||
virtual void UserReset() override {
|
||||
m_Data = m_DefaultData;
|
||||
}
|
||||
|
||||
yycc_u8string m_Data, m_DefaultData;
|
||||
Constraints::Constraint<yycc_u8string> m_Constraint;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
}
|
282
src/YYCCLegacy/ConsoleHelper.cpp
Normal file
282
src/YYCCLegacy/ConsoleHelper.cpp
Normal file
@ -0,0 +1,282 @@
|
||||
#include "ConsoleHelper.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include <iostream>
|
||||
|
||||
// Include Windows used headers in Windows.
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
#pragma region Windows Specific Functions
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
static bool RawEnableColorfulConsole(FILE* fs) {
|
||||
if (!_isatty(_fileno(fs))) return false;
|
||||
|
||||
HANDLE h_output;
|
||||
DWORD dw_mode;
|
||||
|
||||
h_output = (HANDLE)_get_osfhandle(_fileno(fs));
|
||||
if (!GetConsoleMode(h_output, &dw_mode)) return false;
|
||||
if (!SetConsoleMode(h_output, dw_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Reference:
|
||||
* https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
|
||||
* https://stackoverflow.com/questions/69830460/reading-utf-8-input
|
||||
|
||||
There is 3 way to make Windows console enable UTF8 mode.
|
||||
|
||||
First one is calling SetConsoleCP and SetConsoleOutputCP.
|
||||
The side effect of this is std::cin and std::cout is broken,
|
||||
however there is a patch for this issue.
|
||||
|
||||
Second one is calling _set_mode with _O_U8TEXT or _O_U16TEXT to enable Unicode mode for Windows console.
|
||||
This also have side effect which is stronger than first one.
|
||||
All puts family functions (ASCII-based output functions) will throw assertion exception.
|
||||
You only can use putws family functions (wide-char-based output functions).
|
||||
However these functions can not be used without calling _set_mode in Windows design.
|
||||
|
||||
There still is another method, using WriteConsoleW directly visiting console.
|
||||
This function family can output correct string without calling any extra functions!
|
||||
This method is what we adopted.
|
||||
*/
|
||||
|
||||
template<bool _bIsConsole>
|
||||
static yycc_u8string WinConsoleRead(HANDLE hStdIn) {
|
||||
using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>;
|
||||
|
||||
// Prepare an internal buffer because the read data may not be fully used.
|
||||
// For example, we may read x\ny in a single calling but after processing \n, this function will return
|
||||
// so y will temporarily stored in this internal buffer for next using.
|
||||
// Thus this function is not thread safe.
|
||||
static std::basic_string<_TChar> internal_buffer;
|
||||
// create return value buffer
|
||||
std::basic_string<_TChar> return_buffer;
|
||||
|
||||
// Prepare some variables
|
||||
DWORD dwReadNumberOfChars;
|
||||
_TChar szReadChars[64];
|
||||
size_t eol_pos;
|
||||
|
||||
// try fetching EOL
|
||||
while (true) {
|
||||
// if internal buffer is empty,
|
||||
// try fetching it.
|
||||
if (internal_buffer.empty()) {
|
||||
// console and non-console use different method to read.
|
||||
if constexpr (_bIsConsole) {
|
||||
// console handle, use ReadConsoleW.
|
||||
// read from console, the read data is wchar based
|
||||
if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(_TChar), &dwReadNumberOfChars, NULL))
|
||||
break;
|
||||
} else {
|
||||
// anything else, use ReadFile instead.
|
||||
// the read data is utf8 based
|
||||
if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL))
|
||||
break;
|
||||
}
|
||||
|
||||
// send to internal buffer
|
||||
if (dwReadNumberOfChars == 0) break;
|
||||
internal_buffer.append(szReadChars, dwReadNumberOfChars);
|
||||
}
|
||||
|
||||
// try finding EOL in internal buffer
|
||||
if constexpr (std::is_same_v<_TChar, char>) eol_pos = internal_buffer.find_first_of('\n');
|
||||
else eol_pos = internal_buffer.find_first_of(L'\n');
|
||||
// check finding result
|
||||
if (eol_pos == std::wstring::npos) {
|
||||
// the whole string do not include EOL, fully appended to return value
|
||||
return_buffer += internal_buffer;
|
||||
internal_buffer.clear();
|
||||
// need more data, continue while
|
||||
} else {
|
||||
// split result
|
||||
// push into result and remain some in internal buffer.
|
||||
return_buffer.append(internal_buffer, 0u, eol_pos);
|
||||
internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place.
|
||||
// break while mean success finding
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// post-process for return value
|
||||
yycc_u8string real_return_buffer;
|
||||
if constexpr (_bIsConsole) {
|
||||
// console mode need convert wchar to utf8
|
||||
YYCC::EncodingHelper::WcharToUTF8(return_buffer, real_return_buffer);
|
||||
} else {
|
||||
// non-console just copt the result
|
||||
real_return_buffer = EncodingHelper::ToUTF8(return_buffer);
|
||||
}
|
||||
// every mode need delete \r words
|
||||
YYCC::StringHelper::Replace(real_return_buffer, YYCC_U8("\r"), YYCC_U8(""));
|
||||
// return value
|
||||
return real_return_buffer;
|
||||
}
|
||||
|
||||
static void WinConsoleWrite(const yycc_u8string& strl, bool to_stderr) {
|
||||
// Prepare some Win32 variables
|
||||
// fetch stdout handle first
|
||||
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
|
||||
DWORD dwConsoleMode;
|
||||
DWORD dwWrittenNumberOfChars;
|
||||
|
||||
// if stdout was redirected, this handle may point to a file handle or anything else,
|
||||
// WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle
|
||||
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
|
||||
// console handle, use WriteConsoleW.
|
||||
// convert utf8 string to wide char first
|
||||
std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl));
|
||||
size_t wstrl_size = wstrl.size();
|
||||
// write string with size check
|
||||
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(wstrl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
} else {
|
||||
// anything else, use WriteFile instead.
|
||||
// WriteFile do not need extra convertion, because it is direct writing.
|
||||
// check whether string length is overflow
|
||||
size_t strl_size = strl.size() * sizeof(yycc_u8string::value_type);
|
||||
// write string with size check
|
||||
if (strl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#pragma endregion
|
||||
|
||||
bool EnableColorfulConsole() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
bool ret = true;
|
||||
ret &= RawEnableColorfulConsole(stdout);
|
||||
ret &= RawEnableColorfulConsole(stderr);
|
||||
return ret;
|
||||
|
||||
#else
|
||||
|
||||
// just return true and do nothing
|
||||
return true;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
yycc_u8string ReadLine() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// get stdin mode
|
||||
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
// use different method to get according to whether stdin is redirected
|
||||
DWORD dwConsoleMode;
|
||||
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
|
||||
return WinConsoleRead<true>(hStdIn);
|
||||
} else {
|
||||
return WinConsoleRead<false>(hStdIn);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// in linux, directly use C++ function to fetch.
|
||||
std::string cmd;
|
||||
if (std::getline(std::cin, cmd).fail()) cmd.clear();
|
||||
return EncodingHelper::ToUTF8(cmd);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
template<bool bNeedFmt, bool bIsErr, bool bHasEOL>
|
||||
static void RawWrite(const yycc_char8_t* u8_fmt, va_list argptr) {
|
||||
// Buiild string need to be written first
|
||||
// If no format string or plain string for writing, return.
|
||||
if (u8_fmt == nullptr) return;
|
||||
// Build or simply copy string
|
||||
yycc_u8string strl;
|
||||
if constexpr (bNeedFmt) {
|
||||
// treat as format string
|
||||
va_list argcpy;
|
||||
va_copy(argcpy, argptr);
|
||||
strl = YYCC::StringHelper::VPrintf(u8_fmt, argcpy);
|
||||
va_end(argcpy);
|
||||
} else {
|
||||
// treat as plain string
|
||||
strl = u8_fmt;
|
||||
}
|
||||
// Checkout whether add EOL
|
||||
if constexpr (bHasEOL) {
|
||||
strl += YYCC_U8("\n");
|
||||
}
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
// call Windows specific writer
|
||||
WinConsoleWrite(strl, bIsErr);
|
||||
#else
|
||||
// in linux, directly use C function to write.
|
||||
std::fputs(EncodingHelper::ToOrdinary(strl.c_str()), bIsErr ? stderr : stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Format(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, false, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void FormatLine(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, false, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void Write(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, false, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void WriteLine(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, false, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, true, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, true, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ErrWrite(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, true, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ErrWriteLine(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, true, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
}
|
||||
|
163
src/YYCCLegacy/ConsoleHelper.hpp
Normal file
163
src/YYCCLegacy/ConsoleHelper.hpp
Normal file
@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief The helper providing universal C\# style console function and other console related stuff
|
||||
* @details
|
||||
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
|
||||
*/
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
/// @brief The head of ASCII escape code of black color.
|
||||
#define YYCC_COLORHDR_BLACK "\033[30m"
|
||||
/// @brief The head of ASCII escape code of red color.
|
||||
#define YYCC_COLORHDR_RED "\033[31m"
|
||||
/// @brief The head of ASCII escape code of green color.
|
||||
#define YYCC_COLORHDR_GREEN "\033[32m"
|
||||
/// @brief The head of ASCII escape code of yellow color.
|
||||
#define YYCC_COLORHDR_YELLOW "\033[33m"
|
||||
/// @brief The head of ASCII escape code of blue color.
|
||||
#define YYCC_COLORHDR_BLUE "\033[34m"
|
||||
/// @brief The head of ASCII escape code of magenta color.
|
||||
#define YYCC_COLORHDR_MAGENTA "\033[35m"
|
||||
/// @brief The head of ASCII escape code of cyan color.
|
||||
#define YYCC_COLORHDR_CYAN "\033[36m"
|
||||
/// @brief The head of ASCII escape code of white color.
|
||||
#define YYCC_COLORHDR_WHITE "\033[37m"
|
||||
|
||||
/// @brief The head of ASCII escape code of light black color.
|
||||
#define YYCC_COLORHDR_LIGHT_BLACK "\033[90m"
|
||||
/// @brief The head of ASCII escape code of light red color.
|
||||
#define YYCC_COLORHDR_LIGHT_RED "\033[91m"
|
||||
/// @brief The head of ASCII escape code of light green color.
|
||||
#define YYCC_COLORHDR_LIGHT_GREEN "\033[92m"
|
||||
/// @brief The head of ASCII escape code of light yellow color.
|
||||
#define YYCC_COLORHDR_LIGHT_YELLOW "\033[93m"
|
||||
/// @brief The head of ASCII escape code of light blue color.
|
||||
#define YYCC_COLORHDR_LIGHT_BLUE "\033[94m"
|
||||
/// @brief The head of ASCII escape code of light magenta color.
|
||||
#define YYCC_COLORHDR_LIGHT_MAGENTA "\033[95m"
|
||||
/// @brief The head of ASCII escape code of light cyan color.
|
||||
#define YYCC_COLORHDR_LIGHT_CYAN "\033[96m"
|
||||
/// @brief The head of ASCII escape code of light white color.
|
||||
#define YYCC_COLORHDR_LIGHT_WHITE "\033[97m"
|
||||
|
||||
/// @brief The tail of ASCII escape code of every color.
|
||||
#define YYCC_COLORTAIL "\033[0m"
|
||||
|
||||
/// @brief The ASCII escape code pair of black color.
|
||||
#define YYCC_COLOR_BLACK(T) "\033[30m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of red color.
|
||||
#define YYCC_COLOR_RED(T) "\033[31m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of green color.
|
||||
#define YYCC_COLOR_GREEN(T) "\033[32m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of yellow color.
|
||||
#define YYCC_COLOR_YELLOW(T) "\033[33m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of blue color.
|
||||
#define YYCC_COLOR_BLUE(T) "\033[34m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of magenta color.
|
||||
#define YYCC_COLOR_MAGENTA(T) "\033[35m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of cyan color.
|
||||
#define YYCC_COLOR_CYAN(T) "\033[36m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of white color.
|
||||
#define YYCC_COLOR_WHITE(T) "\033[37m" T "\033[0m"
|
||||
|
||||
/// @brief The ASCII escape code pair of light black color.
|
||||
#define YYCC_COLOR_LIGHT_BLACK(T) "\033[90m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light red color.
|
||||
#define YYCC_COLOR_LIGHT_RED(T) "\033[91m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light green color.
|
||||
#define YYCC_COLOR_LIGHT_GREEN(T) "\033[92m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light yellow color.
|
||||
#define YYCC_COLOR_LIGHT_YELLOW(T) "\033[93m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light blue color.
|
||||
#define YYCC_COLOR_LIGHT_BLUE(T) "\033[94m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light magenta color.
|
||||
#define YYCC_COLOR_LIGHT_MAGENTA(T) "\033[95m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light cyan color.
|
||||
#define YYCC_COLOR_LIGHT_CYAN(T) "\033[96m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light white color.
|
||||
#define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m"
|
||||
|
||||
/**
|
||||
* @brief Enable console color support for Windows.
|
||||
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks
|
||||
* This function only works on Windows and do nothing on other platforms such as Linux,
|
||||
* because we assume all terminals existing on other platform support color feature as default.
|
||||
*/
|
||||
bool EnableColorfulConsole();
|
||||
|
||||
/**
|
||||
* @brief Reads the next line of UTF8 characters from the standard input stream.
|
||||
* @return
|
||||
* The next line of UTF8 characters from the input stream.
|
||||
* Empty string if user just press Enter key or function failed.
|
||||
*/
|
||||
yycc_u8string ReadLine();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void Format(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void FormatLine(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void Write(const yycc_char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void WriteLine(const yycc_char8_t* u8_strl);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ErrWrite(const yycc_char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ErrWriteLine(const yycc_char8_t* u8_strl);
|
||||
|
||||
}
|
85
src/YYCCLegacy/Constraints.hpp
Normal file
85
src/YYCCLegacy/Constraints.hpp
Normal file
@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <set>
|
||||
#include <initializer_list>
|
||||
|
||||
/**
|
||||
* @brief The namespace containing constraint declaration
|
||||
* and functions generating common used constraint.
|
||||
*/
|
||||
namespace YYCC::Constraints {
|
||||
|
||||
/**
|
||||
* @brief The constraint applied to settings to limit its stored value.
|
||||
* @tparam _Ty The data type this constraint need to be processed with.
|
||||
*/
|
||||
template<typename _Ty>
|
||||
struct Constraint {
|
||||
/// @brief Return true if value is legal, otherwise false.
|
||||
using CheckFct_t = std::function<bool(const _Ty&)>;
|
||||
/// @brief The function pointer used for checking whether given value is valid.
|
||||
CheckFct_t m_CheckFct;
|
||||
|
||||
/**
|
||||
* @brief Check whether this constraint is valid for using.
|
||||
* @return
|
||||
* True if this constraint is valid, otherwise false.
|
||||
* If this function return false, it means that there is no contraint.
|
||||
* And you should not use this constraint provided any function pointer.
|
||||
*/
|
||||
bool IsValid() const {
|
||||
return m_CheckFct != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get constraint for arithmetic or enum values by minimum and maximum value range.
|
||||
* @tparam _Ty An arithmetic or enum type (except bool) of underlying stored value.
|
||||
* @param[in] min_value The minimum value of range (inclusive).
|
||||
* @param[in] max_value The maximum value of range (inclusive).
|
||||
* @return The generated constraint instance which can be directly applied.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
Constraint<_Ty> GetMinMaxRangeConstraint(_Ty min_value, _Ty max_value) {
|
||||
if (min_value > max_value)
|
||||
throw std::invalid_argument("invalid min max value for NumberRangeConstraint");
|
||||
return Constraint<_Ty> {
|
||||
[min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); }
|
||||
/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get constraint for enum values by enumerating all possible values.
|
||||
* @tparam _Ty An enum type (except bool) of underlying stored value.
|
||||
* @param[in] il An initializer list storing all possible values.
|
||||
* @return The generated constraint instance which can be directly applied.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_enum_v<_Ty>, int> = 0>
|
||||
Constraint<_Ty> GetEnumEnumerationConstraint(const std::initializer_list<_Ty>& il) {
|
||||
std::set<_Ty> data(il);
|
||||
return Constraint<_Ty> {
|
||||
[data](const _Ty& val) -> bool { return data.find(val) != data.end(); }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get constraint for string values by enumerating all possible values.
|
||||
* @param[in] il An initializer list storing all possible values.
|
||||
* @return The generated constraint instance which can be directly applied.
|
||||
* @remarks
|
||||
* Caller must make sure that the string view passed in initializer list is valid until this Constraint life time gone.
|
||||
* Becasue this generator will not copy your given string view into string.
|
||||
*/
|
||||
inline Constraint<yycc_u8string> GetStringEnumerationConstraint(const std::initializer_list<yycc_u8string_view>& il) {
|
||||
std::set<yycc_u8string_view> data(il);
|
||||
return Constraint<yycc_u8string> {
|
||||
[data](const yycc_u8string& val) -> bool { return data.find(yycc_u8string_view(val)) != data.end(); }
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
378
src/YYCCLegacy/DialogHelper.cpp
Normal file
378
src/YYCCLegacy/DialogHelper.cpp
Normal file
@ -0,0 +1,378 @@
|
||||
#include "DialogHelper.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
|
||||
namespace YYCC::DialogHelper {
|
||||
|
||||
#pragma region FileFilters
|
||||
|
||||
bool FileFilters::Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il) {
|
||||
// assign filter name
|
||||
if (filter_name == nullptr) return false;
|
||||
FilterName name(filter_name);
|
||||
|
||||
// assign filter patterns
|
||||
FilterModes modes;
|
||||
for (const yycc_char8_t* pattern : il) {
|
||||
if (pattern != nullptr)
|
||||
modes.emplace_back(yycc_u8string(pattern));
|
||||
}
|
||||
|
||||
// check filter patterns
|
||||
if (modes.empty()) return false;
|
||||
|
||||
// add into pairs and return
|
||||
m_Filters.emplace_back(std::make_pair(std::move(name), std::move(modes)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileFilters::Generate(WinFileFilters& win_result) const {
|
||||
// clear Windows oriented data
|
||||
win_result.Clear();
|
||||
|
||||
// build new Windows oriented string vector first
|
||||
for (const auto& it : m_Filters) {
|
||||
// convert name to wchar
|
||||
WinFileFilters::WinFilterName name;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first, name))
|
||||
return false;
|
||||
|
||||
// convert pattern and join them
|
||||
const auto& filter_modes = it.second;
|
||||
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";")));
|
||||
WinFileFilters::WinFilterModes modes;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes))
|
||||
return false;
|
||||
|
||||
// append new pair
|
||||
win_result.m_WinFilters.emplace_back(std::make_pair(name, modes));
|
||||
}
|
||||
|
||||
// check filter size
|
||||
// if it overflow the maximum value, return false
|
||||
size_t count = win_result.m_WinFilters.size();
|
||||
if (count > std::numeric_limits<UINT>::max())
|
||||
return false;
|
||||
|
||||
// create new win data struct
|
||||
// and assign string pointer from internal built win string vector.
|
||||
win_result.m_WinDataStruct.reset(new COMDLG_FILTERSPEC[count]);
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
win_result.m_WinDataStruct[i].pszName = win_result.m_WinFilters[i].first.c_str();
|
||||
win_result.m_WinDataStruct[i].pszSpec = win_result.m_WinFilters[i].second.c_str();
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region File Dialog
|
||||
|
||||
bool FileDialog::Generate(WinFileDialog& win_result) const {
|
||||
// clear Windows oriented data
|
||||
win_result.Clear();
|
||||
|
||||
// set owner
|
||||
win_result.m_WinOwner = m_Owner;
|
||||
|
||||
// build file filters
|
||||
if (!m_FileTypes.Generate(win_result.m_WinFileTypes))
|
||||
return false;
|
||||
|
||||
// check default file type index
|
||||
// check value overflow (comparing with >= because we need plus 1 for file type index later)
|
||||
if (m_DefaultFileTypeIndex >= std::numeric_limits<UINT>::max())
|
||||
return false;
|
||||
// check invalid index (overflow the length or registered file types if there is file type)
|
||||
if (m_FileTypes.Count() != 0u && m_DefaultFileTypeIndex >= m_FileTypes.Count())
|
||||
return false;
|
||||
// set index with additional plus according to Windows specification.
|
||||
win_result.m_WinDefaultFileTypeIndex = static_cast<UINT>(m_DefaultFileTypeIndex + 1);
|
||||
|
||||
// build title and init file name
|
||||
if (m_HasTitle) {
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_Title, win_result.m_WinTitle))
|
||||
return false;
|
||||
win_result.m_HasTitle = true;
|
||||
}
|
||||
if (m_HasInitFileName) {
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitFileName, win_result.m_WinInitFileName))
|
||||
return false;
|
||||
win_result.m_HasInitFileName = true;
|
||||
}
|
||||
|
||||
// fetch init directory
|
||||
if (m_HasInitDirectory) {
|
||||
// convert to wpath
|
||||
std::wstring w_init_directory;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitDirectory, w_init_directory))
|
||||
return false;
|
||||
|
||||
// fetch IShellItem*
|
||||
// Ref: https://stackoverflow.com/questions/76306324/how-to-set-default-folder-for-ifileopendialog-interface
|
||||
IShellItem* init_directory = NULL;
|
||||
HRESULT hr = SHCreateItemFromParsingName(w_init_directory.c_str(), NULL, IID_PPV_ARGS(&init_directory));
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// assign IShellItem*
|
||||
win_result.m_WinInitDirectory.reset(init_directory);
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Windows Dialog Code
|
||||
|
||||
enum class CommonFileDialogType {
|
||||
OpenFile,
|
||||
OpenMultipleFiles,
|
||||
SaveFile,
|
||||
OpenFolder
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Extract display name from given IShellItem*.
|
||||
* @param item[in] The pointer to IShellItem for extracting.
|
||||
* @param ret[out] Extracted display name container.
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks This is an assist function of CommonFileDialog.
|
||||
*/
|
||||
static bool ExtractDisplayName(IShellItem* item, yycc_u8string& ret) {
|
||||
// fetch display name from IShellItem*
|
||||
LPWSTR _name;
|
||||
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR display_name(_name);
|
||||
|
||||
// convert result
|
||||
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret))
|
||||
return false;
|
||||
|
||||
// finished
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief General file dialog.
|
||||
* @param params[in] User specified parameter controlling the behavior of this file dialog,
|
||||
* including title, file types and etc.
|
||||
* @param ret[out] The path to user selected files or folders.
|
||||
* For multiple selection, the count of items >= 1. For other scenario, the count of item is 1.
|
||||
* @return True if success, otherwise false (input parameters is wrong or user click "Cancel" in popup window).
|
||||
* @remarks This function is the real underlying function of all dialog functions.
|
||||
*/
|
||||
template<CommonFileDialogType EDialogType>
|
||||
static bool CommonFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
|
||||
// prepare result variable
|
||||
HRESULT hr;
|
||||
|
||||
// check whether COM environment has been initialized
|
||||
if (!COMHelper::IsInitialized()) return false;
|
||||
|
||||
// create file dialog instance
|
||||
// fetch dialog CLSID first
|
||||
CLSID dialog_clsid;
|
||||
switch (EDialogType) {
|
||||
case CommonFileDialogType::OpenFile:
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
dialog_clsid = CLSID_FileOpenDialog;
|
||||
break;
|
||||
case CommonFileDialogType::SaveFile:
|
||||
dialog_clsid = CLSID_FileSaveDialog;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
// create raw dialog pointer
|
||||
IFileDialog* _pfd = nullptr;
|
||||
hr = CoCreateInstance(
|
||||
dialog_clsid,
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&_pfd)
|
||||
);
|
||||
if (FAILED(hr)) return false;
|
||||
// create memory-safe dialog pointer
|
||||
COMHelper::SmartIFileDialog pfd(_pfd);
|
||||
|
||||
// set options for dialog
|
||||
// before setting, always get the options first in order.
|
||||
// not to override existing options.
|
||||
DWORD dwFlags;
|
||||
hr = pfd->GetOptions(&dwFlags);
|
||||
if (FAILED(hr)) return false;
|
||||
// modify options
|
||||
switch (EDialogType) {
|
||||
// We want user only can pick file system files: FOS_FORCEFILESYSTEM.
|
||||
// Open dialog default: FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR
|
||||
// Save dialog default: FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR
|
||||
// Pick folder: FOS_PICKFOLDERS
|
||||
case CommonFileDialogType::OpenFile:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
break;
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
dwFlags |= FOS_ALLOWMULTISELECT;
|
||||
break;
|
||||
case CommonFileDialogType::SaveFile:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
break;
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
dwFlags |= FOS_PICKFOLDERS;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
// set folder dialog options
|
||||
hr = pfd->SetOptions(dwFlags);
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// build Windows used file dialog parameters
|
||||
WinFileDialog win_params;
|
||||
if (!params.Generate(win_params))
|
||||
return false;
|
||||
|
||||
// setup title and init file name
|
||||
if (win_params.HasTitle()) {
|
||||
hr = pfd->SetTitle(win_params.GetTitle());
|
||||
if (FAILED(hr)) return false;
|
||||
}
|
||||
if (win_params.HasInitFileName()) {
|
||||
hr = pfd->SetFileName(win_params.GetInitFileName());
|
||||
if (FAILED(hr)) return false;
|
||||
}
|
||||
|
||||
// setup init directory
|
||||
if (win_params.HasInitDirectory()) {
|
||||
hr = pfd->SetFolder(win_params.GetInitDirectory());
|
||||
}
|
||||
|
||||
// set file types and default file index when we picking file
|
||||
if constexpr (EDialogType != CommonFileDialogType::OpenFolder) {
|
||||
// set file types list
|
||||
const auto& file_filters = win_params.GetFileTypes();
|
||||
hr = pfd->SetFileTypes(file_filters.GetFilterCount(), file_filters.GetFilterSpecs());
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// set default file type index
|
||||
hr = pfd->SetFileTypeIndex(win_params.GetDefaultFileTypeIndex());
|
||||
if (FAILED(hr)) return false;
|
||||
}
|
||||
|
||||
// show the dialog
|
||||
hr = pfd->Show(win_params.HasOwner() ? win_params.GetOwner() : nullptr);
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// obtain result when user click "OK" button.
|
||||
switch (EDialogType) {
|
||||
case CommonFileDialogType::OpenFile:
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
case CommonFileDialogType::SaveFile:
|
||||
{
|
||||
// obtain one file entry
|
||||
IShellItem* _item;
|
||||
hr = pfd->GetResult(&_item);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
|
||||
// extract display name
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name))
|
||||
return false;
|
||||
|
||||
// append result
|
||||
ret.emplace_back(std::move(result_name));
|
||||
}
|
||||
break;
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
{
|
||||
// try casting file dialog to file open dialog
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface
|
||||
IFileOpenDialog* _pfod = nullptr;
|
||||
hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod));
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIFileOpenDialog pfod(_pfod);
|
||||
|
||||
// obtain multiple file entires
|
||||
IShellItemArray* _items;
|
||||
hr = pfod->GetResults(&_items);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIShellItemArray result_items(_items);
|
||||
|
||||
// analyze file entries
|
||||
// get array count first
|
||||
DWORD result_items_count = 0u;
|
||||
hr = result_items->GetCount(&result_items_count);
|
||||
if (FAILED(hr)) return false;
|
||||
// iterate array
|
||||
for (DWORD i = 0u; i < result_items_count; ++i) {
|
||||
// fetch item by index
|
||||
IShellItem* _item;;
|
||||
hr = result_items->GetItemAt(i, &_item);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
|
||||
// extract display name
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name))
|
||||
return false;
|
||||
|
||||
// append result
|
||||
ret.emplace_back(std::move(result_name));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Wrapper Functions
|
||||
|
||||
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::OpenFile>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
|
||||
return CommonFileDialog<CommonFileDialogType::OpenMultipleFiles>(params, ret);
|
||||
}
|
||||
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::SaveFile>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
|
||||
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::OpenFolder>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
||||
|
||||
#endif
|
312
src/YYCCLegacy/DialogHelper.hpp
Normal file
312
src/YYCCLegacy/DialogHelper.hpp
Normal file
@ -0,0 +1,312 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include "COMHelper.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The namespace providing Windows universal dialog features.
|
||||
* @details
|
||||
* This namespace only available on Windows platform.
|
||||
* See also \ref dialog_helper.
|
||||
*/
|
||||
namespace YYCC::DialogHelper {
|
||||
|
||||
/**
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileFilters {
|
||||
friend class FileFilters;
|
||||
friend class WinFileDialog;
|
||||
public:
|
||||
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(WinFileFilters);
|
||||
|
||||
/// @brief Get the count of available file filters
|
||||
UINT GetFilterCount() const {
|
||||
return static_cast<UINT>(m_WinFilters.size());
|
||||
}
|
||||
/// @brief Get pointer to Windows used file filters declarations
|
||||
const COMDLG_FILTERSPEC* GetFilterSpecs() const {
|
||||
return m_WinDataStruct.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
using WinFilterModes = std::wstring;
|
||||
using WinFilterName = std::wstring;
|
||||
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
|
||||
|
||||
std::vector<WinFilterPair> m_WinFilters;
|
||||
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
|
||||
|
||||
/// @brief Clear all current file filters
|
||||
void Clear() {
|
||||
m_WinDataStruct.reset();
|
||||
m_WinFilters.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for programmer using.
|
||||
* But you don't need create it on your own.
|
||||
* You can simply fetch it by FileDialog::ConfigreFileTypes ,
|
||||
* because this class is a part of FileDialog.
|
||||
*/
|
||||
class FileFilters {
|
||||
public:
|
||||
FileFilters() : m_Filters() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(FileFilters);
|
||||
|
||||
/**
|
||||
* @brief Add a filter pair in file types list.
|
||||
* @param[in] filter_name The friendly name of the filter.
|
||||
* @param[in] il
|
||||
* A C++ initialize list containing acceptable file filter pattern.
|
||||
* Every entries must be `const yycc_char8_t*` representing a single filter pattern.
|
||||
* The list at least should have one valid pattern.
|
||||
* This function will not validate these filter patterns, so please write them carefully.
|
||||
* @return True if added success, otherwise false.
|
||||
* @remarks
|
||||
* This function allow you register multiple filter patterns for single friendly name.
|
||||
* For example: <TT>Add(u8"Microsoft Word (*.doc; *.docx)", {u8"*.doc", u8"*.docx"})</TT>
|
||||
*/
|
||||
bool Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il);
|
||||
/**
|
||||
* @brief Get the count of added filter pairs.
|
||||
* @return The count of already added filter pairs.
|
||||
*/
|
||||
size_t Count() const { return m_Filters.size(); }
|
||||
|
||||
/// @brief Clear filter pairs for following re-use.
|
||||
void Clear() { m_Filters.clear(); }
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param[out] win_result The class receiving the generated filter data struct.
|
||||
* @return True if generation success, otherwise false.
|
||||
* @remarks
|
||||
* Programmer should not call this function,
|
||||
* this function is used as YYCC internal code.
|
||||
*/
|
||||
bool Generate(WinFileFilters& win_result) const;
|
||||
|
||||
protected:
|
||||
using FilterModes = std::vector<yycc_u8string>;
|
||||
using FilterName = yycc_u8string;
|
||||
using FilterPair = std::pair<FilterName, FilterModes>;
|
||||
|
||||
std::vector<FilterPair> m_Filters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileDialog {
|
||||
friend class FileDialog;
|
||||
public:
|
||||
WinFileDialog() :
|
||||
m_WinOwner(NULL),
|
||||
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
|
||||
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
|
||||
m_WinInitDirectory(nullptr) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(WinFileDialog);
|
||||
|
||||
/// @brief Get whether this dialog has owner.
|
||||
bool HasOwner() const { return m_WinOwner != NULL; }
|
||||
/// @brief Get the \c HWND of dialog owner.
|
||||
HWND GetOwner() const { return m_WinOwner; }
|
||||
|
||||
/// @brief Get the struct holding Windows used file filters data.
|
||||
const WinFileFilters& GetFileTypes() const { return m_WinFileTypes; }
|
||||
/// @brief Get the index of default selected file filter.
|
||||
UINT GetDefaultFileTypeIndex() const { return m_WinDefaultFileTypeIndex; }
|
||||
|
||||
/// @brief Get whether dialog has custom title.
|
||||
bool HasTitle() const { return m_HasTitle; }
|
||||
/// @brief Get custom title of dialog.
|
||||
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
|
||||
/// @brief Get whether dialog has custom initial file name.
|
||||
bool HasInitFileName() const { return m_HasInitFileName; }
|
||||
/// @brief Get custom initial file name of dialog
|
||||
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); }
|
||||
|
||||
/// @brief Get whether dialog has custom initial directory.
|
||||
bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
|
||||
/// @brief Get custom initial directory of dialog.
|
||||
IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); }
|
||||
|
||||
protected:
|
||||
HWND m_WinOwner;
|
||||
WinFileFilters m_WinFileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* This is 1-based index according to Windows specification.
|
||||
* In other words, when generating this struct from FileDialog to this struct this field should plus 1.
|
||||
* Because the same field located in FileDialog is 0-based index.
|
||||
*/
|
||||
UINT m_WinDefaultFileTypeIndex;
|
||||
bool m_HasTitle, m_HasInitFileName;
|
||||
std::wstring m_WinTitle, m_WinInitFileName;
|
||||
COMHelper::SmartIShellItem m_WinInitDirectory;
|
||||
|
||||
/// @brief Clear all data and reset them to default value.
|
||||
void Clear() {
|
||||
m_WinOwner = nullptr;
|
||||
m_WinFileTypes.Clear();
|
||||
m_WinDefaultFileTypeIndex = 0u;
|
||||
m_HasTitle = m_HasInitFileName = false;
|
||||
m_WinTitle.clear();
|
||||
m_WinInitFileName.clear();
|
||||
m_WinInitDirectory.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for programming using to describe every aspectes of the dialog.
|
||||
* For how to use this struct, see \ref dialog_helper.
|
||||
*/
|
||||
class FileDialog {
|
||||
public:
|
||||
FileDialog() :
|
||||
m_Owner(NULL),
|
||||
m_FileTypes(),
|
||||
m_DefaultFileTypeIndex(0u),
|
||||
m_Title(), m_InitFileName(), m_InitDirectory(),
|
||||
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(FileDialog);
|
||||
|
||||
/**
|
||||
* @brief Set the owner of dialog.
|
||||
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
|
||||
*/
|
||||
void SetOwner(HWND owner) { m_Owner = owner; }
|
||||
/**
|
||||
* @brief Set custom title of dialog
|
||||
* @param[in] title The string pointer to custom title, or nullptr to remove it.
|
||||
*/
|
||||
void SetTitle(const yycc_char8_t* title) {
|
||||
if (m_HasTitle = title != nullptr)
|
||||
m_Title = title;
|
||||
}
|
||||
/**
|
||||
* @brief Fetch the struct describing file filters for future configuration.
|
||||
* @return The reference to the struct describing file filters.
|
||||
*/
|
||||
FileFilters& ConfigreFileTypes() {
|
||||
return m_FileTypes;
|
||||
}
|
||||
/**
|
||||
* @brief Set the index of default selected file filter.
|
||||
* @param[in] idx
|
||||
* The index to default one.
|
||||
* This must be a valid index in file filters.
|
||||
*/
|
||||
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
|
||||
/**
|
||||
* @brief Set the initial file name of dialog
|
||||
* @details If set, the file name will always be same one when opening dialog.
|
||||
* @param[in] init_filename String pointer to initial file name, or nullptr to remove it.
|
||||
*/
|
||||
void SetInitFileName(const yycc_char8_t* init_filename) {
|
||||
if (m_HasInitFileName = init_filename != nullptr)
|
||||
m_InitFileName = init_filename;
|
||||
}
|
||||
/**
|
||||
* @brief Set the initial directory of dialog
|
||||
* @details If set, the opended directory will always be the same one when opening dialog
|
||||
* @param[in] init_dir
|
||||
* String pointer to initial directory.
|
||||
* Invalid path or nullptr will remove this feature.
|
||||
*/
|
||||
void SetInitDirectory(const yycc_char8_t* init_dir) {
|
||||
if (m_HasInitDirectory = init_dir != nullptr)
|
||||
m_InitDirectory = init_dir;
|
||||
}
|
||||
|
||||
/// @brief Clear file dialog parameters for following re-use.
|
||||
void Clear() {
|
||||
m_Owner = nullptr;
|
||||
m_HasTitle = m_HasInitFileName = m_HasInitDirectory = false;
|
||||
m_Title.clear();
|
||||
m_InitFileName.clear();
|
||||
m_InitDirectory.clear();
|
||||
m_FileTypes.Clear();
|
||||
m_DefaultFileTypeIndex = 0u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param[out] win_result The class receiving the generated filter data struct.
|
||||
* @return True if generation is success, otherwise false.
|
||||
* @remarks
|
||||
* Programmer should not call this function.
|
||||
* This function is used as YYCC internal code.
|
||||
*/
|
||||
bool Generate(WinFileDialog& win_result) const;
|
||||
|
||||
protected:
|
||||
HWND m_Owner;
|
||||
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
|
||||
yycc_u8string m_Title, m_InitFileName, m_InitDirectory;
|
||||
FileFilters m_FileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* The index Windows used is 1-based index.
|
||||
* But for universal experience, we order this is 0-based index.
|
||||
* And do convertion when generating Windows used struct.
|
||||
*/
|
||||
size_t m_DefaultFileTypeIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected file.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select multiple file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret The list of full path of user selected files.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to save.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected file.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single directory to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected directory.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
409
src/YYCCLegacy/EncodingHelper.cpp
Normal file
409
src/YYCCLegacy/EncodingHelper.cpp
Normal file
@ -0,0 +1,409 @@
|
||||
#include "EncodingHelper.hpp"
|
||||
|
||||
#include <locale>
|
||||
|
||||
namespace YYCC::EncodingHelper {
|
||||
|
||||
#pragma region UTF8 Ordinary Convertion
|
||||
|
||||
const yycc_char8_t* ToUTF8(const char* src) {
|
||||
return reinterpret_cast<const yycc_char8_t*>(src);
|
||||
}
|
||||
yycc_char8_t* ToUTF8(char* src) {
|
||||
return reinterpret_cast<yycc_char8_t*>(src);
|
||||
}
|
||||
yycc_u8string ToUTF8(const std::string_view& src) {
|
||||
return yycc_u8string(reinterpret_cast<const yycc_char8_t*>(src.data()), src.size());
|
||||
}
|
||||
yycc_u8string_view ToUTF8View(const std::string_view& src) {
|
||||
return yycc_u8string_view(reinterpret_cast<const yycc_char8_t*>(src.data()), src.size());
|
||||
}
|
||||
|
||||
const char* ToOrdinary(const yycc_char8_t* src) {
|
||||
return reinterpret_cast<const char*>(src);
|
||||
}
|
||||
char* ToOrdinary(yycc_char8_t* src) {
|
||||
return reinterpret_cast<char*>(src);
|
||||
}
|
||||
std::string ToOrdinary(const yycc_u8string_view& src) {
|
||||
return std::string(reinterpret_cast<const char*>(src.data()), src.size());
|
||||
}
|
||||
std::string_view ToOrdinaryView(const yycc_u8string_view& src) {
|
||||
return std::string_view(reinterpret_cast<const char*>(src.data()), src.size());
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
/* Define some assistant macros for easy writing. */
|
||||
|
||||
#define CONVFCT_TYPE2(fct_name, src_char_type, dst_char_type, ...) if (src == nullptr) return false; \
|
||||
std::basic_string_view<src_char_type> cache(src); \
|
||||
return fct_name(cache, dst, ##__VA_ARGS__);
|
||||
|
||||
#define CONVFCT_TYPE3(fct_name, src_char_type, dst_char_type, ...) std::basic_string<dst_char_type> ret; \
|
||||
if (!fct_name(src, ret, ##__VA_ARGS__)) ret.clear(); \
|
||||
return ret;
|
||||
|
||||
#define CONVFCT_TYPE4(fct_name, src_char_type, dst_char_type, ...) std::basic_string<dst_char_type> ret; \
|
||||
if (!fct_name(src, ret, ##__VA_ARGS__)) ret.clear(); \
|
||||
return ret;
|
||||
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#pragma region WcharToChar
|
||||
|
||||
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page) {
|
||||
// if src is empty, direct output
|
||||
if (src.empty()) {
|
||||
dst.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// init WideCharToMultiByte used variables
|
||||
// setup src pointer
|
||||
LPCWCH lpWideCharStr = reinterpret_cast<LPCWCH>(src.data());
|
||||
// check whether source string is too large.
|
||||
size_t cSrcSize = src.size();
|
||||
if (cSrcSize > std::numeric_limits<int>::max()) return false;
|
||||
int cchWideChar = static_cast<int>(src.size());
|
||||
|
||||
// do convertion
|
||||
// do a dry-run first to fetch desired size.
|
||||
int desired_size = WideCharToMultiByte(code_page, 0, lpWideCharStr, cchWideChar, NULL, 0, NULL, NULL);
|
||||
if (desired_size <= 0) return false;
|
||||
// resize dest for receiving result
|
||||
dst.resize(static_cast<size_t>(desired_size));
|
||||
// do real convertion
|
||||
int write_result = WideCharToMultiByte(code_page, 0, lpWideCharStr, cchWideChar, reinterpret_cast<LPSTR>(dst.data()), desired_size, NULL, NULL);
|
||||
if (write_result <= 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page) {
|
||||
CONVFCT_TYPE2(WcharToChar, wchar_t, char, code_page);
|
||||
}
|
||||
std::string WcharToChar(const std::wstring_view& src, UINT code_page) {
|
||||
CONVFCT_TYPE3(WcharToChar, wchar_t, char, code_page);
|
||||
}
|
||||
std::string WcharToChar(const wchar_t* src, UINT code_page) {
|
||||
CONVFCT_TYPE4(WcharToChar, wchar_t, char, code_page);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region CharToWchar
|
||||
|
||||
bool CharToWchar(const std::string_view& src, std::wstring& dst, UINT code_page) {
|
||||
// if src is empty, direct output
|
||||
if (src.empty()) {
|
||||
dst.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// init WideCharToMultiByte used variables
|
||||
// setup src pointer
|
||||
LPCCH lpMultiByteStr = reinterpret_cast<LPCCH>(src.data());
|
||||
// check whether source string is too large.
|
||||
size_t cSrcSize = src.size();
|
||||
if (cSrcSize > std::numeric_limits<int>::max()) return false;
|
||||
int cbMultiByte = static_cast<int>(src.size());
|
||||
|
||||
// do convertion
|
||||
// do a dry-run first to fetch desired size.
|
||||
int desired_size = MultiByteToWideChar(code_page, 0, lpMultiByteStr, cbMultiByte, NULL, 0);
|
||||
if (desired_size <= 0) return false;
|
||||
// resize dest for receiving result
|
||||
dst.resize(static_cast<size_t>(desired_size));
|
||||
// do real convertion
|
||||
int write_result = MultiByteToWideChar(code_page, 0, lpMultiByteStr, cbMultiByte, reinterpret_cast<LPWSTR>(dst.data()), desired_size);
|
||||
if (write_result <= 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
bool CharToWchar(const char* src, std::wstring& dst, UINT code_page) {
|
||||
CONVFCT_TYPE2(CharToWchar, char, wchar_t, code_page);
|
||||
}
|
||||
std::wstring CharToWchar(const std::string_view& src, UINT code_page) {
|
||||
CONVFCT_TYPE3(CharToWchar, char, wchar_t, code_page);
|
||||
}
|
||||
std::wstring CharToWchar(const char* src, UINT code_page) {
|
||||
CONVFCT_TYPE4(CharToWchar, char, wchar_t, code_page);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region CharToChar
|
||||
|
||||
bool CharToChar(const std::string_view& src, std::string& dst, UINT src_code_page, UINT dst_code_page) {
|
||||
std::wstring intermediary;
|
||||
if (!CharToWchar(src, intermediary, src_code_page)) return false;
|
||||
if (!WcharToChar(intermediary, dst, dst_code_page)) return false;
|
||||
return true;
|
||||
}
|
||||
bool CharToChar(const char* src, std::string& dst, UINT src_code_page, UINT dst_code_page) {
|
||||
CONVFCT_TYPE2(CharToChar, char, char, src_code_page, dst_code_page);
|
||||
}
|
||||
std::string CharToChar(const std::string_view& src, UINT src_code_page, UINT dst_code_page) {
|
||||
CONVFCT_TYPE3(CharToChar, char, char, src_code_page, dst_code_page);
|
||||
}
|
||||
std::string CharToChar(const char* src, UINT src_code_page, UINT dst_code_page) {
|
||||
CONVFCT_TYPE4(CharToChar, char, char, src_code_page, dst_code_page);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region WcharToUTF8
|
||||
|
||||
bool WcharToUTF8(const std::wstring_view& src, yycc_u8string& dst) {
|
||||
std::string adapted_dst;
|
||||
bool ret = WcharToChar(src, adapted_dst, CP_UTF8);
|
||||
if (ret) dst = ToUTF8(adapted_dst);
|
||||
return ret;
|
||||
}
|
||||
bool WcharToUTF8(const wchar_t* src, yycc_u8string& dst) {
|
||||
CONVFCT_TYPE2(WcharToUTF8, wchar_t, yycc_char8_t);
|
||||
}
|
||||
yycc_u8string WcharToUTF8(const std::wstring_view& src) {
|
||||
CONVFCT_TYPE3(WcharToUTF8, wchar_t, yycc_char8_t);
|
||||
}
|
||||
yycc_u8string WcharToUTF8(const wchar_t* src) {
|
||||
CONVFCT_TYPE4(WcharToUTF8, wchar_t, yycc_char8_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8ToWchar
|
||||
|
||||
bool UTF8ToWchar(const yycc_u8string_view& src, std::wstring& dst) {
|
||||
std::string_view adapted_src(ToOrdinaryView(src));
|
||||
return CharToWchar(adapted_src, dst, CP_UTF8);
|
||||
}
|
||||
bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst) {
|
||||
CONVFCT_TYPE2(UTF8ToWchar, yycc_char8_t, wchar_t);
|
||||
}
|
||||
std::wstring UTF8ToWchar(const yycc_u8string_view& src) {
|
||||
CONVFCT_TYPE3(UTF8ToWchar, yycc_char8_t, wchar_t);
|
||||
}
|
||||
std::wstring UTF8ToWchar(const yycc_char8_t* src) {
|
||||
CONVFCT_TYPE4(UTF8ToWchar, yycc_char8_t, wchar_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region CharToUTF8
|
||||
|
||||
bool CharToUTF8(const std::string_view& src, yycc_u8string& dst, UINT code_page) {
|
||||
std::string adapted_dst;
|
||||
bool ret = CharToChar(src, adapted_dst, code_page, CP_UTF8);
|
||||
if (ret) dst = ToUTF8(adapted_dst);
|
||||
return ret;
|
||||
}
|
||||
bool CharToUTF8(const char* src, yycc_u8string& dst, UINT code_page) {
|
||||
CONVFCT_TYPE2(CharToUTF8, char, yycc_char8_t, code_page);
|
||||
}
|
||||
yycc_u8string CharToUTF8(const std::string_view& src, UINT code_page) {
|
||||
CONVFCT_TYPE3(CharToUTF8, char, yycc_char8_t, code_page);
|
||||
}
|
||||
yycc_u8string CharToUTF8(const char* src, UINT code_page) {
|
||||
CONVFCT_TYPE4(CharToUTF8, char, yycc_char8_t, code_page);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8ToChar
|
||||
|
||||
bool UTF8ToChar(const yycc_u8string_view& src, std::string& dst, UINT code_page) {
|
||||
std::string_view adapted_src(ToOrdinaryView(src));
|
||||
return CharToChar(adapted_src, dst, CP_UTF8, code_page);
|
||||
}
|
||||
bool UTF8ToChar(const yycc_char8_t* src, std::string& dst, UINT code_page) {
|
||||
CONVFCT_TYPE2(UTF8ToChar, yycc_char8_t, char, code_page);
|
||||
}
|
||||
std::string UTF8ToChar(const yycc_u8string_view& src, UINT code_page) {
|
||||
CONVFCT_TYPE3(UTF8ToChar, yycc_char8_t, char, code_page);
|
||||
}
|
||||
std::string UTF8ToChar(const yycc_char8_t* src, UINT code_page) {
|
||||
CONVFCT_TYPE4(UTF8ToChar, yycc_char8_t, char, code_page);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#pragma region UTF8 UTF16 UTF32 Help Funcs
|
||||
|
||||
/*
|
||||
According to the documentation introduced in CppReference.
|
||||
The standard library is guaranteed to provide several specific specializations of \c std::codecvt.
|
||||
The UTF8 char type in UTF8 related specializations of \c std::codecvt is different.
|
||||
It is also independend from we defined \c yycc_char8_t.
|
||||
So it is essential define a type which can correctly trigger specific specializations of \c std::codecv in there.
|
||||
*/
|
||||
#if defined(__cpp_char8_t)
|
||||
using CodecvtUTF8Char_t = char8_t;
|
||||
#else
|
||||
using CodecvtUTF8Char_t = char;
|
||||
#endif
|
||||
|
||||
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
|
||||
using CodecvtFacet_t = std::codecvt<_TChar, CodecvtUTF8Char_t, std::mbstate_t>;
|
||||
|
||||
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
|
||||
static bool UTF8ToUTFOther(const yycc_u8string_view& src, std::basic_string<_TChar>& dst) {
|
||||
// Reference:
|
||||
// https://zh.cppreference.com/w/cpp/locale/codecvt/in
|
||||
|
||||
// if src is empty, return directly
|
||||
if (src.empty()) {
|
||||
dst.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// init locale and get codecvt facet
|
||||
// same reason in UTFOtherToUTF8 to keeping reference to locale
|
||||
const auto& this_locale = std::locale::classic();
|
||||
const auto& this_codecvt = std::use_facet<CodecvtFacet_t<_TChar>>(this_locale);
|
||||
|
||||
// convertion preparation
|
||||
std::mbstate_t mb{};
|
||||
dst.resize(src.size());
|
||||
const CodecvtUTF8Char_t* intern_from = reinterpret_cast<const CodecvtUTF8Char_t*>(src.data()),
|
||||
*intern_from_end = reinterpret_cast<const CodecvtUTF8Char_t*>(src.data() + src.size()),
|
||||
*intern_from_next = nullptr;
|
||||
_TChar* extern_to = dst.data(),
|
||||
*extern_to_end = dst.data() + dst.size(),
|
||||
*extern_to_next = nullptr;
|
||||
// do convertion
|
||||
auto result = this_codecvt.in(
|
||||
mb,
|
||||
intern_from, intern_from_end, intern_from_next,
|
||||
extern_to, extern_to_end, extern_to_next
|
||||
);
|
||||
|
||||
// check result
|
||||
if (result != CodecvtFacet_t<_TChar>::ok)
|
||||
return false;
|
||||
// resize result and return
|
||||
dst.resize(extern_to_next - dst.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
|
||||
static bool UTFOtherToUTF8(const std::basic_string_view<_TChar>& src, yycc_u8string& dst) {
|
||||
// Reference:
|
||||
// https://zh.cppreference.com/w/cpp/locale/codecvt/out
|
||||
|
||||
// if src is empty, return directly
|
||||
if (src.empty()) {
|
||||
dst.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
// init locale and get codecvt facet
|
||||
// the reference to locale must be preserved until convertion done.
|
||||
// because the life time of codecvt facet is equal to the reference to locale.
|
||||
const auto& this_locale = std::locale::classic();
|
||||
const auto& this_codecvt = std::use_facet<CodecvtFacet_t<_TChar>>(this_locale);
|
||||
|
||||
// do convertion preparation
|
||||
std::mbstate_t mb{};
|
||||
dst.resize(src.size() * this_codecvt.max_length());
|
||||
const _TChar* intern_from = src.data(),
|
||||
*intern_from_end = src.data() + src.size(),
|
||||
*intern_from_next = nullptr;
|
||||
CodecvtUTF8Char_t* extern_to = reinterpret_cast<CodecvtUTF8Char_t*>(dst.data()),
|
||||
*extern_to_end = reinterpret_cast<CodecvtUTF8Char_t*>(dst.data() + dst.size()),
|
||||
*extern_to_next = nullptr;
|
||||
// do convertion
|
||||
auto result = this_codecvt.out(
|
||||
mb,
|
||||
intern_from, intern_from_end, intern_from_next,
|
||||
extern_to, extern_to_end, extern_to_next
|
||||
);
|
||||
|
||||
// check result
|
||||
if (result != CodecvtFacet_t<_TChar>::ok)
|
||||
return false;
|
||||
// resize result and retuen
|
||||
dst.resize(extern_to_next - reinterpret_cast<CodecvtUTF8Char_t*>(dst.data()));
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8ToUTF16
|
||||
|
||||
bool UTF8ToUTF16(const yycc_u8string_view& src, std::u16string& dst) {
|
||||
return UTF8ToUTFOther<char16_t>(src, dst);
|
||||
}
|
||||
bool UTF8ToUTF16(const yycc_char8_t* src, std::u16string& dst) {
|
||||
CONVFCT_TYPE2(UTF8ToUTF16, yycc_char8_t, char16_t);
|
||||
}
|
||||
std::u16string UTF8ToUTF16(const yycc_u8string_view& src) {
|
||||
CONVFCT_TYPE3(UTF8ToUTF16, yycc_char8_t, char16_t);
|
||||
}
|
||||
std::u16string UTF8ToUTF16(const yycc_char8_t* src) {
|
||||
CONVFCT_TYPE4(UTF8ToUTF16, yycc_char8_t, char16_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF16ToUTF8
|
||||
|
||||
bool UTF16ToUTF8(const std::u16string_view& src, yycc_u8string& dst) {
|
||||
return UTFOtherToUTF8<char16_t>(src, dst);
|
||||
}
|
||||
bool UTF16ToUTF8(const char16_t* src, yycc_u8string& dst) {
|
||||
CONVFCT_TYPE2(UTF16ToUTF8, char16_t, yycc_char8_t);
|
||||
}
|
||||
yycc_u8string UTF16ToUTF8(const std::u16string_view& src) {
|
||||
CONVFCT_TYPE3(UTF16ToUTF8, char16_t, yycc_char8_t);
|
||||
}
|
||||
yycc_u8string UTF16ToUTF8(const char16_t* src) {
|
||||
CONVFCT_TYPE4(UTF16ToUTF8, char16_t, yycc_char8_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8ToUTF32
|
||||
|
||||
bool UTF8ToUTF32(const yycc_u8string_view& src, std::u32string& dst) {
|
||||
return UTF8ToUTFOther<char32_t>(src, dst);
|
||||
}
|
||||
bool UTF8ToUTF32(const yycc_char8_t* src, std::u32string& dst) {
|
||||
CONVFCT_TYPE2(UTF8ToUTF32, yycc_char8_t, char32_t);
|
||||
}
|
||||
std::u32string UTF8ToUTF32(const yycc_u8string_view& src) {
|
||||
CONVFCT_TYPE3(UTF8ToUTF32, yycc_char8_t, char32_t);
|
||||
}
|
||||
std::u32string UTF8ToUTF32(const yycc_char8_t* src) {
|
||||
CONVFCT_TYPE4(UTF8ToUTF32, yycc_char8_t, char32_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF32ToUTF8
|
||||
|
||||
bool UTF32ToUTF8(const std::u32string_view& src, yycc_u8string& dst) {
|
||||
return UTFOtherToUTF8<char32_t>(src, dst);
|
||||
}
|
||||
bool UTF32ToUTF8(const char32_t* src, yycc_u8string& dst) {
|
||||
CONVFCT_TYPE2(UTF32ToUTF8, char32_t, yycc_char8_t);
|
||||
}
|
||||
yycc_u8string UTF32ToUTF8(const std::u32string_view& src) {
|
||||
CONVFCT_TYPE3(UTF32ToUTF8, char32_t, yycc_char8_t);
|
||||
}
|
||||
yycc_u8string UTF32ToUTF8(const char32_t* src) {
|
||||
CONVFCT_TYPE4(UTF32ToUTF8, char32_t, yycc_char8_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#undef CONVFCT_TYPE2
|
||||
#undef CONVFCT_TYPE3
|
||||
#undef CONVFCT_TYPE4
|
||||
|
||||
}
|
||||
|
95
src/YYCCLegacy/EncodingHelper.hpp
Normal file
95
src/YYCCLegacy/EncodingHelper.hpp
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The helper for all encoding stuff.
|
||||
* @details
|
||||
* For more infomations about how to use the functions provided by this namespace,
|
||||
* please see \ref library_encoding and \ref encoding_helper.
|
||||
*/
|
||||
namespace YYCC::EncodingHelper {
|
||||
|
||||
#define _YYCC_U8(strl) u8 ## strl ///< The assistant macro for YYCC_U8.
|
||||
#define YYCC_U8(strl) (reinterpret_cast<const ::YYCC::yycc_char8_t*>(_YYCC_U8(strl))) ///< The macro for creating UTF8 string literal. See \ref library_encoding.
|
||||
#define YYCC_U8_CHAR(chr) (static_cast<YYCC::yycc_char8_t>(chr)) ///< The macro for casting ordinary char type into YYCC UTF8 char type.
|
||||
|
||||
const yycc_char8_t* ToUTF8(const char* src);
|
||||
yycc_char8_t* ToUTF8(char* src);
|
||||
yycc_u8string ToUTF8(const std::string_view& src);
|
||||
yycc_u8string_view ToUTF8View(const std::string_view& src);
|
||||
|
||||
const char* ToOrdinary(const yycc_char8_t* src);
|
||||
char* ToOrdinary(yycc_char8_t* src);
|
||||
std::string ToOrdinary(const yycc_u8string_view& src);
|
||||
std::string_view ToOrdinaryView(const yycc_u8string_view& src);
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page);
|
||||
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page);
|
||||
std::string WcharToChar(const std::wstring_view& src, UINT code_page);
|
||||
std::string WcharToChar(const wchar_t* src, UINT code_page);
|
||||
|
||||
bool CharToWchar(const std::string_view& src, std::wstring& dst, UINT code_page);
|
||||
bool CharToWchar(const char* src, std::wstring& dst, UINT code_page);
|
||||
std::wstring CharToWchar(const std::string_view& src, UINT code_page);
|
||||
std::wstring CharToWchar(const char* src, UINT code_page);
|
||||
|
||||
bool CharToChar(const std::string_view& src, std::string& dst, UINT src_code_page, UINT dst_code_page);
|
||||
bool CharToChar(const char* src, std::string& dst, UINT src_code_page, UINT dst_code_page);
|
||||
std::string CharToChar(const std::string_view& src, UINT src_code_page, UINT dst_code_page);
|
||||
std::string CharToChar(const char* src, UINT src_code_page, UINT dst_code_page);
|
||||
|
||||
|
||||
bool WcharToUTF8(const std::wstring_view& src, yycc_u8string& dst);
|
||||
bool WcharToUTF8(const wchar_t* src, yycc_u8string& dst);
|
||||
yycc_u8string WcharToUTF8(const std::wstring_view& src);
|
||||
yycc_u8string WcharToUTF8(const wchar_t* src);
|
||||
|
||||
bool UTF8ToWchar(const yycc_u8string_view& src, std::wstring& dst);
|
||||
bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst);
|
||||
std::wstring UTF8ToWchar(const yycc_u8string_view& src);
|
||||
std::wstring UTF8ToWchar(const yycc_char8_t* src);
|
||||
|
||||
bool CharToUTF8(const std::string_view& src, yycc_u8string& dst, UINT code_page);
|
||||
bool CharToUTF8(const char* src, yycc_u8string& dst, UINT code_page);
|
||||
yycc_u8string CharToUTF8(const std::string_view& src, UINT code_page);
|
||||
yycc_u8string CharToUTF8(const char* src, UINT code_page);
|
||||
|
||||
bool UTF8ToChar(const yycc_u8string_view& src, std::string& dst, UINT code_page);
|
||||
bool UTF8ToChar(const yycc_char8_t* src, std::string& dst, UINT code_page);
|
||||
std::string UTF8ToChar(const yycc_u8string_view& src, UINT code_page);
|
||||
std::string UTF8ToChar(const yycc_char8_t* src, UINT code_page);
|
||||
|
||||
#endif
|
||||
|
||||
bool UTF8ToUTF16(const yycc_u8string_view& src, std::u16string& dst);
|
||||
bool UTF8ToUTF16(const yycc_char8_t* src, std::u16string& dst);
|
||||
std::u16string UTF8ToUTF16(const yycc_u8string_view& src);
|
||||
std::u16string UTF8ToUTF16(const yycc_char8_t* src);
|
||||
|
||||
bool UTF16ToUTF8(const std::u16string_view& src, yycc_u8string& dst);
|
||||
bool UTF16ToUTF8(const char16_t* src, yycc_u8string& dst);
|
||||
yycc_u8string UTF16ToUTF8(const std::u16string_view& src);
|
||||
yycc_u8string UTF16ToUTF8(const char16_t* src);
|
||||
|
||||
|
||||
bool UTF8ToUTF32(const yycc_u8string_view& src, std::u32string& dst);
|
||||
bool UTF8ToUTF32(const yycc_char8_t* src, std::u32string& dst);
|
||||
std::u32string UTF8ToUTF32(const yycc_u8string_view& src);
|
||||
std::u32string UTF8ToUTF32(const yycc_char8_t* src);
|
||||
|
||||
bool UTF32ToUTF8(const std::u32string_view& src, yycc_u8string& dst);
|
||||
bool UTF32ToUTF8(const char32_t* src, yycc_u8string& dst);
|
||||
yycc_u8string UTF32ToUTF8(const std::u32string_view& src);
|
||||
yycc_u8string UTF32ToUTF8(const char32_t* src);
|
||||
|
||||
}
|
182
src/YYCCLegacy/EnumHelper.hpp
Normal file
182
src/YYCCLegacy/EnumHelper.hpp
Normal file
@ -0,0 +1,182 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
/**
|
||||
* @brief The namespace for convenient C++ enum class logic operations.
|
||||
* @details
|
||||
* C++ enum class statement is a modern way to declare enum in C++.
|
||||
* But it lack essential logic operations which is commonly used by programmer.
|
||||
* So we create this helper to resolve this issue.
|
||||
*/
|
||||
namespace YYCC::EnumHelper {
|
||||
|
||||
//// Reference:
|
||||
//// Enum operator overload: https://stackoverflow.com/a/71107019
|
||||
//// Constexpr operator overload: https://stackoverflow.com/a/17746099
|
||||
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator|(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) | static_cast<ut>(rhs));
|
||||
//}
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator|=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs | rhs;
|
||||
// return lhs;
|
||||
//}
|
||||
//
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator&(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) & static_cast<ut>(rhs));
|
||||
//}
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator&=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs & rhs;
|
||||
// return lhs;
|
||||
//}
|
||||
//
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator^(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) ^ static_cast<ut>(rhs));
|
||||
//}
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator^=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs ^ rhs;
|
||||
// return lhs;
|
||||
//}
|
||||
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr TEnum operator~(TEnum lhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(~(static_cast<ut>(lhs)));
|
||||
//}
|
||||
//
|
||||
//template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
//inline constexpr bool operator bool(TEnum lhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<bool>(static_cast<ut>(lhs));
|
||||
//}
|
||||
|
||||
/**
|
||||
* @brief The helper struct to check all given template argument are the same enum type.
|
||||
* @tparam TEnum The template parameter to be checked (first one).
|
||||
* @tparam Ts The template parameter to be checked.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
struct all_enum_values {
|
||||
public:
|
||||
// Please note it is std::is_same, not std::is_same_v!
|
||||
// That's std::conjunction_v required.
|
||||
static constexpr bool value = std::is_enum_v<std::remove_cv_t<TEnum>> && std::conjunction_v<std::is_same<std::remove_cv_t<TEnum>, std::remove_cv_t<Ts>>...>;
|
||||
};
|
||||
/**
|
||||
* @brief The convenient calling to all_enum_values::value to check enum template parameter.
|
||||
* @tparam TEnum The template parameter to be checked (first one).
|
||||
* @tparam Ts The template parameter to be checked.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
inline constexpr bool all_enum_values_v = all_enum_values<TEnum, Ts...>::value;
|
||||
|
||||
/**
|
||||
* @brief Merge given enum flags like performing <TT>e1 | e2 | ... | en</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] val The first enum flag to be merged.
|
||||
* @param[in] val_left Left enum flags to be merged.
|
||||
* @return The merged enum flag.
|
||||
* @remarks
|
||||
* This function use recursive expansion to get final merge result.
|
||||
* So there is no difference of each arguments.
|
||||
* We independ first argument just served for expansion.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum, Ts...>, int> = 0>
|
||||
constexpr TEnum Merge(TEnum val, Ts... val_left) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
ut result = static_cast<ut>(val);
|
||||
if constexpr (sizeof...(val_left) > 0) {
|
||||
result |= static_cast<ut>(Merge(val_left...));
|
||||
}
|
||||
return static_cast<TEnum>(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reverse given enum flags like performing <TT>~(e)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] e The list of enum flags to be inversed.
|
||||
* @return The inversed enum flag.
|
||||
*/
|
||||
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
constexpr TEnum Invert(TEnum e) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<TEnum>(~(static_cast<ut>(e)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Use specified enum flag to mask given enum flag like performing <TT>e1 &= e2</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flags to be masked.
|
||||
* @param[in] e2 The mask enum flag.
|
||||
*/
|
||||
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
constexpr void Mask(TEnum& e1, TEnum e2) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(e2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add multiple enum flags to given enum flag like performing <TT>e1 |= (e2 | e3 | ... | en)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flag which flags add on.
|
||||
* @param[in] vals The enum flag to be added.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum, Ts...>, int> = 0>
|
||||
constexpr void Add(TEnum& e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) | static_cast<ut>(Merge(vals...)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove multiple enum flags from given enum flag like performing <TT>e1 &= ~(e2 | e3 | ... | en)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flag which flags removed from.
|
||||
* @param[in] vals The enum flag to be removed.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts, std::enable_if_t<all_enum_values_v<TEnum>, int> = 0>
|
||||
constexpr void Remove(TEnum& e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(Invert(Merge(vals...))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether given enum flag has any of specified multiple enum flags (OR) like performing <TT>bool(e1 & (e2 | e3 | ... | en))</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] e1 The enum flag where we check.
|
||||
* @param[in] vals The enum flags for checking.
|
||||
* @return True if it has any of given flags (OR), otherwise false.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
constexpr bool Has(TEnum e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<bool>(static_cast<ut>(e1) & static_cast<ut>(Merge(vals...)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cast given enum flags to its equvalent boolean value like performing <TT>bool(e)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param e The enum flags to be cast.
|
||||
* @return The equvalent bool value of given enum flag.
|
||||
*/
|
||||
template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
constexpr bool Bool(TEnum e) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<bool>(static_cast<ut>(e));
|
||||
}
|
||||
|
||||
}
|
563
src/YYCCLegacy/ExceptionHelper.cpp
Normal file
563
src/YYCCLegacy/ExceptionHelper.cpp
Normal file
@ -0,0 +1,563 @@
|
||||
#include "ExceptionHelper.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include "WinFctHelper.hpp"
|
||||
#include "ConsoleHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include "IOHelper.hpp"
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StdPatch.hpp"
|
||||
#include <filesystem>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cinttypes>
|
||||
#include <mutex>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
namespace YYCC::ExceptionHelper {
|
||||
|
||||
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS);
|
||||
class ExceptionRegister {
|
||||
public:
|
||||
ExceptionRegister() :
|
||||
m_CoreMutex(),
|
||||
m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
|
||||
m_UserCallback(nullptr),
|
||||
m_SingletonMutex(NULL) {}
|
||||
~ExceptionRegister() {
|
||||
Unregister();
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Try to register unhandled exception handler.
|
||||
*/
|
||||
void Register(ExceptionCallback callback) {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
// if we have registered, return
|
||||
if (m_IsRegistered) return;
|
||||
|
||||
// check singleton
|
||||
// build mutex string first
|
||||
yycc_u8string mutex_name;
|
||||
if (!StringHelper::Printf(mutex_name, YYCC_U8("Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}"), GetCurrentProcessId()))
|
||||
return;
|
||||
std::wstring mutex_wname;
|
||||
if (!EncodingHelper::UTF8ToWchar(mutex_name, mutex_wname))
|
||||
return;
|
||||
// create mutex
|
||||
m_SingletonMutex = CreateMutexW(NULL, FALSE, mutex_wname.c_str());
|
||||
DWORD errcode = GetLastError();
|
||||
// check whether be created
|
||||
if (m_SingletonMutex == NULL)
|
||||
return;
|
||||
if (errcode == ERROR_ALREADY_EXISTS) {
|
||||
CloseHandle(m_SingletonMutex);
|
||||
m_SingletonMutex = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// okey, we can register it.
|
||||
// backup old handler
|
||||
m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl);
|
||||
// set user callback
|
||||
m_UserCallback = callback;
|
||||
// mark registered
|
||||
m_IsRegistered = true;
|
||||
}
|
||||
/**
|
||||
* @brief Try to unregister unhandled exception handler.
|
||||
*/
|
||||
void Unregister() {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
// if we are not registered, skip
|
||||
if (!m_IsRegistered) return;
|
||||
|
||||
// unregister handler
|
||||
// reset user callback
|
||||
m_UserCallback = nullptr;
|
||||
// restore old handler
|
||||
SetUnhandledExceptionFilter(m_PrevProcHandler);
|
||||
m_PrevProcHandler = nullptr;
|
||||
|
||||
// release singleton handler
|
||||
if (m_SingletonMutex != NULL) {
|
||||
CloseHandle(m_SingletonMutex);
|
||||
m_SingletonMutex = NULL;
|
||||
}
|
||||
|
||||
// mark unregistered
|
||||
m_IsRegistered = false;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Check whether handler is registered.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsRegistered() const {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
return m_IsRegistered;
|
||||
}
|
||||
/**
|
||||
* @brief Check whether we are processing unhandled exception.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsProcessing() const {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
return m_IsProcessing;
|
||||
}
|
||||
/**
|
||||
* @brief Get the old unhandled exception handler before registering.
|
||||
* @return The fucntion pointer to old unhandled exception handler. May be nullptr.
|
||||
*/
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER GetPrevProcHandler() const {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
return m_PrevProcHandler;
|
||||
}
|
||||
/**
|
||||
* @brief Get user specified callback.
|
||||
* @return The function pointer to user callback. nullptr if no associated callback.
|
||||
*/
|
||||
ExceptionCallback GetUserCallback() const {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
return m_UserCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try to start process unhandled exception.
|
||||
* @return True if you can start to process.
|
||||
* False means there is already a process running. You should not process it now.
|
||||
*/
|
||||
bool StartProcessing() {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
if (m_IsProcessing) return false;
|
||||
else {
|
||||
m_IsProcessing = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Mark current process of unhandled exception has done.
|
||||
* @details This should only be called when StartProcessing() return true.
|
||||
*/
|
||||
void StopProcessing() {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
m_IsProcessing = false;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The core mutex for keeping this class is in synchronized.
|
||||
*/
|
||||
mutable std::mutex m_CoreMutex;
|
||||
|
||||
/**
|
||||
* @brief Whether we have registered unhandled exception handler.
|
||||
* True if it is, otherwise false.
|
||||
*/
|
||||
bool m_IsRegistered;
|
||||
/**
|
||||
* @brief Whether we are processing unhandled exception.
|
||||
* True if it is, otherwise false.
|
||||
*/
|
||||
bool m_IsProcessing;
|
||||
/**
|
||||
* @brief User defined callback.
|
||||
* @details It will be called at the tail of unhandled exception handler, because it may raise exception.
|
||||
* We must make sure all log and coredump have been done before calling it.
|
||||
*/
|
||||
ExceptionCallback m_UserCallback;
|
||||
/**
|
||||
* @brief The backup of old unhandled exception handler.
|
||||
*/
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER m_PrevProcHandler;
|
||||
/**
|
||||
* @brief The Windows mutex handle for singleton implementation.
|
||||
* Because we may have many DLLs using YYCC in the same process.
|
||||
* But the unhandled exception handler only need to be registered once.
|
||||
*/
|
||||
HANDLE m_SingletonMutex;
|
||||
};
|
||||
|
||||
/// @brief Core register singleton.
|
||||
static ExceptionRegister g_ExceptionRegister;
|
||||
|
||||
#pragma region Exception Handler Implementation
|
||||
|
||||
/**
|
||||
* @brief Get human-readable exception string from given exception code.
|
||||
* @param[in] code Exception code
|
||||
* @return The const string pointer to corresponding exception explanation string.
|
||||
*/
|
||||
static const yycc_char8_t* UExceptionGetCodeName(DWORD code) {
|
||||
switch (code) {
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
return YYCC_U8("access violation");
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
return YYCC_U8("array index out of bound");
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
return YYCC_U8("breakpoint reached");
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
return YYCC_U8("misaligned data access");
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
return YYCC_U8("operand had denormal value");
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
return YYCC_U8("floating-point division by zero");
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
return YYCC_U8("no decimal fraction representation for value");
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
return YYCC_U8("invalid floating-point operation");
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
return YYCC_U8("floating-point overflow");
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
return YYCC_U8("floating-point stack corruption");
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
return YYCC_U8("floating-point underflow");
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
return YYCC_U8("illegal instruction");
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
return YYCC_U8("inaccessible page");
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
return YYCC_U8("integer division by zero");
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
return YYCC_U8("integer overflow");
|
||||
case EXCEPTION_INVALID_DISPOSITION:
|
||||
return YYCC_U8("documentation says this should never happen");
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
return YYCC_U8("can't continue after a noncontinuable exception");
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
return YYCC_U8("attempted to execute a privileged instruction");
|
||||
case EXCEPTION_SINGLE_STEP:
|
||||
return YYCC_U8("one instruction has been executed");
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
return YYCC_U8("stack overflow");
|
||||
default:
|
||||
return YYCC_U8("unknown exception");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Error log (including backtrace) used output function
|
||||
* @details
|
||||
* This function will write given string into given file stream and stderr.
|
||||
* @param[in] fs
|
||||
* The file stream where we write.
|
||||
* If it is nullptr, function will skip writing for file stream.
|
||||
* @param[in] strl The string to be written.
|
||||
*/
|
||||
static void UExceptionErrLogWriteLine(std::FILE* fs, const yycc_char8_t* strl) {
|
||||
// write to file
|
||||
if (fs != nullptr) {
|
||||
std::fputs(EncodingHelper::ToOrdinary(strl), fs);
|
||||
std::fputs("\n", fs);
|
||||
}
|
||||
// write to stderr
|
||||
ConsoleHelper::ErrWriteLine(strl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Error log (including backtrace) used output function with format feature
|
||||
* @details
|
||||
* This function will format message first.
|
||||
* And write them into given file stream and stderr.
|
||||
* @param[in] fs
|
||||
* The file stream where we write.
|
||||
* If it is nullptr, function will skip writing for file stream.
|
||||
* @param[in] fmt The format string.
|
||||
* @param[in] ... The argument to be formatted.
|
||||
*/
|
||||
static void UExceptionErrLogFormatLine(std::FILE* fs, const yycc_char8_t* fmt, ...) {
|
||||
// do format first
|
||||
va_list arg;
|
||||
va_start(arg, fmt);
|
||||
auto fmt_result = YYCC::StringHelper::VPrintf(fmt, arg);
|
||||
va_end(arg);
|
||||
// write to file and console
|
||||
UExceptionErrLogWriteLine(fs, fmt_result.c_str());
|
||||
}
|
||||
|
||||
static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) {
|
||||
// setup loading symbol options
|
||||
SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); // lazy load symbol, and load line number.
|
||||
|
||||
// setup handle
|
||||
HANDLE process = GetCurrentProcess();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
|
||||
// init symbol
|
||||
if (!SymInitialize(process, 0, TRUE)) {
|
||||
// fail to init. return
|
||||
UExceptionErrLogWriteLine(fs, YYCC_U8("Fail to initialize symbol handle for process!"));
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== CORE DUMP ==========
|
||||
// prepare frame. setup correct fields
|
||||
// references:
|
||||
// https://github.com/rust-lang/backtrace-rs/blob/9ed25b581cfd2ee60e5a3b9054fd023bf6dced90/src/backtrace/dbghelp.rs
|
||||
// https://sourceforge.net/p/predef/wiki/Architectures/
|
||||
DWORD machine_type = 0;
|
||||
STACKFRAME64 frame;
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
#if defined(_M_IX86) || defined(__i386__)
|
||||
// x86
|
||||
machine_type = IMAGE_FILE_MACHINE_I386;
|
||||
frame.AddrPC.Offset = context->Eip;
|
||||
frame.AddrStack.Offset = context->Esp;
|
||||
frame.AddrFrame.Offset = context->Ebp;
|
||||
#elif defined(_M_AMD64) || defined(__amd64__)
|
||||
// amd64
|
||||
machine_type = IMAGE_FILE_MACHINE_AMD64;
|
||||
frame.AddrPC.Offset = context->Rip;
|
||||
frame.AddrStack.Offset = context->Rsp;
|
||||
frame.AddrFrame.Offset = context->Rbp;
|
||||
#elif defined(_M_ARM) || defined(__arm__)
|
||||
// arm (32bit)
|
||||
machine_type = IMAGE_FILE_MACHINE_ARMNT;
|
||||
frame.AddrPC.Offset = context->Pc;
|
||||
frame.AddrStack.Offset = context->Sp;
|
||||
frame.AddrFrame.Offset = context->R11;
|
||||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||||
// arm64
|
||||
machine_type = IMAGE_FILE_MACHINE_ARM64;
|
||||
frame.AddrPC.Offset = context->Pc;
|
||||
frame.AddrStack.Offset = context->Sp;
|
||||
frame.AddrFrame.Offset = context->DUMMYUNIONNAME.DUMMYSTRUCTNAME.Fp;
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
//IA-64 anybody?
|
||||
|
||||
#endif
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
|
||||
// stack walker
|
||||
while (StackWalk64(machine_type, process, thread, &frame, context,
|
||||
0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
|
||||
|
||||
// depth breaker
|
||||
--maxdepth;
|
||||
if (maxdepth < 0) {
|
||||
UExceptionErrLogWriteLine(fs, YYCC_U8("...")); // indicate there are some frames not listed
|
||||
break;
|
||||
}
|
||||
|
||||
// get module name
|
||||
const yycc_char8_t* no_module_name = YYCC_U8("<unknown module>");
|
||||
yycc_u8string module_name(no_module_name);
|
||||
DWORD64 module_base;
|
||||
if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
|
||||
if (!WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name)) {
|
||||
module_name = no_module_name;
|
||||
}
|
||||
}
|
||||
|
||||
// get source file and line
|
||||
const yycc_char8_t* source_file = YYCC_U8("<unknown source>");
|
||||
DWORD64 source_file_line = 0;
|
||||
DWORD dwDisplacement;
|
||||
IMAGEHLP_LINE64 winline;
|
||||
winline.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &dwDisplacement, &winline)) {
|
||||
source_file = EncodingHelper::ToUTF8(winline.FileName); // TODO: check whether there is UNICODE file name.
|
||||
source_file_line = winline.LineNumber;
|
||||
}
|
||||
|
||||
// write to file
|
||||
// MARK: should not use PRIXPTR to print adddress.
|
||||
// because Windows always use DWORD64 as the type of address.
|
||||
// use PRIX64 instead.
|
||||
UExceptionErrLogFormatLine(fs, YYCC_U8("0x%" PRI_XPTR_LEFT_PADDING PRIX64 "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIX64 "]\t%s#L%" PRIu64),
|
||||
frame.AddrPC.Offset, // memory adress
|
||||
module_name.c_str(), frame.AddrPC.Offset - module_base, // module name + relative address
|
||||
source_file, source_file_line // source file + source line
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// ========== END CORE DUMP ==========
|
||||
|
||||
// free symbol
|
||||
SymCleanup(process);
|
||||
}
|
||||
|
||||
static void UExceptionErrorLog(const yycc_u8string& u8_filename, LPEXCEPTION_POINTERS info) {
|
||||
// open file stream if we have file name
|
||||
std::FILE* fs = nullptr;
|
||||
if (!u8_filename.empty()) {
|
||||
fs = IOHelper::UTF8FOpen(u8_filename.c_str(), YYCC_U8("wb"));
|
||||
}
|
||||
|
||||
// record exception type first
|
||||
PEXCEPTION_RECORD rec = info->ExceptionRecord;
|
||||
UExceptionErrLogFormatLine(fs, YYCC_U8("Unhandled exception occured at 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR ": %s (%" PRIu32 ")."),
|
||||
rec->ExceptionAddress,
|
||||
UExceptionGetCodeName(rec->ExceptionCode),
|
||||
rec->ExceptionCode
|
||||
);
|
||||
|
||||
// special proc for 2 exceptions
|
||||
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
|
||||
if (rec->NumberParameters >= 2) {
|
||||
const yycc_char8_t* op =
|
||||
rec->ExceptionInformation[0] == 0 ? YYCC_U8("read") :
|
||||
rec->ExceptionInformation[0] == 1 ? YYCC_U8("written") : YYCC_U8("executed");
|
||||
UExceptionErrLogFormatLine(fs, YYCC_U8("The data at memory address 0x%" PRI_XPTR_LEFT_PADDING PRIxPTR " could not be %s."),
|
||||
rec->ExceptionInformation[1], op);
|
||||
}
|
||||
}
|
||||
|
||||
// output stacktrace
|
||||
UExceptionBacktrace(fs, info->ContextRecord, 1024);
|
||||
|
||||
// close file if necessary
|
||||
if (fs != nullptr) {
|
||||
std::fclose(fs);
|
||||
}
|
||||
}
|
||||
|
||||
static void UExceptionCoreDump(const yycc_u8string& u8_filename, LPEXCEPTION_POINTERS info) {
|
||||
// convert file encoding
|
||||
std::wstring filename;
|
||||
if (u8_filename.empty())
|
||||
return; // if no given file name, return
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filename, filename))
|
||||
return; // if convertion failed, return
|
||||
|
||||
// open file and write
|
||||
HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION exception_info;
|
||||
exception_info.ThreadId = GetCurrentThreadId();
|
||||
exception_info.ExceptionPointers = info;
|
||||
exception_info.ClientPointers = TRUE;
|
||||
MiniDumpWriteDump(
|
||||
GetCurrentProcess(), GetCurrentProcessId(), hFile,
|
||||
MiniDumpNormal,
|
||||
&exception_info,
|
||||
NULL, NULL
|
||||
);
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
}
|
||||
|
||||
static bool UExceptionFetchRecordPath(yycc_u8string& log_path, yycc_u8string& coredump_path) {
|
||||
// build two file names like: "error.exe.1234.log" and "error.exe.1234.dmp".
|
||||
// "error.exe" is the name of current process. "1234" is current process id.
|
||||
// get process name
|
||||
yycc_u8string u8_process_name;
|
||||
{
|
||||
// get full path of process
|
||||
yycc_u8string u8_process_path;
|
||||
if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_path))
|
||||
return false;
|
||||
// extract file name from full path by std::filesystem::path
|
||||
std::filesystem::path process_path(StdPatch::ToStdPath(u8_process_path));
|
||||
u8_process_name = StdPatch::ToUTF8Path(process_path.filename());
|
||||
}
|
||||
// then get process id
|
||||
DWORD process_id = GetCurrentProcessId();
|
||||
// conbine them as a file name prefix
|
||||
yycc_u8string u8_filename_prefix;
|
||||
if (!YYCC::StringHelper::Printf(u8_filename_prefix, YYCC_U8("%s.%" PRIu32), u8_process_name.c_str(), process_id))
|
||||
return false;
|
||||
// then get file name for log and minidump
|
||||
yycc_u8string u8_log_filename = u8_filename_prefix + YYCC_U8(".log");
|
||||
yycc_u8string u8_coredump_filename = u8_filename_prefix + YYCC_U8(".dmp");
|
||||
|
||||
// fetch crash report path
|
||||
// get local appdata folder
|
||||
yycc_u8string u8_localappdata_path;
|
||||
if (!WinFctHelper::GetLocalAppData(u8_localappdata_path))
|
||||
return false;
|
||||
// convert to std::filesystem::path
|
||||
std::filesystem::path crash_report_path(StdPatch::ToStdPath(u8_localappdata_path));
|
||||
// slash into crash report folder
|
||||
crash_report_path /= StdPatch::ToStdPath(YYCC_U8("CrashDumps"));
|
||||
// use create function to make sure it is existing
|
||||
std::filesystem::create_directories(crash_report_path);
|
||||
|
||||
// build log path and coredump path
|
||||
// build std::filesystem::path first
|
||||
std::filesystem::path log_filepath = crash_report_path / StdPatch::ToStdPath(u8_log_filename);
|
||||
std::filesystem::path coredump_filepath = crash_report_path / StdPatch::ToStdPath(u8_coredump_filename);
|
||||
// output to result
|
||||
log_path = StdPatch::ToUTF8Path(log_filepath);
|
||||
coredump_path = StdPatch::ToUTF8Path(coredump_filepath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
|
||||
// try to start process current unhandled exception
|
||||
// to prevent any possible recursive calling.
|
||||
if (!g_ExceptionRegister.StartProcessing()) goto end_proc;
|
||||
|
||||
// core implementation
|
||||
{
|
||||
// fetch error report path first
|
||||
yycc_u8string log_path, coredump_path;
|
||||
if (!UExceptionFetchRecordPath(log_path, coredump_path)) {
|
||||
// fail to fetch path, clear them.
|
||||
// we still can handle crash without them
|
||||
log_path.clear();
|
||||
coredump_path.clear();
|
||||
// and tell user we can not output file
|
||||
ConsoleHelper::ErrWriteLine(YYCC_U8("Crash occurs, but we can not create crash log and coredump!"));
|
||||
} else {
|
||||
// okey. output file path to tell user the path where you can find.
|
||||
ConsoleHelper::ErrFormatLine(YYCC_U8("Crash Log: %s"), log_path.c_str());
|
||||
ConsoleHelper::ErrFormatLine(YYCC_U8("Crash Coredump: %s"), coredump_path.c_str());
|
||||
}
|
||||
|
||||
// write crash log
|
||||
UExceptionErrorLog(log_path, info);
|
||||
// write crash coredump
|
||||
UExceptionCoreDump(coredump_path, info);
|
||||
|
||||
// call user callback
|
||||
ExceptionCallback user_callback = g_ExceptionRegister.GetUserCallback();
|
||||
if (user_callback != nullptr)
|
||||
user_callback(log_path, coredump_path);
|
||||
}
|
||||
|
||||
// stop process
|
||||
g_ExceptionRegister.StopProcessing();
|
||||
|
||||
end_proc:
|
||||
// if backup proc can be run, run it
|
||||
// otherwise directly return.
|
||||
auto prev_proc = g_ExceptionRegister.GetPrevProcHandler();
|
||||
if (prev_proc != nullptr) {
|
||||
return prev_proc(info);
|
||||
} else {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
void Register(ExceptionCallback callback) {
|
||||
g_ExceptionRegister.Register(callback);
|
||||
}
|
||||
|
||||
void Unregister() {
|
||||
g_ExceptionRegister.Unregister();
|
||||
}
|
||||
|
||||
#if defined(YYCC_DEBUG_UE_FILTER)
|
||||
long __stdcall DebugCallUExceptionImpl(void* data) {
|
||||
return UExceptionImpl(static_cast<LPEXCEPTION_POINTERS>(data));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
64
src/YYCCLegacy/ExceptionHelper.hpp
Normal file
64
src/YYCCLegacy/ExceptionHelper.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
/**
|
||||
* @brief Windows specific unhandled exception processor.
|
||||
* @details
|
||||
* This namespace is Windows specific. On other platforms, the whole namespace is unavailable.
|
||||
* For how to utilize this namespace, please see \ref exception_helper.
|
||||
*
|
||||
*/
|
||||
namespace YYCC::ExceptionHelper {
|
||||
|
||||
/**
|
||||
* @brief The callback function prototype which will be called when unhandled exception happened after registering.
|
||||
* @details
|
||||
* During registering unhandled exception handler,
|
||||
* caller can optionally provide a function pointer matching this prorotype to register.
|
||||
* Then it will be called if unhandled exception hanppened.
|
||||
*
|
||||
* This callback will provide 2 readonly arguments.
|
||||
* First is the path to error log file.
|
||||
* Second is the path to core dump file.
|
||||
* These pathes may be empty if internal handler fail to create them.
|
||||
*
|
||||
* This callback is convenient for programmer using an explicit way to tell user an exception happened.
|
||||
* Because in default, handler will only write error log to \c stderr and file.
|
||||
* It will be totally invisible on a GUI application.
|
||||
*/
|
||||
using ExceptionCallback = void(*)(const yycc_u8string& log_path, const yycc_u8string& coredump_path);
|
||||
|
||||
/**
|
||||
* @brief Register unhandled exception handler
|
||||
* @details
|
||||
* This function will set an internal function as unhandled exception handler on Windows.
|
||||
*
|
||||
* When unhandled exception raised,
|
||||
* That internal function will output error stacktrace in standard output,
|
||||
* and generate log file and dump file in \c \%APPDATA\%/CrashDumps folder if it is possible.
|
||||
* (for convenient debugging of developer when reporting bugs.)
|
||||
*
|
||||
* This function usually is called at the start of program.
|
||||
* @param[in] callback User defined callback called when unhandled exception happened. nullptr if no callback.
|
||||
*/
|
||||
void Register(ExceptionCallback callback = nullptr);
|
||||
/**
|
||||
* @brief Unregister unhandled exception handler
|
||||
* @details
|
||||
* The reverse operation of Register().
|
||||
*
|
||||
* This function and Register() should always be used as a pair.
|
||||
* You must call this function to release reources if you have called Register().
|
||||
*
|
||||
* This function usually is called at the end of program.
|
||||
*/
|
||||
void Unregister();
|
||||
|
||||
#if defined(YYCC_DEBUG_UE_FILTER)
|
||||
long __stdcall DebugCallUExceptionImpl(void*);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
37
src/YYCCLegacy/IOHelper.cpp
Normal file
37
src/YYCCLegacy/IOHelper.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "IOHelper.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::IOHelper {
|
||||
|
||||
std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode) {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// convert mode and file path to wchar
|
||||
std::wstring wmode, wpath;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_mode, wmode))
|
||||
return nullptr;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filepath, wpath))
|
||||
return nullptr;
|
||||
|
||||
// call microsoft specified fopen which support wchar as argument.
|
||||
return _wfopen(wpath.c_str(), wmode.c_str());
|
||||
|
||||
#else
|
||||
return std::fopen(EncodingHelper::ToOrdinary(u8_filepath), EncodingHelper::ToOrdinary(u8_mode));
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
55
src/YYCCLegacy/IOHelper.hpp
Normal file
55
src/YYCCLegacy/IOHelper.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
|
||||
/**
|
||||
* @brief Some IO related stuff
|
||||
* @details
|
||||
* See also \ref io_helper.
|
||||
*/
|
||||
namespace YYCC::IOHelper {
|
||||
|
||||
#if UINTPTR_MAX == UINT32_MAX
|
||||
#define PRI_XPTR_LEFT_PADDING "08"
|
||||
#elif UINTPTR_MAX == UINT64_MAX
|
||||
/**
|
||||
* @brief The left-padding zero format string of HEX-printed pointer type.
|
||||
* @details
|
||||
* When printing a pointer with HEX style, we always hope it can be left-padded with some zero for easy reading.
|
||||
* In different architecture, the size of this padding is differnet too so we create this macro.
|
||||
*
|
||||
* In 32-bit environment, it will be "08" meaning left pad zero until 8 number position.
|
||||
* In 64-bit environment, it will be "016" meaning left pad zero until 16 number position.
|
||||
*/
|
||||
#define PRI_XPTR_LEFT_PADDING "016"
|
||||
#else
|
||||
#error "Not supported pointer size."
|
||||
#endif
|
||||
|
||||
/// @brief C++ standard deleter for std::FILE*
|
||||
class StdFileDeleter {
|
||||
public:
|
||||
StdFileDeleter() {}
|
||||
void operator() (std::FILE* ptr) {
|
||||
if (ptr != nullptr) {
|
||||
std::fclose(ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
/// @brief Smart unique pointer of \c std::FILE*
|
||||
using SmartStdFile = std::unique_ptr<std::FILE, StdFileDeleter>;
|
||||
|
||||
/**
|
||||
* @brief The UTF8 version of \c std::fopen.
|
||||
* @param[in] u8_filepath The UTF8 encoded path to the file to be opened.
|
||||
* @param[in] u8_mode UTF8 encoded mode string of the file to be opened.
|
||||
* @remarks
|
||||
* This function is suit for Windows because std::fopen do not support UTF8 on Windows.
|
||||
* On other platforms, this function will delegate request directly to std::fopen.
|
||||
* @return \c FILE* of the file to be opened, or nullptr if failed.
|
||||
*/
|
||||
std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode);
|
||||
|
||||
}
|
221
src/YYCCLegacy/ParserHelper.hpp
Normal file
221
src/YYCCLegacy/ParserHelper.hpp
Normal file
@ -0,0 +1,221 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include <string>
|
||||
#include <cinttypes>
|
||||
#include <type_traits>
|
||||
#include <stdexcept>
|
||||
#include <charconv>
|
||||
#include <array>
|
||||
|
||||
/**
|
||||
* @brief The helper involving convertion between arithmetic types (integral, floating point and bool) and string
|
||||
* @details
|
||||
* See also \ref parser_helper.
|
||||
*/
|
||||
namespace YYCC::ParserHelper {
|
||||
|
||||
// Reference: https://zh.cppreference.com/w/cpp/utility/from_chars
|
||||
|
||||
/**
|
||||
* @brief Try parsing given string to floating point types.
|
||||
* @tparam _Ty The type derived from floating point type.
|
||||
* @param[in] strl The string need to be parsed.
|
||||
* @param[out] num
|
||||
* The variable receiving result.
|
||||
* There is no guarantee that the content is not modified when parsing failed.
|
||||
* @param[in] fmt The floating point format used when try parsing.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
|
||||
bool TryParse(const yycc_u8string_view& strl, _Ty& num, std::chars_format fmt = std::chars_format::general) {
|
||||
auto [ptr, ec] = std::from_chars(
|
||||
EncodingHelper::ToOrdinary(strl.data()),
|
||||
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
|
||||
num, fmt
|
||||
);
|
||||
if (ec == std::errc()) {
|
||||
// check whether the full string is matched
|
||||
return ptr == EncodingHelper::ToOrdinary(strl.data() + strl.size());
|
||||
} else if (ec == std::errc::invalid_argument) {
|
||||
// given string is invalid
|
||||
return false;
|
||||
} else if (ec == std::errc::result_out_of_range) {
|
||||
// given string is out of range
|
||||
return false;
|
||||
} else {
|
||||
// unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Try parsing given string to integral types.
|
||||
* @tparam _Ty The type derived from integral type except bool type.
|
||||
* @param[in] strl The string need to be parsed.
|
||||
* @param[out] num
|
||||
* The variable receiving result.
|
||||
* There is no guarantee that the content is not modified when parsing failed.
|
||||
* @param[in] base Integer base to use: a value between 2 and 36 (inclusive).
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
bool TryParse(const yycc_u8string_view& strl, _Ty& num, int base = 10) {
|
||||
auto [ptr, ec] = std::from_chars(
|
||||
EncodingHelper::ToOrdinary(strl.data()),
|
||||
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
|
||||
num, base
|
||||
);
|
||||
if (ec == std::errc()) {
|
||||
// check whether the full string is matched
|
||||
return ptr == EncodingHelper::ToOrdinary(strl.data() + strl.size());
|
||||
} else if (ec == std::errc::invalid_argument) {
|
||||
// given string is invalid
|
||||
return false;
|
||||
} else if (ec == std::errc::result_out_of_range) {
|
||||
// given string is out of range
|
||||
return false;
|
||||
} else {
|
||||
// unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Try parsing given string to bool types.
|
||||
* @tparam _Ty The type derived from bool type.
|
||||
* @param[in] strl The string need to be parsed ("true" or "false", case insensitive).
|
||||
* @param[out] num
|
||||
* The variable receiving result.
|
||||
* There is no guarantee that the content is not modified when parsing failed.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
|
||||
bool TryParse(const yycc_u8string_view& strl, _Ty& num) {
|
||||
// get lower case
|
||||
yycc_u8string lower_case(strl);
|
||||
YYCC::StringHelper::Lower(lower_case);
|
||||
// compare result
|
||||
if (strl == YYCC_U8("true")) num = true;
|
||||
else if (strl == YYCC_U8("false")) num = false;
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse given string to floating point types.
|
||||
* @tparam _Ty The type derived from floating point type.
|
||||
* @param[in] strl The string need to be parsed.
|
||||
* @param[in] fmt The floating point format used when try parsing.
|
||||
* @return
|
||||
* The parsing result.
|
||||
* There is no guarantee about the content of this return value when parsing failed.
|
||||
* It may be any possible value but usually is its default value.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
|
||||
_Ty Parse(const yycc_u8string_view& strl, std::chars_format fmt = std::chars_format::general) {
|
||||
_Ty ret;
|
||||
TryParse(strl, ret, fmt);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* @brief Parse given string to integral type types.
|
||||
* @tparam _Ty The type derived from integral type except bool type.
|
||||
* @param[in] strl The string need to be parsed.
|
||||
* @param[in] base Integer base to use: a value between 2 and 36 (inclusive).
|
||||
* @return
|
||||
* The parsing result.
|
||||
* There is no guarantee about the content of this return value when parsing failed.
|
||||
* It may be any possible value but usually is its default value.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
_Ty Parse(const yycc_u8string_view& strl, int base = 10) {
|
||||
_Ty ret;
|
||||
TryParse(strl, ret, base);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* @brief Parse given string to bool types.
|
||||
* @tparam _Ty The type derived from bool type.
|
||||
* @param[in] strl The string need to be parsed ("true" or "false", case insensitive).
|
||||
* @return
|
||||
* The parsing result.
|
||||
* There is no guarantee about the content of this return value when parsing failed.
|
||||
* It may be any possible value but usually is its default value.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
|
||||
_Ty Parse(const yycc_u8string_view& strl) {
|
||||
_Ty ret;
|
||||
TryParse(strl, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Reference: https://en.cppreference.com/w/cpp/utility/to_chars
|
||||
|
||||
/**
|
||||
* @brief Return the string representation of given floating point value.
|
||||
* @tparam _Ty The type derived from floating point type.
|
||||
* @param[in] num The value need to get string representation.
|
||||
* @param[in] fmt The floating point format used when getting string representation.
|
||||
* @param[in] precision The floating point precision used when getting string representation.
|
||||
* @return The string representation of given value.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
|
||||
yycc_u8string ToString(_Ty num, std::chars_format fmt = std::chars_format::general, int precision = 6) {
|
||||
// default precision = 6 is gotten from: https://en.cppreference.com/w/c/io/fprintf
|
||||
std::array<yycc_char8_t, 64> buffer;
|
||||
auto [ptr, ec] = std::to_chars(
|
||||
EncodingHelper::ToOrdinary(buffer.data()),
|
||||
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
|
||||
num, fmt, precision
|
||||
);
|
||||
if (ec == std::errc()) {
|
||||
return yycc_u8string(buffer.data(), EncodingHelper::ToUTF8(ptr) - buffer.data());
|
||||
} else if (ec == std::errc::value_too_large) {
|
||||
// too short buffer
|
||||
// this should not happened
|
||||
throw std::out_of_range("ToString() buffer is not sufficient.");
|
||||
} else {
|
||||
// unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Return the string representation of given integral value.
|
||||
* @tparam _Ty The type derived from integral type except bool type.
|
||||
* @param[in] num The value need to get string representation.
|
||||
* @param[in] base Integer base used when getting string representation: a value between 2 and 36 (inclusive).
|
||||
* @return The string representation of given value.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
yycc_u8string ToString(_Ty num, int base = 10) {
|
||||
std::array<yycc_char8_t, 64> buffer;
|
||||
auto [ptr, ec] = std::to_chars(
|
||||
EncodingHelper::ToOrdinary(buffer.data()),
|
||||
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
|
||||
num, base
|
||||
);
|
||||
if (ec == std::errc()) {
|
||||
return yycc_u8string(buffer.data(), EncodingHelper::ToUTF8(ptr) - buffer.data());
|
||||
} else if (ec == std::errc::value_too_large) {
|
||||
// too short buffer
|
||||
// this should not happened
|
||||
throw std::out_of_range("ToString() buffer is not sufficient.");
|
||||
} else {
|
||||
// unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Return the string representation of given bool value.
|
||||
* @tparam _Ty The type derived from bool type.
|
||||
* @param[in] num The value need to get string representation.
|
||||
* @return The string representation of given value ("true" or "false").
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
|
||||
yycc_u8string ToString(_Ty num) {
|
||||
if (num) return yycc_u8string(YYCC_U8("true"));
|
||||
else return yycc_u8string(YYCC_U8("false"));
|
||||
}
|
||||
|
||||
}
|
41
src/YYCCLegacy/StdPatch.cpp
Normal file
41
src/YYCCLegacy/StdPatch.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include "StdPatch.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace YYCC::StdPatch {
|
||||
|
||||
std::filesystem::path ToStdPath(const yycc_u8string_view& u8_path) {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// convert path to wchar
|
||||
std::wstring wpath;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_path, wpath))
|
||||
throw std::invalid_argument("Fail to convert given UTF8 string.");
|
||||
// return path with wchar_t ctor
|
||||
return std::filesystem::path(wpath);
|
||||
|
||||
#else
|
||||
std::string cache = YYCC::EncodingHelper::ToOrdinary(u8_path);
|
||||
return std::filesystem::path(cache.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
yycc_u8string ToUTF8Path(const std::filesystem::path& path) {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// get and convert to utf8
|
||||
yycc_u8string u8_path;
|
||||
if (!YYCC::EncodingHelper::WcharToUTF8(path.c_str(), u8_path))
|
||||
throw std::invalid_argument("Fail to convert to UTF8 string.");
|
||||
|
||||
// return utf8 path
|
||||
return u8_path;
|
||||
|
||||
#else
|
||||
return EncodingHelper::ToUTF8(path.string());
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
218
src/YYCCLegacy/StdPatch.hpp
Normal file
218
src/YYCCLegacy/StdPatch.hpp
Normal file
@ -0,0 +1,218 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @brief \c Standard library related patches for UTF8 compatibility and the limitation of C++ standard version.
|
||||
* @details
|
||||
* See also \ref std_patch.
|
||||
*/
|
||||
namespace YYCC::StdPatch {
|
||||
|
||||
/**
|
||||
* @brief Constructs \c std::filesystem::path from UTF8 path.
|
||||
* @param[in] u8_path UTF8 path string for building.
|
||||
* @return \c std::filesystem::path instance.
|
||||
* @exception std::invalid_argument Fail to parse given UTF8 string (maybe invalid?).
|
||||
*/
|
||||
std::filesystem::path ToStdPath(const yycc_u8string_view& u8_path);
|
||||
|
||||
/**
|
||||
* @brief Returns the UTF8 representation of given \c std::filesystem::path.
|
||||
* @param[in] path The \c std::filesystem::path instance converting to UTF8 path.
|
||||
* @return The UTF8 representation of given \c std::filesystem::path.
|
||||
* @exception std::invalid_argument Fail to convert to UTF8 string.
|
||||
*/
|
||||
yycc_u8string ToUTF8Path(const std::filesystem::path& path);
|
||||
|
||||
#pragma region StartsWith EndsWith
|
||||
|
||||
// Reference:
|
||||
// https://en.cppreference.com/w/cpp/string/basic_string_view/starts_with
|
||||
// https://en.cppreference.com/w/cpp/string/basic_string_view/ends_with
|
||||
// https://en.cppreference.com/w/cpp/string/basic_string/starts_with
|
||||
// https://en.cppreference.com/w/cpp/string/basic_string/ends_with
|
||||
|
||||
#pragma region String View
|
||||
|
||||
/**
|
||||
* @brief Checks if the string view begins with the given prefix
|
||||
* @param[in] that The string view to find.
|
||||
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
|
||||
* @return True if the string view begins with the provided prefix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
|
||||
return std::basic_string_view<CharT, Traits>(that.data(), std::min(that.size(), sv.size())) == sv;
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the string view begins with the given prefix
|
||||
* @param[in] that The string view to find.
|
||||
* @param[in] ch A single character.
|
||||
* @return True if the string view begins with the provided prefix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, CharT ch) noexcept {
|
||||
return !that.empty() && Traits::eq(that.front(), ch);
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the string view begins with the given prefix
|
||||
* @param[in] that The string view to find.
|
||||
* @param[in] s A null-terminated character string.
|
||||
* @return True if the string view begins with the provided prefix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, const CharT* s) noexcept {
|
||||
return StartsWith(that, std::basic_string_view(s));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the string view ends with the given suffix
|
||||
* @param[in] that The string view to find.
|
||||
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
|
||||
* @return True if the string view ends with the provided suffix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
|
||||
return that.size() >= sv.size() && that.compare(that.size() - sv.size(), std::basic_string_view<CharT, Traits>::npos, sv) == 0;
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the string view ends with the given suffix
|
||||
* @param[in] that The string view to find.
|
||||
* @param[in] ch A single character.
|
||||
* @return True if the string view ends with the provided suffix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, CharT ch) noexcept {
|
||||
return !that.empty() && Traits::eq(that.back(), ch);
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the string view ends with the given suffix
|
||||
* @param[in] that The string view to find.
|
||||
* @param[in] s A null-terminated character string.
|
||||
* @return True if the string view ends with the provided suffix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, const CharT* s) noexcept {
|
||||
return EndsWith(that, std::basic_string_view(s));
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region String
|
||||
|
||||
/**
|
||||
* @brief Checks if the string begins with the given prefix
|
||||
* @param[in] that The string to find.
|
||||
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
|
||||
* @return True if the string view begins with the provided prefix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool StartsWith(const std::basic_string<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
|
||||
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), sv);
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the string begins with the given prefix
|
||||
* @param[in] that The string to find.
|
||||
* @param[in] ch A single character.
|
||||
* @return True if the string view begins with the provided prefix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool StartsWith(const std::basic_string<CharT, Traits>& that, CharT ch) noexcept {
|
||||
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), ch);
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the string begins with the given prefix
|
||||
* @param[in] that The string to find.
|
||||
* @param[in] s A null-terminated character string.
|
||||
* @return True if the string view begins with the provided prefix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool StartsWith(const std::basic_string<CharT, Traits>& that, const CharT* s) noexcept {
|
||||
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the string ends with the given suffix
|
||||
* @param[in] that The string to find.
|
||||
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
|
||||
* @return True if the string view ends with the provided suffix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool EndsWith(const std::basic_string<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
|
||||
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), sv);
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the string ends with the given suffix
|
||||
* @param[in] that The string to find.
|
||||
* @param[in] ch A single character.
|
||||
* @return True if the string view ends with the provided suffix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool EndsWith(const std::basic_string<CharT, Traits>& that, CharT ch) noexcept {
|
||||
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), ch);
|
||||
}
|
||||
/**
|
||||
* @brief Checks if the string ends with the given suffix
|
||||
* @param[in] that The string to find.
|
||||
* @param[in] s A null-terminated character string.
|
||||
* @return True if the string view ends with the provided suffix, false otherwise.
|
||||
*/
|
||||
template<class CharT, class Traits = std::char_traits<CharT>>
|
||||
bool EndsWith(const std::basic_string<CharT, Traits>& that, const CharT* s) noexcept {
|
||||
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), s);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Contain
|
||||
|
||||
/**
|
||||
* @brief Checks if there is an element with key equivalent to key in the container.
|
||||
* @details
|
||||
* The polyfill to \c Contains function of unordered and ordered associative container.
|
||||
* Because this function only present after C++ 20.
|
||||
* This function will use our custom polyfill if the version of C++ standard you are using lower than C++ 20.
|
||||
* Otherwise it will fallback to vanilla standard library function.
|
||||
* @tparam _TContainer
|
||||
* The type of container. This container must have \c find() and \c end() member functions.
|
||||
* @tparam _TKey
|
||||
* The type of key of container.
|
||||
* If the container is a set, this type is the type of item in set.
|
||||
* If the container is a map, this type is the key type of map.
|
||||
* @param[in] container The reference to container to find.
|
||||
* @param[in] key Key value of the element to search for
|
||||
* @return True if there is such an element, otherwise false.
|
||||
* @remarks
|
||||
* This template function do not have constraint check.
|
||||
* If container type has \c find() and \c end() member functions, this template function will be created without any error.
|
||||
* However, this function should be used for standard library associative container according to its original purpose.
|
||||
* It means that the type of container usually and should be one of following types:
|
||||
* \li \c std::set
|
||||
* \li \c std::multiset
|
||||
* \li \c std::map
|
||||
* \li \c std::multimap
|
||||
* \li \c std::unordered_set
|
||||
* \li \c std::unordered_multiset
|
||||
* \li \c std::unordered_map
|
||||
* \li \c std::unordered_multimap
|
||||
*/
|
||||
template<class _TContainer, class _TKey>
|
||||
bool Contains(const _TContainer& container, const _TKey& key) {
|
||||
// __cplusplus macro need special compiler switch enabled when compiling.
|
||||
// So we use _MSVC_LANG check it instead.
|
||||
#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
|
||||
return container.contains(key);
|
||||
#else
|
||||
return container.find(key) != container.end();
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
223
src/YYCCLegacy/StringHelper.cpp
Normal file
223
src/YYCCLegacy/StringHelper.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
#include "StringHelper.hpp"
|
||||
#include "EncodingHelper.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace YYCC::StringHelper {
|
||||
|
||||
#pragma region Printf VPrintf
|
||||
|
||||
bool Printf(yycc_u8string& strl, const yycc_char8_t* format, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
bool ret = VPrintf(strl, format, argptr);
|
||||
va_end(argptr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VPrintf(yycc_u8string& strl, const yycc_char8_t* format, va_list argptr) {
|
||||
va_list args1;
|
||||
va_copy(args1, argptr);
|
||||
va_list args2;
|
||||
va_copy(args2, argptr);
|
||||
|
||||
// the return value is desired char count without NULL terminal.
|
||||
// minus number means error
|
||||
int count = std::vsnprintf(
|
||||
nullptr,
|
||||
0,
|
||||
EncodingHelper::ToOrdinary(format),
|
||||
args1
|
||||
);
|
||||
if (count < 0) {
|
||||
// invalid length returned by vsnprintf.
|
||||
return false;
|
||||
}
|
||||
va_end(args1);
|
||||
|
||||
// resize std::string to desired count.
|
||||
// and pass its length + 1 to std::vsnprintf,
|
||||
// because std::vsnprintf only can write "buf_size - 1" chars with a trailing NULL.
|
||||
// however std::vsnprintf already have a trailing NULL, so we plus 1 for it.
|
||||
strl.resize(count);
|
||||
int write_result = std::vsnprintf(
|
||||
EncodingHelper::ToOrdinary(strl.data()),
|
||||
strl.size() + 1,
|
||||
EncodingHelper::ToOrdinary(format),
|
||||
args2
|
||||
);
|
||||
va_end(args2);
|
||||
|
||||
if (write_result < 0 || write_result > count) {
|
||||
// invalid write result in vsnprintf.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
yycc_u8string Printf(const yycc_char8_t* format, ...) {
|
||||
yycc_u8string ret;
|
||||
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
VPrintf(ret, format, argptr);
|
||||
va_end(argptr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
yycc_u8string VPrintf(const yycc_char8_t* format, va_list argptr) {
|
||||
yycc_u8string ret;
|
||||
|
||||
va_list argcpy;
|
||||
va_copy(argcpy, argptr);
|
||||
VPrintf(ret, format, argcpy);
|
||||
va_end(argcpy);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Replace
|
||||
|
||||
void Replace(yycc_u8string& strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl) {
|
||||
// Reference: https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
|
||||
|
||||
// check requirements
|
||||
// from string should not be empty
|
||||
yycc_u8string from_strl(_from_strl);
|
||||
yycc_u8string to_strl(_to_strl);
|
||||
if (from_strl.empty()) return;
|
||||
|
||||
// start replace one by one
|
||||
size_t start_pos = 0;
|
||||
while ((start_pos = strl.find(from_strl, start_pos)) != yycc_u8string::npos) {
|
||||
strl.replace(start_pos, from_strl.size(), to_strl);
|
||||
start_pos += to_strl.size(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||
}
|
||||
}
|
||||
|
||||
yycc_u8string Replace(const yycc_u8string_view& _strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl) {
|
||||
// prepare result
|
||||
yycc_u8string strl(_strl);
|
||||
Replace(strl, _from_strl, _to_strl);
|
||||
// return value
|
||||
return strl;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Join
|
||||
|
||||
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer) {
|
||||
yycc_u8string ret;
|
||||
bool is_first = true;
|
||||
yycc_u8string_view element;
|
||||
|
||||
// fetch element
|
||||
while (fct_data(element)) {
|
||||
// insert decilmer
|
||||
if (is_first) is_first = false;
|
||||
else {
|
||||
// append decilmer.
|
||||
ret.append(decilmer);
|
||||
}
|
||||
|
||||
// insert element if it is not empty
|
||||
if (!element.empty())
|
||||
ret.append(element);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Upper Lower
|
||||
|
||||
template<bool bIsToLower>
|
||||
static void GeneralStringLowerUpper(yycc_u8string& strl) {
|
||||
// References:
|
||||
// https://en.cppreference.com/w/cpp/algorithm/transform
|
||||
// https://en.cppreference.com/w/cpp/string/byte/tolower
|
||||
std::transform(
|
||||
strl.cbegin(), strl.cend(), strl.begin(),
|
||||
[](unsigned char c) -> char {
|
||||
if constexpr (bIsToLower) return std::tolower(c);
|
||||
else return std::toupper(c);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void Lower(yycc_u8string& strl) {
|
||||
GeneralStringLowerUpper<true>(strl);
|
||||
}
|
||||
|
||||
yycc_u8string Lower(const yycc_u8string_view& strl) {
|
||||
yycc_u8string ret(strl);
|
||||
Lower(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Upper(yycc_u8string& strl) {
|
||||
GeneralStringLowerUpper<false>(strl);
|
||||
}
|
||||
|
||||
yycc_u8string Upper(const yycc_u8string_view& strl) {
|
||||
// same as Lower, just replace char transform function.
|
||||
yycc_u8string ret(strl);
|
||||
Upper(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Split
|
||||
|
||||
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
|
||||
// call split view
|
||||
auto view_result = SplitView(strl, _decilmer);
|
||||
|
||||
// copy string view result to string
|
||||
std::vector<yycc_u8string> elems;
|
||||
elems.reserve(view_result.size());
|
||||
for (const auto& strl_view : view_result) {
|
||||
elems.emplace_back(yycc_u8string(strl_view));
|
||||
}
|
||||
// return copied result
|
||||
return elems;
|
||||
}
|
||||
|
||||
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
|
||||
// Reference:
|
||||
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
|
||||
|
||||
// prepare return value
|
||||
std::vector<yycc_u8string_view> elems;
|
||||
|
||||
// if string need to be splitted is empty, return original string (empty string).
|
||||
// if decilmer is empty, return original string.
|
||||
yycc_u8string decilmer(_decilmer);
|
||||
if (strl.empty() || decilmer.empty()) {
|
||||
elems.emplace_back(strl);
|
||||
return elems;
|
||||
}
|
||||
|
||||
// start spliting
|
||||
std::size_t previous = 0, current;
|
||||
while ((current = strl.find(decilmer.c_str(), previous)) != yycc_u8string::npos) {
|
||||
elems.emplace_back(strl.substr(previous, current - previous));
|
||||
previous = current + decilmer.size();
|
||||
}
|
||||
// try insert last part but prevent possible out of range exception
|
||||
if (previous <= strl.size()) {
|
||||
elems.emplace_back(strl.substr(previous));
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
}
|
159
src/YYCCLegacy/StringHelper.hpp
Normal file
159
src/YYCCLegacy/StringHelper.hpp
Normal file
@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <cstdarg>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @brief The helper containing string operations
|
||||
* @details
|
||||
* See also \ref string_helper.
|
||||
*/
|
||||
namespace YYCC::StringHelper {
|
||||
|
||||
/**
|
||||
* @brief Perform a string formatting operation.
|
||||
* @param[out] strl
|
||||
* The string container receiving the result.
|
||||
* There is no guarantee that the content is not modified when function failed.
|
||||
* @param[in] format The format string.
|
||||
* @param[in] ... Argument list of format string.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool Printf(yycc_u8string& strl, const yycc_char8_t* format, ...);
|
||||
/**
|
||||
* @brief Perform a string formatting operation.
|
||||
* @param[out] strl
|
||||
* The string container receiving the result.
|
||||
* There is no guarantee that the content is not modified when function failed.
|
||||
* @param[in] format The format string.
|
||||
* @param[in] argptr Argument list of format string.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool VPrintf(yycc_u8string& strl, const yycc_char8_t* format, va_list argptr);
|
||||
/**
|
||||
* @brief Perform a string formatting operation.
|
||||
* @param[in] format The format string.
|
||||
* @param[in] ... Argument list of format string.
|
||||
* @return The formatting result. Empty string if error happened.
|
||||
*/
|
||||
yycc_u8string Printf(const yycc_char8_t* format, ...);
|
||||
/**
|
||||
* @brief Perform a string formatting operation.
|
||||
* @param[in] format The format string.
|
||||
* @param[in] argptr Argument list of format string.
|
||||
* @return The formatting result. Empty string if error happened.
|
||||
*/
|
||||
yycc_u8string VPrintf(const yycc_char8_t* format, va_list argptr);
|
||||
|
||||
/**
|
||||
* @brief Modify given string with all occurrences of substring \e old replaced by \e new.
|
||||
* @param[in,out] strl The string for replacing
|
||||
* @param[in] _from_strl The \e old string.
|
||||
* @param[in] _to_strl The \e new string.
|
||||
*/
|
||||
void Replace(yycc_u8string& strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl);
|
||||
/**
|
||||
* @brief Return a copy with all occurrences of substring \e old replaced by \e new.
|
||||
* @param[in] _strl The string for replacing
|
||||
* @param[in] _from_strl The \e old string.
|
||||
* @param[in] _to_strl The \e new string.
|
||||
* @return The result of replacement.
|
||||
*/
|
||||
yycc_u8string Replace(const yycc_u8string_view& _strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl);
|
||||
|
||||
/**
|
||||
* @brief The data provider of general join function.
|
||||
* @details
|
||||
* For programmer using lambda to implement this function pointer:
|
||||
* \li During calling, implementation should assign the reference of string view passed in argument
|
||||
* to the string which need to be joined.
|
||||
* \li Function return true to continue joining. otherwise return false to stop joining.
|
||||
* The argument content assigned in the calling returning false is not included in join process.
|
||||
*/
|
||||
using JoinDataProvider = std::function<bool(yycc_u8string_view&)>;
|
||||
/**
|
||||
* @brief Universal join function.
|
||||
* @details
|
||||
* This function use function pointer as a general data provider interface,
|
||||
* so this function suit for all types container.
|
||||
* You can use this universal join function for any custom container by
|
||||
* using C++ lambda syntax to create a code block adapted to this function pointer.
|
||||
* @param[in] fct_data The function pointer in JoinDataProvider type prividing the data to be joined.
|
||||
* @param[in] decilmer The decilmer used for joining.
|
||||
* @return The result string of joining.
|
||||
*/
|
||||
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer);
|
||||
/**
|
||||
* @brief Specialized join function for standard library container.
|
||||
* @tparam InputIt
|
||||
* Must meet the requirements of LegacyInputIterator.
|
||||
* It also can be dereferenced and then implicitly converted to yycc_u8string_view.
|
||||
* @param[in] first The beginning of the range of elements to join.
|
||||
* @param[in] last The terminal of the range of elements to join (exclusive).
|
||||
* @param[in] decilmer The decilmer used for joining.
|
||||
* @return The result string of joining.
|
||||
*/
|
||||
template<class InputIt>
|
||||
yycc_u8string Join(InputIt first, InputIt last, const yycc_u8string_view& decilmer) {
|
||||
return Join([&first, &last](yycc_u8string_view& view) -> bool {
|
||||
// if we reach tail, return false to stop join process
|
||||
if (first == last) return false;
|
||||
// otherwise fetch data, inc iterator and return.
|
||||
view = *first;
|
||||
++first;
|
||||
return true;
|
||||
}, decilmer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert given string to lowercase.
|
||||
* @param[in,out] strl The string to be lowercase.
|
||||
*/
|
||||
void Lower(yycc_u8string& strl);
|
||||
/**
|
||||
* @brief Return a copy of the string converted to lowercase.
|
||||
* @param[in] strl The string to be lowercase.
|
||||
* @return The copy of the string converted to lowercase.
|
||||
*/
|
||||
yycc_u8string Lower(const yycc_u8string_view& strl);
|
||||
/**
|
||||
* @brief Convert given string to uppercase.
|
||||
* @param[in,out] strl The string to be uppercase.
|
||||
*/
|
||||
void Upper(yycc_u8string& strl);
|
||||
/**
|
||||
* @brief Return a copy of the string converted to uppercase.
|
||||
* @param[in] strl The string to be uppercase.
|
||||
* @return The copy of the string converted to uppercase.
|
||||
*/
|
||||
yycc_u8string Upper(const yycc_u8string_view& strl);
|
||||
|
||||
/**
|
||||
* @brief Split given string with specified decilmer.
|
||||
* @param[in] strl The string need to be splitting.
|
||||
* @param[in] _decilmer The decilmer for splitting.
|
||||
* @return
|
||||
* The split result.
|
||||
* \par
|
||||
* If given string or decilmer are empty,
|
||||
* the result container will only contain 1 entry which is equal to given string.
|
||||
*/
|
||||
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
|
||||
/**
|
||||
* @brief Split given string with specified decilmer as string view.
|
||||
* @param[in] strl The string need to be splitting.
|
||||
* @param[in] _decilmer The decilmer for splitting.
|
||||
* @return
|
||||
* The split result with string view format.
|
||||
* This will not produce any copy of original string.
|
||||
* \par
|
||||
* If given string or decilmer are empty,
|
||||
* the result container will only contain 1 entry which is equal to given string.
|
||||
* @see Split(const yycc_u8string_view&, const yycc_char8_t*)
|
||||
*/
|
||||
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
|
||||
|
||||
}
|
115
src/YYCCLegacy/WinFctHelper.cpp
Normal file
115
src/YYCCLegacy/WinFctHelper.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include "WinFctHelper.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "COMHelper.hpp"
|
||||
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
HMODULE GetCurrentModule() {
|
||||
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
|
||||
HMODULE hModule = NULL;
|
||||
::GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter.
|
||||
(LPCWSTR)GetCurrentModule,
|
||||
&hModule);
|
||||
|
||||
return hModule;
|
||||
}
|
||||
|
||||
bool GetTempDirectory(yycc_u8string& ret) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD expected_size;
|
||||
|
||||
// fetch temp folder
|
||||
while (true) {
|
||||
if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
|
||||
// failed, set to empty
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected_size > static_cast<DWORD>(wpath.size())) {
|
||||
// buffer is too short, need enlarge and do fetching again
|
||||
wpath.resize(expected_size);
|
||||
} else {
|
||||
// ok. shrink to real length, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(expected_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath, ret);
|
||||
}
|
||||
|
||||
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD copied_size;
|
||||
|
||||
while (true) {
|
||||
if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
|
||||
// failed, return
|
||||
return false;
|
||||
}
|
||||
|
||||
// check insufficient buffer
|
||||
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
// buffer is not enough, enlarge it and try again.
|
||||
wpath.resize(wpath.size() + MAX_PATH);
|
||||
} else {
|
||||
// ok, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(copied_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath, ret);
|
||||
}
|
||||
|
||||
bool GetLocalAppData(yycc_u8string& ret) {
|
||||
// check whether com initialized
|
||||
if (!COMHelper::IsInitialized()) return false;
|
||||
|
||||
// fetch path
|
||||
LPWSTR _known_path;
|
||||
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &_known_path);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR known_path(_known_path);
|
||||
|
||||
// convert to utf8
|
||||
return YYCC::EncodingHelper::WcharToUTF8(known_path.get(), ret);
|
||||
}
|
||||
|
||||
bool IsValidCodePage(UINT code_page) {
|
||||
CPINFOEXW cpinfo;
|
||||
return ::GetCPInfoExW(code_page, 0, &cpinfo);
|
||||
}
|
||||
|
||||
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists) {
|
||||
std::wstring wExistingFileName, wNewFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
|
||||
return ::CopyFileW(wExistingFileName.c_str(), wNewFileName.c_str(), bFailIfExists);
|
||||
}
|
||||
|
||||
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName) {
|
||||
std::wstring wExistingFileName, wNewFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
|
||||
return ::MoveFileW(wExistingFileName.c_str(), wNewFileName.c_str());
|
||||
}
|
||||
|
||||
BOOL DeleteFile(const yycc_u8string_view& lpFileName) {
|
||||
std::wstring wFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpFileName, wFileName)) return FALSE;
|
||||
return ::DeleteFileW(wFileName.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
106
src/YYCCLegacy/WinFctHelper.hpp
Normal file
106
src/YYCCLegacy/WinFctHelper.hpp
Normal file
@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The helper providing assistance of Win32 functions.
|
||||
* @details
|
||||
* This helper is Windows specific.
|
||||
* If current environment is not Windows, the whole namespace will be unavailable.
|
||||
* See also \ref win_fct_helper
|
||||
*/
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
/**
|
||||
* @brief Get Windows used HANDLE for current module.
|
||||
* @details
|
||||
* If your target is EXE, the current module simply is your program self.
|
||||
* However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL.
|
||||
*
|
||||
* This function is frequently used by DLL.
|
||||
* Because some design need the HANDLE of current module, not the host EXE loading your DLL.
|
||||
* For example, you may want to get the path of your built DLL, or fetch resources from your DLL at runtime,
|
||||
* then you should pass current module HANDLE, not NULL or the HANDLE of EXE.
|
||||
* @return A Windows HANDLE pointing to current module, NULL if failed.
|
||||
*/
|
||||
HMODULE GetCurrentModule();
|
||||
|
||||
/**
|
||||
* @brief Get path to Windows temporary folder.
|
||||
* @details Windows temporary folder usually is the target of \%TEMP\%.
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to Windows temp folder.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetTempDirectory(yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Get the file name of given module HANDLE
|
||||
* @param[in] hModule
|
||||
* The HANDLE to the module where you want to get file name.
|
||||
* It is same as the HANDLE parameter of Win32 \c GetModuleFileName.
|
||||
* @param[out] ret The variable receiving UTF8 encoded file name of given module.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Get the path to \%LOCALAPPDATA\%.
|
||||
* @details \%LOCALAPPDATA\% usually was used as putting local app data files
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to LOCALAPPDATA.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetLocalAppData(yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Check whether given code page number is a valid one.
|
||||
* @param[in] code_page The code page number.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
bool IsValidCodePage(UINT code_page);
|
||||
|
||||
/**
|
||||
* @brief Copies an existing file to a new file.
|
||||
* @param lpExistingFileName The name of an existing file.
|
||||
* @param lpNewFileName The name of the new file.
|
||||
* @param bFailIfExists
|
||||
* If this parameter is TRUE and the new file specified by \c lpNewFileName already exists, the function fails.
|
||||
* If this parameter is FALSE and the new file already exists, the function overwrites the existing file and succeeds.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c CopyFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
|
||||
*/
|
||||
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists);
|
||||
|
||||
/**
|
||||
* @brief Moves an existing file or a directory, including its children.
|
||||
* @param lpExistingFileName The current name of the file or directory on the local computer.
|
||||
* @param lpNewFileName
|
||||
* The new name for the file or directory. The new name must not already exist.
|
||||
* A new file may be on a different file system or drive. A new directory must be on the same drive.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c MoveFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefilew
|
||||
*/
|
||||
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName);
|
||||
|
||||
/**
|
||||
* @brief Deletes an existing file.
|
||||
* @param lpFileName The name of the file to be deleted.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c DeleteFile: https://learn.microsoft.com/e-us/windows/win32/api/winbase/nf-winbase-deletefile
|
||||
*/
|
||||
BOOL DeleteFile(const yycc_u8string_view& lpFileName);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
19
src/YYCCLegacy/WinImportPrefix.hpp
Normal file
19
src/YYCCLegacy/WinImportPrefix.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
// It is by design that no pragma once or #if to prevent deplicated including.
|
||||
// Because this header is the part of wrapper, not a real header.
|
||||
// #pragma once
|
||||
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// Define 2 macros to disallow Windows generate MIN and MAX macros
|
||||
// which cause std::min and std::max can not function as normal.
|
||||
#if !defined(WIN32_LEAN_AND_MEAN)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#if !defined(NOMINMAX)
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#endif
|
23
src/YYCCLegacy/WinImportSuffix.hpp
Normal file
23
src/YYCCLegacy/WinImportSuffix.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
// It is by design that no pragma once or #if to prevent deplicated including.
|
||||
// Because this header is the part of wrapper, not a real header.
|
||||
// #pragma once
|
||||
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// Windows also will generate following macros
|
||||
// which may cause the function sign is different in Windows and other platforms.
|
||||
// So we simply remove them.
|
||||
// Because #undef will not throw error if there are no matched macro,
|
||||
// so we simply #undef them directly.
|
||||
#undef GetObject
|
||||
#undef GetClassName
|
||||
#undef LoadImage
|
||||
#undef GetTempPath
|
||||
#undef GetModuleFileName
|
||||
#undef CopyFile
|
||||
#undef MoveFile
|
||||
#undef DeleteFile
|
||||
|
||||
#endif
|
147
src/YYCCLegacy/YYCCInternal.hpp
Normal file
147
src/YYCCLegacy/YYCCInternal.hpp
Normal file
@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#pragma region Library Version and Comparison Macros
|
||||
|
||||
#include "YYCCVersion.hpp"
|
||||
|
||||
/// @brief Return true if left version number is equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3) ((av1) == (bv1) && (av2) == (bv2) && (av3) == (bv3))
|
||||
/// @brief Return true if left version number is not equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NE(av1, av2, av3, bv1, bv2, bv3) (!YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is greater than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_G(av1, av2, av3, bv1, bv2, bv3) ( \
|
||||
((av1) > (bv1)) || \
|
||||
((av1) == (bv1) && (av2) > (bv2)) || \
|
||||
((av1) == (bv1) && (av2) == (bv2) && (av3) > (bv3)) \
|
||||
)
|
||||
/// @brief Return true if left version number is greater than or equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_GE(av1, av2, av3, bv1, bv2, bv3) (YYCC_VERCMP_G(av1, av2, av3, bv1, bv2, bv3) || YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is not lower than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NL(av1, av2, av3, bv1, bv2, bv3) YYCC_VERCMP_GE(av1, av2, av3, bv1, bv2, bv3)
|
||||
/// @brief Return true if left version number is lower than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_L(av1, av2, av3, bv1, bv2, bv3) ( \
|
||||
((av1) < (bv1)) || \
|
||||
((av1) == (bv1) && (av2) < (bv2)) || \
|
||||
((av1) == (bv1) && (av2) == (bv2) && (av3) < (bv3)) \
|
||||
)
|
||||
/// @brief Return true if left version number is lower than or equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_LE(av1, av2, av3, bv1, bv2, bv3) (YYCC_VERCMP_L(av1, av2, av3, bv1, bv2, bv3) || YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is not greater than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NG(av1, av2, av3, bv1, bv2, bv3) YYCC_VERCMP_LE(av1, av2, av3, bv1, bv2, bv3)
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Operating System Identifier Macros
|
||||
|
||||
// Define operating system macros
|
||||
#define YYCC_OS_WINDOWS 2
|
||||
#define YYCC_OS_LINUX 3
|
||||
// Check current operating system.
|
||||
#if defined(_WIN32)
|
||||
#define YYCC_OS YYCC_OS_WINDOWS
|
||||
#else
|
||||
#define YYCC_OS YYCC_OS_LINUX
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Windows Shitty Behavior Disable Macros
|
||||
|
||||
// If we are in Windows,
|
||||
// we need add 2 macros to disable Windows shitty warnings and errors of
|
||||
// depracted functions and not secure functions.
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#if !defined(_CRT_NONSTDC_NO_DEPRECATE)
|
||||
#define _CRT_NONSTDC_NO_DEPRECATE
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region YYCC UTF8 Types
|
||||
|
||||
// Define the UTF8 char type we used.
|
||||
// And do a polyfill if no embedded char8_t type.
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @brief Library core namespace
|
||||
* @details Almost library functions are located in this namespace.
|
||||
*/
|
||||
namespace YYCC {
|
||||
#if defined(__cpp_char8_t)
|
||||
using yycc_char8_t = char8_t;
|
||||
using yycc_u8string = std::u8string;
|
||||
using yycc_u8string_view = std::u8string_view;
|
||||
#else
|
||||
using yycc_char8_t = unsigned char;
|
||||
using yycc_u8string = std::basic_string<yycc_char8_t>;
|
||||
using yycc_u8string_view = std::basic_string_view<yycc_char8_t>;
|
||||
#endif
|
||||
}
|
||||
/**
|
||||
\typedef YYCC::yycc_char8_t
|
||||
\brief YYCC UTF8 char type.
|
||||
\details
|
||||
This char type is an alias to \c std::char8_t if your current C++ standard support it.
|
||||
Otherwise it is defined as <TT>unsigned char</TT> as C++ 20 stdandard does.
|
||||
*/
|
||||
/**
|
||||
\typedef YYCC::yycc_u8string
|
||||
\brief YYCC UTF8 string container type.
|
||||
\details
|
||||
This type is defined as \c std::basic_string<yycc_char8_t>.
|
||||
It is equal to \c std::u8string if your current C++ standard support it.
|
||||
*/
|
||||
/**
|
||||
\typedef YYCC::yycc_u8string_view
|
||||
\brief YYCC UTF8 string view type.
|
||||
\details
|
||||
This type is defined as \c std::basic_string_view<yycc_char8_t>.
|
||||
It is equal to \c std::u8string_view if your current C++ standard support it.
|
||||
*/
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Batch Class Move / Copy Function Macros
|
||||
|
||||
/// @brief Explicitly remove copy (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEL_CLS_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&) = delete; \
|
||||
CLSNAME& operator=(const CLSNAME&) = delete;
|
||||
|
||||
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEL_CLS_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = delete; \
|
||||
CLSNAME& operator=(CLSNAME&&) = delete;
|
||||
|
||||
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEL_CLS_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DEL_CLS_COPY(CLSNAME) \
|
||||
YYCC_DEL_CLS_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Explicitly set default copy (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEF_CLS_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&) = default; \
|
||||
CLSNAME& operator=(const CLSNAME&) = default;
|
||||
|
||||
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEF_CLS_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = default; \
|
||||
CLSNAME& operator=(CLSNAME&&) = default;
|
||||
|
||||
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEF_CLS_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DEF_CLS_COPY(CLSNAME) \
|
||||
YYCC_DEF_CLS_MOVE(CLSNAME)
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
Reference in New Issue
Block a user