19 Commits

Author SHA1 Message Date
c91df3a74f fix: fix issues.
- add chars format argument for floating point overload of ParserHelper::TryParse.
- add overload for ParserHelper::Parse to match with ParserHelper::TryParse.
- fix the issue that we can not specify c++ standard in command line when configuring project.
- update documentation for changes.
- change following function's argument from const yycc_char8_t* to const yycc_u8string_view&.
	- StringHelper::Split, StringHelper::SplitView
	- StringHelper::Lower, StringHelper::Upper
	- StringHelper::Join
	- StringHelper::Replace
- use iterator type, not std::vector<yycc_u8string> for specialized StringHelper::Join to have more wide usage.
2024-08-26 11:58:20 +08:00
3858b4f3ec fix: use new way to detect c++ version in MSVC.
- use new macro to check C++ version in MSVC, instead of use compiler switch and __cplusplus macro.
2024-08-15 16:50:15 +08:00
f3a88e951c fix: add testbench for new added code. fix issues.
- add testbench for new added code in StdPatch.
- add documentation for new added code.
- fix the old usage of StdPatch::ToStdPath in ExceptionHelper.
2024-08-15 10:38:58 +08:00
59c185a424 doc: update build script and documentation.
- update build script.
- update documentation about build script changes.
2024-08-14 17:26:38 +08:00
dc98486fff chore: add new script for building
- add a python script for generating windows build script but not tested now.
2024-08-14 11:05:36 +08:00
72a48b703f feat: add various functions
- Add Win32 CopyFile, MoveFile, DeleteFile functions in WinFctHelper.
- rename FsPathPatch to StdPatch because this namespace will hold all standard library patches in future.
- add polyfill for std:basic_string::starts_with, std::basic_string::ends_with std::basic_string_view::starts_with, std::basic_string_view::ends_with in StdPatch.
- add polyfill for unordered and ordered associative standard library container's contains function in StdPatch.
- documentation and testbench will be fixed in later commits.
2024-08-13 09:38:12 +08:00
33cb284eb7 feat: add switch for build script to disable documentation build.
- add a new switch to disable documentation build which cost much time and disk space during building.
- add corresponding codumentation for this feature.
2024-08-05 14:46:59 +08:00
e6c24b8b61 feat: add helper macro and new Win32 function.
- add IsValidCodePage in WinFctHelper to check whether code page number is valid.
- add 6 macros to batchly (add / set default) (move / copy) (constructor / assign operator).
- add default or delete (copy / move) (constructor / assign operator) for some classes.
2024-08-04 11:57:56 +08:00
6da990876e feat: add smart FILE pointer.
- use std::unique_ptr and custom deleter to implement smart FILE pointer for convenient auto free.
2024-08-02 09:50:15 +08:00
0ac6b477f9 fix: fix fatal error of ExceptionHelper in x86 environemnt.
- fix a wrong placeholder of printf in ExceptionHelper which cause crash in unhandled exception handler.
- improve format function in ExceptionHelper.
- add a new debugging option and macro in CMake script and code for the convenience of debugging unhandled exception handler.
- add docuementation about previous term.
2024-07-31 20:32:11 +08:00
1cfbcb3b18 doc: update documentation
- use namespace bracket all content in documentation to reduce useless namespace prefix.
- change the argument type of AbstractSetting and CoreManager to yycc_u8string_view instead of const yycc_char8_t*.
- throw exception if given setting name is invalid in ConfigManager, instead of slient fallback.
2024-07-31 14:14:38 +08:00
598aae69ae doc: update documentation 2024-07-31 12:08:30 +08:00
656495f22e doc: update documentation 2024-07-30 22:13:59 +08:00
e167479de3 doc: add documentation for new added features 2024-07-30 17:31:38 +08:00
19023cb949 doc: add callback documentation in ExceptionHelper.
- add callback documentation in ExceptionHelper.
- fix other misc documentation issue.
2024-07-30 10:35:41 +08:00
650fcd12ec feat: add callback for unhandled exception handler.
- add callback for unhandled exception handler to give programmer a chance to fetch log and coredump path, especially for GUI application because its stderr is invisible.
- fix fatal anto-recursive calling bug in unhandled exception handler.
2024-07-29 21:42:27 +08:00
e8a0299fbc feat: finish ArgParser help text output 2024-07-29 19:31:17 +08:00
d1c1743dc9 feat: basically finish ArgParser
- finish ArgParser and test it.
- Help output function will be added in next commit.
2024-07-29 16:58:52 +08:00
35318505e4 feat: add arg parser feature but not finished 2024-07-28 22:42:16 +08:00
50 changed files with 2594 additions and 550 deletions

View File

@ -1,12 +1,13 @@
cmake_minimum_required(VERSION 3.23) cmake_minimum_required(VERSION 3.23)
project(YYCC project(YYCC
VERSION 1.1.0 VERSION 1.2.0
LANGUAGES CXX LANGUAGES CXX
) )
# Provide options # Provide options
option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF) option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF)
option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF) option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF)
option(YYCC_DEBUG_UE_FILTER "YYCC developer used switch for testing Windows unhandled exception filter. Should not set to ON!!!" OFF)
# Setup install path from CMake provided install path for convenient use. # Setup install path from CMake provided install path for convenient use.
include(GNUInstallDirs) include(GNUInstallDirs)

View File

@ -8,9 +8,10 @@ For more usage about this library, please build documentation of this project vi
And I also highly recommend that you read documentation first before writing with this library. And I also highly recommend that you read documentation first before writing with this library.
However, the documentation need CMake to build and you may don't know how to use CMake in this project. So as the alternative, you also can browse the raw Doxygen documentation file: `doc/src/intro.dox` for how to build this project (including documentation) first.
## Build ## Build
This project require at least CMake 3.23 to build. We suggest that you only use stable version (tagged commit). The latest commit may still work in progress and not stable. This project require at least CMake 3.23 to build. We suggest that you only use stable version (tagged commit). The latest commit may still work in progress and not stable.
For Windows builing, you can browse GitHub action script to have a preview. It actually is a simple calling to script file. See documentation for how to build this project.
For other platforms building (e.g. Linux), you can following common builing way of CMake project.

200
doc/src/arg_parser.dox Normal file
View File

@ -0,0 +1,200 @@
namespace YYCC::ArgParser {
/**
\page arg_parser Universal Argument Parser
YYCC::ArgParser provides an universal way to parsing command line arguments.
Universal argument parser has similar design with universal config manager,
it is highly recommand that read \ref config_manager chapter first,
because you will have a clear understanding of this namespace after reading universal config manager chapter.
There is an example about how to use universal argument parser.
In following content, we will describe it in detail.
\code{.cpp}
class TestArgParser {
public:
TestArgParser() :
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the testbench of argument parser."), {
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
&m_BoolArgument, &m_ClampedFloatArgument
}) {}
~TestArgParser() {}
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
YYCC::ArgParser::StringArgument m_StringArgument;
YYCC::ArgParser::SwitchArgument m_BoolArgument;
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
YYCC::ArgParser::OptionContext m_OptionContext;
};
// Initialize argument parser.
TestArgParser test;
// Get argument list for parsing from standard C main function.
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
// Start parsing
test.Parse(al);
// Get captured string argument
if (test.m_StringArgument.IsCaptured())
auto val = test.m_StringArgument.Get();
\endcode
These code can resolve following command line:
\code{.sh}
exec -i 114514 -f 2.0 --string fuck -b --clamped-float 0.5
\endcode
For convenience, we define following terms used in this article.
\li Every items in command line: Argument.
\li \c -i, \c --clamped-float: \b Switch / \b Option. the argument starts with dash or double dash.
\li \c 114514: \b Value. the value of switch.
\section arg_parser__argument Argument
Argument is the leaf of argument parser.
It has the same position as setting in universal config manager.
\subsection arg_parser__argument__presets Argument Presets
Like setting in universal config manager,
we also provide various common used argument presets.
Current'y we support following argument presets:
\li NumberArgument: The argument storing arithmetic type (except \c bool) inside. Such as <TT>-i 114514</TT> in example.
\li StringArgument: The argument storing string inside. Such as <TT>--string fuck</TT> in example.
\li SwitchArgument: The argument storing nothing. It is just a simple switch. Such as <TT>-b</TT> in example.
When constructing these argument,
you need provide one from long name or short name, or both of them.
Short name is the argument starting with dash and long name starts with double dash.
You don't need add dash or double dash prefix when providing these names.
Please note only ASCII characters, which can be displayed on screen, can be used in these names.
Optionally, you can provide description when constructing,
which will tell user how this switch does and more infomation about this switch.
And, you can add an example to tell user which value is valid.
Next, you can specify an argument to be optional.
Optional argument can be absent in command line.
Oppositely, non-optional argument must be presented in command line,
otherwise parser will return false to indicate an error.
For checking whether an optional argument is specified,
please call AbstractArgument::IsCaptured().
Last, you can optionally assign a constraint to it,
to help argument limit its value.
However SwitchArgument must be optional argument.
Because it is true if user specify it explicit it,
and will be false if user do not give this flag.
SwitchArgument doesn't have constraint features,
because it doesn't store any value inside.
Thus no need to limit this.
\subsection arg_parser__argument__custom Custom Argument
In most cases, the combination use of argument presets and constraints is enough.
However, if you still are urge to create your personal argument,
please inherit AbstractArgument and implement essential class functions.
For the class functions you need to implement,
please refer to our argument presets.
\section arg_parser__argument_list Argument List
Argument list is a struct used by parser for parsing.
It is a higher wrapper of a simple list containing argument items.
We provide 2 ways to get argument list.
\li ArgumentList::CreateFromStd: Create argument list from standard C main function parameters.
\li ArgumentList::CreateFromWin32: Create argument list from Win32 functions in Windows.
You should use this function in Windows instead of ArgumentList::CreateFromStd.
Because the command line passed in standard C main function has encoding issue in Windows.
Use this function you will fetch correct argument list especially command including non-ASCII characters.
Please note the first argument in given command line will be stripped.
Because in most cases it point to the executable self,
and should not be seen as the part of argument list.
\section arg_parser__option_context Option Context
Please note any unknow argument will let the parser return false.
This is different with other argument parsers.
In other common argument parsers,
they will collect all unknow argument as positional argument,
or just simply ignore them.
OptionContext also will not add \c -h or \c --help switch automatically.
This is also differnent with other parsers.
You should manually add it.
However, OptionContext provide a universal help print function, OptionContext::Help.
You can directly call it to output help text if you needed (fail to parse or user order help).
\section arg_parser__limitation Limitation
This universal argument parser is a tiny parser.
It only just fulfill my personal requirements.
So it only accepts limited command line syntax.
In following content I will tell you some syntaxes which this parser \b not accept.
\subsection arg_parser__limitation__flag_combination Flag Combination
\code{.sh}
exec -l -s -h
exec -lsh
\endcode
Parser accept first line but not accept the second line.
You must write these flags independently.
\subsection arg_parser__limitation__equal_symbol Equal Symbol
\code{.sh}
exec --value 114514
exec --value=114514
exec --value:114514
\endcode
Parser only accept first line command.
You can not use equal symbol or any other symbol to assign value for specified argument.
You must write value after the argument immediately please.
\subsection arg_parser__limitation__variable_argument Variable Argument
\code{.sh}
exec -DSOME_VARABLE=SOME_VALUE
exec -D SOME_VARIABLE=SOME_VALUE
\endcode
Parser only accept second line.
However you nned to write a custom argument or constraint to holding this value.
\subsection arg_parser__limitation__switch_dependency Switch Dependency
\code{.sh}
exec --action-a --action-b
\endcode
For command line written above,
if you hope \c --action-a and \c --action-b is exclusive,
or \c --action-b only be valid if \c --action-a specified,
you should manually implement this.
Parser don't have such features to process this switch dependency.
The thing you need to do is set these switches are \b not optional.
And after parser do a success parsing,
manually calling AbstractArgument::IsCaptured to fetch whether corresponding switches are captured,
then do your personal dependency check.
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::COMHelper {
/** /**
\page com_helper COM Helper \page com_helper COM Helper
@ -23,11 +24,12 @@ This namespace contain a COM Guard which make sure COM was initialized in curren
It is essential because all calling to COM functions should be under the premise that COM has been initialized. It is essential because all calling to COM functions should be under the premise that COM has been initialized.
This guard also will uninitialize COM when unloading this module. This guard also will uninitialize COM when unloading this module.
There is only an exposed function called YYCC::COMHelper::IsInitialized for user calling. There is only an exposed function called #IsInitialized for user calling.
This function will check whether COM environment is initialized. This function will check whether COM environment is initialized.
If you want YYCC automatically initialize COM environment for you, If you want YYCC automatically initialize COM environment for you,
you must call this function in your program at least one time. you must call this function in your program at least one time.
Otherwise COM Guard code may be unavailable, Otherwise COM Guard code may be unavailable,
because compiler may think they are not essential code and drop them. because compiler may think they are not essential code and drop them.
*/ */
}

View File

@ -1,8 +1,9 @@
namespace YYCC::ConfigManager {
/** /**
\page config_manager Universal Config Manager \page config_manager Universal Config Manager
Universal config manager give programmer an universal way to manage its program settings. YYCC::ConfigManager give programmer an universal way to manage its program settings.
There is an example about how to use universal config manager. There is an example about how to use universal config manager.
In following content, we will describe it in detail. In following content, we will describe it in detail.
@ -16,7 +17,7 @@ public:
TestConfigManager() : TestConfigManager() :
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)), m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")), m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
m_FloatSetting(YYCC_U8("float-setting"), 0.0f, YYCC::ConfigManager::ConstraintPresets::GetNumberRangeConstraint<float>(-1.0f, 1.0f)), m_FloatSetting(YYCC_U8("float-setting"), 0.0f, YYCC::Constraints::GetNumberRangeConstraint<float>(-1.0f, 1.0f)),
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1), m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), { m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
&m_IntSetting, &m_StringSetting, &m_FloatSetting, &m_EnumSetting &m_IntSetting, &m_StringSetting, &m_FloatSetting, &m_EnumSetting
@ -32,11 +33,11 @@ public:
YYCC::ConfigManager::CoreManager m_CoreManager; YYCC::ConfigManager::CoreManager m_CoreManager;
}; };
// init cfg manager // Initialize config manager
TestConfigManager test; TestConfigManager test;
// load string // Load settings.
test.m_CoreManager.Load() test.m_CoreManager.Load()
// get string value // Get string setting value.
auto val = test.m_StringSetting.Get(); auto val = test.m_StringSetting.Get();
\endcode \endcode
@ -49,56 +50,31 @@ Each setting describe a single configuration entry.
We currently provide 2 setting preset classes which you can directly use. We currently provide 2 setting preset classes which you can directly use.
\li YYCC::ConfigManager::NumberSetting: The setting storing a number inside. \li NumberSetting: The setting storing a number inside.
It is a template class. Support all arithmetic and enum types (integral, floating point, bool, enum). It is a template class. Support all arithmetic and enum types (integral, floating point, bool, enum).
\li YYCC::ConfigManager::StringSetting: The setting storing a string inside. \li StringSetting: The setting storing a string inside.
When constructing these settings, When constructing these settings,
you need to provide its unique name which will be used when saving to file or reading from file. you need to provide its unique name which will be used when saving to file or reading from file.
Also you need to provide a default value for it. Also you need to provide a default value for it.
It will be used when fail to read file or initializing itself. It will be used when fail to read file or initializing itself.
Optionally, you can assign a constraint to it which we will introduce in following section.
Optionally, you also can provide a constraint to setting.
Constraint is the struct instructing library to limit value in specified range.
It usually is used for making sure the setting stored value is valid.
See \ref constraints chapters to know how we provide constraints.
\subsection config_manager__setting__custom Custom Setting \subsection config_manager__setting__custom Custom Setting
In most cases, the combination use of setting presets and constraints introduced in following is enough. In most cases, the combination use of setting presets and constraints is enough.
However, if you still are urge to create your personal setting, However, if you still are urge to create your personal setting,
please inherit YYCC::ConfigManager::AbstractSetting and implement essential class functions. please inherit AbstractSetting and implement essential class functions.
For the class functions you need to implement, For the class functions you need to implement,
please refer to our setting presets, YYCC::ConfigManager::NumberSetting and YYCC::ConfigManager::StringSetting. please refer to our setting presets, NumberSetting and StringSetting.
\section config_manager__constraint Constraint
Constraint can be applied to specific setting instance,
and limit its value to specific values,
such as minimum maximum value, specific string format and etc.
\subsection config_manager__constraint__presets Constraint Presets
YYCC::ConfigManager provide some constraint presets in YYCC::ConfigManager::Constraints namespace.
All functions inside this namespace will return a YYCC::ConfigManager::Constraint instance,
and you can directly assign it to the constructor of setting.
Currently there is only one constraint preset:
\li YYCC::ConfigManager::Constraints::GetNumberRangeConstraint: Constrain the number value in minimum maximum value range (inclusive).
\subsection config_manager__constraint__custom Custom Constraint
For creating your personal constraint,
you need to create YYCC::ConfigManager::Constraint instance manually.
First you need decide the template argument of YYCC::ConfigManager::Constraint.
The type you assigned to template argument always is
the same type which is accepted by the setting this constraint will be applied to.
Second, you need assign class member of YYCC::ConfigManager::Constraint.
Currently there is only one class member.
It is a function pointer called when correcting value.
See our constraint presets for more infomation about how to write it.
\section config_manager__core_manager Core Manager \section config_manager__core_manager Core Manager
YYCC::ConfigManager::CoreManager manage a collection of settings. CoreManager manage a collection of settings.
And have responsibility to reading and writing config file. And have responsibility to reading and writing config file.
We highly suggest that you create a personal config manager class like example does. We highly suggest that you create a personal config manager class like example does.
@ -117,4 +93,5 @@ Core manager will reject reading and use default value for all settings.
Otherwise, core manager will try to read config file and do proper migration if possible. Otherwise, core manager will try to read config file and do proper migration if possible.
The last argument is an initializer list which contain the \b pointer to all settings this manager managed. The last argument is an initializer list which contain the \b pointer to all settings this manager managed.
*/ */
}

View File

@ -1,3 +1,4 @@
namespace YYCC::ConsoleHelper {
/** /**
\page console_helper Console Helper \page console_helper Console Helper
@ -22,9 +23,9 @@ That's ASCII Escape Code.
As we introduced in above, As we introduced in above,
you may know Windows console does not support ASCII Escape Code color in default. you may know Windows console does not support ASCII Escape Code color in default.
However YYCC::ConsoleHelper::EnableColorfulConsole can fix this issue. However #EnableColorfulConsole can fix this issue.
YYCC::ConsoleHelper::EnableColorfulConsole will forcely enable ASCII Escape Code support in Windows console if possible. #EnableColorfulConsole will forcely enable ASCII Escape Code support in Windows console if possible.
Thus you can write colorful text in Windows console freely. Thus you can write colorful text in Windows console freely.
We suggest you to call this function at the beginning of program. We suggest you to call this function at the beginning of program.
@ -46,7 +47,7 @@ And for second line, it will make <TT>"Light Red"</TT> to be shown in light red
but <TT>"I am "</TT> will keep default console font color. but <TT>"I am "</TT> will keep default console font color.
You also may notice this macro is used with YYCC_U8 macro. You also may notice this macro is used with YYCC_U8 macro.
Because YYCC::ConsoleHelper::WriteLine only accept UTF8 argument. Because #WriteLine only accept UTF8 argument.
So please note if you use console color macro with YYCC_U8, So please note if you use console color macro with YYCC_U8,
please make YYCC_U8 always is located the outside. please make YYCC_U8 always is located the outside.
Otherwise, YYCC_U8 will fail to make the whole become UTF8 stirng as we introduced in \ref library_encoding. Otherwise, YYCC_U8 will fail to make the whole become UTF8 stirng as we introduced in \ref library_encoding.
@ -176,4 +177,5 @@ only write plain string like \c std::fputs().
This is commonly used, otherwise functions will only write the text provided by arguments, This is commonly used, otherwise functions will only write the text provided by arguments,
without adding something. without adding something.
*/ */
}

49
doc/src/constraints.dox Normal file
View File

@ -0,0 +1,49 @@
namespace YYCC::Constraints {
/**
\page constraints Constraints
YYCC::Constraints namespace provide Constraint struct declaration
and various common constraint generator function.
This namespace is specifically used by YYCC::ConfigManager and YYCC::ArgParser namespaces.
See \ref config_manager chapter and \ref arg_parser chapter for how to utlize this namespace.
\section constraints__prototype Prototype
Constraint instruct library how check whether given value is in range,
and how to clamp it if it is invalid.
For example, you can use constraint to limit a number in given minimum maximum value,
or limit a string in specific format by using regex and etc.
Constraint is a template struct.
The argument of template is the underlying data type which need to be checked.
The struct with different template argument is not compatible.
Currently, this struct only contain 1 function pointer,
which is used for detecting whether given value is in range / valid.
\subsection constraints__presets Constraint Presets
YYCC::Constraints provides some constraint presets which are commonly used.
All functions inside this namespace will return a Constraint instance,
and you can directly use it.
There is a list of all provided functions:
\li GetMinMaxRangeConstraint(): Limit the number value in given minimum maximum value range (inclusive).
\li GetEnumEnumerationConstraint(): Limit the enum value by given all possible value set.
\li GetStringEnumerationConstraint(): Limit the string by given all possible value set.
\subsection config_manager__constraint__custom Custom Constraint
For creating your personal constraint,
you need to create Constraint instance manually.
You can browse all existing constraint preset functions code for know how to write it.
The things you need to do is simple.
First, you need decide the template argument of Constraint.
Second, you need assign class member of Constraint by C++ lambda syntax.
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::DialogHelper {
/** /**
\page dialog_helper Dialog Helper \page dialog_helper Dialog Helper
@ -12,7 +13,7 @@ It will be totally invisible if you are in other platforms.
\section dialog_helper__file_dialog Configure File Dialog \section dialog_helper__file_dialog Configure File Dialog
The first thing is that we should initialize YYCC::DialogHelper::FileDialog, The first thing is that we should initialize FileDialog,
and configure it according to your requirements. and configure it according to your requirements.
This class is the data struct representing all aspects of file dialog. This class is the data struct representing all aspects of file dialog.
@ -28,7 +29,7 @@ params.SetInitDirectory(initial_directory_getter());
\subsection dialog_helper__file_dialog__owner Owner \subsection dialog_helper__file_dialog__owner Owner
YYCC::DialogHelper::FileDialog::SetOwner will set owner of this dialog. FileDialog::SetOwner will set owner of this dialog.
It accepts a Microsoft defined \c HWND as argument which should be familiar with Windows programmer. It accepts a Microsoft defined \c HWND as argument which should be familiar with Windows programmer.
If you pass \c NULL to it or skip calling this function, it indicate that there is no owner of this dialog. If you pass \c NULL to it or skip calling this function, it indicate that there is no owner of this dialog.
<I> <I>
@ -38,7 +39,7 @@ But it would be better to have an owner if possible.
\subsection dialog_helper__file_dialog__title Title \subsection dialog_helper__file_dialog__title Title
YYCC::DialogHelper::FileDialog::SetTitle will set dialog title of this dialog. FileDialog::SetTitle will set dialog title of this dialog.
If you pass \c nullptr or skip calling it, If you pass \c nullptr or skip calling it,
the title of dialog will be filled by system and the function type you calling. the title of dialog will be filled by system and the function type you calling.
For example, the title will be "Open..." if you call open file function, For example, the title will be "Open..." if you call open file function,
@ -49,7 +50,7 @@ So I suggest you do not set title except you really want to modify title.
\subsection dialog_helper__file_dialog__init_file_name Initial File Name \subsection dialog_helper__file_dialog__init_file_name Initial File Name
YYCC::DialogHelper::FileDialog::SetInitFileName will set the initial file name presented in dialog file name input box. FileDialog::SetInitFileName will set the initial file name presented in dialog file name input box.
If you pass \c nullptr or skip calling it, the text in dialog file name input box will be empty. If you pass \c nullptr or skip calling it, the text in dialog file name input box will be empty.
User can modify the name presented in input box later. User can modify the name presented in input box later.
@ -59,7 +60,7 @@ However, if you specify this field, the dialog will always presented your specif
\subsection dialog_helper__file_dialog__init_directory Initial Directory \subsection dialog_helper__file_dialog__init_directory Initial Directory
YYCC::DialogHelper::FileDialog::SetInitDirectory will set the initial directory (startup directory) when opening dialog. FileDialog::SetInitDirectory will set the initial directory (startup directory) when opening dialog.
In following cases, initial directory will fall back to system behavior: In following cases, initial directory will fall back to system behavior:
@ -80,7 +81,7 @@ It is beneficial to let user get the file which they want in a directory includi
Because the file dialog picking directory does not have file filter drop down box. Because the file dialog picking directory does not have file filter drop down box.
Directory can not be filtered. Directory can not be filtered.
YYCC::DialogHelper::FileFilters takes responsibility for this feature: FileFilters takes responsibility for this feature:
\code \code
auto& filters = params.ConfigreFileTypes(); auto& filters = params.ConfigreFileTypes();
@ -94,9 +95,9 @@ params.SetDefaultFileTypeIndex(0u);
\subsection dialog_helper__file_filters__setup File Filters \subsection dialog_helper__file_filters__setup File Filters
We don't need to initialize YYCC::DialogHelper::FileFilters by ourselves. We don't need to initialize FileFilters by ourselves.
Oppositely, we fetch it from YYCC::DialogHelper::FileDialog instance by calling YYCC::DialogHelper::FileDialog::ConfigreFileTypes. Oppositely, we fetch it from FileDialog instance by calling FileDialog::ConfigreFileTypes.
After fetching, we can call YYCC::DialogHelper::FileFilters::Add to add a filter pair for file filters. After fetching, we can call FileFilters::Add to add a filter pair for file filters.
The first argument is the display text which user will see in file filter drop down box. The first argument is the display text which user will see in file filter drop down box.
@ -106,21 +107,21 @@ It is okey to use multiple wildcard string in list.
This is suit for those file types involving multiple file extensions, such as the old and new file types of Microsoft Office as we illustracted. This is suit for those file types involving multiple file extensions, such as the old and new file types of Microsoft Office as we illustracted.
Empty list not allowed Empty list not allowed
YYCC::DialogHelper::FileFilters::Add also will return a bool to indicate the success of this adding. FileFilters::Add also will return a bool to indicate the success of this adding.
It should at least has one file filter in file dialog. It should at least has one file filter in file dialog.
I don't know the consequence if you don't provide any file filter. I don't know the consequence if you don't provide any file filter.
\subsection dialog_helper__file_filters__default_filter Default File Type \subsection dialog_helper__file_filters__default_filter Default File Type
YYCC::DialogHelper::FileDialog::SetDefaultFileTypeIndex will set the default selected file filter of this dialog. FileDialog::SetDefaultFileTypeIndex will set the default selected file filter of this dialog.
It accepts an index pointing to the file filter which you want to show in default for this file dialog. It accepts an index pointing to the file filter which you want to show in default for this file dialog.
The index of file filters is the order where you call YYCC::DialogHelper::FileFilters::Add above. The index of file filters is the order where you call FileFilters::Add above.
If you pass \c NULL to it or skip calling this function, the first one will be default. If you pass \c NULL to it or skip calling this function, the first one will be default.
\section dialog_helper__result Create Dialog and Get Result \section dialog_helper__result Create Dialog and Get Result
Finally, we can call file dialog functions by we initialized YYCC::DialogHelper::FileDialog Finally, we can call file dialog functions by we initialized FileDialog
\code \code
YYCC::yycc_u8string single_selection; YYCC::yycc_u8string single_selection;
@ -134,14 +135,14 @@ YYCC::DialogHelper::OpenFolderDialog(params, single_selection);
There are 4 file dialogs you can choose: There are 4 file dialogs you can choose:
\li YYCC::DialogHelper::OpenFileDialog: Open single file \li #OpenFileDialog: Open single file
\li YYCC::DialogHelper::OpenMultipleFileDialog: Open multiple files \li #OpenMultipleFileDialog: Open multiple files
\li YYCC::DialogHelper::SaveFileDialog: Save single file \li #SaveFileDialog: Save single file
\li YYCC::DialogHelper::OpenFolderDialog: Open single directory \li #OpenFolderDialog: Open single directory
\subsection dialog_helper__result__arguments Arguments \subsection dialog_helper__result__arguments Arguments
Among these 4 functions, the first argument always is the reference to YYCC::DialogHelper::FileDialog. Among these 4 functions, the first argument always is the reference to FileDialog.
Function will use it to decide what should be shown in this file dialog. Function will use it to decide what should be shown in this file dialog.
The second argument always is the reference to the container receiving the result. The second argument always is the reference to the container receiving the result.
@ -160,8 +161,9 @@ You may notice there are various classes which we never introduce.
Because they are intermediate classes and should not be used by programmer. Because they are intermediate classes and should not be used by programmer.
For example: For example:
\li YYCC::DialogHelper::WinFileDialog: The converted YYCC::DialogHelper::FileDialog passed to Windows. \li WinFileDialog: The converted FileDialog passed to Windows.
\li YYCC::DialogHelper::WinFileFilters: Same as YYCC::DialogHelper::WinFileDialog. It will be passed to Windows functions. \li WinFileFilters: Same as WinFileDialog. It will be passed to Windows functions.
\li etc... \li etc...
*/ */
}

View File

@ -1,3 +1,4 @@
namespace YYCC::EncodingHelper {
/** /**
\page encoding_helper Encoding Helper \page encoding_helper Encoding Helper
@ -15,10 +16,10 @@ See \ref library_encoding for more infomation.
YYCC supports following convertions: YYCC supports following convertions:
\li YYCC::EncodingHelper::ToUTF8: Convert ordinary string to UTF8 string. \li #ToUTF8: Convert ordinary string to UTF8 string.
\li YYCC::EncodingHelper::ToUTF8View: Same as ToUTF8, but return string view instead. \li #ToUTF8View: Same as ToUTF8, but return string view instead.
\li YYCC::EncodingHelper::ToOrdinary: Convert UTF8 string to ordinary string. \li #ToOrdinary: Convert UTF8 string to ordinary string.
\li YYCC::EncodingHelper::ToOrdinaryView: Same as ToOrdinary, but return string view instead. \li #ToOrdinaryView: Same as ToOrdinary, but return string view instead.
\section encoding_helper__win_conv Windows Specific Convertion \section encoding_helper__win_conv Windows Specific Convertion
@ -32,13 +33,13 @@ Please use them carefully (make sure that you are using them only in Windows env
YYCC supports following convertions: YYCC supports following convertions:
\li YYCC::EncodingHelper::WcharToChar: Convert \c wchar_t string to code page specified string. \li #WcharToChar: Convert \c wchar_t string to code page specified string.
\li YYCC::EncodingHelper::CharToWchar: The reversed convertion of WcharToChar. \li #CharToWchar: The reversed convertion of WcharToChar.
\li YYCC::EncodingHelper::CharToChar: Convert string between 2 different code pages. It's a shortcut of calling CharToWchar and WcharToChar successively. \li #CharToChar: Convert string between 2 different code pages. It's a shortcut of calling CharToWchar and WcharToChar successively.
\li YYCC::EncodingHelper::WcharToUTF8: Convert \c wchar_t string to UTF8 string. \li #WcharToUTF8: Convert \c wchar_t string to UTF8 string.
\li YYCC::EncodingHelper::UTF8ToWchar: The reversed convertion of WcharToUTF8. \li #UTF8ToWchar: The reversed convertion of WcharToUTF8.
\li YYCC::EncodingHelper::CharToUTF8: Convert code page specified string to UTF8 string. \li #CharToUTF8: Convert code page specified string to UTF8 string.
\li YYCC::EncodingHelper::UTF8ToChar: The reversed convertion of CharToUTF8. \li #UTF8ToChar: The reversed convertion of CharToUTF8.
Code Page is a Windows concept. Code Page is a Windows concept.
If you don't understand it, please view corresponding Microsoft documentation. If you don't understand it, please view corresponding Microsoft documentation.
@ -55,15 +56,15 @@ They can be used in any platform, not confined in Windows platforms.
YYCC supports following convertions: YYCC supports following convertions:
\li YYCC::EncodingHelper::UTF8ToUTF16: Convert UTF8 string to UTF16 string. \li #UTF8ToUTF16: Convert UTF8 string to UTF16 string.
\li YYCC::EncodingHelper::UTF16ToUTF8: The reversed convertion of UTF8ToUTF16. \li #UTF16ToUTF8: The reversed convertion of UTF8ToUTF16.
\li YYCC::EncodingHelper::UTF8ToUTF32: Convert UTF8 string to UTF32 string. \li #UTF8ToUTF32: Convert UTF8 string to UTF32 string.
\li YYCC::EncodingHelper::UTF32ToUTF8: The reversed convertion of UTF8ToUTF32. \li #UTF32ToUTF8: The reversed convertion of UTF8ToUTF32.
\section encoding_helper__overloads Function Overloads \section encoding_helper__overloads Function Overloads
Every encoding convertion functions (except the convertion between UTF8 and ordinary string) have 4 different overloads for different scenarios. Every encoding convertion functions (except the convertion between UTF8 and ordinary string) have 4 different overloads for different scenarios.
Take YYCC::EncodingHelper::WcharToChar for example. Take #WcharToChar for example.
There are following 4 overloads: There are following 4 overloads:
\code \code
@ -98,7 +99,7 @@ For the first type, please note that there is \b NO guarantee that the argument
Even the convertion is failed, the argument holding return value may still be changed by function itself. Even the convertion is failed, the argument holding return value may still be changed by function itself.
In this case, the type of result is \c std::string because this is function required. In this case, the type of result is \c std::string because this is function required.
In other functions, such as YYCC::EncodingHelper::WcharToUTF8, the type of result can be \c yycc_u8string or etc. In other functions, such as #WcharToUTF8, the type of result can be \c yycc_u8string or etc.
So please note the type of result is decided by convertion function itself, not only \c std::string. So please note the type of result is decided by convertion function itself, not only \c std::string.
\subsection encoding_helper__overloads__source Source String \subsection encoding_helper__overloads__source Source String
@ -106,7 +107,7 @@ So please note the type of result is decided by convertion function itself, not
According to the way providing source string, According to the way providing source string,
these 4 overload also can be divided into 2 types. these 4 overload also can be divided into 2 types.
The first type take a reference to constant \c std::wstring_view. The first type take a reference to constant \c std::wstring_view.
The second type take a pointer to constant wchar_t. The second type take a pointer to constant \c wchar_t.
For first type, it will take the whole string for convertion, including \b embedded NUL terminal. For first type, it will take the whole string for convertion, including \b embedded NUL terminal.
Please note we use string view as argument. Please note we use string view as argument.
@ -120,12 +121,12 @@ If you want to process string with \b embedded NUL terminal, please choose first
Otherwise the second type overload is enough. Otherwise the second type overload is enough.
Same as destination string, the type of source is also decided by the convertion function itself. Same as destination string, the type of source is also decided by the convertion function itself.
For exmaple, the type of source in YYCC::EncodingHelper::UTF8ToWchar is \c yycc_u8string_view and \c yycc_char8_t, For exmaple, the type of source in #UTF8ToWchar is \c yycc_u8string_view and \c yycc_char8_t,
not \c std::wstring and \c wchar_t. not \c std::wstring and \c wchar_t.
\subsection encoding_helper__overloads__extra Extra Argument \subsection encoding_helper__overloads__extra Extra Argument
There is an extra argument called \c code_page for YYCC::EncodingHelper::WcharToChar. There is an extra argument called \c code_page for #WcharToChar.
It indicates the code page of destination string, It indicates the code page of destination string,
because this function will convert \c wchar_t string to the string with specified code page encoding. because this function will convert \c wchar_t string to the string with specified code page encoding.
@ -143,4 +144,5 @@ we have 4 different overload as we illustrated before.
Programmer can use them freely according to your requirements. Programmer can use them freely according to your requirements.
And don't forget to provide extra argument if function required. And don't forget to provide extra argument if function required.
*/ */
}

View File

@ -1,3 +1,4 @@
namespace YYCC::ExceptionHelper {
/** /**
\page exception_helper Unhandled Exception Handler \page exception_helper Unhandled Exception Handler
@ -18,11 +19,24 @@ It will be invisible on other platforms.
\subsection exception_helper__usage__code Register Code \subsection exception_helper__usage__code Register Code
In most scenarios, programmer only need call YYCC::ExceptionHelper::Register() when program started or module loaded. In most scenarios, programmer only need call #Register when program started or module loaded.
And call YYCC::ExceptionHelper::Unregister when program exited or module unloaded. And call #Unregister when program exited or module unloaded.
All details are hidden by these 2 feature. All details are hidden by these 2 feature.
Programmer do not need worried about the implementation of unhandled exception handler. Programmer do not need worried about the implementation of unhandled exception handler.
Optionally, you can provide a function pointer during calling #Register as a callback.
The prototype of this function pointer is #ExceptionCallback.
This callback will be called if any unhandled exception happened.
It provides 2 pathes to log file and core dump file respectively.
So that you can use an explicit way, e.g. \c MessageBox, to tell user exception happened and where are the log files,
especially in GUI application because the default output stream, \c stderr, is invisible in GUI application.
However, please note the pathes provided by callback may be empty.
In this case, it means that handler fail to create corresponding log files.
Also, if you trying to register unhandled exception handler on the same process in different module with different callback,
only the callback provided in first success registering will be called when unhandled exception happened,
due to \ref exception_helper__notes__singleton design.
\subsection exception_helper__usage__location Location \subsection exception_helper__usage__location Location
When unhandled exception occurs, When unhandled exception occurs,
@ -64,16 +78,16 @@ YYCC::ExceptionHelper also have a mechanism that make sure the same unhandled ex
For example, you have an executable program A.exe, and 2 dynamic libraries B.dll and C.dll. For example, you have an executable program A.exe, and 2 dynamic libraries B.dll and C.dll.
A.exe and B.dll use YYCC unhandled exception handler feature but C.dll not. A.exe and B.dll use YYCC unhandled exception handler feature but C.dll not.
A.exe will load B.dll and C.dll at runtime. A.exe will load B.dll and C.dll at runtime.
Although both A.exe and B.dll call YYCC::ExceptionHelper::Register(), Although both A.exe and B.dll call #Register,
when unhandled exception occurs, there is only one error report output, when unhandled exception occurs, there is only one error report output,
which may be generated by A.exe or B.dll accoridng to their order of loading. which may be generated by A.exe or B.dll accoridng to their order of loading.
The core purpose of this is making sure the program will not output too many error report for the same unhandled exception, The core purpose of this is making sure the program will not output too many error report for the same unhandled exception,
no matter how many modules calling YYCC::ExceptionHelper::Register() are loaded. no matter how many modules calling #Register are loaded.
Only one error report is enough. Only one error report is enough.
More precisely, we use \c CreateMutexW to create an unique mutex in Windows global scope, More precisely, we use \c CreateMutexW to create an unique mutex in Windows global scope,
to make sure YYCC::ExceptionHelper::Register() only run once in the same process. to make sure #Register only run once in the same process.
It is very like the implementation of singleton application. It is very like the implementation of singleton application.
\subsection exception_helper__notes__recursive_calling Recursive Calling \subsection exception_helper__notes__recursive_calling Recursive Calling
@ -84,4 +98,13 @@ YYCC::ExceptionHelper has internal mechanism to prevent this bad case.
If this really happened, the handler will quit silent and will not cause any issue. If this really happened, the handler will quit silent and will not cause any issue.
Programmer don't need to worry about this. Programmer don't need to worry about this.
\subsection exception_helper__notes__user_callback The Timing of User Callback
The timing of calling user callback is the tail of unhandled exception handler.
It means that all log and coredump have been written if possible before calling callback.
Because user callback may still raise exception.
We want all essential log files has been written before calling it,
so that at least we can visit them on disk or console.
*/ */
}

View File

@ -29,7 +29,7 @@
\li \subpage intro \li \subpage intro
\li \subpage platform_checker \li \subpage library_macros
\li \subpage library_encoding \li \subpage library_encoding
@ -43,12 +43,16 @@
\li \subpage io_helper \li \subpage io_helper
\li \subpage fs_path_patch \li \subpage std_patch
<B>Advanced Features</B> <B>Advanced Features</B>
\li \subpage constraints
\li \subpage config_manager \li \subpage config_manager
\li \subpage arg_parser
</TD> </TD>
<TD ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT" VALIGN="TOP">

View File

@ -29,12 +29,12 @@ During programming, I found Windows is super lack in UTF8 supports.
Programmer loves UTF8, because it can handle all charcaters over the world in one encoding and is still compatible with C-Style string. Programmer loves UTF8, because it can handle all charcaters over the world in one encoding and is still compatible with C-Style string.
However, Windows use a weird way to achieve internationalization, 2 different function trailing, A and W for legacy code and modern code respectively. However, Windows use a weird way to achieve internationalization, 2 different function trailing, A and W for legacy code and modern code respectively.
The worst things is that the char type W trailing function used, \c WCHAR, is defined as 2 bytes long, not 4 bytes long as Linux does (\c wchar_t). The worst things is that the char type W trailing function used, \c WCHAR, is defined as 2 bytes long, not 4 bytes long as Linux does (\c wchar_t).
It mean that one emoji charcater will be torn into 2 \c WCHAR on Windows because emoji code unit is higher than the manimum value of \c WCHAR. It mean that one emoji charcater will be torn into 2 \c WCHAR on Windows because emoji code unit is higher than the maximum value of \c WCHAR.
Also, there are various issues which should not be presented. Also, there are various issues which should not be presented.
For example, Microsoft invents various \e safe standard library functions to prevent possible overflow issues raised by \c std::fgets and etc. For example, Microsoft invents various \e safe standard library functions to prevent possible overflow issues raised by \c std::fgets and etc.
also, MSVC may throw weird error when you using some specific standard library functions. also, MSVC may throw weird error when you using some specific standard library functions.
You need to define some weird macro to disable this shitty behavior. You need to define some weird macros to disable this shitty behavior.
There are various non-standard issue you may faced on Windows programming. There are various non-standard issue you may faced on Windows programming.
All in all, programming on Windows is a tough work. All in all, programming on Windows is a tough work.
@ -102,10 +102,33 @@ Another one is MSVC distribution, this distribution is served for other MSVC pro
These have different directory layout which is specifically designed for corresponding build tools. These have different directory layout which is specifically designed for corresponding build tools.
See following section for more details. See following section for more details.
\subsection intro__usage__win__execute Execute Build Script
For creating distribution on Windows, please execute script <TT>python3 script/gen_win_build.py</TT> first.
Then execute <TT>script/win_build.bat</TT> to generate final result.
\c script/gen_win_build.py is the generator of \c script/win_build.bat.
It will accept various arguments and generate a proper real build script for you.
Currently \c script/gen_win_build.py supports following arguments:
\li \c -c, \c --cpp \c [cpp_version]: Specify the version of C++ standard for building.
Due to the different defination of UTF8 char type,
C++ 20 program can not use this library built by C++ 17 environment.
So this switch give you a chance to decide the version of C++ standard used when building.
The lowest and defult version of C++ standard is 17.
\li \c -d, \c --no-doc: Specify this if you don't want to build documentation.
End user usually needs documentation,
however if you are the developer of this library, you may need this switch.
Because documentation take too much disk space and cost a bunch of time for building and copying.
In default, generator will produce script which build documentation automatically.
After script done, you will find CMake distribution in directory <TT>bin/<I>cpp_ver</I>/install</TT>.
and you will also find your MSVC distribution in directory <TT>bin/<I>cpp_ver</I>/msvc_install</TT>.
\e cpp_ver in path will be replaced by the C++ version you specified.
\subsubsection intro__usage__win__cmake CMake Distribution \subsubsection intro__usage__win__cmake CMake Distribution
For creating CMake distribution, please execute script <TT>script/win_build.bat</TT>. CMake distribution has following directory structure.
After script done, you will find CMake distribution in directory <TT>bin/install</TT> with following structure.
\verbatim \verbatim
YYCC YYCC
@ -132,11 +155,7 @@ So that CMake will automatically utilize correct package when switching build ty
\subsubsection intro__usage__win__msvc MSVC Distribution \subsubsection intro__usage__win__msvc MSVC Distribution
Before creating MSVC distribution, you should create CMake distribution first, MSVC distribution has following directory structure.
because MSVC distribution depend on CMake distribution.
After creating CMake distribution, you can simply create MSVC distribution by executing <TT>script/win_msvc_build.bat</TT>.
Then you will find your MSVC distribution in directory <TT>bin/msvc_install</TT> with following structure.
\verbatim \verbatim
YYCC YYCC
@ -159,4 +178,11 @@ that MSVC distribution places all static library under one director \c lib.
Thus in MSVC project user can simply spcify the install path of YYCC, Thus in MSVC project user can simply spcify the install path of YYCC,
and use MSVC macros in path to choose correct static library for linking and use MSVC macros in path to choose correct static library for linking
\section intro__debug Debug Tips
YYCC CMake build script contains a special option called \c YYCC_DEBUG_UE_FILTER.
If you set it to true, it will add a public macro \c YYCC_DEBUG_UE_FILTER to YYCC project.
This macro will enable special code path for the convenience of debugging \ref exception_helper related features.
So in common use, user should not enable this option.
*/ */

View File

@ -1,8 +1,9 @@
namespace YYCC::IOHelper {
/** /**
\page io_helper IO Helper \page io_helper IO Helper
YYCC::IOHelper currently only has one function and one macro. Actually, YYCC::IOHelper includes functions which can not be placed in other place.
\section io_helper__ptr_pri_padding Pointer Print Padding \section io_helper__ptr_pri_padding Pointer Print Padding
@ -24,6 +25,11 @@ std::printf(stdout, "Raw Pointer 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR, raw_ptr);
Note \c PRIXPTR is defined by standard library for formatting pointer as hexadecimal style. Note \c PRIXPTR is defined by standard library for formatting pointer as hexadecimal style.
\section io_helper__smart_file Smart FILE Pointer
#SmartStdFile use \c std::unique_ptr with custom deleter to implement smart \c FILE*.
It is useful in the cases that you want to automatically free opened file when leaving corresponding scope.
\section io_helper__utf8_fopen UTF8 fopen \section io_helper__utf8_fopen UTF8 fopen
In Windows, standard \c std::fopen can not handle UTF8 file name in common environment. In Windows, standard \c std::fopen can not handle UTF8 file name in common environment.
@ -40,4 +46,5 @@ There is a simple example:
FILE* fs = YYCC::IOHelper::FOpen(YYCC_U8("/path/to/file"), YYCC_U8("rb")); FILE* fs = YYCC::IOHelper::FOpen(YYCC_U8("/path/to/file"), YYCC_U8("rb"));
\endcode \endcode
*/ */
}

View File

@ -1,3 +1,4 @@
namespace YYCC {
/** /**
\page library_encoding Library Encoding \page library_encoding Library Encoding
@ -224,3 +225,4 @@ Linux user do not need care this.
Because almost Linux distro use UTF8 in default. Because almost Linux distro use UTF8 in default.
*/ */
}

View File

@ -0,0 +1,80 @@
namespace YYCC {
/**
\page library_macros Library Macros
In this page we will introduce the macros defined by this library
which can not be grouped in other topic.
\section library_macros__platform_checker Platform Checker
In many cross platform applications,
programmer usually write code adapted to different platforms in one source file
and enable them respectively by macros representing the target platform.
As a cross platform library,
YYCC also has this feature and you can utilize it if you don't have other ways to so the same things.
\subsection library_macros__platform_checker__values Values
YYCC always define a macro called \c YYCC_OS to indicate the system of target platform.
In implementation, it will check following list from top to bottom to set matched value for it.
\li \c YYCC_OS_WINDOWS: Windows environment. It is done by checking whether environment define \c _WIN32 macro.
\li \c YYCC_OS_LINUX: In current implementation, this means target platform is \b NOT Windows.
\subsection library_macros__platform_checker__usage Usage
Now you know any possible value of \c YYCC_OS.
The next step is how to use it to enable specified code in specific target platform.
We take Windows platform for example.
Assume \c blabla() function is Windows specific.
We have following example code:
\code
#if YYCC_OS == YYCC_OS_WINDOWS
blabla();
#endif
\endcode
It's enough and simple that use \c \#if to bracket the Windows specified code.
\section library_macros__batch_class_copy_move Batch Class Copy / Move Functions
YYCC provides 6 macros to batchly remove class copy constructor and move constructor,
or set default class copy constructor and move constructor.
<UL>
<LI>
\c YYCC_DEL_CLS_COPY: Declare following 2 statements which delete copy constrcutor and copy assign operator.
<UL>
<LI><TT>CLSNAME(const CLSNAME&) = delete;</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&) = delete;</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DEL_CLS_MOVE: Declare following 2 statements which delete move constrcutor and move assign operator.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) = delete;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) = delete;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DEL_CLS_COPY_MOVE: The combination of \c YYCC_DEL_CLS_COPY and \c YYCC_DEL_CLS_MOVE.</LI>
<LI>
\c YYCC_DEF_CLS_COPY: Declare following 2 statements which set default copy constrcutor and copy assign operator.
<UL>
<LI><TT>CLSNAME(const CLSNAME&) = default;</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&) = default;</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DEF_CLS_MOVE: Declare following 2 statements which set default move constrcutor and move assign operator.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) = default;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) = default;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DEF_CLS_COPY_MOVE: The combination of \c YYCC_DEF_CLS_COPY and \c YYCC_DEF_CLS_MOVE.</LI>
</UL>
*/
}

View File

@ -1,3 +1,4 @@
namespace YYCC::ParserHelper {
/** /**
\page parser_helper Parser Helper \page parser_helper Parser Helper
@ -14,11 +15,11 @@ Functions located in this helper support the convertion between string and follo
Please note in C++, \c bool is integral type but we list it individually because parser will treat it specially. Please note in C++, \c bool is integral type but we list it individually because parser will treat it specially.
For \c bool type, parser will try doing convertion between it and \c "true" \c "false" string. For \c bool type, parser will try doing convertion between it and \c "true" \c "false" string.
(\b case-sensitive. It means that \c true will only be converted to \c "true" and \c "TRUE" can not be recognised.) (\b case-insensitive. It means that \c true can be converted from \c "true", \c "True" or \c "TRUE".)
\section parser_helper__try_parse Try Parse \section parser_helper__try_parse Try Parse
YYCC::ParserHelper::TryParse will try to parse string into caller specified type. #TryParse will try to parse string into caller specified type.
All of them accept an UTF8 string view at first argument, All of them accept an UTF8 string view at first argument,
require that you provide a container receiving converted result in the second argument, require that you provide a container receiving converted result in the second argument,
and return a bool value to indicate whether the convertion is successful. and return a bool value to indicate whether the convertion is successful.
@ -30,11 +31,12 @@ YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("123"), val);
YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("7fff"), val, 16); YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("7fff"), val, 16);
\endcode \endcode
For floating point type, this function allows caller to specify extra argument providing the format of given number string (\c std::chars_format).
For integral type, this function allows caller to specify extra argument providing the base of given number string. For integral type, this function allows caller to specify extra argument providing the base of given number string.
\section parser_helper__parse Parse \section parser_helper__parse Parse
YYCC::ParserHelper::Parse is similar to YYCC::ParserHelper::TryParse. #Parse is similar to #TryParse.
But it will not return bool value to indicate success and doesn't have the argument receiving result. But it will not return bool value to indicate success and doesn't have the argument receiving result.
It only accepts an UTF8 string view as the only one argument, and return result directly. It only accepts an UTF8 string view as the only one argument, and return result directly.
If the convertion failed, the return value is \b undefined (but usually is the default value of given type). If the convertion failed, the return value is \b undefined (but usually is the default value of given type).
@ -44,15 +46,15 @@ There is an example:
uint32_t val = YYCC::ParserHelper::Parse<uint32_t>(YYCC_U8("123")); uint32_t val = YYCC::ParserHelper::Parse<uint32_t>(YYCC_U8("123"));
\endcode \endcode
Please note, for integral types, there is no base argument in YYCC::ParserHelper::Parse. For integral and floating point value,
Please use YYCC::ParserHelper::TryParse instead. it has same extra argument with #TryParse to provide more number infomation.
Using this function is dangerous if the validation of your input is important. Using this function is dangerous if the validation of your input is important.
In this case, please use YYCC::ParserHelper::TryParse instead. In this case, please use #TryParse instead.
\section parser_helper__to_string To String \section parser_helper__to_string To String
YYCC::ParserHelper::ToString basically is the reversed operation of YYCC::ParserHelper::Parse. #ToString basically is the reversed operation of #Parse.
It gets the string representation of given type. It gets the string representation of given type.
The only argument of these functions is the type which need to be converted to its string representation. The only argument of these functions is the type which need to be converted to its string representation.
And they will return yycc_u8string as result. And they will return yycc_u8string as result.
@ -62,6 +64,11 @@ There is an example:
auto result = YYCC::ParserHelper::ToString<uint32_t>(UINT32_C(114)); auto result = YYCC::ParserHelper::ToString<uint32_t>(UINT32_C(114));
\endcode \endcode
For floating point type, this function allows caller to specify extra arguments
which provides the format (\c std::chars_format) and precision when getting string representation.
For integral type, this function allows caller to specify extra argument
providing the base of number when getting string representation.
\section parser_helper__notes Notes \section parser_helper__notes Notes
All functions within this helper are implementated by standard library functions. All functions within this helper are implementated by standard library functions.
@ -77,4 +84,5 @@ The argument of template is the type these functions need to be processed.
Although C++ have \e smart template type deduction, Although C++ have \e smart template type deduction,
it would be better to specify template argument manually to explicitly specify your desired type. it would be better to specify template argument manually to explicitly specify your desired type.
*/ */
}

View File

@ -1,35 +0,0 @@
/**
\page platform_checker Platform Checker
In many cross platform applications,
programmer usually write code adapted to different platforms in one source file
and enable them respectively by macros representing the target platform.
As a cross platform library,
YYCC also has this feature and you can utilize it if you don't have other ways to so the same things.
\section platform_checker__values Values
YYCC always define a macro called \c YYCC_OS to indicate the system of target platform.
In implementation, it will check following list from top to bottom to set matched value for it.
\li \c YYCC_OS_WINDOWS: Windows environment. It is done by checking whether environment define \c _WIN32 macro.
\li \c YYCC_OS_LINUX: In current implementation, this means target platform is \b NOT Windows.
\section platform_checker__usage Usage
Now you know any possible value of \c YYCC_OS.
The next step is how to use it to enable specified code in specific target platform.
We take Windows platform for example.
Assume \c blabla() function is Windows specific.
We have following example code:
\code
#if YYCC_OS == YYCC_OS_WINDOWS
blabla();
#endif
\endcode
It's enough and simple that use \c \#if to bracket the Windows specified code.
*/

View File

@ -1,6 +1,44 @@
namespace YYCC::StdPatch {
/** /**
\page fs_path_patch std::filesystem::path Patch \page std_patch Standard Library Patch
\section std_patch__starts_with_ends_with Starts With & Ends With
\c std::basic_string::starts_with and \c std::basic_string::ends_with (also available in \c std::basic_string_view)
are functions introduced in C++ 20 and unavailable in C++ 17.
YYCC::StdPatch provides a patch for these function in C++ 17 environment.
Please note these implementations are following implementation instruction presented by CppReference website.
And it should have the same performance with vanilla functions because Microsoft STL use the same way to implement.
These implementations will not fallback to vanilla function even they are available.
Because their performance are good.
To use these functions, you just need to call them like corresponding vanilla functions.
Our implementations provide all necessary overloads.
The only thing you need to do is provide the string self as the first argument,
because our implementations can not be inserted as a class member of string.
There is an example:
\code
YYCC::StdPatch::StartsWith(YYCC_U8("aabbcc"), YYCC_U8("aa"));
YYCC::StdPatch::EndsWith(YYCC_U8("aabbcc"), YYCC_U8("cc"));
\endcode
\section std_patch__contains Contains
\c Contains function in standard library ordered and unordered successive container are also introduced in C++ 20.
YYCC::StdPatch provides a patch for this function in C++ 17 environment.
Please note this implementation will fallback to vanilla function if it is available.
Because our implementation is a remedy (there is no way to use public class member to have the same performance of vanilla function).
There is an example about how to use it:
\code
std::set<int> test { 1, 5 };
YYCC::StdPatch::Contains(test, static_cast<int>(5));
\endcode
\section std_patch__fs_path std::filesystem::path Patch
As you know, the underlying char type of \c std::filesystem::path is \c wchar_t on Windows, As you know, the underlying char type of \c std::filesystem::path is \c wchar_t on Windows,
and in other platforms, it is simple \c char. and in other platforms, it is simple \c char.
@ -22,17 +60,17 @@ This patch is served for Windows but also works on other plaftoms.
If you are in Windows, this patch will perform extra operations to achieve goals, If you are in Windows, this patch will perform extra operations to achieve goals,
and in other platforms, they just redirect request to corresponding vanilla C++ functions. and in other platforms, they just redirect request to corresponding vanilla C++ functions.
\section fs_path_patch__from_utf8_path Create Path from UTF8 String \subsection std_patch__fs_path__from_utf8_path Create Path from UTF8 String
YYCC::FsPathPatch::FromUTF8Path provides this feature. #ToStdPath provides this feature.
It accepts an string pointer to UTF8 string and try to create \c std::filesystem::path from it. It accepts an string pointer to UTF8 string and try to create \c std::filesystem::path from it.
Function will throw exception if encoding convertion or constructor self failed. Function will throw exception if encoding convertion or constructor self failed.
There are some example: There are some example:
\code \code
auto foobar_path = YYCC::FsPathPatch::FromUTF8Path(YYCC_U8("/foo/bar")); auto foobar_path = YYCC::StdPatch::ToStdPath(YYCC_U8("/foo/bar"));
auto slashed_path = foobar_path / YYCC::FsPathPatch::FromUTF8Path(YYCC_U8("test")); auto slashed_path = foobar_path / YYCC::StdPatch::ToStdPath(YYCC_U8("test"));
auto replaced_ext = foobar_path.replace_extension(YYCC::FsPathPatch::FromUTF8Path(YYCC_U8(".txt"))); auto replaced_ext = foobar_path.replace_extension(YYCC::StdPatch::ToStdPath(YYCC_U8(".txt")));
\endcode \endcode
For first line in example, it is obvious that you can create a \c std::filesystem::path from this function. For first line in example, it is obvious that you can create a \c std::filesystem::path from this function.
@ -57,17 +95,18 @@ However it is depracted since C++ 20,
because \c std::filesystem::path directly supports UTF8 by \c char8_t since C++ 20. because \c std::filesystem::path directly supports UTF8 by \c char8_t since C++ 20.
Because C++ standard is volatile, we create this function to have an uniform programming experience. Because C++ standard is volatile, we create this function to have an uniform programming experience.
\section fs_path_patch__to_utf8_path Extract UTF8 Path String from Path \subsection std_patch__fs_path__to_utf8_path Extract UTF8 Path String from Path
YYCC::FsPathPatch::ToUTF8Path provides this feature. #ToUTF8Path provides this feature.
It basically is the reversed operation of YYCC::FsPathPatch::FromUTF8Path. It basically is the reversed operation of #ToStdPath.
It is usually used when you have done all path work in \c std::filesystem::path It is usually used when you have done all path work in \c std::filesystem::path
and want to get the result. and want to get the result.
There is an example: There is an example:
\code \code
auto foobar_path = YYCC::FsPathPatch::FromUTF8Path(YYCC_U8("/foo/bar")); auto foobar_path = YYCC::StdPatch::ToStdPath(YYCC_U8("/foo/bar"));
auto result = YYCC::FsPathPatch::ToUTF8Path(foobar_path / YYCC::FsPathPatch::FromUTF8Path(YYCC_U8("test"))); auto result = YYCC::StdPatch::ToUTF8Path(foobar_path / YYCC::StdPatch::ToStdPath(YYCC_U8("test")));
\endcode \endcode
*/ */
}

View File

@ -1,3 +1,4 @@
namespace YYCC::StringHelper {
/** /**
\page string_helper String Helper \page string_helper String Helper
@ -14,10 +15,10 @@ yycc_u8string Printf(const yycc_char8_t*, ...);
yycc_u8string VPrintf(const yycc_char8_t*, va_list argptr); yycc_u8string VPrintf(const yycc_char8_t*, va_list argptr);
\endcode \endcode
YYCC::StringHelper::Printf and YYCC::StringHelper::VPrintf is similar to \c std::sprintf and \c std::vsprintf. #Printf and #VPrintf is similar to \c std::sprintf and \c std::vsprintf.
YYCC::StringHelper::Printf accepts UTF8 format string and variadic arguments specifying data to print. #Printf accepts UTF8 format string and variadic arguments specifying data to print.
This is commonly used by programmer. This is commonly used by programmer.
However, YYCC::StringHelper::VPrintf also do the same work but its second argument is \c va_list, However, #VPrintf also do the same work but its second argument is \c va_list,
the representation of variadic arguments. the representation of variadic arguments.
It is mostly used by other function which has variadic arguments. It is mostly used by other function which has variadic arguments.
@ -26,7 +27,7 @@ that you don't need to worry about whether the space of given buffer is enough,
because these functions help you to calculate this internally. because these functions help you to calculate this internally.
There is the same design like we introduced in \ref encoding_helper. There is the same design like we introduced in \ref encoding_helper.
There are 2 overloads for YYCC::StringHelper::Printf and YYCC::StringHelper::VPrintf respectively. There are 2 overloads for #Printf and #VPrintf respectively.
First overload return bool value and require a string container as argument for storing result. First overload return bool value and require a string container as argument for storing result.
The second overload return result string directly. The second overload return result string directly.
As you expected, first overload will return false if fail to format string (this is barely happened). As you expected, first overload will return false if fail to format string (this is barely happened).
@ -37,18 +38,18 @@ and second overload will return empty string when formatter failed.
YYCC::StringHelper provide 2 functions for programmer do string replacement: YYCC::StringHelper provide 2 functions for programmer do string replacement:
\code \code
void Replace(yycc_u8string&, const yycc_char8_t*, const yycc_char8_t*); void Replace(yycc_u8string&, const yycc_u8string_view&, const yycc_u8string_view&);
yycc_u8string Replace(const yycc_char8_t*, const yycc_char8_t*, const yycc_char8_t*); yycc_u8string Replace(const yycc_u8string_view&, const yycc_u8string_view&, const yycc_u8string_view&);
\endcode \endcode
The first overload will do replacement in given string container directly. The first overload will do replacement in given string container directly.
The second overload will produce a copy of original string and do replacement on the copied string. The second overload will produce a copy of original string and do replacement on the copied string.
YYCC::StringHelper::Replace has special treatments for following scenarios: #Replace has special treatments for following scenarios:
\li If given string is empty or nullptr, the return value will be empty. \li If given string is empty, the return value will be empty.
\li If the character sequence to be replaced is nullptr or empty string, no replacement will happen. \li If the character sequence to be replaced is empty string, no replacement will happen.
\li If the character sequence will be replaced into string is nullptr or empty, it will simply delete found character sequence from given string. \li If the character sequence will be replaced into string is or empty, it will simply delete found character sequence from given string.
\section string_helper__join Join \section string_helper__join Join
@ -58,10 +59,10 @@ YYCC::StringHelper provide an universal way for joining string and various speci
Because C++ list types are various. Because C++ list types are various.
There is no unique and convenient way to create an universal join function. There is no unique and convenient way to create an universal join function.
So we create YYCC::StringHelper::JoinDataProvider to describe join context. So we create #JoinDataProvider to describe join context.
Before using universal join function, Before using universal join function,
you should setup YYCC::StringHelper::JoinDataProvider first, the context of join function. you should setup #JoinDataProvider first, the context of join function.
It actually is an \c std::function object which can be easily fetched by C++ lambda syntax. It actually is an \c std::function object which can be easily fetched by C++ lambda syntax.
This function pointer accept a reference to \c yycc_u8string_view, This function pointer accept a reference to \c yycc_u8string_view,
programmer should set it to the string to be joined when at each calling. programmer should set it to the string to be joined when at each calling.
@ -69,7 +70,7 @@ And this function pointer return a bool value to indicate the end of join.
You can simply return \c false to terminate join process. You can simply return \c false to terminate join process.
The argument you assigned to argument will not be taken into join process when you return false. The argument you assigned to argument will not be taken into join process when you return false.
Then, you can pass the created YYCC::StringHelper::JoinDataProvider object to YYCC::StringHelper::Join function. Then, you can pass the created #JoinDataProvider object to #Join function.
And specify decilmer at the same time. And specify decilmer at the same time.
Then you can get the final joined string. Then you can get the final joined string.
There is an example: There is an example:
@ -94,10 +95,18 @@ auto joined_string = YYCC::StringHelper::Join(
\subsection string_helper__join__specialized Specialized Join Function \subsection string_helper__join__specialized Specialized Join Function
Despite universal join function, Despite universal join function,
YYCC::StringHelper also provide some specialized join functions for commonly used types. YYCC::StringHelper also provide a specialized join functions for standard library container.
Current we support following join function: For example, the code written above can be written in following code by using this specialized overload.
The first two argument is just the begin and end iterator.
However, you must make sure that we can dereference it and then implicitly convert it to yycc_u8string_view.
Otherwise this overload will throw template error.
\li \c std::vector<yycc_u8string>: With an extra option which allow join it with reversed order. \code
std::vector<yycc_u8string> data {
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
};
auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), decilmer);
\endcode
\section string_helper__lower_upper Lower Upper \section string_helper__lower_upper Lower Upper
@ -105,11 +114,11 @@ String helper provides Python-like string lower and upper function.
Both lower and upper function have 2 overloads: Both lower and upper function have 2 overloads:
\code \code
yycc_u8string Lower(const yycc_char8_t*); yycc_u8string Lower(const yycc_u8string_view&);
void Lower(yycc_u8string&); void Lower(yycc_u8string&);
\endcode \endcode
First overload accepts a NULL-terminated string as argument and return a \b copy whose content are all the lower case of original string. First overload accepts a string view as argument and return a \b copy whose content are all the lower case of original string.
Second overload accepts a mutable string container as argument and will make all characters stored in it become their lower case. Second overload accepts a mutable string container as argument and will make all characters stored in it become their lower case.
You can choose on of them for your flavor and requirements. You can choose on of them for your flavor and requirements.
Upper also has similar 2 overloads. Upper also has similar 2 overloads.
@ -120,20 +129,21 @@ String helper provides Python-like string split function.
It has 2 types for you: It has 2 types for you:
\code \code
std::vector<yycc_u8string> Split(const yycc_u8string_view&, const yycc_char8_t*); std::vector<yycc_u8string> Split(const yycc_u8string_view&, const yycc_u8string_view&);
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view&, const yycc_char8_t*); std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view&, const yycc_u8string_view&);
\endcode \endcode
All these overloads take a string view as the first argument representing the string need to be split. All these overloads take a string view as the first argument representing the string need to be split.
The second argument is a raw string pointer representing the decilmer for splitting. The second argument is a string view representing the decilmer for splitting.
The only difference between these 2 split function are overt according to their names. The only difference between these 2 split function are overt according to their names.
The first split function will return a list of copied string as its split result. The first split function will return a list of copied string as its split result.
The second split function will return a list of string view as its split result, The second split function will return a list of string view as its split result,
and it will keep valid as long as the life time of your given string view argument. and it will keep valid as long as the life time of your given string view argument.
It also means that the last overload will cost less memory if you don't need the copy of original string. It also means that the last overload will cost less memory if you don't need the copy of original string.
If the source string (the string need to be split) is empty, or the decilmer is \c nullptr or empty, If the source string (the string need to be split) is empty, or the decilmer is empty,
the result will only has 1 item and this item is source string itself. the result will only has 1 item and this item is source string itself.
There is no way that these methods return an empty list, except the code is buggy. There is no way that these methods return an empty list, except the code is buggy.
*/ */
}

View File

@ -1,3 +1,4 @@
namespace YYCC::WinFctHelper {
/** /**
\page win_fct_helper Windows Function Helper \page win_fct_helper Windows Function Helper
@ -9,9 +10,14 @@ It will be entirely invisible in other platforms.
Currently this namespace has following functions: Currently this namespace has following functions:
\li YYCC::WinFctHelper::GetCurrentModule: Get the handle to current module. \li #GetCurrentModule: Get the handle to current module.
\li YYCC::WinFctHelper::GetTempDirectory: Get temporary directory in Windows. \li #GetTempDirectory: Get temporary directory in Windows.
\li YYCC::WinFctHelper::GetModuleFileName: Get the path to module in file system by given handle. \li #GetModuleFileName: Get the path to module in file system by given handle.
\li YYCC::WinFctHelper::GetLocalAppData: Get the path inside \%LOCALAPPDATA\% \li #GetLocalAppData: Get the path inside \%LOCALAPPDATA\%
\li #IsValidCodePage: Check whether given code page number is valid.
\li #CopyFile: The UTF8 version of Win32 \c CopyFile.
\li #MoveFile: The UTF8 version of Win32 \c MoveFile.
\li #DeleteFile: The UTF8 version of Win32 \c DeleteFile.
*/ */
}

View File

@ -1,3 +1,4 @@
namespace YYCC {
/** /**
\page win_import Windows Import Guard \page win_import Windows Import Guard
@ -66,4 +67,5 @@ because the headers use \c \#if to check environment out and will do nothing in
However, we still highly recommend you use this pair with platform checker bracket like example does, However, we still highly recommend you use this pair with platform checker bracket like example does,
if your program need to be run on multiple platforms. if your program need to be run on multiple platforms.
*/ */
}

2
script/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# -------------------- Output --------------------
win_build.bat

170
script/gen_win_build.py Normal file
View File

@ -0,0 +1,170 @@
import argparse
import os
import io
import re
def validate_cpp_ver(ver: str) -> str:
if re.match(r'^[0-9]+$', ver) is not None: return ver
else: raise argparse.ArgumentTypeError('invalid version of C++ standard.')
def write_line(f: io.TextIOWrapper, val: str) -> None:
f.write(val)
f.write('\n')
# Reference: https://stackoverflow.com/questions/29213106/how-to-securely-escape-command-line-arguments-for-the-cmd-exe-shell-on-windows
def escape_argument(arg):
if not arg or re.search(r'(["\s])', arg):
arg = '"' + arg.replace('"', r'\"') + '"'
return escape_for_cmd_exe(arg)
def escape_for_cmd_exe(arg):
meta_re = re.compile(r'([()%!^"<>&|])')
return meta_re.sub('^\1', arg)
class ScriptSettings:
m_CppVersion: str
m_NoDoc: bool
def __init__(self, cpp_ver: str, no_doc: bool):
self.m_CppVersion = cpp_ver
self.m_NoDoc = no_doc
def script_head(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# change directory to root folder
write_line(f, ':: Navigate to project root directory')
root_dir: str = os.path.dirname(os.path.dirname(__file__))
write_line(f, f'CD /d {escape_argument(root_dir)}')
# create build directory and enter
write_line(f, ':: Create build directory and enter it')
write_line(f, 'MKDIR bin')
write_line(f, 'CD bin')
cpp_dir: str = f'cpp{s.m_CppVersion}'
write_line(f, f'MKDIR {cpp_dir}')
write_line(f, f'CD {cpp_dir}')
# blank line
write_line(f, '')
def script_tail(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# leave build directory and report success
write_line(f, ':: Leave build directory and report')
write_line(f, 'CD ..\\..')
write_line(f, 'ECHO Windows CMake Build Done')
def create_directory(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# create build directory
write_line(f, ':: Create internal build directory')
write_line(f, 'MKDIR Win32')
write_line(f, 'MKDIR x64')
write_line(f, 'MKDIR documentation')
# create install directory
write_line(f, ':: Create internal install directory')
write_line(f, 'MKDIR install')
write_line(f, 'CD install')
write_line(f, 'MKDIR Win32_Debug')
write_line(f, 'MKDIR Win32_Release')
write_line(f, 'MKDIR x64_Debug')
write_line(f, 'MKDIR x64_Release')
write_line(f, 'CD ..')
# create msvc install directory
write_line(f, ':: Create internal MSVC specific install directory')
write_line(f, 'MKDIR msvc_install')
write_line(f, 'CD msvc_install')
write_line(f, 'MKDIR bin')
write_line(f, 'MKDIR include')
write_line(f, 'MKDIR lib')
write_line(f, 'MKDIR share')
write_line(f, 'CD bin')
write_line(f, 'MKDIR Win32')
write_line(f, 'MKDIR x64')
write_line(f, 'CD ..')
write_line(f, 'CD lib')
write_line(f, 'MKDIR Win32\\Debug')
write_line(f, 'MKDIR Win32\\Release')
write_line(f, 'MKDIR x64\\Debug')
write_line(f, 'MKDIR x64\\Release')
write_line(f, 'CD ..')
write_line(f, 'CD ..')
# blank line
write_line(f, '')
def cmake_build(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# build for Win32
write_line(f, ':: Build for Win32')
write_line(f, 'CD Win32')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_TESTBENCH=ON ../../..')
write_line(f, 'cmake --build . --config Debug')
write_line(f, 'cmake --install . --prefix=../install/Win32_Debug --config Debug')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --install . --prefix=../install/Win32_Release --config Release')
write_line(f, 'CD ..')
# build for x64
write_line(f, ':: Build for x64')
write_line(f, 'CD x64')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_TESTBENCH=ON ../../..')
write_line(f, 'cmake --build . --config Debug')
write_line(f, 'cmake --install . --prefix=../install/x64_Debug --config Debug')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --install . --prefix=../install/x64_Release --config Release')
write_line(f, 'CD ..')
# build for documentation
if not s.m_NoDoc:
write_line(f, ':: Build for documentation')
write_line(f, 'CD documentation')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_DOC=ON ../../..')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --build . --target YYCCDocumentation')
write_line(f, 'cmake --install . --prefix=../install/x64_Release --config Release')
write_line(f, 'CD ..')
# blank line
write_line(f, '')
def msvc_build(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# copy include from x64_Release build
write_line(f, ':: Copy header files')
write_line(f, 'XCOPY install\\x64_Release\\include msvc_install\\include\\ /E /Y')
# copy binary testbench
write_line(f, ':: Copy binary files')
write_line(f, 'COPY install\\Win32_Release\\bin\\YYCCTestbench.exe msvc_install\\bin\\Win32\\YYCCTestbench.exe /Y')
write_line(f, 'COPY install\\x64_Release\\bin\\YYCCTestbench.exe msvc_install\\bin\\x64\\YYCCTestbench.exe /Y')
# copy static library
write_line(f, ':: Copy library files')
write_line(f, 'COPY install\\Win32_Debug\\lib\\YYCCommonplace.lib msvc_install\\lib\\Win32\\Debug\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\Win32_Release\\lib\\YYCCommonplace.lib msvc_install\\lib\\Win32\\Release\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\x64_Debug\\lib\\YYCCommonplace.lib msvc_install\\lib\\x64\\Debug\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\x64_Release\\lib\\YYCCommonplace.lib msvc_install\\lib\\x64\\Release\\YYCCommonplace.lib /Y')
# Copy document from x64_Release build
if not s.m_NoDoc:
write_line(f, ':: Copy documentation files')
write_line(f, 'XCOPY install\\x64_Release\\share msvc_install\\share\\ /E /Y')
# blank line
write_line(f, '')
if __name__ == '__main__':
# parse argument
parser = argparse.ArgumentParser(
prog='YYCC Windows Build Script Generator',
description='YYCC Windows Build Script Generator'
)
parser.add_argument(
'-c', '--cpp',
action='store', default='17', dest='cpp', type=validate_cpp_ver,
help='The version of C++ standard used when building.'
)
parser.add_argument(
'-d', '--no-doc',
action='store_true', dest='no_doc',
help='Build YYCC without documentation.'
)
args = parser.parse_args()
# build settings
settings = ScriptSettings(args.cpp, args.no_doc)
# write result
filepath = os.path.join(os.path.dirname(__file__), 'win_build.bat')
with open(filepath, 'w') as f:
write_line(f, '@ECHO OFF')
script_head(f, settings)
create_directory(f, settings)
cmake_build(f, settings)
msvc_build(f, settings)
script_tail(f, settings)

View File

@ -1,4 +1,5 @@
@ECHO OFF @ECHO OFF
:: Check environment
SET README_PATH=%CD%\README.md SET README_PATH=%CD%\README.md
IF EXIST %README_PATH% ( IF EXIST %README_PATH% (
REM DO NOTHING REM DO NOTHING
@ -42,12 +43,14 @@ cmake --install . --prefix=../install/x64_Release --config Release
CD .. CD ..
:: Build for documentation :: Build for documentation
CD documentation IF NOT "%1"=="NODOC" (
cmake -G "Visual Studio 16 2019" -A x64 -DYYCC_BUILD_DOC=ON ../.. CD documentation
cmake --build . --config Release cmake -G "Visual Studio 16 2019" -A x64 -DYYCC_BUILD_DOC=ON ../..
cmake --build . --target YYCCDocumentation cmake --build . --config Release
cmake --install . --prefix=../install/x64_Release --config Release cmake --build . --target YYCCDocumentation
CD .. cmake --install . --prefix=../install/x64_Release --config Release
CD ..
)
:: Exit to original path :: Exit to original path
CD .. CD ..

View File

@ -35,7 +35,9 @@ CD ..
:: Copy include from x64_Release build :: Copy include from x64_Release build
XCOPY install\x64_Release\include msvc_install\include\ /E /Y XCOPY install\x64_Release\include msvc_install\include\ /E /Y
:: Copy document from x64_Release build :: Copy document from x64_Release build
XCOPY install\x64_Release\share msvc_install\share\ /E /Y IF NOT "%1"=="NODOC" (
XCOPY install\x64_Release\share msvc_install\share\ /E /Y
)
:: Copy binary testbench :: Copy binary testbench
COPY install\Win32_Release\bin\YYCCTestbench.exe msvc_install\bin\Win32\YYCCTestbench.exe /Y COPY install\Win32_Release\bin\YYCCTestbench.exe msvc_install\bin\Win32\YYCCTestbench.exe /Y
COPY install\x64_Release\bin\YYCCTestbench.exe msvc_install\bin\x64\YYCCTestbench.exe /Y COPY install\x64_Release\bin\YYCCTestbench.exe msvc_install\bin\x64\YYCCTestbench.exe /Y

348
src/ArgParser.cpp Normal file
View 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
}

464
src/ArgParser.hpp Normal file
View File

@ -0,0 +1,464 @@
#pragma once
#include "YYCCInternal.hpp"
#include "Constraints.hpp"
#include "EncodingHelper.hpp"
#include "ParserHelper.hpp"
#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
}

View File

@ -5,12 +5,13 @@ target_sources(YYCCommonplace
PRIVATE PRIVATE
# Sources # Sources
COMHelper.cpp COMHelper.cpp
ArgParser.cpp
ConfigManager.cpp ConfigManager.cpp
ConsoleHelper.cpp ConsoleHelper.cpp
DialogHelper.cpp DialogHelper.cpp
EncodingHelper.cpp EncodingHelper.cpp
ExceptionHelper.cpp ExceptionHelper.cpp
FsPathPatch.cpp StdPatch.cpp
IOHelper.cpp IOHelper.cpp
StringHelper.cpp StringHelper.cpp
WinFctHelper.cpp WinFctHelper.cpp
@ -23,13 +24,15 @@ FILE_SET HEADERS
FILES FILES
# Headers # Headers
# Common headers # Common headers
Constraints.hpp
COMHelper.hpp COMHelper.hpp
ArgParser.hpp
ConfigManager.hpp ConfigManager.hpp
ConsoleHelper.hpp ConsoleHelper.hpp
DialogHelper.hpp DialogHelper.hpp
EncodingHelper.hpp EncodingHelper.hpp
ExceptionHelper.hpp ExceptionHelper.hpp
FsPathPatch.hpp StdPatch.hpp
IOHelper.hpp IOHelper.hpp
ParserHelper.hpp ParserHelper.hpp
StringHelper.hpp StringHelper.hpp
@ -53,20 +56,20 @@ PRIVATE
$<$<BOOL:${WIN32}>:DbgHelp.lib> $<$<BOOL:${WIN32}>:DbgHelp.lib>
) )
# Setup C++ standard # Setup C++ standard
set_target_properties(YYCCommonplace target_compile_features(YYCCommonplace PUBLIC cxx_std_17)
PROPERTIES set_target_properties(YYCCommonplace PROPERTIES CXX_EXTENSION OFF)
CXX_STANDARD 17 # Setup macros
CXX_STANDARD_REQUIRED 17
CXX_EXTENSION OFF
)
# Order Unicode charset for private using
target_compile_definitions(YYCCommonplace target_compile_definitions(YYCCommonplace
# Debug macro should populate to child projects
PUBLIC
$<$<BOOL:${YYCC_DEBUG_UE_FILTER}>:YYCC_DEBUG_UE_FILTER>
# Unicode charset for private using
PRIVATE PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:UNICODE> $<$<CXX_COMPILER_ID:MSVC>:UNICODE>
$<$<CXX_COMPILER_ID:MSVC>:_UNICODE> $<$<CXX_COMPILER_ID:MSVC>:_UNICODE>
) )
# Order build as UTF-8 in MSVC
target_compile_options(YYCCommonplace target_compile_options(YYCCommonplace
# Order build as UTF-8 in MSVC
PRIVATE PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/utf-8> $<$<CXX_COMPILER_ID:MSVC>:/utf-8>
) )

View File

@ -6,16 +6,34 @@
namespace YYCC::ConfigManager { 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 #pragma region Core Manager
CoreManager::CoreManager( CoreManager::CoreManager(
const yycc_char8_t* cfg_file_path, const yycc_u8string_view& cfg_file_path,
uint64_t version_identifier, uint64_t version_identifier,
std::initializer_list<AbstractSetting*> settings) : std::initializer_list<AbstractSetting*> settings) :
m_CfgFilePath(), m_VersionIdentifier(version_identifier), m_Settings() { m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
// assign cfg path // Mark: no need to check cfg file path
if (cfg_file_path != nullptr) // it will be checked at creating file handle
m_CfgFilePath = cfg_file_path;
// assign settings // assign settings
for (auto* setting : settings) { for (auto* setting : settings) {
auto result = m_Settings.try_emplace(setting->GetName(), setting); auto result = m_Settings.try_emplace(setting->GetName(), setting);
@ -31,7 +49,7 @@ namespace YYCC::ConfigManager {
Reset(); Reset();
// get file handle // get file handle
auto fs = this->GetFileHandle(YYCC_U8("rb")); IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("rb")));
if (fs.get() == nullptr) { if (fs.get() == nullptr) {
// if we fail to get, it means that we do not have corresponding cfg file. // if we fail to get, it means that we do not have corresponding cfg file.
// all settings should be reset to default value. // all settings should be reset to default value.
@ -94,7 +112,7 @@ namespace YYCC::ConfigManager {
bool CoreManager::Save() { bool CoreManager::Save() {
// get file handle // get file handle
auto fs = this->GetFileHandle(YYCC_U8("wb")); IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("wb")));
// if we fail to get, return false. // if we fail to get, return false.
if (fs == nullptr) return false; if (fs == nullptr) return false;
@ -139,15 +157,6 @@ namespace YYCC::ConfigManager {
} }
} }
CoreManager::FileHandleGuard_t CoreManager::GetFileHandle(const yycc_char8_t* mode) const {
return CoreManager::FileHandleGuard_t(
IOHelper::UTF8FOpen(this->m_CfgFilePath.c_str(), mode),
[](FILE* fs) -> void {
if (fs != nullptr) std::fclose(fs);
}
);
}
#pragma endregion #pragma endregion
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "YYCCInternal.hpp" #include "YYCCInternal.hpp"
#include "Constraints.hpp"
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <map> #include <map>
@ -17,83 +18,43 @@
*/ */
namespace YYCC::ConfigManager { namespace YYCC::ConfigManager {
/**
* @brief The constraint applied to settings to limit its stored value.
* @tparam _Ty The internal data type stroed in corresponding setting.
*/
template<typename _Ty>
struct Constraint {
using CheckFct_t = std::function<bool(const _Ty&)>;
//using CorrectFct_t = std::function<_Ty(const _Ty&)>;
CheckFct_t m_CheckFct;
//CorrectFct_t m_CorrectFct;
bool IsValid() const {
return m_CheckFct != nullptr/* && m_CorrectFct != nullptr*/;
}
};
/**
* @brief The namespace containing functions generating common used constraint.
*/
namespace ConstraintPresets {
/**
* @brief Get constraint for arithmetic values by minimum and maximum value range.
* @tparam _Ty The underlying arithmetic type.
* @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_enum_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
Constraint<_Ty> GetNumberRangeConstraint(_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 The base class of every setting. /// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential to create custom setting. /// @details Programmer can inherit this class and implement essential functions to create custom setting.
class AbstractSetting { class AbstractSetting {
friend class CoreManager; friend class CoreManager;
public: public:
/** /**
* @brief Construct a setting * @brief Construct a setting
* @param[in] name The name of this setting. * @param[in] name The name of this setting.
* @exception std::invalid_argument Name of setting is empty.
*/ */
AbstractSetting(const yycc_char8_t* name) : m_Name(), m_RawData() { AbstractSetting(const yycc_u8string_view& name);
if (name != nullptr) m_Name = name; virtual ~AbstractSetting();
} YYCC_DEL_CLS_COPY_MOVE(AbstractSetting);
virtual ~AbstractSetting() {}
// Name interface // Name interface
public: public:
/// @brief Get name of this setting. /// @brief Get name of this setting.
/// @details Name was used in storing setting in file. /// @details Name was used in storing setting in file.
const yycc_u8string& GetName() const { return m_Name; } const yycc_u8string& GetName() const;
private: private:
yycc_u8string m_Name; yycc_u8string m_Name;
// User Implementations // User Implementations
protected: protected:
/// @brief User implemented custom load functions /// @brief User implemented custom load function
/// @remarks /// @remarks
/// In this function, programmer should read data from internal buffer /// In this function, programmer should read data from internal buffer
/// and store it to its own another internal variables. /// and store it to its own another internal variables.
/// @return True if success, otherwise false. /// @return True if success, otherwise false.
virtual bool UserLoad() = 0; virtual bool UserLoad() = 0;
/// @brief User implemented custom save functions /// @brief User implemented custom save function
/// @remarks /// @remarks
/// In this function, programmer should write data, /// In this function, programmer should write data,
/// which is stored in another variavle by it own, to internal buffer. /// which is stored in another variavle by it own, to internal buffer.
/// @return True if success, otherwise false. /// @return True if success, otherwise false.
virtual bool UserSave() = 0; virtual bool UserSave() = 0;
/// @brief User implemented custom reset functions /// @brief User implemented custom reset function
/// @remarks In this function, programmer should reset its internal variable to default value. /// @remarks In this function, programmer should reset its internal variable to default value.
virtual void UserReset() = 0; virtual void UserReset() = 0;
@ -102,19 +63,19 @@ namespace YYCC::ConfigManager {
/// @brief Resize internal buffer to given size. /// @brief Resize internal buffer to given size.
/// @remarks It is usually used in UserSave. /// @remarks It is usually used in UserSave.
/// @param[in] new_size The new size of internal buffer. /// @param[in] new_size The new size of internal buffer.
void ResizeData(size_t new_size) { m_RawData.resize(new_size); } void ResizeData(size_t new_size);
/// @brief Get data pointer to internal buffer. /// @brief Get data pointer to internal buffer.
/// @remarks It is usually used in UserLoad. /// @remarks It is usually used in UserLoad.
const void* GetDataPtr() const { return m_RawData.data(); } const void* GetDataPtr() const;
/// @brief Get mutable data pointer to internal buffer. /// @brief Get mutable data pointer to internal buffer.
/// @remarks It is usually used in UserSave. /// @remarks It is usually used in UserSave.
void* GetDataPtr() { return m_RawData.data(); } void* GetDataPtr();
/// @brief Get the length of internal buffer. /// @brief Get the length of internal buffer.
size_t GetDataSize() const { return m_RawData.size(); } size_t GetDataSize() const;
private: private:
std::vector<uint8_t> m_RawData; std::vector<uint8_t> m_RawData;
}; };
/// @brief Settings manager and config file reader writer. /// @brief Settings manager and config file reader writer.
class CoreManager { class CoreManager {
public: public:
@ -125,10 +86,11 @@ namespace YYCC::ConfigManager {
* @param[in] settings An initializer list containing pointers to all managed settings. * @param[in] settings An initializer list containing pointers to all managed settings.
*/ */
CoreManager( CoreManager(
const yycc_char8_t* cfg_file_path, const yycc_u8string_view& cfg_file_path,
uint64_t version_identifier, uint64_t version_identifier,
std::initializer_list<AbstractSetting*> settings); std::initializer_list<AbstractSetting*> settings);
~CoreManager() {} ~CoreManager() {}
YYCC_DEL_CLS_COPY_MOVE(CoreManager);
// Core functions // Core functions
public: public:
@ -143,9 +105,6 @@ namespace YYCC::ConfigManager {
void Reset(); void Reset();
private: private:
using FileHandleGuard_t = std::unique_ptr<FILE, std::function<void(FILE*)>>;
FileHandleGuard_t GetFileHandle(const yycc_char8_t* mode) const;
yycc_u8string m_CfgFilePath; yycc_u8string m_CfgFilePath;
uint64_t m_VersionIdentifier; uint64_t m_VersionIdentifier;
std::map<yycc_u8string, AbstractSetting*> m_Settings; std::map<yycc_u8string, AbstractSetting*> m_Settings;
@ -165,11 +124,15 @@ namespace YYCC::ConfigManager {
* @param[in] name The name of this setting. * @param[in] name The name of this setting.
* @param[in] default_value The default value of this setting. * @param[in] default_value The default value of this setting.
* @param[in] constraint The constraint applied to this setting. * @param[in] constraint The constraint applied to this setting.
* @exception std::invalid_argument Name of setting is empty.
*/ */
NumberSetting(const yycc_char8_t* name, _Ty default_value, Constraint<_Ty> constraint = Constraint<_Ty> {}) : 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) {} AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {}
virtual ~NumberSetting() {} virtual ~NumberSetting() {}
YYCC_DEL_CLS_COPY_MOVE(NumberSetting);
/// @brief Get stored data in setting. /// @brief Get stored data in setting.
_Ty Get() const { return m_Data; } _Ty Get() const { return m_Data; }
/** /**
@ -208,7 +171,7 @@ namespace YYCC::ConfigManager {
} }
_Ty m_Data, m_DefaultData; _Ty m_Data, m_DefaultData;
Constraint<_Ty> m_Constraint; Constraints::Constraint<_Ty> m_Constraint;
}; };
/// @brief String type setting /// @brief String type setting
@ -219,14 +182,18 @@ namespace YYCC::ConfigManager {
* @param[in] name The name of this setting. * @param[in] name The name of this setting.
* @param[in] default_value The default value of this setting. * @param[in] default_value The default value of this setting.
* @param[in] constraint The constraint applied to this setting. * @param[in] constraint The constraint applied to this setting.
* @exception std::invalid_argument Name of setting is empty.
*/ */
StringSetting(const yycc_char8_t* name, const yycc_u8string_view& default_value, Constraint<yycc_u8string_view> constraint = Constraint<yycc_u8string_view> {}) : 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) { AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) {
m_Data = default_value; m_Data = default_value;
m_DefaultData = default_value; m_DefaultData = default_value;
} }
virtual ~StringSetting() {} virtual ~StringSetting() {}
YYCC_DEL_CLS_COPY_MOVE(StringSetting);
/// @brief Get reference to stored string. /// @brief Get reference to stored string.
const yycc_u8string& Get() const { return m_Data; } const yycc_u8string& Get() const { return m_Data; }
/** /**
@ -236,10 +203,11 @@ namespace YYCC::ConfigManager {
*/ */
bool Set(const yycc_u8string_view& new_data) { bool Set(const yycc_u8string_view& new_data) {
// check data validation // check data validation
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data)) yycc_u8string new_data_cache(new_data);
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data_cache))
return false; return false;
// assign data // assign data
m_Data = new_data; m_Data = std::move(new_data_cache);
return true; return true;
} }
@ -279,7 +247,7 @@ namespace YYCC::ConfigManager {
} }
yycc_u8string m_Data, m_DefaultData; yycc_u8string m_Data, m_DefaultData;
Constraint<yycc_u8string_view> m_Constraint; Constraints::Constraint<yycc_u8string> m_Constraint;
}; };
#pragma endregion #pragma endregion

85
src/Constraints.hpp Normal file
View 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(); }
};
}
}

View File

@ -40,7 +40,8 @@ namespace YYCC::DialogHelper {
return false; return false;
// convert pattern and join them // convert pattern and join them
yycc_u8string joined_modes(YYCC::StringHelper::Join(it.second, YYCC_U8(";"))); const auto& filter_modes = it.second;
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";")));
WinFileFilters::WinFilterModes modes; WinFileFilters::WinFilterModes modes;
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes)) if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes))
return false; return false;

View File

@ -31,6 +31,7 @@ namespace YYCC::DialogHelper {
friend class WinFileDialog; friend class WinFileDialog;
public: public:
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {} WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
YYCC_DEL_CLS_COPY_MOVE(WinFileFilters);
/// @brief Get the count of available file filters /// @brief Get the count of available file filters
UINT GetFilterCount() const { UINT GetFilterCount() const {
@ -67,6 +68,7 @@ namespace YYCC::DialogHelper {
class FileFilters { class FileFilters {
public: public:
FileFilters() : m_Filters() {} FileFilters() : m_Filters() {}
YYCC_DEL_CLS_COPY_MOVE(FileFilters);
/** /**
* @brief Add a filter pair in file types list. * @brief Add a filter pair in file types list.
@ -123,6 +125,7 @@ namespace YYCC::DialogHelper {
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u), m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(), m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
m_WinInitDirectory(nullptr) {} m_WinInitDirectory(nullptr) {}
YYCC_DEL_CLS_COPY_MOVE(WinFileDialog);
/// @brief Get whether this dialog has owner. /// @brief Get whether this dialog has owner.
bool HasOwner() const { return m_WinOwner != NULL; } bool HasOwner() const { return m_WinOwner != NULL; }
@ -189,6 +192,7 @@ namespace YYCC::DialogHelper {
m_DefaultFileTypeIndex(0u), m_DefaultFileTypeIndex(0u),
m_Title(), m_InitFileName(), m_InitDirectory(), m_Title(), m_InitFileName(), m_InitDirectory(),
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {} m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
YYCC_DEL_CLS_COPY_MOVE(FileDialog);
/** /**
* @brief Set the owner of dialog. * @brief Set the owner of dialog.

View File

@ -6,7 +6,7 @@
#include "StringHelper.hpp" #include "StringHelper.hpp"
#include "IOHelper.hpp" #include "IOHelper.hpp"
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "FsPathPatch.hpp" #include "StdPatch.hpp"
#include <filesystem> #include <filesystem>
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
@ -26,6 +26,7 @@ namespace YYCC::ExceptionHelper {
ExceptionRegister() : ExceptionRegister() :
m_CoreMutex(), m_CoreMutex(),
m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr), m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
m_UserCallback(nullptr),
m_SingletonMutex(NULL) {} m_SingletonMutex(NULL) {}
~ExceptionRegister() { ~ExceptionRegister() {
Unregister(); Unregister();
@ -35,7 +36,7 @@ namespace YYCC::ExceptionHelper {
/** /**
* @brief Try to register unhandled exception handler. * @brief Try to register unhandled exception handler.
*/ */
void Register() { void Register(ExceptionCallback callback) {
std::lock_guard<std::mutex> locker(m_CoreMutex); std::lock_guard<std::mutex> locker(m_CoreMutex);
// if we have registered, return // if we have registered, return
if (m_IsRegistered) return; if (m_IsRegistered) return;
@ -63,6 +64,8 @@ namespace YYCC::ExceptionHelper {
// okey, we can register it. // okey, we can register it.
// backup old handler // backup old handler
m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl); m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl);
// set user callback
m_UserCallback = callback;
// mark registered // mark registered
m_IsRegistered = true; m_IsRegistered = true;
} }
@ -75,6 +78,8 @@ namespace YYCC::ExceptionHelper {
if (!m_IsRegistered) return; if (!m_IsRegistered) return;
// unregister handler // unregister handler
// reset user callback
m_UserCallback = nullptr;
// restore old handler // restore old handler
SetUnhandledExceptionFilter(m_PrevProcHandler); SetUnhandledExceptionFilter(m_PrevProcHandler);
m_PrevProcHandler = nullptr; m_PrevProcHandler = nullptr;
@ -115,6 +120,14 @@ namespace YYCC::ExceptionHelper {
std::lock_guard<std::mutex> locker(m_CoreMutex); std::lock_guard<std::mutex> locker(m_CoreMutex);
return m_PrevProcHandler; 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. * @brief Try to start process unhandled exception.
@ -154,6 +167,12 @@ namespace YYCC::ExceptionHelper {
* True if it is, otherwise false. * True if it is, otherwise false.
*/ */
bool m_IsProcessing; 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. * @brief The backup of old unhandled exception handler.
*/ */
@ -166,6 +185,7 @@ namespace YYCC::ExceptionHelper {
HANDLE m_SingletonMutex; HANDLE m_SingletonMutex;
}; };
/// @brief Core register singleton.
static ExceptionRegister g_ExceptionRegister; static ExceptionRegister g_ExceptionRegister;
#pragma region Exception Handler Implementation #pragma region Exception Handler Implementation
@ -222,33 +242,6 @@ namespace YYCC::ExceptionHelper {
} }
} }
/**
* @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, ...) {
// write to file
if (fs != nullptr) {
va_list arg1;
va_start(arg1, fmt);
std::vfprintf(fs, EncodingHelper::ToOrdinary(fmt), arg1);
std::fputs("\n", fs);
va_end(arg1);
}
// write to stderr
va_list arg2;
va_start(arg2, fmt);
ConsoleHelper::ErrWriteLine(YYCC::StringHelper::VPrintf(fmt, arg2).c_str());
va_end(arg2);
}
/** /**
* @brief Error log (including backtrace) used output function * @brief Error log (including backtrace) used output function
* @details * @details
@ -268,6 +261,27 @@ namespace YYCC::ExceptionHelper {
ConsoleHelper::ErrWriteLine(strl); 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) { static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) {
// setup loading symbol options // setup loading symbol options
SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); // lazy load symbol, and load line number. SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); // lazy load symbol, and load line number.
@ -323,7 +337,7 @@ namespace YYCC::ExceptionHelper {
frame.AddrPC.Mode = AddrModeFlat; frame.AddrPC.Mode = AddrModeFlat;
frame.AddrStack.Mode = AddrModeFlat; frame.AddrStack.Mode = AddrModeFlat;
frame.AddrFrame.Mode = AddrModeFlat; frame.AddrFrame.Mode = AddrModeFlat;
// stack walker // stack walker
while (StackWalk64(machine_type, process, thread, &frame, context, while (StackWalk64(machine_type, process, thread, &frame, context,
0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) { 0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
@ -336,12 +350,12 @@ namespace YYCC::ExceptionHelper {
} }
// get module name // get module name
const yycc_char8_t* module_name = YYCC_U8("<unknown module>"); const yycc_char8_t* no_module_name = YYCC_U8("<unknown module>");
yycc_u8string module_name_raw; yycc_u8string module_name(no_module_name);
DWORD64 module_base; DWORD64 module_base;
if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) { if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
if (WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name_raw)) { if (!WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name)) {
module_name = module_name_raw.c_str(); module_name = no_module_name;
} }
} }
@ -357,9 +371,12 @@ namespace YYCC::ExceptionHelper {
} }
// write to file // write to file
UExceptionErrLogFormatLine(fs, YYCC_U8("0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "]\t%s#L%" PRIu64), // 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 frame.AddrPC.Offset, // memory adress
module_name, frame.AddrPC.Offset - module_base, // module name + relative address module_name.c_str(), frame.AddrPC.Offset - module_base, // module name + relative address
source_file, source_file_line // source file + source line source_file, source_file_line // source file + source line
); );
@ -442,8 +459,8 @@ namespace YYCC::ExceptionHelper {
if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_path)) if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_path))
return false; return false;
// extract file name from full path by std::filesystem::path // extract file name from full path by std::filesystem::path
std::filesystem::path process_path(FsPathPatch::FromUTF8Path(u8_process_path.c_str())); std::filesystem::path process_path(StdPatch::ToStdPath(u8_process_path));
u8_process_name = FsPathPatch::ToUTF8Path(process_path.filename()); u8_process_name = StdPatch::ToUTF8Path(process_path.filename());
} }
// then get process id // then get process id
DWORD process_id = GetCurrentProcessId(); DWORD process_id = GetCurrentProcessId();
@ -461,19 +478,19 @@ namespace YYCC::ExceptionHelper {
if (!WinFctHelper::GetLocalAppData(u8_localappdata_path)) if (!WinFctHelper::GetLocalAppData(u8_localappdata_path))
return false; return false;
// convert to std::filesystem::path // convert to std::filesystem::path
std::filesystem::path crash_report_path(FsPathPatch::FromUTF8Path(u8_localappdata_path.c_str())); std::filesystem::path crash_report_path(StdPatch::ToStdPath(u8_localappdata_path));
// slash into crash report folder // slash into crash report folder
crash_report_path /= FsPathPatch::FromUTF8Path(YYCC_U8("CrashDumps")); crash_report_path /= StdPatch::ToStdPath(YYCC_U8("CrashDumps"));
// use create function to make sure it is existing // use create function to make sure it is existing
std::filesystem::create_directories(crash_report_path); std::filesystem::create_directories(crash_report_path);
// build log path and coredump path // build log path and coredump path
// build std::filesystem::path first // build std::filesystem::path first
std::filesystem::path log_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_log_filename.c_str()); std::filesystem::path log_filepath = crash_report_path / StdPatch::ToStdPath(u8_log_filename);
std::filesystem::path coredump_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_coredump_filename.c_str()); std::filesystem::path coredump_filepath = crash_report_path / StdPatch::ToStdPath(u8_coredump_filename);
// output to result // output to result
log_path = FsPathPatch::ToUTF8Path(log_filepath); log_path = StdPatch::ToUTF8Path(log_filepath);
coredump_path = FsPathPatch::ToUTF8Path(coredump_filepath); coredump_path = StdPatch::ToUTF8Path(coredump_filepath);
return true; return true;
} }
@ -505,10 +522,14 @@ namespace YYCC::ExceptionHelper {
// write crash coredump // write crash coredump
UExceptionCoreDump(coredump_path, info); 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 // stop process
g_ExceptionRegister.StartProcessing(); g_ExceptionRegister.StopProcessing();
end_proc: end_proc:
// if backup proc can be run, run it // if backup proc can be run, run it
@ -523,14 +544,20 @@ namespace YYCC::ExceptionHelper {
#pragma endregion #pragma endregion
void Register() { void Register(ExceptionCallback callback) {
g_ExceptionRegister.Register(); g_ExceptionRegister.Register(callback);
} }
void Unregister() { void Unregister() {
g_ExceptionRegister.Unregister(); g_ExceptionRegister.Unregister();
} }
#if defined(YYCC_DEBUG_UE_FILTER)
long __stdcall DebugCallUExceptionImpl(void* data) {
return UExceptionImpl(static_cast<LPEXCEPTION_POINTERS>(data));
}
#endif
} }
#endif #endif

View File

@ -11,6 +11,24 @@
*/ */
namespace YYCC::ExceptionHelper { 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 * @brief Register unhandled exception handler
* @details * @details
@ -22,8 +40,9 @@ namespace YYCC::ExceptionHelper {
* (for convenient debugging of developer when reporting bugs.) * (for convenient debugging of developer when reporting bugs.)
* *
* This function usually is called at the start of program. * 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(); void Register(ExceptionCallback callback = nullptr);
/** /**
* @brief Unregister unhandled exception handler * @brief Unregister unhandled exception handler
* @details * @details
@ -36,6 +55,10 @@ namespace YYCC::ExceptionHelper {
*/ */
void Unregister(); void Unregister();
#if defined(YYCC_DEBUG_UE_FILTER)
long __stdcall DebugCallUExceptionImpl(void*);
#endif
} }
#endif #endif

View File

@ -1,29 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <filesystem>
/**
* @brief \c std::filesystem::path related patches for UTF8 compatibility
* @details
* See also \ref fs_path_patch.
*/
namespace YYCC::FsPathPatch {
/**
* @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 FromUTF8Path(const yycc_char8_t* 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);
}

View File

@ -5,6 +5,7 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <stdexcept> #include <stdexcept>
#include <memory>
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
#include "WinImportPrefix.hpp" #include "WinImportPrefix.hpp"
@ -13,8 +14,8 @@
#endif #endif
namespace YYCC::IOHelper { namespace YYCC::IOHelper {
FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode) { std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode) {
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
// convert mode and file path to wchar // convert mode and file path to wchar

View File

@ -27,6 +27,19 @@ namespace YYCC::IOHelper {
#else #else
#error "Not supported pointer size." #error "Not supported pointer size."
#endif #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. * @brief The UTF8 version of \c std::fopen.
@ -37,6 +50,6 @@ namespace YYCC::IOHelper {
* On other platforms, this function will delegate request directly to std::fopen. * 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. * @return \c FILE* of the file to be opened, or nullptr if failed.
*/ */
FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode); std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode);
} }

View File

@ -2,6 +2,7 @@
#include "YYCCInternal.hpp" #include "YYCCInternal.hpp"
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "StringHelper.hpp"
#include <string> #include <string>
#include <cinttypes> #include <cinttypes>
#include <type_traits> #include <type_traits>
@ -17,7 +18,7 @@
namespace YYCC::ParserHelper { namespace YYCC::ParserHelper {
// Reference: https://zh.cppreference.com/w/cpp/utility/from_chars // Reference: https://zh.cppreference.com/w/cpp/utility/from_chars
/** /**
* @brief Try parsing given string to floating point types. * @brief Try parsing given string to floating point types.
* @tparam _Ty The type derived from floating point type. * @tparam _Ty The type derived from floating point type.
@ -25,14 +26,15 @@ namespace YYCC::ParserHelper {
* @param[out] num * @param[out] num
* The variable receiving result. * The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed. * 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. * @return True if success, otherwise false.
*/ */
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0> template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num) { bool TryParse(const yycc_u8string_view& strl, _Ty& num, std::chars_format fmt = std::chars_format::general) {
auto [ptr, ec] = std::from_chars( auto [ptr, ec] = std::from_chars(
EncodingHelper::ToOrdinary(strl.data()), EncodingHelper::ToOrdinary(strl.data()),
EncodingHelper::ToOrdinary(strl.data() + strl.size()), EncodingHelper::ToOrdinary(strl.data() + strl.size()),
num, std::chars_format::general num, fmt
); );
if (ec == std::errc()) { if (ec == std::errc()) {
// check whether the full string is matched // check whether the full string is matched
@ -50,12 +52,12 @@ namespace YYCC::ParserHelper {
} }
/** /**
* @brief Try parsing given string to integral types. * @brief Try parsing given string to integral types.
* @tparam _Ty The type derived from integral type. * @tparam _Ty The type derived from integral type except bool type.
* @param[in] strl The string need to be parsed. * @param[in] strl The string need to be parsed.
* @param[out] num * @param[out] num
* The variable receiving result. * The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed. * 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). * @param[in] base Integer base to use: a value between 2 and 36 (inclusive).
* @return True if success, otherwise false. * @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> template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
@ -82,7 +84,7 @@ namespace YYCC::ParserHelper {
/** /**
* @brief Try parsing given string to bool types. * @brief Try parsing given string to bool types.
* @tparam _Ty The type derived from bool type. * @tparam _Ty The type derived from bool type.
* @param[in] strl The string need to be parsed ("true" or "false"). * @param[in] strl The string need to be parsed ("true" or "false", case insensitive).
* @param[out] num * @param[out] num
* The variable receiving result. * The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed. * There is no guarantee that the content is not modified when parsing failed.
@ -90,22 +92,58 @@ namespace YYCC::ParserHelper {
*/ */
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0> template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num) { 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; if (strl == YYCC_U8("true")) num = true;
else if (strl == YYCC_U8("false")) num = false; else if (strl == YYCC_U8("false")) num = false;
else return false; else return false;
return true; return true;
} }
/** /**
* @brief Parse given string to arithmetic types. * @brief Parse given string to floating point types.
* @tparam _Ty The type derived from arithmetic type. * @tparam _Ty The type derived from floating point type.
* @param[in] strl The string need to be parsed. * @param[in] strl The string need to be parsed.
* @param[in] fmt The floating point format used when try parsing.
* @return * @return
* The parsing result. * The parsing result.
* There is no guarantee about the content of this return value when parsing failed. * 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. * It may be any possible value but usually is its default value.
*/ */
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0> 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 Parse(const yycc_u8string_view& strl) {
_Ty ret; _Ty ret;
TryParse(strl, ret); TryParse(strl, ret);
@ -115,18 +153,21 @@ namespace YYCC::ParserHelper {
// Reference: https://en.cppreference.com/w/cpp/utility/to_chars // Reference: https://en.cppreference.com/w/cpp/utility/to_chars
/** /**
* @brief Return a string version of given arithmetic value. * @brief Return the string representation of given floating point value.
* @tparam _Ty The type derived from arithmetic type. * @tparam _Ty The type derived from floating point type.
* @param[in] num The value getting string version. * @param[in] num The value need to get string representation.
* @return The string version of given value. * @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_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0> template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
yycc_u8string ToString(_Ty num) { 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; std::array<yycc_char8_t, 64> buffer;
auto [ptr, ec] = std::to_chars( auto [ptr, ec] = std::to_chars(
EncodingHelper::ToOrdinary(buffer.data()), EncodingHelper::ToOrdinary(buffer.data()),
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()), EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
num num, fmt, precision
); );
if (ec == std::errc()) { if (ec == std::errc()) {
return yycc_u8string(buffer.data(), EncodingHelper::ToUTF8(ptr) - buffer.data()); return yycc_u8string(buffer.data(), EncodingHelper::ToUTF8(ptr) - buffer.data());
@ -140,10 +181,36 @@ namespace YYCC::ParserHelper {
} }
} }
/** /**
* @brief Return a string version of given bool value. * @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. * @tparam _Ty The type derived from bool type.
* @param[in] num The value getting string version. * @param[in] num The value need to get string representation.
* @return The string version of given value ("true" or "false"). * @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> template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
yycc_u8string ToString(_Ty num) { yycc_u8string ToString(_Ty num) {

View File

@ -1,24 +1,24 @@
#include "FsPathPatch.hpp" #include "StdPatch.hpp"
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include <string> #include <string>
#include <stdexcept> #include <stdexcept>
namespace YYCC::FsPathPatch { namespace YYCC::StdPatch {
std::filesystem::path FromUTF8Path(const yycc_char8_t* u8_path) { std::filesystem::path ToStdPath(const yycc_u8string_view& u8_path) {
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
// convert path to wchar // convert path to wchar
std::wstring wpath; std::wstring wpath;
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_path, wpath)) if (!YYCC::EncodingHelper::UTF8ToWchar(u8_path, wpath))
throw std::invalid_argument("Fail to convert given UTF8 string."); throw std::invalid_argument("Fail to convert given UTF8 string.");
// return path with wchar_t ctor // return path with wchar_t ctor
return std::filesystem::path(wpath); return std::filesystem::path(wpath);
#else #else
return std::filesystem::path(EncodingHelper::ToOrdinary(u8_path)); std::string cache = YYCC::EncodingHelper::ToOrdinary(u8_path);
return std::filesystem::path(cache.c_str());
#endif #endif
} }

218
src/StdPatch.hpp Normal file
View 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
}

View File

@ -23,9 +23,9 @@ namespace YYCC::StringHelper {
// the return value is desired char count without NULL terminal. // the return value is desired char count without NULL terminal.
// minus number means error // minus number means error
int count = std::vsnprintf( int count = std::vsnprintf(
nullptr, nullptr,
0, 0,
EncodingHelper::ToOrdinary(format), EncodingHelper::ToOrdinary(format),
args1 args1
); );
if (count < 0) { if (count < 0) {
@ -41,8 +41,8 @@ namespace YYCC::StringHelper {
strl.resize(count); strl.resize(count);
int write_result = std::vsnprintf( int write_result = std::vsnprintf(
EncodingHelper::ToOrdinary(strl.data()), EncodingHelper::ToOrdinary(strl.data()),
strl.size() + 1, strl.size() + 1,
EncodingHelper::ToOrdinary(format), EncodingHelper::ToOrdinary(format),
args2 args2
); );
va_end(args2); va_end(args2);
@ -81,12 +81,10 @@ namespace YYCC::StringHelper {
#pragma region Replace #pragma region Replace
void Replace(yycc_u8string& strl, const yycc_char8_t* _from_strl, const yycc_char8_t* _to_strl) { 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 // Reference: https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
// check requirements // check requirements
// from string and to string should not be nullptr.
if (_from_strl == nullptr || _to_strl == nullptr) return;
// from string should not be empty // from string should not be empty
yycc_u8string from_strl(_from_strl); yycc_u8string from_strl(_from_strl);
yycc_u8string to_strl(_to_strl); yycc_u8string to_strl(_to_strl);
@ -100,14 +98,10 @@ namespace YYCC::StringHelper {
} }
} }
yycc_u8string Replace(const yycc_char8_t* _strl, const yycc_char8_t* _from_strl, const yycc_char8_t* _to_strl) { yycc_u8string Replace(const yycc_u8string_view& _strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl) {
// prepare result // prepare result
yycc_u8string strl; yycc_u8string strl(_strl);
// if given string is not nullptr, assign it and process it. Replace(strl, _from_strl, _to_strl);
if (_strl != nullptr) {
strl = _strl;
Replace(strl, _from_strl, _to_strl);
}
// return value // return value
return strl; return strl;
} }
@ -116,7 +110,7 @@ namespace YYCC::StringHelper {
#pragma region Join #pragma region Join
yycc_u8string Join(JoinDataProvider fct_data, const yycc_char8_t* decilmer) { yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer) {
yycc_u8string ret; yycc_u8string ret;
bool is_first = true; bool is_first = true;
yycc_u8string_view element; yycc_u8string_view element;
@ -126,9 +120,8 @@ namespace YYCC::StringHelper {
// insert decilmer // insert decilmer
if (is_first) is_first = false; if (is_first) is_first = false;
else { else {
// only insert non-nullptr decilmer. // append decilmer.
if (decilmer != nullptr) ret.append(decilmer);
ret.append(decilmer);
} }
// insert element if it is not empty // insert element if it is not empty
@ -139,32 +132,6 @@ namespace YYCC::StringHelper {
return ret; return ret;
} }
yycc_u8string Join(const std::vector<yycc_u8string>& data, const yycc_char8_t* decilmer, bool reversed) {
if (reversed) {
auto iter = data.crbegin();
auto stop = data.crend();
return Join([&iter, &stop](yycc_u8string_view& view) -> bool {
// if we reach tail, return false
if (iter == stop) return false;
// otherwise fetch data, inc iterator and return.
view = *iter;
++iter;
return true;
}, decilmer);
} else {
auto iter = data.cbegin();
auto stop = data.cend();
return Join([&iter, &stop](yycc_u8string_view& view) -> bool {
// if we reach tail, return nullptr
if (iter == stop) return false;
// otherwise fetch data, inc iterator and return.
view = *iter;
++iter;
return true;
}, decilmer);
}
}
#pragma endregion #pragma endregion
#pragma region Upper Lower #pragma region Upper Lower
@ -183,24 +150,13 @@ namespace YYCC::StringHelper {
); );
} }
yycc_u8string Lower(const yycc_char8_t* strl) {
yycc_u8string ret;
if (strl == nullptr) return ret;
else ret = strl;
Lower(ret);
return ret;
}
void Lower(yycc_u8string& strl) { void Lower(yycc_u8string& strl) {
GeneralStringLowerUpper<true>(strl); GeneralStringLowerUpper<true>(strl);
} }
yycc_u8string Upper(const yycc_char8_t* strl) { yycc_u8string Lower(const yycc_u8string_view& strl) {
// same as Lower, just replace char transform function. yycc_u8string ret(strl);
yycc_u8string ret; Lower(ret);
if (strl == nullptr) return ret;
else ret = strl;
Upper(ret);
return ret; return ret;
} }
@ -208,16 +164,24 @@ namespace YYCC::StringHelper {
GeneralStringLowerUpper<false>(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 endregion
#pragma region Split #pragma region Split
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_char8_t* _decilmer) { std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
// call split view // call split view
auto view_result = SplitView(strl, _decilmer); auto view_result = SplitView(strl, _decilmer);
// copy string view result to string // copy string view result to string
std::vector<yycc_u8string> elems; std::vector<yycc_u8string> elems;
elems.reserve(view_result.size());
for (const auto& strl_view : view_result) { for (const auto& strl_view : view_result) {
elems.emplace_back(yycc_u8string(strl_view)); elems.emplace_back(yycc_u8string(strl_view));
} }
@ -225,17 +189,17 @@ namespace YYCC::StringHelper {
return elems; return elems;
} }
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_char8_t* _decilmer) { std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
// Reference: // Reference:
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c // https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
// prepare return value // prepare return value
std::vector<yycc_u8string_view> elems; std::vector<yycc_u8string_view> elems;
// if string need to be splitted is empty, return original string (empty item). // if string need to be splitted is empty, return original string (empty string).
// if decilmer is nullptr, or decilmer is zero length, return original string. // if decilmer is empty, return original string.
yycc_u8string decilmer; yycc_u8string decilmer(_decilmer);
if (strl.empty() || _decilmer == nullptr || (decilmer = _decilmer, decilmer.empty())) { if (strl.empty() || decilmer.empty()) {
elems.emplace_back(strl); elems.emplace_back(strl);
return elems; return elems;
} }

View File

@ -54,7 +54,7 @@ namespace YYCC::StringHelper {
* @param[in] _from_strl The \e old string. * @param[in] _from_strl The \e old string.
* @param[in] _to_strl The \e new string. * @param[in] _to_strl The \e new string.
*/ */
void Replace(yycc_u8string& strl, const yycc_char8_t* _from_strl, const yycc_char8_t* _to_strl); 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. * @brief Return a copy with all occurrences of substring \e old replaced by \e new.
* @param[in] _strl The string for replacing * @param[in] _strl The string for replacing
@ -62,7 +62,7 @@ namespace YYCC::StringHelper {
* @param[in] _to_strl The \e new string. * @param[in] _to_strl The \e new string.
* @return The result of replacement. * @return The result of replacement.
*/ */
yycc_u8string Replace(const yycc_char8_t* _strl, const yycc_char8_t* _from_strl, const yycc_char8_t* _to_strl); 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. * @brief The data provider of general join function.
@ -85,38 +85,51 @@ namespace YYCC::StringHelper {
* @param[in] decilmer The decilmer used for joining. * @param[in] decilmer The decilmer used for joining.
* @return The result string of joining. * @return The result string of joining.
*/ */
yycc_u8string Join(JoinDataProvider fct_data, const yycc_char8_t* decilmer); yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer);
/** /**
* @brief Specialized join function for \c std::vector. * @brief Specialized join function for standard library container.
* @param[in] data The list to be joined. * @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. * @param[in] decilmer The decilmer used for joining.
* @param[in] reversed True if this list should be joined in reversed order.
* @return The result string of joining. * @return The result string of joining.
*/ */
yycc_u8string Join(const std::vector<yycc_u8string>& data, const yycc_char8_t* decilmer, bool reversed = false); 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 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_char8_t* strl);
/** /**
* @brief Convert given string to lowercase. * @brief Convert given string to lowercase.
* @param[in,out] strl The string to be lowercase. * @param[in,out] strl The string to be lowercase.
*/ */
void Lower(yycc_u8string& strl); void Lower(yycc_u8string& strl);
/** /**
* @brief Return a copy of the string converted to uppercase. * @brief Return a copy of the string converted to lowercase.
* @param[in] strl The string to be uppercase. * @param[in] strl The string to be lowercase.
* @return The copy of the string converted to uppercase. * @return The copy of the string converted to lowercase.
*/ */
yycc_u8string Upper(const yycc_char8_t* strl); yycc_u8string Lower(const yycc_u8string_view& strl);
/** /**
* @brief Convert given string to uppercase. * @brief Convert given string to uppercase.
* @param[in,out] strl The string to be uppercase. * @param[in,out] strl The string to be uppercase.
*/ */
void Upper(yycc_u8string& strl); 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. * @brief Split given string with specified decilmer.
@ -125,10 +138,10 @@ namespace YYCC::StringHelper {
* @return * @return
* The split result. * The split result.
* \par * \par
* If given string is empty, or decilmer is nullptr or empty, * If given string or decilmer are empty,
* the result container will only contain 1 entry which is equal to given string. * 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_char8_t* _decilmer); 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. * @brief Split given string with specified decilmer as string view.
* @param[in] strl The string need to be splitting. * @param[in] strl The string need to be splitting.
@ -137,10 +150,10 @@ namespace YYCC::StringHelper {
* The split result with string view format. * The split result with string view format.
* This will not produce any copy of original string. * This will not produce any copy of original string.
* \par * \par
* If given string is empty, or decilmer is nullptr or empty, * If given string or decilmer are empty,
* the result container will only contain 1 entry which is equal to given string. * the result container will only contain 1 entry which is equal to given string.
* @see Split(const yycc_u8string_view&, const yycc_char8_t*) * @see Split(const yycc_u8string_view&, const yycc_char8_t*)
*/ */
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_char8_t* _decilmer); std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
} }

View File

@ -9,7 +9,7 @@ namespace YYCC::WinFctHelper {
HMODULE GetCurrentModule() { HMODULE GetCurrentModule() {
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code // Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
HMODULE hModule = NULL; HMODULE hModule = NULL;
GetModuleHandleExW( ::GetModuleHandleExW(
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter. GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter.
(LPCWSTR)GetCurrentModule, (LPCWSTR)GetCurrentModule,
&hModule); &hModule);
@ -24,7 +24,7 @@ namespace YYCC::WinFctHelper {
// fetch temp folder // fetch temp folder
while (true) { while (true) {
if ((expected_size = GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) { if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
// failed, set to empty // failed, set to empty
return false; return false;
} }
@ -50,13 +50,13 @@ namespace YYCC::WinFctHelper {
DWORD copied_size; DWORD copied_size;
while (true) { while (true) {
if ((copied_size = GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) { if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
// failed, return // failed, return
return false; return false;
} }
// check insufficient buffer // check insufficient buffer
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
// buffer is not enough, enlarge it and try again. // buffer is not enough, enlarge it and try again.
wpath.resize(wpath.size() + MAX_PATH); wpath.resize(wpath.size() + MAX_PATH);
} else { } else {
@ -85,6 +85,31 @@ namespace YYCC::WinFctHelper {
return YYCC::EncodingHelper::WcharToUTF8(known_path.get(), ret); 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 #endif

View File

@ -57,6 +57,50 @@ namespace YYCC::WinFctHelper {
*/ */
bool GetLocalAppData(yycc_u8string& ret); 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 #endif

View File

@ -16,5 +16,8 @@
#undef LoadImage #undef LoadImage
#undef GetTempPath #undef GetTempPath
#undef GetModuleFileName #undef GetModuleFileName
#undef CopyFile
#undef MoveFile
#undef DeleteFile
#endif #endif

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#pragma region Operating System Identifier Macros
// Define operating system macros // Define operating system macros
#define YYCC_OS_WINDOWS 2 #define YYCC_OS_WINDOWS 2
#define YYCC_OS_LINUX 3 #define YYCC_OS_LINUX 3
@ -10,6 +12,10 @@
#define YYCC_OS YYCC_OS_LINUX #define YYCC_OS YYCC_OS_LINUX
#endif #endif
#pragma endregion
#pragma region Windows Shitty Behavior Disable Macros
// If we are in Windows, // If we are in Windows,
// we need add 2 macros to disable Windows shitty warnings and errors of // we need add 2 macros to disable Windows shitty warnings and errors of
// depracted functions and not secure functions. // depracted functions and not secure functions.
@ -24,6 +30,10 @@
#endif #endif
#pragma endregion
#pragma region YYCC UTF8 Types
// Define the UTF8 char type we used. // Define the UTF8 char type we used.
// And do a polyfill if no embedded char8_t type. // And do a polyfill if no embedded char8_t type.
@ -46,25 +56,61 @@ namespace YYCC {
#endif #endif
} }
/** /**
\typedef yycc_char8_t \typedef YYCC::yycc_char8_t
\brief YYCC UTF8 char type. \brief YYCC UTF8 char type.
\details \details
This char type is an alias to \c std::char8_t if your current C++ standard support it. 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. Otherwise it is defined as <TT>unsigned char</TT> as C++ 20 stdandard does.
*/ */
/** /**
\typedef yycc_u8string \typedef YYCC::yycc_u8string
\brief YYCC UTF8 string container type. \brief YYCC UTF8 string container type.
\details \details
This type is defined as \c std::basic_string<yycc_char8_t>. 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. It is equal to \c std::u8string if your current C++ standard support it.
*/ */
/** /**
\typedef yycc_u8string_view \typedef YYCC::yycc_u8string_view
\brief YYCC UTF8 string view type. \brief YYCC UTF8 string view type.
\details \details
This type is defined as \c std::basic_string_view<yycc_char8_t>. 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. 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

View File

@ -10,7 +10,8 @@
#include "ParserHelper.hpp" #include "ParserHelper.hpp"
#include "IOHelper.hpp" #include "IOHelper.hpp"
#include "WinFctHelper.hpp" #include "WinFctHelper.hpp"
#include "FsPathPatch.hpp" #include "StdPatch.hpp"
#include "ExceptionHelper.hpp" #include "ExceptionHelper.hpp"
#include "ConfigManager.hpp" #include "ConfigManager.hpp"
#include "ArgParser.hpp"

View File

@ -15,12 +15,8 @@ PRIVATE
YYCCommonplace YYCCommonplace
) )
# Setup C++ standard # Setup C++ standard
set_target_properties(YYCCTestbench target_compile_features(YYCCTestbench PUBLIC cxx_std_17)
PROPERTIES set_target_properties(YYCCTestbench PROPERTIES CXX_EXTENSION OFF)
CXX_STANDARD 17
CXX_STANDARD_REQUIRED 17
CXX_EXTENSION OFF
)
# Order Unicode charset for private using # Order Unicode charset for private using
target_compile_definitions(YYCCTestbench target_compile_definitions(YYCCTestbench
PRIVATE PRIVATE

View File

@ -1,5 +1,7 @@
#include <YYCCommonplace.hpp> #include <YYCCommonplace.hpp>
#include <cstdio> #include <cstdio>
#include <set>
#include <map>
namespace Console = YYCC::ConsoleHelper; namespace Console = YYCC::ConsoleHelper;
@ -193,17 +195,13 @@ namespace YYCCTestbench {
Assert(test_replace == YYCC_U8("aaddcc"), YYCC_U8("YYCC::StringHelper::Replace")); Assert(test_replace == YYCC_U8("aaddcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("zz"), YYCC_U8("yy")); // no replace test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("zz"), YYCC_U8("yy")); // no replace
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace")); Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8(""), YYCC_U8("zz")); // empty finding test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC::yycc_u8string_view(), YYCC_U8("zz")); // empty finding
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), nullptr, YYCC_U8("zz")); // nullptr finding
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace")); Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaaabbaa"), YYCC_U8("aa"), YYCC_U8("")); // no replaced string test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaaabbaa"), YYCC_U8("aa"), YYCC_U8("")); // no replaced string
Assert(test_replace == YYCC_U8("bb"), YYCC_U8("YYCC::StringHelper::Replace")); Assert(test_replace == YYCC_U8("bb"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaxcc"), YYCC_U8("x"), YYCC_U8("yx")); // nested replacing test_replace = YYCC::StringHelper::Replace(YYCC_U8("aaxcc"), YYCC_U8("x"), YYCC_U8("yx")); // nested replacing
Assert(test_replace == YYCC_U8("aayxcc"), YYCC_U8("YYCC::StringHelper::Replace")); Assert(test_replace == YYCC_U8("aayxcc"), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(YYCC_U8(""), YYCC_U8(""), YYCC_U8("xy")); // empty source string test_replace = YYCC::StringHelper::Replace(YYCC::yycc_u8string_view(), YYCC_U8(""), YYCC_U8("xy")); // empty source string
Assert(test_replace == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Replace"));
test_replace = YYCC::StringHelper::Replace(nullptr, YYCC_U8(""), YYCC_U8("xy")); // nullptr source string
Assert(test_replace == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Replace")); Assert(test_replace == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Replace"));
// Test Upper / Lower // Test Upper / Lower
@ -216,10 +214,8 @@ namespace YYCCTestbench {
std::vector<YYCC::yycc_u8string> test_join_container { std::vector<YYCC::yycc_u8string> test_join_container {
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("") YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
}; };
auto test_join = YYCC::StringHelper::Join(test_join_container, YYCC_U8(", ")); auto test_join = YYCC::StringHelper::Join(test_join_container.begin(), test_join_container.end(), YYCC_U8(", "));
Assert(test_join == YYCC_U8(", 1, 2, "), YYCC_U8("YYCC::StringHelper::Join")); Assert(test_join == YYCC_U8(", 1, 2, "), YYCC_U8("YYCC::StringHelper::Join"));
test_join = YYCC::StringHelper::Join(test_join_container, YYCC_U8(", "), true);
Assert(test_join == YYCC_U8(", 2, 1, "), YYCC_U8("YYCC::StringHelper::Join"));
// Test Split // Test Split
auto test_split = YYCC::StringHelper::Split(YYCC_U8(", 1, 2, "), YYCC_U8(", ")); // normal auto test_split = YYCC::StringHelper::Split(YYCC_U8(", 1, 2, "), YYCC_U8(", ")); // normal
@ -231,7 +227,7 @@ namespace YYCCTestbench {
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched decilmer test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched decilmer
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split")); Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split")); Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("")); // empty decilmer test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC::yycc_u8string_view()); // empty decilmer
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split")); Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split")); Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split"));
test_split = YYCC::StringHelper::Split(YYCC::yycc_u8string_view(), YYCC_U8("")); // empty source string test_split = YYCC::StringHelper::Split(YYCC::yycc_u8string_view(), YYCC_U8("")); // empty source string
@ -243,12 +239,12 @@ namespace YYCCTestbench {
static void ParserTestbench() { static void ParserTestbench() {
// Test success TryParse // Test success TryParse
#define TEST_MACRO(type_t, value, string_value) { \ #define TEST_MACRO(type_t, value, string_value, ...) { \
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \ YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
type_t cache; \ type_t cache; \
Assert(YYCC::ParserHelper::TryParse<type_t>(cache_string, cache) && cache == value, YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \ Assert(YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, __VA_ARGS__) && cache == value, YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
} }
TEST_MACRO(int8_t, INT8_C(-61), "-61"); TEST_MACRO(int8_t, INT8_C(-61), "-61");
TEST_MACRO(uint8_t, UINT8_C(200), "200"); TEST_MACRO(uint8_t, UINT8_C(200), "200");
TEST_MACRO(int16_t, INT16_C(6161), "6161"); TEST_MACRO(int16_t, INT16_C(6161), "6161");
@ -257,33 +253,38 @@ namespace YYCCTestbench {
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293"); TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161"); TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807"); TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807");
TEST_MACRO(uint32_t, UINT32_C(0xffff), "ffff", 16);
TEST_MACRO(float, 1.0f, "1.0");
TEST_MACRO(double, 1.0, "1.0");
TEST_MACRO(bool, true, "true"); TEST_MACRO(bool, true, "true");
#undef TEST_MACRO #undef TEST_MACRO
// Test failed TryParse // Test failed TryParse
#define TEST_MACRO(type_t, value, string_value) { \ #define TEST_MACRO(type_t, string_value, ...) { \
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \ YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
type_t cache; \ type_t cache; \
Assert(!YYCC::ParserHelper::TryParse<type_t>(cache_string, cache), YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \ Assert(!YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, __VA_ARGS__), YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
} }
TEST_MACRO(int8_t, INT8_C(-61), "6161"); TEST_MACRO(int8_t, "6161");
TEST_MACRO(uint8_t, UINT8_C(200), "32800"); TEST_MACRO(uint8_t, "32800");
TEST_MACRO(int16_t, INT16_C(6161), "61616161"); TEST_MACRO(int16_t, "61616161");
TEST_MACRO(uint16_t, UINT16_C(32800), "4294967293"); TEST_MACRO(uint16_t, "4294967293");
TEST_MACRO(int32_t, INT32_C(61616161), "616161616161"); TEST_MACRO(int32_t, "616161616161");
TEST_MACRO(uint32_t, UINT32_C(4294967293), "9223372036854775807"); TEST_MACRO(uint32_t, "9223372036854775807");
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161616161616161"); TEST_MACRO(int64_t, "616161616161616161616161");
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "92233720368547758079223372036854775807"); TEST_MACRO(uint64_t, "92233720368547758079223372036854775807");
TEST_MACRO(bool, true, "hello, world!"); TEST_MACRO(float, "1e40");
TEST_MACRO(double, "1e114514");
TEST_MACRO(bool, "hello, world!");
#undef TEST_MACRO #undef TEST_MACRO
// Test ToString // Test ToString
#define TEST_MACRO(type_t, value, string_value) { \ #define TEST_MACRO(type_t, value, string_value, ...) { \
type_t cache = value; \ type_t cache = value; \
YYCC::yycc_u8string ret(YYCC::ParserHelper::ToString<type_t>(cache)); \ YYCC::yycc_u8string ret(YYCC::ParserHelper::ToString<type_t>(cache, __VA_ARGS__)); \
Assert(ret == YYCC_U8(string_value), YYCC_U8("YYCC::StringHelper::ToString<" #type_t ">")); \ Assert(ret == YYCC_U8(string_value), YYCC_U8("YYCC::StringHelper::ToString<" #type_t ">")); \
} }
@ -295,10 +296,13 @@ namespace YYCCTestbench {
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293"); TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161"); TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807"); TEST_MACRO(uint64_t, UINT64_C(9223372036854775807), "9223372036854775807");
TEST_MACRO(uint32_t, UINT32_C(0xffff), "ffff", 16);
TEST_MACRO(float, 1.0f, "1.0", std::chars_format::fixed, 1);
TEST_MACRO(double, 1.0, "1.0", std::chars_format::fixed, 1);
TEST_MACRO(bool, true, "true"); TEST_MACRO(bool, true, "true");
#undef TEST_MACRO #undef TEST_MACRO
} }
static void DialogTestbench() { static void DialogTestbench() {
@ -338,11 +342,31 @@ namespace YYCCTestbench {
static void ExceptionTestbench() { static void ExceptionTestbench() {
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
YYCC::ExceptionHelper::Register(); YYCC::ExceptionHelper::Register([](const YYCC::yycc_u8string& log_path, const YYCC::yycc_u8string& coredump_path) -> void {
MessageBoxW(
NULL,
YYCC::EncodingHelper::UTF8ToWchar(
YYCC::StringHelper::Printf(YYCC_U8("Log generated:\nLog path: %s\nCore dump path: %s"), log_path.c_str(), coredump_path.c_str())
).c_str(),
L"Fatal Error", MB_OK + MB_ICONERROR
);
}
);
// Perform a div zero exception. // Perform a div zero exception.
#if defined (YYCC_DEBUG_UE_FILTER)
// Reference: https://stackoverflow.com/questions/20981982/is-it-possible-to-debug-unhandledexceptionfilters-with-a-debugger
__try {
// all of code normally inside of main or WinMain here...
int i = 1, j = 0;
int k = i / j;
} __except (YYCC::ExceptionHelper::DebugCallUExceptionImpl(GetExceptionInformation())) {
OutputDebugStringW(L"executed filter function\n");
}
#else
int i = 1, j = 0; int i = 1, j = 0;
int k = i / j; int k = i / j;
#endif
YYCC::ExceptionHelper::Unregister(); YYCC::ExceptionHelper::Unregister();
@ -368,46 +392,69 @@ namespace YYCCTestbench {
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), YYCC_U8("YYCC::WinFctHelper::GetLocalAppData")); Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), YYCC_U8("YYCC::WinFctHelper::GetLocalAppData"));
Console::FormatLine(YYCC_U8("Local AppData: %s"), test_localappdata_path.c_str()); Console::FormatLine(YYCC_U8("Local AppData: %s"), test_localappdata_path.c_str());
Assert(YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(1252)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
Assert(!YYCC::WinFctHelper::IsValidCodePage(static_cast<UINT>(114514)), YYCC_U8("YYCC::WinFctHelper::IsValidCodePage"));
// MARK: There is no testbench for MoveFile, CopyFile DeleteFile.
// Because they can operate file system files.
// And may cause test environment entering unstable status.
#endif #endif
} }
static void FsPathPatch() { static void StdPatch() {
// Std Path
std::filesystem::path test_path; std::filesystem::path test_path;
for (const auto& strl : c_UTF8TestStrTable) { for (const auto& strl : c_UTF8TestStrTable) {
test_path /= YYCC::FsPathPatch::FromUTF8Path(strl.c_str()); test_path /= YYCC::StdPatch::ToStdPath(strl);
} }
YYCC::yycc_u8string test_slashed_path(YYCC::FsPathPatch::ToUTF8Path(test_path)); YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path));
#if YYCC_OS == YYCC_OS_WINDOWS #if YYCC_OS == YYCC_OS_WINDOWS
std::wstring wdecilmer(1u, std::filesystem::path::preferred_separator); std::wstring wdecilmer(1u, std::filesystem::path::preferred_separator);
YYCC::yycc_u8string decilmer(YYCC::EncodingHelper::WcharToUTF8(wdecilmer.c_str())); YYCC::yycc_u8string decilmer(YYCC::EncodingHelper::WcharToUTF8(wdecilmer));
#else #else
YYCC::yycc_u8string decilmer(1u, std::filesystem::path::preferred_separator); YYCC::yycc_u8string decilmer(1u, std::filesystem::path::preferred_separator);
#endif #endif
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable, decilmer.c_str())); YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), decilmer));
Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::FsPathPatch")); Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::StdPatch::ToStdPath, YYCC::StdPatch::ToUTF8Path"));
// StartsWith, EndsWith
YYCC::yycc_u8string test_starts_ends_with(YYCC_U8("aaabbbccc"));
Assert(YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::StartsWith"));
Assert(!YYCC::StdPatch::StartsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::StartsWith"));
Assert(!YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("aaa")), YYCC_U8("YYCC::StdPatch::EndsWith"));
Assert(YYCC::StdPatch::EndsWith(test_starts_ends_with, YYCC_U8("ccc")), YYCC_U8("YYCC::StdPatch::EndsWith"));
// Contains
std::set<int> test_set { 1, 2, 3, 4, 6, 7 };
Assert(YYCC::StdPatch::Contains(test_set, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
Assert(!YYCC::StdPatch::Contains(test_set, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
std::map<int, float> test_map { { 1, 1.0f }, { 4, 4.0f } };
Assert(YYCC::StdPatch::Contains(test_map, static_cast<int>(1)), YYCC_U8("YYCC::StdPatch::Contains"));
Assert(!YYCC::StdPatch::Contains(test_map, static_cast<int>(5)), YYCC_U8("YYCC::StdPatch::Contains"));
} }
enum class TestEnum : int8_t {
Test1, Test2, Test3
};
class TestConfigManager { class TestConfigManager {
public: public:
enum class TestEnum : int8_t {
Test1, Test2, Test3
};
TestConfigManager() : TestConfigManager() :
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)), m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
m_FloatSetting(YYCC_U8("float-setting"), 0.0f), m_FloatSetting(YYCC_U8("float-setting"), 0.0f),
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")), m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
m_BoolSetting(YYCC_U8("bool-setting"), false), m_BoolSetting(YYCC_U8("bool-setting"), false),
m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::ConfigManager::ConstraintPresets::GetNumberRangeConstraint<float>(-1.0f, 1.0f)), m_ClampedFloatSetting(YYCC_U8("clamped-float-setting"), 0.0f, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1), m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), { m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
&m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting &m_IntSetting, &m_FloatSetting, &m_StringSetting, &m_BoolSetting, &m_ClampedFloatSetting, &m_EnumSetting
}) }) {}
{}
~TestConfigManager() {} ~TestConfigManager() {}
void PrintSettings() { void PrintSettings() {
@ -438,8 +485,8 @@ namespace YYCCTestbench {
TestConfigManager test; TestConfigManager test;
// test constraint works // test constraint works
Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::ConfigManager::Constraint")); Assert(!test.m_ClampedFloatSetting.Set(2.0f), YYCC_U8("YYCC::Constraints::Constraint"));
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::Constraint")); Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::Constraints::Constraint"));
// test modify settings // test modify settings
#define TEST_MACRO(member_name, set_val) { \ #define TEST_MACRO(member_name, set_val) { \
@ -452,10 +499,10 @@ namespace YYCCTestbench {
TEST_MACRO(m_StringSetting, YYCC_U8("fuck")); TEST_MACRO(m_StringSetting, YYCC_U8("fuck"));
TEST_MACRO(m_BoolSetting, true); TEST_MACRO(m_BoolSetting, true);
TEST_MACRO(m_ClampedFloatSetting, 0.5f); TEST_MACRO(m_ClampedFloatSetting, 0.5f);
TEST_MACRO(m_EnumSetting, TestConfigManager::TestEnum::Test2); TEST_MACRO(m_EnumSetting, TestEnum::Test2);
#undef TEST_MACRO #undef TEST_MACRO
// test save // test save
test.PrintSettings(); test.PrintSettings();
Assert(test.m_CoreManager.Save(), YYCC_U8("YYCC::ConfigManager::CoreManager::Save")); Assert(test.m_CoreManager.Save(), YYCC_U8("YYCC::ConfigManager::CoreManager::Save"));
@ -468,7 +515,7 @@ namespace YYCCTestbench {
Assert(test.m_StringSetting.Get() == YYCC_U8(""), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset")); Assert(test.m_StringSetting.Get() == YYCC_U8(""), YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_BoolSetting.Get() == false, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset")); Assert(test.m_BoolSetting.Get() == false, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset")); Assert(test.m_ClampedFloatSetting.Get() == 0.0f, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
Assert(test.m_EnumSetting.Get() == TestConfigManager::TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset")); Assert(test.m_EnumSetting.Get() == TestEnum::Test1, YYCC_U8("YYCC::ConfigManager::CoreManager::Reset"));
// test load // test load
Assert(test.m_CoreManager.Load(), YYCC_U8("YYCC::ConfigManager::CoreManager::Load")); Assert(test.m_CoreManager.Load(), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
@ -478,22 +525,140 @@ namespace YYCCTestbench {
Assert(test.m_StringSetting.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ConfigManager::CoreManager::Load")); Assert(test.m_StringSetting.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_BoolSetting.Get() == true, YYCC_U8("YYCC::ConfigManager::CoreManager::Load")); Assert(test.m_BoolSetting.Get() == true, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_ClampedFloatSetting.Get() == 0.5f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load")); Assert(test.m_ClampedFloatSetting.Get() == 0.5f, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
Assert(test.m_EnumSetting.Get() == TestConfigManager::TestEnum::Test2, YYCC_U8("YYCC::ConfigManager::CoreManager::Load")); Assert(test.m_EnumSetting.Get() == TestEnum::Test2, YYCC_U8("YYCC::ConfigManager::CoreManager::Load"));
}
class TestArgParser {
public:
TestArgParser() :
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the testbench of argument parser."), {
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
&m_BoolArgument, &m_ClampedFloatArgument
}) {}
~TestArgParser() {}
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
YYCC::ArgParser::StringArgument m_StringArgument;
YYCC::ArgParser::SwitchArgument m_BoolArgument;
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
YYCC::ArgParser::OptionContext m_OptionContext;
};
static void ArgParserTestbench(int argc, char* argv[]) {
// test command line getter
{
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromStd"));
auto result = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
for (result.Reset(); !result.IsEOF(); result.Next()) {
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
}
}
#if YYCC_OS == YYCC_OS_WINDOWS
{
YYCC::ConsoleHelper::WriteLine(YYCC_U8("YYCC::ArgParser::ArgumentList::CreateFromWin32"));
auto result = YYCC::ArgParser::ArgumentList::CreateFromWin32();
for (result.Reset(); !result.IsEOF(); result.Next()) {
YYCC::ConsoleHelper::FormatLine(YYCC_U8("\t%s"), result.Argument().c_str());
}
}
#endif
// test option context
// init option context
TestArgParser test;
#define PREPARE_DATA(...) const char* test_argv[] = { __VA_ARGS__ }; \
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(sizeof(test_argv) / sizeof(char*), const_cast<char**>(test_argv));
// normal test
{
PREPARE_DATA("exec", "-i", "114514");
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_FloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_StringArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(!test.m_ClampedFloatArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// no argument
{
PREPARE_DATA("exec");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// error argument
{
PREPARE_DATA("exec", "-?", "114514");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// lost argument
{
PREPARE_DATA("exec", "-i");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// dplicated assign
{
PREPARE_DATA("exec", "-i", "114514" "--int", "114514");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// extra useless argument
{
PREPARE_DATA("exec", "-i", "114514" "1919810");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// invalid clamp argument
{
PREPARE_DATA("exec", "-i", "114514", "--clamped-float", "114.0");
Assert(!test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// full argument
{
PREPARE_DATA("exec", "-i", "114514", "-f", "2.0", "--string", "fuck", "-b", "--clamped-float", "0.5");
Assert(test.m_OptionContext.Parse(al), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_IntArgument.IsCaptured() && test.m_IntArgument.Get() == UINT32_C(114514), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_FloatArgument.IsCaptured() && test.m_FloatArgument.Get() == 2.0f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_StringArgument.IsCaptured() && test.m_StringArgument.Get() == YYCC_U8("fuck"), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_BoolArgument.IsCaptured(), YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
Assert(test.m_ClampedFloatArgument.IsCaptured() && test.m_ClampedFloatArgument.Get() == 0.5f, YYCC_U8("YYCC::ArgParser::OptionContext::Parse"));
test.m_OptionContext.Reset();
}
// Help text
test.m_OptionContext.Help();
#undef PREPARE_DATA
} }
} }
int main(int argc, char** args) { int main(int argc, char* argv[]) {
// common testbench // common testbench
// normal // normal
YYCCTestbench::EncodingTestbench(); YYCCTestbench::EncodingTestbench();
YYCCTestbench::StringTestbench(); YYCCTestbench::StringTestbench();
YYCCTestbench::ParserTestbench(); YYCCTestbench::ParserTestbench();
YYCCTestbench::WinFctTestbench(); YYCCTestbench::WinFctTestbench();
YYCCTestbench::FsPathPatch(); YYCCTestbench::StdPatch();
// advanced // advanced
YYCCTestbench::ConfigManagerTestbench(); YYCCTestbench::ConfigManagerTestbench();
YYCCTestbench::ArgParserTestbench(argc, argv);
// testbench which may terminal app or ordering input // testbench which may terminal app or ordering input
YYCCTestbench::ConsoleTestbench(); YYCCTestbench::ConsoleTestbench();