Compare commits
32 Commits
51d288ac4b
...
master
Author | SHA1 | Date | |
---|---|---|---|
ccd0219ead | |||
4bfba6f243 | |||
9e994dd4f0 | |||
d6034f8cb0 | |||
0694d923f3 | |||
580b096cb3 | |||
f9365481b9 | |||
15aade052f | |||
050bed400d | |||
244e39c4d1 | |||
d52630ac5c | |||
a76f10722d | |||
8a72e6a655 | |||
dfc0c127c5 | |||
2f11ba6023 | |||
00c8f09907 | |||
734cd01da8 | |||
bdeaea294f | |||
ff8c7d04cc | |||
0ab470367c | |||
c4d441f5fa | |||
f8a696b4e8 | |||
f65eff6edf | |||
8fcfa180b4 | |||
e23a1346eb | |||
2576523dbb | |||
9ce52e8d4b | |||
7785773196 | |||
cfbc3c68e0 | |||
8dbe32cb8e | |||
664763afbb | |||
a34bab07c1 |
@ -300,6 +300,9 @@ StatementMacros:
|
||||
- YYCC_DEFAULT_COPY
|
||||
- YYCC_DEFAULT_MOVE
|
||||
- YYCC_DEFAULT_COPY_MOVE
|
||||
- YYCC_DECL_COPY
|
||||
- YYCC_DECL_MOVE
|
||||
- YYCC_DECL_COPY_MOVE
|
||||
TableGenBreakInsideDAGArg: DontBreak
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
|
106
COMPILE.md
Normal file
106
COMPILE.md
Normal file
@ -0,0 +1,106 @@
|
||||
# Compile Manual
|
||||
|
||||
## Choose Version
|
||||
|
||||
We suggest that you only use stable version (tagged commit).
|
||||
The latest commit always present current works.
|
||||
It means that it is not stable and work in progress.
|
||||
|
||||
## Requirements
|
||||
|
||||
* CMake 3.23 at least.
|
||||
* The common compiler supporting C++ 23 (GCC / Clang / MSVC).
|
||||
* Iconv (Optional on Windows. Required on other systems).
|
||||
* [GoogleTest](https://github.com/google/googletest) (Required if you build testbench).
|
||||
* Doxygen (Required if you build documentation).
|
||||
* Python and Astral UV (Required if you use "User Build" method)
|
||||
|
||||
> [!WARNING]
|
||||
> You may face some issues when building on macOS with Clang. That's not your fault.
|
||||
> Clang used libc++ library lacks some essential features used by this project.
|
||||
> You may try other solutions for compiling this project on macOS or with Clang.
|
||||
|
||||
## Preparing
|
||||
|
||||
### GoogleTest
|
||||
|
||||
GoogleTest is required if you need to build testbench.
|
||||
If you don't need this please skip this chapter.
|
||||
|
||||
We use GoogleTest v1.17.0.
|
||||
It would be okey use other versions but I have not test on them.
|
||||
|
||||
> [!WARNING]
|
||||
> When building this project, you may face link error with GoogleTest, especially on Linux.
|
||||
> This issue is caused by that the binary provided by your package manager is built in C++17 and its ABI is incompatible with C++23.
|
||||
> See this [GitHub Issue](https://github.com/google/googletest/issues/4591) for more infomation.
|
||||
> The solution is that download GoogleTest source code and build it in C++23 on your own.
|
||||
> Following content tell you how to do this.
|
||||
|
||||
There are the steps instructing you how to compile GoogleTest manually.
|
||||
|
||||
1. Download GoogleTest source code with given version in GitHub Release page.
|
||||
1. Extract it into a directory.
|
||||
1. Enter this directory and create 2 subdirectory `build` and `install` for CMake build and install respectively.
|
||||
1. Enter `build` directory and configure CMake with extra `-DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON` parameters.
|
||||
1. Use CMake to build GoogleTest
|
||||
1. Use CMake to install GoogleTest into previous we created `install` directory.
|
||||
|
||||
### Iconv
|
||||
|
||||
Iconv is optional on Windows and disabled in default.
|
||||
However, if you are building project on MSYS2 or MINGW platform in Windows, we suggest you enable Iconv feature for more functions.
|
||||
Once you enable this feature, you must prepare installed Iconv which is no problem for MSYS2 environment via package manager.
|
||||
You also can enable this feature under MSVC but you must make sure that you can compile Iconv under MSVC.
|
||||
For how to enable this feature forcely, see following chapters for more infomations.
|
||||
|
||||
On other platforms, Iconv is enabled automatically and can not be disabled.
|
||||
Because there is no other encoding convertion libraries that we can use (Windows has a builtin set of encoding convertion Win32 functions).
|
||||
|
||||
### Doxygen
|
||||
|
||||
Doxygen is required only if you need to build documentation.
|
||||
If you don't need this please skip this chapter.
|
||||
|
||||
We use Doxygen 1.9.7.
|
||||
It would be okey use other versions but I have not test on them.
|
||||
|
||||
YYCCommonplace use Doxygen as its documentation system.
|
||||
So before compiling, you must make sure `doxygen` are presented in your environment.
|
||||
|
||||
## Build and Install
|
||||
|
||||
There are 2 different ways to build this project.
|
||||
If you are the user of this project (just want this project to make something work), please choose "User Build".
|
||||
If you are a developer (developer of this project, or use this project as dependency to develop your project), please choose "Developer Build".
|
||||
|
||||
### User Build
|
||||
|
||||
"User Build" is basically how GitHub Action build this project.
|
||||
|
||||
We use Python 3.11 and UV 0.7.17 to manage our build script generator.
|
||||
It would be okey use other versions but I have not test on them.
|
||||
|
||||
TODO...
|
||||
|
||||
### Developer Build
|
||||
|
||||
TODO...
|
||||
|
||||
There is a list listing all variables you may configure during compiling.
|
||||
|
||||
* `YYCC_BUILD_TESTBENCH`: Set it to `ON` to build testbench. `OFF` in default.
|
||||
It is useful for the developer of this project.
|
||||
It also suit for the user who has runtime issues on their platforms to check whether this project works as expected.
|
||||
* `YYCC_BUILD_DOC`: Set it to `ON` to build documentation. `OFF` in default.
|
||||
It may be useful for the developer who firstly use this project in their own projects.
|
||||
Please note that generated documentation is different in different platforms.
|
||||
* `YYCC_ENFORCE_ICONV`: Set it to `ON` to enable Iconv feature forcely. `OFF` in default.
|
||||
The usage of this option has been introduced in previous "Iconv" chapter.
|
||||
* `GTest_ROOT`: TODO
|
||||
* `Iconv_ROOT`: TODO
|
||||
* `CMAKE_CXX_STANDARD`: Set C++ standard version of project.
|
||||
`23` in default and this version can not be lower than C++23.
|
||||
You usually do not need change this.
|
||||
* `CMAKE_POSITION_INDEPENDENT_CODE`: Set it to `True` to enable PIC.
|
||||
This is essential for those project which use this project and produce dynamicing library as final artifact.
|
16
README.md
16
README.md
@ -4,19 +4,9 @@ YYC Commonplace, or YYCCommonplace (abbr. YYCC) is a static library specifically
|
||||
|
||||
## Usage
|
||||
|
||||
For more usage about this library, please build documentation of this project via Doxygen and read it.
|
||||
|
||||
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.
|
||||
For more usage about this library, please read documentation after building this project with documentation.
|
||||
I also highly recommend that you read documentation first before writing with this library.
|
||||
|
||||
## 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.
|
||||
|
||||
See documentation for how to build this project.
|
||||
|
||||
> [!NOTE]
|
||||
> When building with testbench, you may face link error with GoogleTest. This issue is caused by that the binary provided by your package manager is built in C++ 17 and its ABI is incompatible with C++ 23. The solution is that download GoogleTest source code and build it in C++ 23 on your own. See this [GitHub Issue](https://github.com/google/googletest/issues/4591) for more infomation.
|
||||
> Oppositely, you don't need care about this issue if you just want to build YYCC self.
|
||||
|
||||
See [Compile Manual](./COMPILE.md).
|
||||
|
@ -29,6 +29,9 @@
|
||||
|
||||
\li \subpage intro
|
||||
|
||||
\li \subpage premise_and_principle
|
||||
|
||||
<!--
|
||||
\li \subpage library_macros
|
||||
|
||||
\li \subpage library_encoding
|
||||
@ -46,20 +49,24 @@
|
||||
\li \subpage std_patch
|
||||
|
||||
\li \subpage enum_helper
|
||||
-->
|
||||
|
||||
<B>Advanced Features</B>
|
||||
|
||||
<!--
|
||||
\li \subpage constraints
|
||||
|
||||
\li \subpage config_manager
|
||||
|
||||
\li \subpage arg_parser
|
||||
-->
|
||||
|
||||
</TD>
|
||||
<TD ALIGN="LEFT" VALIGN="TOP">
|
||||
|
||||
<B>Windows Specific Features</B>
|
||||
|
||||
<!--
|
||||
\li \subpage win_import
|
||||
|
||||
\li \subpage com_helper
|
||||
@ -69,6 +76,7 @@
|
||||
\li \subpage win_fct_helper
|
||||
|
||||
\li \subpage exception_helper
|
||||
-->
|
||||
|
||||
</TD>
|
||||
</TR>
|
||||
|
@ -6,7 +6,7 @@ YYCCommonplace, or YYC Commonplace (abbr. YYCC),
|
||||
is a static library providing various useful C++ functions
|
||||
when programming with standard library or Windows environment.
|
||||
|
||||
During the development of a few projects,
|
||||
At the beginning, during the development of a few projects,
|
||||
I gradually understand how Windows make the compromise with the code written by its old developers,
|
||||
and what is developer wanted in contemporary C++ standard library under Windows environment.
|
||||
So I create this static library for all of my C++ project.
|
||||
@ -15,6 +15,16 @@ I can use a clear and easy way to manage these codes.
|
||||
I can easily fix issues found in project using this library by updating a single project,
|
||||
rather than fixing these duplicated code in each project one by one
|
||||
because all of them share the same implementations.
|
||||
This is the origin of the 1.x version of YYCC.
|
||||
|
||||
After a few years ago, I start to write in Rust and more complicated codes.
|
||||
I was allured by Rust and hope all these feature Rust holded can be adapted into C++,
|
||||
so I start to refactor this library in modern way.
|
||||
However, the compatibility with low C++ standard version is now become the shortcoming of this library.
|
||||
I was forced to considering the compatibility with C++ 17 and it cause a huge work.
|
||||
So, after think twice, I decide to drop the support of C++ 17, which the higest C++ standard I can used for Virtools project.
|
||||
And increase the standard to C++ 23 directly to have better experience.
|
||||
That's the origin of the 2.x version of YYCC.
|
||||
|
||||
This project mainly is served for my personal use.
|
||||
But I would be honored if you would like to use this in your project.
|
||||
@ -46,17 +56,18 @@ Thus I can have a similar Linux C++ programming experience on Windows.
|
||||
|
||||
The eccentric decision of standard commission also is the reason why I create this library.
|
||||
|
||||
\li C++ standard commission loves to bring one feature with N concepts and P assistant classes.
|
||||
\li C++ standard commission prefer to provide one function with very fundamental classes and functions
|
||||
and programmer need to write too much code to achieve a simple work.
|
||||
\li C++ standard commission seems doesn't want to bring any features the programmer urgent needed.
|
||||
\li C++ standard commission loves delete programmer loved convenient functions and classes.
|
||||
\li etc...
|
||||
|
||||
There is not a proper way to \e format a string in C++ until C++ 20 (\c std::format).
|
||||
String related functions, such as split, lower, higher, replace, now still not be included in standard library.
|
||||
Programmer loved, easy to used UTF8 procession functions and classes was deprecate now and will be removed in future.
|
||||
Programmer loved, easy to used encoding convertion functions and classes are deprecate now and will be removed in future.
|
||||
|
||||
That's why I create this library.
|
||||
I bring these function in this library.
|
||||
I bring these functions in this library.
|
||||
Not industrial level, but easy to use and have enough performance in my project.
|
||||
|
||||
\subsection intro__why__boost Boost Issues
|
||||
@ -65,7 +76,7 @@ Bosst is a powerful C++ library. But the shortcoming is overt. It's tooooo big.
|
||||
This drawback will be more obvious considering the bad dependency mechanism of C++.
|
||||
Although the most of Boost sub-library is header-only, but some library still need to link with Boost.
|
||||
It order you download the whole Boost library and extract it in your hard disk.
|
||||
Cost more than half hours, 5+ GB disk space and the life time of your disk.
|
||||
Cost more than half hour of your life, 5+ GB disk space and the life time of your disk.
|
||||
|
||||
The functions belonging to Boost is industrial level.
|
||||
But what I want is not industrial level functions.
|
||||
@ -74,6 +85,7 @@ I don't need extreme performance. I just want my code works.
|
||||
|
||||
So I create this library, bring some Boost functions with ordinary but not bad implementation.
|
||||
|
||||
<!--
|
||||
\section intro__usage Library Usage
|
||||
|
||||
Before using this library, I suggest you read this manual fully to have a full overview of this library.
|
||||
@ -187,5 +199,5 @@ 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.
|
||||
|
||||
-->
|
||||
*/
|
||||
|
47
doc/src/premise_and_principle.dox
Normal file
47
doc/src/premise_and_principle.dox
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
|
||||
\page premise_and_principle Premise and Principle
|
||||
|
||||
When programming with this library, there is some premise and principle you should noticed.
|
||||
|
||||
\section premise_and_principle__exception_is_error Exception is Error
|
||||
|
||||
The most crucial spot of this library is <B>"Exception is Error"</B>.
|
||||
When some functions throw exception, it should cause program paniked, rather than recover from it.
|
||||
This is inspired from Rust, and also the compromise with STL.
|
||||
|
||||
Most functions this library provided has Rust-Result-like return value.
|
||||
It means that programmer can handle error correctly.
|
||||
However, this library is based on STL, another library that may throw C++ exception to indicate error.
|
||||
We can not control this behavior of STL, so I forcely apply this rule.
|
||||
|
||||
\section premise_and_principle__os_encoding OS Encoding
|
||||
|
||||
This library has special treat with Windows to make it works on Windows.
|
||||
However, for other operating system, it do not have too much care.
|
||||
We brutally make a premise that other operating systems are UNIX-liked and use UTF8 as its encoding.
|
||||
|
||||
\section premise_and_principle__string_encoding String Encoding
|
||||
|
||||
Before using this library, you should know the encoding strategy of this library first.
|
||||
In short words, this library use UTF8 encoding everywhere except some special cases list following (not all).
|
||||
|
||||
\li Traditional format function in yycc::string::op.
|
||||
Traditional format function provide some overloads for ordinary string formatting.
|
||||
That's because this feature is so common to use in some cases.
|
||||
\li The message of Rust panic in yycc::rust::panic.
|
||||
Due to the limitation of \c std::format, we only can use ordinary string as its message content.
|
||||
\li The message of standard library exception.
|
||||
For the compatibility with C++ standard library exception,
|
||||
we only can use ordinary string as the message of exception.
|
||||
|
||||
\section premise_and_principle__cmake All in CMake
|
||||
|
||||
Since YYCC 2.0 version, we do not provide MSVC install layout.
|
||||
Any projects use this project should use CMake or CMake-compatible software as its build system.
|
||||
|
||||
The reason why we make this decision is that some essential contents are written in CMake files.
|
||||
For example, some environment detection macros and Windows environment patches.
|
||||
If you do not use CMake, these contents will not be presented in project and cause bad behavior when using this project.
|
||||
|
||||
*/
|
@ -27,9 +27,10 @@ def escape_sh_argument(arg):
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ScriptSettings:
|
||||
pic: bool
|
||||
cpp_version: str
|
||||
build_doc: bool
|
||||
pic: bool
|
||||
build_testbench: bool
|
||||
|
||||
class TemplateRender:
|
||||
loader: jinja2.BaseLoader
|
||||
@ -86,7 +87,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument(
|
||||
'-d', '--build-doc',
|
||||
action='store_true', dest='build_doc',
|
||||
help='Build YYCC without documentation.'
|
||||
help='Build YYCC with documentation.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--pic',
|
||||
|
@ -2,28 +2,16 @@
|
||||
# Navigate to project root directory
|
||||
cd {{ repo_root_dir }}
|
||||
|
||||
# Create main binary directory
|
||||
mkdir bin
|
||||
cd bin
|
||||
# Create build directory
|
||||
# Create build and install directory
|
||||
mkdir build
|
||||
# Create install directory
|
||||
mkdir install
|
||||
cd install
|
||||
mkdir Debug
|
||||
mkdir Release
|
||||
cd ..
|
||||
|
||||
# Build current system debug and release version
|
||||
# Build as release version
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} ../.. --fresh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} {{ '-DYYCC_BUILD_DOC=ON' if build_doc }} {{ '-DYYCC_BUILD_TESTBENCH=ON' if build_testbench }} ../.. --fresh
|
||||
cmake --build .
|
||||
cmake --install . --prefix ../install/Debug
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} -DYYCC_BUILD_TESTBENCH=ON ../.. --fresh
|
||||
cmake --build .
|
||||
cmake --install . --prefix ../install/Release
|
||||
cd ..
|
||||
cmake --install . --prefix ../install
|
||||
|
||||
# Exit to original path
|
||||
cd ..
|
||||
echo "Linux CMake Build Done"
|
||||
echo "YYCC Linux CMake build done"
|
||||
|
@ -13,12 +13,23 @@ PRIVATE
|
||||
# Sources
|
||||
yycc/string/reinterpret.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/string/stream.cpp
|
||||
yycc/patch/fopen.cpp
|
||||
yycc/rust/panic.cpp
|
||||
yycc/encoding/stlcvt.cpp
|
||||
yycc/rust/env.cpp
|
||||
yycc/windows/com.cpp
|
||||
yycc/windows/dialog.cpp
|
||||
yycc/windows/winfct.cpp
|
||||
yycc/windows/console.cpp
|
||||
yycc/encoding/stl.cpp
|
||||
yycc/encoding/windows.cpp
|
||||
yycc/encoding/iconv.cpp
|
||||
#yycc/encoding/pycodec.cpp
|
||||
|
||||
yycc/carton/pycodec.cpp
|
||||
yycc/carton/termcolor.cpp
|
||||
yycc/carton/wcwidth.cpp
|
||||
yycc/carton/tabulate.cpp
|
||||
yycc/carton/csconsole.cpp
|
||||
)
|
||||
target_sources(YYCCommonplace
|
||||
PUBLIC
|
||||
@ -37,6 +48,7 @@ FILES
|
||||
yycc/flag_enum.hpp
|
||||
yycc/string/reinterpret.hpp
|
||||
yycc/string/op.hpp
|
||||
yycc/string/stream.hpp
|
||||
yycc/patch/ptr_pad.hpp
|
||||
yycc/patch/fopen.hpp
|
||||
yycc/num/parse.hpp
|
||||
@ -49,14 +61,24 @@ FILES
|
||||
yycc/rust/panic.hpp
|
||||
yycc/rust/option.hpp
|
||||
yycc/rust/result.hpp
|
||||
yycc/rust/env.hpp
|
||||
yycc/windows/import_guard_head.hpp
|
||||
yycc/windows/import_guard_tail.hpp
|
||||
yycc/windows/com.hpp
|
||||
yycc/windows/dialog.hpp
|
||||
yycc/windows/winfct.hpp
|
||||
yycc/windows/console.hpp
|
||||
yycc/constraint.hpp
|
||||
yycc/constraint/builder.hpp
|
||||
yycc/encoding/stlcvt.hpp
|
||||
yycc/encoding/stl.hpp
|
||||
yycc/encoding/windows.hpp
|
||||
yycc/encoding/iconv.hpp
|
||||
yycc/encoding/pycodec.hpp
|
||||
|
||||
yycc/carton/pycodec.hpp
|
||||
yycc/carton/termcolor.hpp
|
||||
yycc/carton/wcwidth.hpp
|
||||
yycc/carton/tabulate.hpp
|
||||
yycc/carton/csconsole.hpp
|
||||
)
|
||||
# Setup header infomations
|
||||
target_include_directories(YYCCommonplace
|
||||
@ -84,7 +106,9 @@ PUBLIC
|
||||
# OS macro
|
||||
$<$<PLATFORM_ID:Windows>:YYCC_OS_WINDOWS>
|
||||
$<$<PLATFORM_ID:Linux>:YYCC_OS_LINUX>
|
||||
$<$<PLATFORM_ID:Android>:YYCC_OS_LINUX> # We brutally think Android as Linux.
|
||||
$<$<PLATFORM_ID:Darwin>:YYCC_OS_MACOS>
|
||||
$<$<PLATFORM_ID:iOS>:YYCC_OS_MACOS> # We brutally think iOS as macOS.
|
||||
# Compiler macro
|
||||
$<$<CXX_COMPILER_ID:GNU>:YYCC_CC_GCC>
|
||||
$<$<CXX_COMPILER_ID:Clang>:YYCC_CC_CLANG>
|
||||
@ -111,17 +135,12 @@ target_compile_options(YYCCommonplace
|
||||
PUBLIC
|
||||
# Order build as UTF-8 in MSVC
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
|
||||
# Order preprocessor conformance mode (fix __VA_OPT__ error in MSVC)
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/Zc:preprocessor>
|
||||
# Resolve MSVC __cplusplus macro value error.
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>
|
||||
)
|
||||
|
||||
# Fix GCC std::stacktrace link error
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
|
||||
target_link_libraries(YYCCommonplace PRIVATE stdc++exp)
|
||||
else ()
|
||||
target_link_libraries(YYCCommonplace PRIVATE stdc++_libbacktrace)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Install binary and headers
|
||||
install(TARGETS YYCCommonplace
|
||||
EXPORT YYCCommonplaceTargets
|
||||
|
@ -1,48 +0,0 @@
|
||||
#include "COMHelper.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
namespace YYCC::COMHelper {
|
||||
|
||||
/**
|
||||
* @brief The guard for initialize COM environment.
|
||||
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
|
||||
* and it also will try uninitializing COM environment when destructing.
|
||||
* If initialization failed, uninitialization will not be executed.
|
||||
*/
|
||||
class ComGuard {
|
||||
public:
|
||||
ComGuard() : m_HasInit(false) {
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (SUCCEEDED(hr)) m_HasInit = true;
|
||||
}
|
||||
~ComGuard() {
|
||||
if (m_HasInit) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_HasInit;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_HasInit;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The instance of COM environment guard.
|
||||
* @details Dialog related function need COM environment,
|
||||
* so we need initializing COM environment when loading this module,
|
||||
* and uninitializing COM environment when we no longer use this module.
|
||||
* So we use a static instance in here.
|
||||
* And make it be const so no one can change it.
|
||||
*/
|
||||
static const ComGuard c_ComGuard {};
|
||||
|
||||
bool IsInitialized() {
|
||||
return c_ComGuard.IsInitialized();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief Windows COM related types and checker.
|
||||
* @details
|
||||
* This namespace is Windows specific.
|
||||
* In other platforms, this whole namespace will be unavailable.
|
||||
*
|
||||
* See also \ref com_helper.
|
||||
*/
|
||||
namespace YYCC::COMHelper {
|
||||
|
||||
/// @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
|
||||
class ComPtrDeleter {
|
||||
public:
|
||||
ComPtrDeleter() {}
|
||||
void operator() (IUnknown* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
com_ptr->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of \c IFileDialog
|
||||
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IFileOpenDialog
|
||||
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItem
|
||||
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItemArray
|
||||
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellFolder
|
||||
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
|
||||
|
||||
/// @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
|
||||
class CoTaskMemDeleter {
|
||||
public:
|
||||
CoTaskMemDeleter() {}
|
||||
void operator() (void* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
CoTaskMemFree(com_ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of COM created \c WCHAR sequence.
|
||||
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
|
||||
|
||||
/**
|
||||
* @brief Check whether COM environment has been initialized.
|
||||
* @return True if it is, otherwise false.
|
||||
* @remarks
|
||||
* This function will call corresponding function of COM Guard.
|
||||
* Do not remove this function and you must preserve at least one reference to this function in final program.
|
||||
* Some compiler will try to drop COM Guard in final program if no reference to it and it will cause the initialization of COM environment failed.
|
||||
* This is the reason why I order you do the things said above.
|
||||
*/
|
||||
bool IsInitialized();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,282 +0,0 @@
|
||||
#include "ConsoleHelper.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include <iostream>
|
||||
|
||||
// Include Windows used headers in Windows.
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
#pragma region Windows Specific Functions
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
static bool RawEnableColorfulConsole(FILE* fs) {
|
||||
if (!_isatty(_fileno(fs))) return false;
|
||||
|
||||
HANDLE h_output;
|
||||
DWORD dw_mode;
|
||||
|
||||
h_output = (HANDLE)_get_osfhandle(_fileno(fs));
|
||||
if (!GetConsoleMode(h_output, &dw_mode)) return false;
|
||||
if (!SetConsoleMode(h_output, dw_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Reference:
|
||||
* https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
|
||||
* https://stackoverflow.com/questions/69830460/reading-utf-8-input
|
||||
|
||||
There is 3 way to make Windows console enable UTF8 mode.
|
||||
|
||||
First one is calling SetConsoleCP and SetConsoleOutputCP.
|
||||
The side effect of this is std::cin and std::cout is broken,
|
||||
however there is a patch for this issue.
|
||||
|
||||
Second one is calling _set_mode with _O_U8TEXT or _O_U16TEXT to enable Unicode mode for Windows console.
|
||||
This also have side effect which is stronger than first one.
|
||||
All puts family functions (ASCII-based output functions) will throw assertion exception.
|
||||
You only can use putws family functions (wide-char-based output functions).
|
||||
However these functions can not be used without calling _set_mode in Windows design.
|
||||
|
||||
There still is another method, using WriteConsoleW directly visiting console.
|
||||
This function family can output correct string without calling any extra functions!
|
||||
This method is what we adopted.
|
||||
*/
|
||||
|
||||
template<bool _bIsConsole>
|
||||
static yycc_u8string WinConsoleRead(HANDLE hStdIn) {
|
||||
using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>;
|
||||
|
||||
// Prepare an internal buffer because the read data may not be fully used.
|
||||
// For example, we may read x\ny in a single calling but after processing \n, this function will return
|
||||
// so y will temporarily stored in this internal buffer for next using.
|
||||
// Thus this function is not thread safe.
|
||||
static std::basic_string<_TChar> internal_buffer;
|
||||
// create return value buffer
|
||||
std::basic_string<_TChar> return_buffer;
|
||||
|
||||
// Prepare some variables
|
||||
DWORD dwReadNumberOfChars;
|
||||
_TChar szReadChars[64];
|
||||
size_t eol_pos;
|
||||
|
||||
// try fetching EOL
|
||||
while (true) {
|
||||
// if internal buffer is empty,
|
||||
// try fetching it.
|
||||
if (internal_buffer.empty()) {
|
||||
// console and non-console use different method to read.
|
||||
if constexpr (_bIsConsole) {
|
||||
// console handle, use ReadConsoleW.
|
||||
// read from console, the read data is wchar based
|
||||
if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(_TChar), &dwReadNumberOfChars, NULL))
|
||||
break;
|
||||
} else {
|
||||
// anything else, use ReadFile instead.
|
||||
// the read data is utf8 based
|
||||
if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL))
|
||||
break;
|
||||
}
|
||||
|
||||
// send to internal buffer
|
||||
if (dwReadNumberOfChars == 0) break;
|
||||
internal_buffer.append(szReadChars, dwReadNumberOfChars);
|
||||
}
|
||||
|
||||
// try finding EOL in internal buffer
|
||||
if constexpr (std::is_same_v<_TChar, char>) eol_pos = internal_buffer.find_first_of('\n');
|
||||
else eol_pos = internal_buffer.find_first_of(L'\n');
|
||||
// check finding result
|
||||
if (eol_pos == std::wstring::npos) {
|
||||
// the whole string do not include EOL, fully appended to return value
|
||||
return_buffer += internal_buffer;
|
||||
internal_buffer.clear();
|
||||
// need more data, continue while
|
||||
} else {
|
||||
// split result
|
||||
// push into result and remain some in internal buffer.
|
||||
return_buffer.append(internal_buffer, 0u, eol_pos);
|
||||
internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place.
|
||||
// break while mean success finding
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// post-process for return value
|
||||
yycc_u8string real_return_buffer;
|
||||
if constexpr (_bIsConsole) {
|
||||
// console mode need convert wchar to utf8
|
||||
YYCC::EncodingHelper::WcharToUTF8(return_buffer, real_return_buffer);
|
||||
} else {
|
||||
// non-console just copt the result
|
||||
real_return_buffer = EncodingHelper::ToUTF8(return_buffer);
|
||||
}
|
||||
// every mode need delete \r words
|
||||
YYCC::StringHelper::Replace(real_return_buffer, YYCC_U8("\r"), YYCC_U8(""));
|
||||
// return value
|
||||
return real_return_buffer;
|
||||
}
|
||||
|
||||
static void WinConsoleWrite(const yycc_u8string& strl, bool to_stderr) {
|
||||
// Prepare some Win32 variables
|
||||
// fetch stdout handle first
|
||||
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
|
||||
DWORD dwConsoleMode;
|
||||
DWORD dwWrittenNumberOfChars;
|
||||
|
||||
// if stdout was redirected, this handle may point to a file handle or anything else,
|
||||
// WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle
|
||||
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
|
||||
// console handle, use WriteConsoleW.
|
||||
// convert utf8 string to wide char first
|
||||
std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl));
|
||||
size_t wstrl_size = wstrl.size();
|
||||
// write string with size check
|
||||
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(wstrl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
} else {
|
||||
// anything else, use WriteFile instead.
|
||||
// WriteFile do not need extra convertion, because it is direct writing.
|
||||
// check whether string length is overflow
|
||||
size_t strl_size = strl.size() * sizeof(yycc_u8string::value_type);
|
||||
// write string with size check
|
||||
if (strl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#pragma endregion
|
||||
|
||||
bool EnableColorfulConsole() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
bool ret = true;
|
||||
ret &= RawEnableColorfulConsole(stdout);
|
||||
ret &= RawEnableColorfulConsole(stderr);
|
||||
return ret;
|
||||
|
||||
#else
|
||||
|
||||
// just return true and do nothing
|
||||
return true;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
yycc_u8string ReadLine() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
// get stdin mode
|
||||
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
// use different method to get according to whether stdin is redirected
|
||||
DWORD dwConsoleMode;
|
||||
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
|
||||
return WinConsoleRead<true>(hStdIn);
|
||||
} else {
|
||||
return WinConsoleRead<false>(hStdIn);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// in linux, directly use C++ function to fetch.
|
||||
std::string cmd;
|
||||
if (std::getline(std::cin, cmd).fail()) cmd.clear();
|
||||
return EncodingHelper::ToUTF8(cmd);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
template<bool bNeedFmt, bool bIsErr, bool bHasEOL>
|
||||
static void RawWrite(const yycc_char8_t* u8_fmt, va_list argptr) {
|
||||
// Buiild string need to be written first
|
||||
// If no format string or plain string for writing, return.
|
||||
if (u8_fmt == nullptr) return;
|
||||
// Build or simply copy string
|
||||
yycc_u8string strl;
|
||||
if constexpr (bNeedFmt) {
|
||||
// treat as format string
|
||||
va_list argcpy;
|
||||
va_copy(argcpy, argptr);
|
||||
strl = YYCC::StringHelper::VPrintf(u8_fmt, argcpy);
|
||||
va_end(argcpy);
|
||||
} else {
|
||||
// treat as plain string
|
||||
strl = u8_fmt;
|
||||
}
|
||||
// Checkout whether add EOL
|
||||
if constexpr (bHasEOL) {
|
||||
strl += YYCC_U8("\n");
|
||||
}
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// call Windows specific writer
|
||||
WinConsoleWrite(strl, bIsErr);
|
||||
#else
|
||||
// in linux, directly use C function to write.
|
||||
std::fputs(EncodingHelper::ToOrdinary(strl.c_str()), bIsErr ? stderr : stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Format(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, false, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void FormatLine(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, false, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void Write(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, false, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void WriteLine(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, false, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, true, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, true, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ErrWrite(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, true, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ErrWriteLine(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, true, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,163 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief The helper providing universal C\# style console function and other console related stuff
|
||||
* @details
|
||||
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
|
||||
*/
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
/// @brief The head of ASCII escape code of black color.
|
||||
#define YYCC_COLORHDR_BLACK "\033[30m"
|
||||
/// @brief The head of ASCII escape code of red color.
|
||||
#define YYCC_COLORHDR_RED "\033[31m"
|
||||
/// @brief The head of ASCII escape code of green color.
|
||||
#define YYCC_COLORHDR_GREEN "\033[32m"
|
||||
/// @brief The head of ASCII escape code of yellow color.
|
||||
#define YYCC_COLORHDR_YELLOW "\033[33m"
|
||||
/// @brief The head of ASCII escape code of blue color.
|
||||
#define YYCC_COLORHDR_BLUE "\033[34m"
|
||||
/// @brief The head of ASCII escape code of magenta color.
|
||||
#define YYCC_COLORHDR_MAGENTA "\033[35m"
|
||||
/// @brief The head of ASCII escape code of cyan color.
|
||||
#define YYCC_COLORHDR_CYAN "\033[36m"
|
||||
/// @brief The head of ASCII escape code of white color.
|
||||
#define YYCC_COLORHDR_WHITE "\033[37m"
|
||||
|
||||
/// @brief The head of ASCII escape code of light black color.
|
||||
#define YYCC_COLORHDR_LIGHT_BLACK "\033[90m"
|
||||
/// @brief The head of ASCII escape code of light red color.
|
||||
#define YYCC_COLORHDR_LIGHT_RED "\033[91m"
|
||||
/// @brief The head of ASCII escape code of light green color.
|
||||
#define YYCC_COLORHDR_LIGHT_GREEN "\033[92m"
|
||||
/// @brief The head of ASCII escape code of light yellow color.
|
||||
#define YYCC_COLORHDR_LIGHT_YELLOW "\033[93m"
|
||||
/// @brief The head of ASCII escape code of light blue color.
|
||||
#define YYCC_COLORHDR_LIGHT_BLUE "\033[94m"
|
||||
/// @brief The head of ASCII escape code of light magenta color.
|
||||
#define YYCC_COLORHDR_LIGHT_MAGENTA "\033[95m"
|
||||
/// @brief The head of ASCII escape code of light cyan color.
|
||||
#define YYCC_COLORHDR_LIGHT_CYAN "\033[96m"
|
||||
/// @brief The head of ASCII escape code of light white color.
|
||||
#define YYCC_COLORHDR_LIGHT_WHITE "\033[97m"
|
||||
|
||||
/// @brief The tail of ASCII escape code of every color.
|
||||
#define YYCC_COLORTAIL "\033[0m"
|
||||
|
||||
/// @brief The ASCII escape code pair of black color.
|
||||
#define YYCC_COLOR_BLACK(T) "\033[30m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of red color.
|
||||
#define YYCC_COLOR_RED(T) "\033[31m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of green color.
|
||||
#define YYCC_COLOR_GREEN(T) "\033[32m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of yellow color.
|
||||
#define YYCC_COLOR_YELLOW(T) "\033[33m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of blue color.
|
||||
#define YYCC_COLOR_BLUE(T) "\033[34m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of magenta color.
|
||||
#define YYCC_COLOR_MAGENTA(T) "\033[35m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of cyan color.
|
||||
#define YYCC_COLOR_CYAN(T) "\033[36m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of white color.
|
||||
#define YYCC_COLOR_WHITE(T) "\033[37m" T "\033[0m"
|
||||
|
||||
/// @brief The ASCII escape code pair of light black color.
|
||||
#define YYCC_COLOR_LIGHT_BLACK(T) "\033[90m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light red color.
|
||||
#define YYCC_COLOR_LIGHT_RED(T) "\033[91m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light green color.
|
||||
#define YYCC_COLOR_LIGHT_GREEN(T) "\033[92m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light yellow color.
|
||||
#define YYCC_COLOR_LIGHT_YELLOW(T) "\033[93m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light blue color.
|
||||
#define YYCC_COLOR_LIGHT_BLUE(T) "\033[94m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light magenta color.
|
||||
#define YYCC_COLOR_LIGHT_MAGENTA(T) "\033[95m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light cyan color.
|
||||
#define YYCC_COLOR_LIGHT_CYAN(T) "\033[96m" T "\033[0m"
|
||||
/// @brief The ASCII escape code pair of light white color.
|
||||
#define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m"
|
||||
|
||||
/**
|
||||
* @brief Enable console color support for Windows.
|
||||
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks
|
||||
* This function only works on Windows and do nothing on other platforms such as Linux,
|
||||
* because we assume all terminals existing on other platform support color feature as default.
|
||||
*/
|
||||
bool EnableColorfulConsole();
|
||||
|
||||
/**
|
||||
* @brief Reads the next line of UTF8 characters from the standard input stream.
|
||||
* @return
|
||||
* The next line of UTF8 characters from the input stream.
|
||||
* Empty string if user just press Enter key or function failed.
|
||||
*/
|
||||
yycc_u8string ReadLine();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void Format(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void FormatLine(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void Write(const yycc_char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void WriteLine(const yycc_char8_t* u8_strl);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ErrWrite(const yycc_char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ErrWriteLine(const yycc_char8_t* u8_strl);
|
||||
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
#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(); }
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,378 +0,0 @@
|
||||
#include "DialogHelper.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
|
||||
namespace YYCC::DialogHelper {
|
||||
|
||||
#pragma region FileFilters
|
||||
|
||||
bool FileFilters::Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il) {
|
||||
// assign filter name
|
||||
if (filter_name == nullptr) return false;
|
||||
FilterName name(filter_name);
|
||||
|
||||
// assign filter patterns
|
||||
FilterModes modes;
|
||||
for (const yycc_char8_t* pattern : il) {
|
||||
if (pattern != nullptr)
|
||||
modes.emplace_back(yycc_u8string(pattern));
|
||||
}
|
||||
|
||||
// check filter patterns
|
||||
if (modes.empty()) return false;
|
||||
|
||||
// add into pairs and return
|
||||
m_Filters.emplace_back(std::make_pair(std::move(name), std::move(modes)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileFilters::Generate(WinFileFilters& win_result) const {
|
||||
// clear Windows oriented data
|
||||
win_result.Clear();
|
||||
|
||||
// build new Windows oriented string vector first
|
||||
for (const auto& it : m_Filters) {
|
||||
// convert name to wchar
|
||||
WinFileFilters::WinFilterName name;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first, name))
|
||||
return false;
|
||||
|
||||
// convert pattern and join them
|
||||
const auto& filter_modes = it.second;
|
||||
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";")));
|
||||
WinFileFilters::WinFilterModes modes;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes))
|
||||
return false;
|
||||
|
||||
// append new pair
|
||||
win_result.m_WinFilters.emplace_back(std::make_pair(name, modes));
|
||||
}
|
||||
|
||||
// check filter size
|
||||
// if it overflow the maximum value, return false
|
||||
size_t count = win_result.m_WinFilters.size();
|
||||
if (count > std::numeric_limits<UINT>::max())
|
||||
return false;
|
||||
|
||||
// create new win data struct
|
||||
// and assign string pointer from internal built win string vector.
|
||||
win_result.m_WinDataStruct.reset(new COMDLG_FILTERSPEC[count]);
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
win_result.m_WinDataStruct[i].pszName = win_result.m_WinFilters[i].first.c_str();
|
||||
win_result.m_WinDataStruct[i].pszSpec = win_result.m_WinFilters[i].second.c_str();
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region File Dialog
|
||||
|
||||
bool FileDialog::Generate(WinFileDialog& win_result) const {
|
||||
// clear Windows oriented data
|
||||
win_result.Clear();
|
||||
|
||||
// set owner
|
||||
win_result.m_WinOwner = m_Owner;
|
||||
|
||||
// build file filters
|
||||
if (!m_FileTypes.Generate(win_result.m_WinFileTypes))
|
||||
return false;
|
||||
|
||||
// check default file type index
|
||||
// check value overflow (comparing with >= because we need plus 1 for file type index later)
|
||||
if (m_DefaultFileTypeIndex >= std::numeric_limits<UINT>::max())
|
||||
return false;
|
||||
// check invalid index (overflow the length or registered file types if there is file type)
|
||||
if (m_FileTypes.Count() != 0u && m_DefaultFileTypeIndex >= m_FileTypes.Count())
|
||||
return false;
|
||||
// set index with additional plus according to Windows specification.
|
||||
win_result.m_WinDefaultFileTypeIndex = static_cast<UINT>(m_DefaultFileTypeIndex + 1);
|
||||
|
||||
// build title and init file name
|
||||
if (m_HasTitle) {
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_Title, win_result.m_WinTitle))
|
||||
return false;
|
||||
win_result.m_HasTitle = true;
|
||||
}
|
||||
if (m_HasInitFileName) {
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitFileName, win_result.m_WinInitFileName))
|
||||
return false;
|
||||
win_result.m_HasInitFileName = true;
|
||||
}
|
||||
|
||||
// fetch init directory
|
||||
if (m_HasInitDirectory) {
|
||||
// convert to wpath
|
||||
std::wstring w_init_directory;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitDirectory, w_init_directory))
|
||||
return false;
|
||||
|
||||
// fetch IShellItem*
|
||||
// Ref: https://stackoverflow.com/questions/76306324/how-to-set-default-folder-for-ifileopendialog-interface
|
||||
IShellItem* init_directory = NULL;
|
||||
HRESULT hr = SHCreateItemFromParsingName(w_init_directory.c_str(), NULL, IID_PPV_ARGS(&init_directory));
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// assign IShellItem*
|
||||
win_result.m_WinInitDirectory.reset(init_directory);
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Windows Dialog Code
|
||||
|
||||
enum class CommonFileDialogType {
|
||||
OpenFile,
|
||||
OpenMultipleFiles,
|
||||
SaveFile,
|
||||
OpenFolder
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Extract display name from given IShellItem*.
|
||||
* @param item[in] The pointer to IShellItem for extracting.
|
||||
* @param ret[out] Extracted display name container.
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks This is an assist function of CommonFileDialog.
|
||||
*/
|
||||
static bool ExtractDisplayName(IShellItem* item, yycc_u8string& ret) {
|
||||
// fetch display name from IShellItem*
|
||||
LPWSTR _name;
|
||||
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR display_name(_name);
|
||||
|
||||
// convert result
|
||||
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret))
|
||||
return false;
|
||||
|
||||
// finished
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief General file dialog.
|
||||
* @param params[in] User specified parameter controlling the behavior of this file dialog,
|
||||
* including title, file types and etc.
|
||||
* @param ret[out] The path to user selected files or folders.
|
||||
* For multiple selection, the count of items >= 1. For other scenario, the count of item is 1.
|
||||
* @return True if success, otherwise false (input parameters is wrong or user click "Cancel" in popup window).
|
||||
* @remarks This function is the real underlying function of all dialog functions.
|
||||
*/
|
||||
template<CommonFileDialogType EDialogType>
|
||||
static bool CommonFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
|
||||
// prepare result variable
|
||||
HRESULT hr;
|
||||
|
||||
// check whether COM environment has been initialized
|
||||
if (!COMHelper::IsInitialized()) return false;
|
||||
|
||||
// create file dialog instance
|
||||
// fetch dialog CLSID first
|
||||
CLSID dialog_clsid;
|
||||
switch (EDialogType) {
|
||||
case CommonFileDialogType::OpenFile:
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
dialog_clsid = CLSID_FileOpenDialog;
|
||||
break;
|
||||
case CommonFileDialogType::SaveFile:
|
||||
dialog_clsid = CLSID_FileSaveDialog;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
// create raw dialog pointer
|
||||
IFileDialog* _pfd = nullptr;
|
||||
hr = CoCreateInstance(
|
||||
dialog_clsid,
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&_pfd)
|
||||
);
|
||||
if (FAILED(hr)) return false;
|
||||
// create memory-safe dialog pointer
|
||||
COMHelper::SmartIFileDialog pfd(_pfd);
|
||||
|
||||
// set options for dialog
|
||||
// before setting, always get the options first in order.
|
||||
// not to override existing options.
|
||||
DWORD dwFlags;
|
||||
hr = pfd->GetOptions(&dwFlags);
|
||||
if (FAILED(hr)) return false;
|
||||
// modify options
|
||||
switch (EDialogType) {
|
||||
// We want user only can pick file system files: FOS_FORCEFILESYSTEM.
|
||||
// Open dialog default: FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR
|
||||
// Save dialog default: FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR
|
||||
// Pick folder: FOS_PICKFOLDERS
|
||||
case CommonFileDialogType::OpenFile:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
break;
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
dwFlags |= FOS_ALLOWMULTISELECT;
|
||||
break;
|
||||
case CommonFileDialogType::SaveFile:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
break;
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
dwFlags |= FOS_PICKFOLDERS;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
// set folder dialog options
|
||||
hr = pfd->SetOptions(dwFlags);
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// build Windows used file dialog parameters
|
||||
WinFileDialog win_params;
|
||||
if (!params.Generate(win_params))
|
||||
return false;
|
||||
|
||||
// setup title and init file name
|
||||
if (win_params.HasTitle()) {
|
||||
hr = pfd->SetTitle(win_params.GetTitle());
|
||||
if (FAILED(hr)) return false;
|
||||
}
|
||||
if (win_params.HasInitFileName()) {
|
||||
hr = pfd->SetFileName(win_params.GetInitFileName());
|
||||
if (FAILED(hr)) return false;
|
||||
}
|
||||
|
||||
// setup init directory
|
||||
if (win_params.HasInitDirectory()) {
|
||||
hr = pfd->SetFolder(win_params.GetInitDirectory());
|
||||
}
|
||||
|
||||
// set file types and default file index when we picking file
|
||||
if constexpr (EDialogType != CommonFileDialogType::OpenFolder) {
|
||||
// set file types list
|
||||
const auto& file_filters = win_params.GetFileTypes();
|
||||
hr = pfd->SetFileTypes(file_filters.GetFilterCount(), file_filters.GetFilterSpecs());
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// set default file type index
|
||||
hr = pfd->SetFileTypeIndex(win_params.GetDefaultFileTypeIndex());
|
||||
if (FAILED(hr)) return false;
|
||||
}
|
||||
|
||||
// show the dialog
|
||||
hr = pfd->Show(win_params.HasOwner() ? win_params.GetOwner() : nullptr);
|
||||
if (FAILED(hr)) return false;
|
||||
|
||||
// obtain result when user click "OK" button.
|
||||
switch (EDialogType) {
|
||||
case CommonFileDialogType::OpenFile:
|
||||
case CommonFileDialogType::OpenFolder:
|
||||
case CommonFileDialogType::SaveFile:
|
||||
{
|
||||
// obtain one file entry
|
||||
IShellItem* _item;
|
||||
hr = pfd->GetResult(&_item);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
|
||||
// extract display name
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name))
|
||||
return false;
|
||||
|
||||
// append result
|
||||
ret.emplace_back(std::move(result_name));
|
||||
}
|
||||
break;
|
||||
case CommonFileDialogType::OpenMultipleFiles:
|
||||
{
|
||||
// try casting file dialog to file open dialog
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface
|
||||
IFileOpenDialog* _pfod = nullptr;
|
||||
hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod));
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIFileOpenDialog pfod(_pfod);
|
||||
|
||||
// obtain multiple file entires
|
||||
IShellItemArray* _items;
|
||||
hr = pfod->GetResults(&_items);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIShellItemArray result_items(_items);
|
||||
|
||||
// analyze file entries
|
||||
// get array count first
|
||||
DWORD result_items_count = 0u;
|
||||
hr = result_items->GetCount(&result_items_count);
|
||||
if (FAILED(hr)) return false;
|
||||
// iterate array
|
||||
for (DWORD i = 0u; i < result_items_count; ++i) {
|
||||
// fetch item by index
|
||||
IShellItem* _item;;
|
||||
hr = result_items->GetItemAt(i, &_item);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
|
||||
// extract display name
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name))
|
||||
return false;
|
||||
|
||||
// append result
|
||||
ret.emplace_back(std::move(result_name));
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Wrapper Functions
|
||||
|
||||
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::OpenFile>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
|
||||
return CommonFileDialog<CommonFileDialogType::OpenMultipleFiles>(params, ret);
|
||||
}
|
||||
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::SaveFile>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
|
||||
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::OpenFolder>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,312 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "COMHelper.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The namespace providing Windows universal dialog features.
|
||||
* @details
|
||||
* This namespace only available on Windows platform.
|
||||
* See also \ref dialog_helper.
|
||||
*/
|
||||
namespace YYCC::DialogHelper {
|
||||
|
||||
/**
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileFilters {
|
||||
friend class FileFilters;
|
||||
friend class WinFileDialog;
|
||||
public:
|
||||
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(WinFileFilters);
|
||||
|
||||
/// @brief Get the count of available file filters
|
||||
UINT GetFilterCount() const {
|
||||
return static_cast<UINT>(m_WinFilters.size());
|
||||
}
|
||||
/// @brief Get pointer to Windows used file filters declarations
|
||||
const COMDLG_FILTERSPEC* GetFilterSpecs() const {
|
||||
return m_WinDataStruct.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
using WinFilterModes = std::wstring;
|
||||
using WinFilterName = std::wstring;
|
||||
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
|
||||
|
||||
std::vector<WinFilterPair> m_WinFilters;
|
||||
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
|
||||
|
||||
/// @brief Clear all current file filters
|
||||
void Clear() {
|
||||
m_WinDataStruct.reset();
|
||||
m_WinFilters.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for programmer using.
|
||||
* But you don't need create it on your own.
|
||||
* You can simply fetch it by FileDialog::ConfigreFileTypes ,
|
||||
* because this class is a part of FileDialog.
|
||||
*/
|
||||
class FileFilters {
|
||||
public:
|
||||
FileFilters() : m_Filters() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(FileFilters);
|
||||
|
||||
/**
|
||||
* @brief Add a filter pair in file types list.
|
||||
* @param[in] filter_name The friendly name of the filter.
|
||||
* @param[in] il
|
||||
* A C++ initialize list containing acceptable file filter pattern.
|
||||
* Every entries must be `const yycc_char8_t*` representing a single filter pattern.
|
||||
* The list at least should have one valid pattern.
|
||||
* This function will not validate these filter patterns, so please write them carefully.
|
||||
* @return True if added success, otherwise false.
|
||||
* @remarks
|
||||
* This function allow you register multiple filter patterns for single friendly name.
|
||||
* For example: <TT>Add(u8"Microsoft Word (*.doc; *.docx)", {u8"*.doc", u8"*.docx"})</TT>
|
||||
*/
|
||||
bool Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il);
|
||||
/**
|
||||
* @brief Get the count of added filter pairs.
|
||||
* @return The count of already added filter pairs.
|
||||
*/
|
||||
size_t Count() const { return m_Filters.size(); }
|
||||
|
||||
/// @brief Clear filter pairs for following re-use.
|
||||
void Clear() { m_Filters.clear(); }
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param[out] win_result The class receiving the generated filter data struct.
|
||||
* @return True if generation success, otherwise false.
|
||||
* @remarks
|
||||
* Programmer should not call this function,
|
||||
* this function is used as YYCC internal code.
|
||||
*/
|
||||
bool Generate(WinFileFilters& win_result) const;
|
||||
|
||||
protected:
|
||||
using FilterModes = std::vector<yycc_u8string>;
|
||||
using FilterName = yycc_u8string;
|
||||
using FilterPair = std::pair<FilterName, FilterModes>;
|
||||
|
||||
std::vector<FilterPair> m_Filters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileDialog {
|
||||
friend class FileDialog;
|
||||
public:
|
||||
WinFileDialog() :
|
||||
m_WinOwner(NULL),
|
||||
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
|
||||
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
|
||||
m_WinInitDirectory(nullptr) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(WinFileDialog);
|
||||
|
||||
/// @brief Get whether this dialog has owner.
|
||||
bool HasOwner() const { return m_WinOwner != NULL; }
|
||||
/// @brief Get the \c HWND of dialog owner.
|
||||
HWND GetOwner() const { return m_WinOwner; }
|
||||
|
||||
/// @brief Get the struct holding Windows used file filters data.
|
||||
const WinFileFilters& GetFileTypes() const { return m_WinFileTypes; }
|
||||
/// @brief Get the index of default selected file filter.
|
||||
UINT GetDefaultFileTypeIndex() const { return m_WinDefaultFileTypeIndex; }
|
||||
|
||||
/// @brief Get whether dialog has custom title.
|
||||
bool HasTitle() const { return m_HasTitle; }
|
||||
/// @brief Get custom title of dialog.
|
||||
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
|
||||
/// @brief Get whether dialog has custom initial file name.
|
||||
bool HasInitFileName() const { return m_HasInitFileName; }
|
||||
/// @brief Get custom initial file name of dialog
|
||||
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); }
|
||||
|
||||
/// @brief Get whether dialog has custom initial directory.
|
||||
bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
|
||||
/// @brief Get custom initial directory of dialog.
|
||||
IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); }
|
||||
|
||||
protected:
|
||||
HWND m_WinOwner;
|
||||
WinFileFilters m_WinFileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* This is 1-based index according to Windows specification.
|
||||
* In other words, when generating this struct from FileDialog to this struct this field should plus 1.
|
||||
* Because the same field located in FileDialog is 0-based index.
|
||||
*/
|
||||
UINT m_WinDefaultFileTypeIndex;
|
||||
bool m_HasTitle, m_HasInitFileName;
|
||||
std::wstring m_WinTitle, m_WinInitFileName;
|
||||
COMHelper::SmartIShellItem m_WinInitDirectory;
|
||||
|
||||
/// @brief Clear all data and reset them to default value.
|
||||
void Clear() {
|
||||
m_WinOwner = nullptr;
|
||||
m_WinFileTypes.Clear();
|
||||
m_WinDefaultFileTypeIndex = 0u;
|
||||
m_HasTitle = m_HasInitFileName = false;
|
||||
m_WinTitle.clear();
|
||||
m_WinInitFileName.clear();
|
||||
m_WinInitDirectory.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for programming using to describe every aspectes of the dialog.
|
||||
* For how to use this struct, see \ref dialog_helper.
|
||||
*/
|
||||
class FileDialog {
|
||||
public:
|
||||
FileDialog() :
|
||||
m_Owner(NULL),
|
||||
m_FileTypes(),
|
||||
m_DefaultFileTypeIndex(0u),
|
||||
m_Title(), m_InitFileName(), m_InitDirectory(),
|
||||
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(FileDialog);
|
||||
|
||||
/**
|
||||
* @brief Set the owner of dialog.
|
||||
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
|
||||
*/
|
||||
void SetOwner(HWND owner) { m_Owner = owner; }
|
||||
/**
|
||||
* @brief Set custom title of dialog
|
||||
* @param[in] title The string pointer to custom title, or nullptr to remove it.
|
||||
*/
|
||||
void SetTitle(const yycc_char8_t* title) {
|
||||
if (m_HasTitle = title != nullptr)
|
||||
m_Title = title;
|
||||
}
|
||||
/**
|
||||
* @brief Fetch the struct describing file filters for future configuration.
|
||||
* @return The reference to the struct describing file filters.
|
||||
*/
|
||||
FileFilters& ConfigreFileTypes() {
|
||||
return m_FileTypes;
|
||||
}
|
||||
/**
|
||||
* @brief Set the index of default selected file filter.
|
||||
* @param[in] idx
|
||||
* The index to default one.
|
||||
* This must be a valid index in file filters.
|
||||
*/
|
||||
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
|
||||
/**
|
||||
* @brief Set the initial file name of dialog
|
||||
* @details If set, the file name will always be same one when opening dialog.
|
||||
* @param[in] init_filename String pointer to initial file name, or nullptr to remove it.
|
||||
*/
|
||||
void SetInitFileName(const yycc_char8_t* init_filename) {
|
||||
if (m_HasInitFileName = init_filename != nullptr)
|
||||
m_InitFileName = init_filename;
|
||||
}
|
||||
/**
|
||||
* @brief Set the initial directory of dialog
|
||||
* @details If set, the opended directory will always be the same one when opening dialog
|
||||
* @param[in] init_dir
|
||||
* String pointer to initial directory.
|
||||
* Invalid path or nullptr will remove this feature.
|
||||
*/
|
||||
void SetInitDirectory(const yycc_char8_t* init_dir) {
|
||||
if (m_HasInitDirectory = init_dir != nullptr)
|
||||
m_InitDirectory = init_dir;
|
||||
}
|
||||
|
||||
/// @brief Clear file dialog parameters for following re-use.
|
||||
void Clear() {
|
||||
m_Owner = nullptr;
|
||||
m_HasTitle = m_HasInitFileName = m_HasInitDirectory = false;
|
||||
m_Title.clear();
|
||||
m_InitFileName.clear();
|
||||
m_InitDirectory.clear();
|
||||
m_FileTypes.Clear();
|
||||
m_DefaultFileTypeIndex = 0u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param[out] win_result The class receiving the generated filter data struct.
|
||||
* @return True if generation is success, otherwise false.
|
||||
* @remarks
|
||||
* Programmer should not call this function.
|
||||
* This function is used as YYCC internal code.
|
||||
*/
|
||||
bool Generate(WinFileDialog& win_result) const;
|
||||
|
||||
protected:
|
||||
HWND m_Owner;
|
||||
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
|
||||
yycc_u8string m_Title, m_InitFileName, m_InitDirectory;
|
||||
FileFilters m_FileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* The index Windows used is 1-based index.
|
||||
* But for universal experience, we order this is 0-based index.
|
||||
* And do convertion when generating Windows used struct.
|
||||
*/
|
||||
size_t m_DefaultFileTypeIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected file.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select multiple file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret The list of full path of user selected files.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to save.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected file.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single directory to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected directory.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,37 +0,0 @@
|
||||
#include "IOHelper.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::IOHelper {
|
||||
|
||||
std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
// convert mode and file path to wchar
|
||||
std::wstring wmode, wpath;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_mode, wmode))
|
||||
return nullptr;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filepath, wpath))
|
||||
return nullptr;
|
||||
|
||||
// call microsoft specified fopen which support wchar as argument.
|
||||
return _wfopen(wpath.c_str(), wmode.c_str());
|
||||
|
||||
#else
|
||||
return std::fopen(EncodingHelper::ToOrdinary(u8_filepath), EncodingHelper::ToOrdinary(u8_mode));
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
|
||||
/**
|
||||
* @brief Some IO related stuff
|
||||
* @details
|
||||
* See also \ref io_helper.
|
||||
*/
|
||||
namespace YYCC::IOHelper {
|
||||
|
||||
/// @brief C++ standard deleter for std::FILE*
|
||||
class StdFileDeleter {
|
||||
public:
|
||||
StdFileDeleter() {}
|
||||
void operator() (std::FILE* ptr) {
|
||||
if (ptr != nullptr) {
|
||||
std::fclose(ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
/// @brief Smart unique pointer of \c std::FILE*
|
||||
using SmartStdFile = std::unique_ptr<std::FILE, StdFileDeleter>;
|
||||
|
||||
/**
|
||||
* @brief The UTF8 version of \c std::fopen.
|
||||
* @param[in] u8_filepath The UTF8 encoded path to the file to be opened.
|
||||
* @param[in] u8_mode UTF8 encoded mode string of the file to be opened.
|
||||
* @remarks
|
||||
* This function is suit for Windows because std::fopen do not support UTF8 on Windows.
|
||||
* On other platforms, this function will delegate request directly to std::fopen.
|
||||
* @return \c FILE* of the file to be opened, or nullptr if failed.
|
||||
*/
|
||||
std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode);
|
||||
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
#include "WinFctHelper.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "COMHelper.hpp"
|
||||
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
HMODULE GetCurrentModule() {
|
||||
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
|
||||
HMODULE hModule = NULL;
|
||||
::GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter.
|
||||
(LPCWSTR)GetCurrentModule,
|
||||
&hModule);
|
||||
|
||||
return hModule;
|
||||
}
|
||||
|
||||
bool GetTempDirectory(yycc_u8string& ret) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD expected_size;
|
||||
|
||||
// fetch temp folder
|
||||
while (true) {
|
||||
if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
|
||||
// failed, set to empty
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected_size > static_cast<DWORD>(wpath.size())) {
|
||||
// buffer is too short, need enlarge and do fetching again
|
||||
wpath.resize(expected_size);
|
||||
} else {
|
||||
// ok. shrink to real length, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(expected_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath, ret);
|
||||
}
|
||||
|
||||
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD copied_size;
|
||||
|
||||
while (true) {
|
||||
if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
|
||||
// failed, return
|
||||
return false;
|
||||
}
|
||||
|
||||
// check insufficient buffer
|
||||
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
// buffer is not enough, enlarge it and try again.
|
||||
wpath.resize(wpath.size() + MAX_PATH);
|
||||
} else {
|
||||
// ok, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(copied_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath, ret);
|
||||
}
|
||||
|
||||
bool GetLocalAppData(yycc_u8string& ret) {
|
||||
// check whether com initialized
|
||||
if (!COMHelper::IsInitialized()) return false;
|
||||
|
||||
// fetch path
|
||||
LPWSTR _known_path;
|
||||
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &_known_path);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR known_path(_known_path);
|
||||
|
||||
// convert to utf8
|
||||
return YYCC::EncodingHelper::WcharToUTF8(known_path.get(), ret);
|
||||
}
|
||||
|
||||
bool IsValidCodePage(UINT code_page) {
|
||||
CPINFOEXW cpinfo;
|
||||
return ::GetCPInfoExW(code_page, 0, &cpinfo);
|
||||
}
|
||||
|
||||
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists) {
|
||||
std::wstring wExistingFileName, wNewFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
|
||||
return ::CopyFileW(wExistingFileName.c_str(), wNewFileName.c_str(), bFailIfExists);
|
||||
}
|
||||
|
||||
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName) {
|
||||
std::wstring wExistingFileName, wNewFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
|
||||
return ::MoveFileW(wExistingFileName.c_str(), wNewFileName.c_str());
|
||||
}
|
||||
|
||||
BOOL DeleteFile(const yycc_u8string_view& lpFileName) {
|
||||
std::wstring wFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpFileName, wFileName)) return FALSE;
|
||||
return ::DeleteFileW(wFileName.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,106 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The helper providing assistance of Win32 functions.
|
||||
* @details
|
||||
* This helper is Windows specific.
|
||||
* If current environment is not Windows, the whole namespace will be unavailable.
|
||||
* See also \ref win_fct_helper
|
||||
*/
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
/**
|
||||
* @brief Get Windows used HANDLE for current module.
|
||||
* @details
|
||||
* If your target is EXE, the current module simply is your program self.
|
||||
* However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL.
|
||||
*
|
||||
* This function is frequently used by DLL.
|
||||
* Because some design need the HANDLE of current module, not the host EXE loading your DLL.
|
||||
* For example, you may want to get the path of your built DLL, or fetch resources from your DLL at runtime,
|
||||
* then you should pass current module HANDLE, not NULL or the HANDLE of EXE.
|
||||
* @return A Windows HANDLE pointing to current module, NULL if failed.
|
||||
*/
|
||||
HMODULE GetCurrentModule();
|
||||
|
||||
/**
|
||||
* @brief Get path to Windows temporary folder.
|
||||
* @details Windows temporary folder usually is the target of \%TEMP\%.
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to Windows temp folder.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetTempDirectory(yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Get the file name of given module HANDLE
|
||||
* @param[in] hModule
|
||||
* The HANDLE to the module where you want to get file name.
|
||||
* It is same as the HANDLE parameter of Win32 \c GetModuleFileName.
|
||||
* @param[out] ret The variable receiving UTF8 encoded file name of given module.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Get the path to \%LOCALAPPDATA\%.
|
||||
* @details \%LOCALAPPDATA\% usually was used as putting local app data files
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to LOCALAPPDATA.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetLocalAppData(yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Check whether given code page number is a valid one.
|
||||
* @param[in] code_page The code page number.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
bool IsValidCodePage(UINT code_page);
|
||||
|
||||
/**
|
||||
* @brief Copies an existing file to a new file.
|
||||
* @param lpExistingFileName The name of an existing file.
|
||||
* @param lpNewFileName The name of the new file.
|
||||
* @param bFailIfExists
|
||||
* If this parameter is TRUE and the new file specified by \c lpNewFileName already exists, the function fails.
|
||||
* If this parameter is FALSE and the new file already exists, the function overwrites the existing file and succeeds.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c CopyFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
|
||||
*/
|
||||
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists);
|
||||
|
||||
/**
|
||||
* @brief Moves an existing file or a directory, including its children.
|
||||
* @param lpExistingFileName The current name of the file or directory on the local computer.
|
||||
* @param lpNewFileName
|
||||
* The new name for the file or directory. The new name must not already exist.
|
||||
* A new file may be on a different file system or drive. A new directory must be on the same drive.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c MoveFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefilew
|
||||
*/
|
||||
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName);
|
||||
|
||||
/**
|
||||
* @brief Deletes an existing file.
|
||||
* @param lpFileName The name of the file to be deleted.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c DeleteFile: https://learn.microsoft.com/e-us/windows/win32/api/winbase/nf-winbase-deletefile
|
||||
*/
|
||||
BOOL DeleteFile(const yycc_u8string_view& lpFileName);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,147 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#pragma region Library Version and Comparison Macros
|
||||
|
||||
#include "YYCCVersion.hpp"
|
||||
|
||||
/// @brief Return true if left version number is equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3) ((av1) == (bv1) && (av2) == (bv2) && (av3) == (bv3))
|
||||
/// @brief Return true if left version number is not equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NE(av1, av2, av3, bv1, bv2, bv3) (!YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is greater than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_G(av1, av2, av3, bv1, bv2, bv3) ( \
|
||||
((av1) > (bv1)) || \
|
||||
((av1) == (bv1) && (av2) > (bv2)) || \
|
||||
((av1) == (bv1) && (av2) == (bv2) && (av3) > (bv3)) \
|
||||
)
|
||||
/// @brief Return true if left version number is greater than or equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_GE(av1, av2, av3, bv1, bv2, bv3) (YYCC_VERCMP_G(av1, av2, av3, bv1, bv2, bv3) || YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is not lower than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NL(av1, av2, av3, bv1, bv2, bv3) YYCC_VERCMP_GE(av1, av2, av3, bv1, bv2, bv3)
|
||||
/// @brief Return true if left version number is lower than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_L(av1, av2, av3, bv1, bv2, bv3) ( \
|
||||
((av1) < (bv1)) || \
|
||||
((av1) == (bv1) && (av2) < (bv2)) || \
|
||||
((av1) == (bv1) && (av2) == (bv2) && (av3) < (bv3)) \
|
||||
)
|
||||
/// @brief Return true if left version number is lower than or equal to right version number, otherwise false.
|
||||
#define YYCC_VERCMP_LE(av1, av2, av3, bv1, bv2, bv3) (YYCC_VERCMP_L(av1, av2, av3, bv1, bv2, bv3) || YYCC_VERCMP_E(av1, av2, av3, bv1, bv2, bv3))
|
||||
/// @brief Return true if left version number is not greater than right version number, otherwise false.
|
||||
#define YYCC_VERCMP_NG(av1, av2, av3, bv1, bv2, bv3) YYCC_VERCMP_LE(av1, av2, av3, bv1, bv2, bv3)
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Operating System Identifier Macros
|
||||
|
||||
// Define operating system macros
|
||||
#define YYCC_OS_WINDOWS 2
|
||||
#define YYCC_OS_LINUX 3
|
||||
// Check current operating system.
|
||||
#if defined(_WIN32)
|
||||
#define YYCC_OS YYCC_OS_WINDOWS
|
||||
#else
|
||||
#define YYCC_OS YYCC_OS_LINUX
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Windows Shitty Behavior Disable Macros
|
||||
|
||||
// If we are in Windows,
|
||||
// we need add 2 macros to disable Windows shitty warnings and errors of
|
||||
// depracted functions and not secure functions.
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#if !defined(_CRT_NONSTDC_NO_DEPRECATE)
|
||||
#define _CRT_NONSTDC_NO_DEPRECATE
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region YYCC UTF8 Types
|
||||
|
||||
// Define the UTF8 char type we used.
|
||||
// And do a polyfill if no embedded char8_t type.
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @brief Library core namespace
|
||||
* @details Almost library functions are located in this namespace.
|
||||
*/
|
||||
namespace YYCC {
|
||||
#if defined(__cpp_char8_t)
|
||||
using yycc_char8_t = char8_t;
|
||||
using yycc_u8string = std::u8string;
|
||||
using yycc_u8string_view = std::u8string_view;
|
||||
#else
|
||||
using yycc_char8_t = unsigned char;
|
||||
using yycc_u8string = std::basic_string<yycc_char8_t>;
|
||||
using yycc_u8string_view = std::basic_string_view<yycc_char8_t>;
|
||||
#endif
|
||||
}
|
||||
/**
|
||||
\typedef YYCC::yycc_char8_t
|
||||
\brief YYCC UTF8 char type.
|
||||
\details
|
||||
This char type is an alias to \c std::char8_t if your current C++ standard support it.
|
||||
Otherwise it is defined as <TT>unsigned char</TT> as C++ 20 stdandard does.
|
||||
*/
|
||||
/**
|
||||
\typedef YYCC::yycc_u8string
|
||||
\brief YYCC UTF8 string container type.
|
||||
\details
|
||||
This type is defined as \c std::basic_string<yycc_char8_t>.
|
||||
It is equal to \c std::u8string if your current C++ standard support it.
|
||||
*/
|
||||
/**
|
||||
\typedef YYCC::yycc_u8string_view
|
||||
\brief YYCC UTF8 string view type.
|
||||
\details
|
||||
This type is defined as \c std::basic_string_view<yycc_char8_t>.
|
||||
It is equal to \c std::u8string_view if your current C++ standard support it.
|
||||
*/
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Batch Class Move / Copy Function Macros
|
||||
|
||||
/// @brief Explicitly remove copy (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEL_CLS_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&) = delete; \
|
||||
CLSNAME& operator=(const CLSNAME&) = delete;
|
||||
|
||||
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEL_CLS_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = delete; \
|
||||
CLSNAME& operator=(CLSNAME&&) = delete;
|
||||
|
||||
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEL_CLS_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DEL_CLS_COPY(CLSNAME) \
|
||||
YYCC_DEL_CLS_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Explicitly set default copy (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEF_CLS_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&) = default; \
|
||||
CLSNAME& operator=(const CLSNAME&) = default;
|
||||
|
||||
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEF_CLS_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = default; \
|
||||
CLSNAME& operator=(CLSNAME&&) = default;
|
||||
|
||||
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEF_CLS_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DEF_CLS_COPY(CLSNAME) \
|
||||
YYCC_DEF_CLS_MOVE(CLSNAME)
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "YYCC/YYCCInternal.hpp"
|
||||
|
||||
#include "YYCC/EncodingHelper.hpp"
|
||||
#include "YYCC/StringHelper.hpp"
|
||||
#include "YYCC/ConsoleHelper.hpp"
|
||||
#include "YYCC/COMHelper.hpp"
|
||||
#include "YYCC/DialogHelper.hpp"
|
||||
#include "YYCC/ParserHelper.hpp"
|
||||
#include "YYCC/IOHelper.hpp"
|
||||
#include "YYCC/WinFctHelper.hpp"
|
||||
#include "YYCC/StdPatch.hpp"
|
||||
#include "YYCC/EnumHelper.hpp"
|
||||
#include "YYCC/ExceptionHelper.hpp"
|
||||
|
||||
#include "YYCC/ConfigManager.hpp"
|
||||
#include "YYCC/ArgParser.hpp"
|
247
src/yycc/carton/csconsole.cpp
Normal file
247
src/yycc/carton/csconsole.cpp
Normal file
@ -0,0 +1,247 @@
|
||||
#include "csconsole.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
|
||||
#include "../string/op.hpp"
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include "../encoding/windows.hpp"
|
||||
#include <iostream>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "../windows/import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include "../windows/import_guard_tail.hpp"
|
||||
#endif
|
||||
|
||||
#define OP ::yycc::string::op
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
#define ENC ::yycc::encoding::windows
|
||||
|
||||
namespace yycc::carton::csconsole {
|
||||
|
||||
#pragma region Windows Console Specific Functions
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
template<bool BIsConsole>
|
||||
static std::u8string win_console_read(HANDLE hStdIn) {
|
||||
using TChar = std::conditional_t<BIsConsole, wchar_t, char>;
|
||||
|
||||
// Prepare an internal buffer because the read data may not be fully used.
|
||||
// For example, we may read x\ny in a single calling but after processing \n, this function will return
|
||||
// so y will temporarily stored in this internal buffer for next using.
|
||||
// Thus this function is not thread safe.
|
||||
static std::basic_string<TChar> internal_buffer;
|
||||
// create return value buffer
|
||||
std::basic_string<TChar> return_buffer;
|
||||
|
||||
// Prepare some variables
|
||||
DWORD dwReadNumberOfChars;
|
||||
TChar szReadChars[64];
|
||||
size_t eol_pos;
|
||||
|
||||
// try fetching EOL
|
||||
while (true) {
|
||||
// if internal buffer is empty,
|
||||
// try fetching it.
|
||||
if (internal_buffer.empty()) {
|
||||
// console and non-console use different method to read.
|
||||
if constexpr (BIsConsole) {
|
||||
// console handle, use ReadConsoleW.
|
||||
// read from console, the read data is wchar based
|
||||
if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(TChar), &dwReadNumberOfChars, NULL)) break;
|
||||
} else {
|
||||
// anything else, use ReadFile instead.
|
||||
// the read data is utf8 based
|
||||
if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL)) break;
|
||||
}
|
||||
|
||||
// send to internal buffer
|
||||
if (dwReadNumberOfChars == 0) break;
|
||||
internal_buffer.append(szReadChars, dwReadNumberOfChars);
|
||||
}
|
||||
|
||||
// try finding EOL in internal buffer
|
||||
if constexpr (std::is_same_v<TChar, char>) eol_pos = internal_buffer.find_first_of('\n');
|
||||
else eol_pos = internal_buffer.find_first_of(L'\n');
|
||||
// check finding result
|
||||
if (eol_pos == std::wstring::npos) {
|
||||
// the whole string do not include EOL, fully appended to return value
|
||||
return_buffer += internal_buffer;
|
||||
internal_buffer.clear();
|
||||
// need more data, continue while
|
||||
} else {
|
||||
// split result
|
||||
// push into result and remain some in internal buffer.
|
||||
return_buffer.append(internal_buffer, 0u, eol_pos);
|
||||
internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place.
|
||||
// break while mean success finding
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// post-process for return value
|
||||
std::u8string real_return_buffer;
|
||||
if constexpr (BIsConsole) {
|
||||
// console mode need convert wchar to utf8
|
||||
auto rv = ENC::to_utf8(return_buffer);
|
||||
if (rv.has_value()) real_return_buffer = std::move(rv.value());
|
||||
} else {
|
||||
// non-console just copt the result
|
||||
real_return_buffer = REINTERPRET::as_utf8(return_buffer);
|
||||
}
|
||||
// every mode need delete \r words
|
||||
OP::replace(real_return_buffer, u8"\r", u8"");
|
||||
// return value
|
||||
return real_return_buffer;
|
||||
}
|
||||
|
||||
static void win_console_write(const std::u8string_view& strl, bool to_stderr) {
|
||||
// Prepare some Win32 variables
|
||||
// fetch stdout handle first
|
||||
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
|
||||
DWORD dwConsoleMode;
|
||||
DWORD dwWrittenNumberOfChars;
|
||||
|
||||
// if stdout was redirected, this handle may point to a file handle or anything else,
|
||||
// WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle
|
||||
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
|
||||
// console handle, use WriteConsoleW.
|
||||
// convert utf8 string to wide char first
|
||||
auto rv = ENC::to_wchar(strl);
|
||||
if (!rv.has_value()) return;
|
||||
std::wstring wstrl(std::move(rv.value()));
|
||||
size_t wstrl_size = wstrl.size();
|
||||
// write string with size check
|
||||
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(wstrl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
} else {
|
||||
// anything else, use WriteFile instead.
|
||||
// WriteFile do not need extra convertion, because it is direct writing.
|
||||
// check whether string length is overflow
|
||||
size_t strl_size = strl.size() * sizeof(std::u8string_view::value_type);
|
||||
// write string with size check
|
||||
if (strl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteFile(hStdOut, strl.data(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Read Functions
|
||||
|
||||
std::u8string read_line() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
// get stdin mode
|
||||
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
// use different method to get according to whether stdin is redirected
|
||||
DWORD dwConsoleMode;
|
||||
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
|
||||
return win_console_read<true>(hStdIn);
|
||||
} else {
|
||||
return win_console_read<false>(hStdIn);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// in linux, directly use C++ function to fetch.
|
||||
std::string cmd;
|
||||
if (std::getline(std::cin, cmd).fail()) cmd.clear();
|
||||
return REINTERPRET::as_utf8(cmd);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Write Functions
|
||||
|
||||
template<bool BNeedFmt, bool BIsErr, bool BHasEOL>
|
||||
static void raw_write(const char8_t* u8_fmt, va_list argptr) {
|
||||
// Buiild string need to be written first
|
||||
// If no format string or plain string for writing, return.
|
||||
if (u8_fmt == nullptr) return;
|
||||
// Build or simply copy string
|
||||
std::u8string strl;
|
||||
if constexpr (BNeedFmt) {
|
||||
// treat as format string
|
||||
va_list argcpy;
|
||||
va_copy(argcpy, argptr);
|
||||
auto rv = OP::vprintf(u8_fmt, argcpy);
|
||||
va_end(argcpy);
|
||||
// check format result
|
||||
if (!rv.has_value()) return;
|
||||
else strl = std::move(rv.value());
|
||||
} else {
|
||||
// treat as plain string
|
||||
strl = u8_fmt;
|
||||
}
|
||||
// Checkout whether add EOL
|
||||
if constexpr (BHasEOL) {
|
||||
strl += u8'\n';
|
||||
}
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// call Windows specific writer
|
||||
win_console_write(strl, BIsErr);
|
||||
#else
|
||||
// in linux, directly use C function to write.
|
||||
std::fputs(REINTERPRET::as_ordinary(strl.c_str()), BIsErr ? stderr : stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
void format(const char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
raw_write<true, false, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void format_line(const char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
raw_write<true, false, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void write(const char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
raw_write<false, false, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void write_line(const char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
raw_write<false, false, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void eformat(const char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
raw_write<true, true, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void eformat_line(const char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
raw_write<true, true, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ewrite(const char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
raw_write<false, true, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ewrite_line(const char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
raw_write<false, true, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
111
src/yycc/carton/csconsole.hpp
Normal file
111
src/yycc/carton/csconsole.hpp
Normal file
@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief The helper providing universal C\# style console function and other console related stuff
|
||||
* @details
|
||||
* The origin of this namespace is coming from the requirement of UTF8 console in Windows.
|
||||
* There are 3 ways to make Windows console enable UTF8 mode.
|
||||
*
|
||||
* First one is calling \c SetConsoleCP and \c SetConsoleOutputCP.
|
||||
* The side effect of this is \c std::cin and \c std::cout is broken,
|
||||
* however there is a patch for this issue.
|
||||
*
|
||||
* Second one is calling \c _set_mode with \c _O_U8TEXT or \c _O_U16TEXT to enable Unicode mode for Windows console.
|
||||
* This also have side effect which is stronger than first one.
|
||||
* All "puts" family functions (ASCII-based output functions) will throw assertion exception.
|
||||
* You only can use "putws" family functions (wide-char-based output functions).
|
||||
* However these functions can not be used without calling \c _set_mode in Windows design.
|
||||
*
|
||||
* There still is another method, using \c WriteConsoleW directly visiting console.
|
||||
* This function family can output correct string without calling any extra functions!
|
||||
* This method is what we adopted and finally become this namespace.
|
||||
*
|
||||
* Reference:
|
||||
* \li https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
|
||||
* \li https://stackoverflow.com/questions/69830460/reading-utf-8-input
|
||||
*
|
||||
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
|
||||
*
|
||||
* @warning
|
||||
* All functions provided by this namespace are too aggressive.
|
||||
* Once you use it, you should not use any other input output functions.
|
||||
*
|
||||
* @deprecated
|
||||
* This namespace provided functions are too aggressive and can not cover all use scenario.
|
||||
* So I start to give this up when migrating this namespace during developing YYCC 2.x version.
|
||||
* I just do a simple type fix and rename when migrating this namespace to make it "just works".
|
||||
* There is no suggestion for using this namespace. And there is no update for this namespace.
|
||||
* Programmer should treat Windows UTF8 issue on their own.
|
||||
*/
|
||||
namespace yycc::carton::csconsole {
|
||||
|
||||
/**
|
||||
* @brief Reads the next line of UTF8 characters from the standard input stream.
|
||||
* @return
|
||||
* The next line of UTF8 characters from the input stream.
|
||||
* Empty string if user just press Enter key or function failed.
|
||||
*/
|
||||
std::u8string read_line();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void format(const char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void format_line(const char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void write(const char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void write_line(const char8_t* u8_strl);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void eformat(const char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void eformat_line(const char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ewrite(const char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ewrite_line(const char8_t* u8_strl);
|
||||
|
||||
}
|
@ -6,10 +6,20 @@
|
||||
using namespace std::literals::string_view_literals;
|
||||
namespace op = ::yycc::string::op;
|
||||
|
||||
namespace yycc::encoding::pycodec {
|
||||
namespace yycc::carton::pycodec {
|
||||
|
||||
/// @brief Error occurs when fetching token from encoding name.
|
||||
enum class FetchError {
|
||||
NoSuchName, ///< Given name can not be resolved.
|
||||
};
|
||||
|
||||
/// @brief The Result type used when fetching token from encoding name.
|
||||
template<typename T>
|
||||
using FetchResult = std::expected<T, FetchError>;
|
||||
|
||||
#pragma region Encoding Name
|
||||
|
||||
// clang-format off
|
||||
static const std::map<std::u8string_view, std::u8string_view> ALIAS_MAP{
|
||||
{u8"646"sv, u8"ascii"sv},
|
||||
{u8"us-ascii"sv, u8"ascii"sv},
|
||||
@ -215,6 +225,7 @@ namespace yycc::encoding::pycodec {
|
||||
{u8"utf-8"sv, u8"utf_8"sv},
|
||||
{u8"cp65001"sv, u8"utf_8"sv},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @brief Resolve encoding name alias and fetch real encoding name.
|
||||
@ -239,55 +250,89 @@ namespace yycc::encoding::pycodec {
|
||||
|
||||
using CodePage = YYCC_PYCODEC_BACKEND_NS::CodePage;
|
||||
|
||||
static const std::map<std::u8string_view, CodePage> WINCP_MAP{
|
||||
{u8"ascii"sv, static_cast<CodePage>(437u)}, {u8"big5"sv, static_cast<CodePage>(950u)},
|
||||
{u8"cp037"sv, static_cast<CodePage>(037u)}, {u8"cp437"sv, static_cast<CodePage>(437u)},
|
||||
{u8"cp500"sv, static_cast<CodePage>(500u)}, {u8"cp720"sv, static_cast<CodePage>(720u)},
|
||||
{u8"cp737"sv, static_cast<CodePage>(737u)}, {u8"cp775"sv, static_cast<CodePage>(775u)},
|
||||
{u8"cp850"sv, static_cast<CodePage>(850u)}, {u8"cp852"sv, static_cast<CodePage>(852u)},
|
||||
{u8"cp855"sv, static_cast<CodePage>(855u)}, {u8"cp857"sv, static_cast<CodePage>(857u)},
|
||||
{u8"cp858"sv, static_cast<CodePage>(858u)}, {u8"cp860"sv, static_cast<CodePage>(860u)},
|
||||
{u8"cp861"sv, static_cast<CodePage>(861u)}, {u8"cp862"sv, static_cast<CodePage>(862u)},
|
||||
{u8"cp863"sv, static_cast<CodePage>(863u)}, {u8"cp864"sv, static_cast<CodePage>(864u)},
|
||||
{u8"cp865"sv, static_cast<CodePage>(865u)}, {u8"cp866"sv, static_cast<CodePage>(866u)},
|
||||
{u8"cp869"sv, static_cast<CodePage>(869u)}, {u8"cp874"sv, static_cast<CodePage>(874u)},
|
||||
{u8"cp875"sv, static_cast<CodePage>(875u)}, {u8"cp932"sv, static_cast<CodePage>(932u)},
|
||||
{u8"cp949"sv, static_cast<CodePage>(949u)}, {u8"cp950"sv, static_cast<CodePage>(950u)},
|
||||
{u8"cp1026"sv, static_cast<CodePage>(1026u)}, {u8"cp1140"sv, static_cast<CodePage>(1140u)},
|
||||
{u8"cp1250"sv, static_cast<CodePage>(1250u)}, {u8"cp1251"sv, static_cast<CodePage>(1251u)},
|
||||
{u8"cp1252"sv, static_cast<CodePage>(1252u)}, {u8"cp1253"sv, static_cast<CodePage>(1253u)},
|
||||
{u8"cp1254"sv, static_cast<CodePage>(1254u)}, {u8"cp1255"sv, static_cast<CodePage>(1255u)},
|
||||
{u8"cp1256"sv, static_cast<CodePage>(1256u)}, {u8"cp1257"sv, static_cast<CodePage>(1257u)},
|
||||
{u8"cp1258"sv, static_cast<CodePage>(1258u)}, {u8"euc_jp"sv, static_cast<CodePage>(20932u)},
|
||||
{u8"euc_kr"sv, static_cast<CodePage>(51949u)}, {u8"gb2312"sv, static_cast<CodePage>(936u)},
|
||||
{u8"gbk"sv, static_cast<CodePage>(936u)}, {u8"gb18030"sv, static_cast<CodePage>(54936u)},
|
||||
{u8"hz"sv, static_cast<CodePage>(52936u)}, {u8"iso2022_jp"sv, static_cast<CodePage>(50220u)},
|
||||
{u8"iso2022_kr"sv, static_cast<CodePage>(50225u)}, {u8"latin_1"sv, static_cast<CodePage>(28591u)},
|
||||
{u8"iso8859_2"sv, static_cast<CodePage>(28592u)}, {u8"iso8859_3"sv, static_cast<CodePage>(28593u)},
|
||||
{u8"iso8859_4"sv, static_cast<CodePage>(28594u)}, {u8"iso8859_5"sv, static_cast<CodePage>(28595u)},
|
||||
{u8"iso8859_6"sv, static_cast<CodePage>(28596u)}, {u8"iso8859_7"sv, static_cast<CodePage>(28597u)},
|
||||
{u8"iso8859_8"sv, static_cast<CodePage>(28598u)}, {u8"iso8859_9"sv, static_cast<CodePage>(28599u)},
|
||||
{u8"iso8859_13"sv, static_cast<CodePage>(28603u)}, {u8"iso8859_15"sv, static_cast<CodePage>(28605u)},
|
||||
{u8"johab"sv, static_cast<CodePage>(1361u)}, {u8"mac_cyrillic"sv, static_cast<CodePage>(10007u)},
|
||||
{u8"mac_greek"sv, static_cast<CodePage>(10006u)}, {u8"mac_iceland"sv, static_cast<CodePage>(10079u)},
|
||||
{u8"mac_turkish"sv, static_cast<CodePage>(10081u)}, {u8"shift_jis"sv, static_cast<CodePage>(932u)},
|
||||
{u8"utf_7"sv, static_cast<CodePage>(65000u)}, {u8"utf_8"sv, static_cast<CodePage>(65001u)},
|
||||
// clang-format off
|
||||
static const std::map<std::u8string_view, CodePage> WINCP_MAP {
|
||||
{ u8"ascii"sv, static_cast<CodePage>(437u) },
|
||||
{ u8"big5"sv, static_cast<CodePage>(950u) },
|
||||
{ u8"cp037"sv, static_cast<CodePage>(037u) },
|
||||
{ u8"cp437"sv, static_cast<CodePage>(437u) },
|
||||
{ u8"cp500"sv, static_cast<CodePage>(500u) },
|
||||
{ u8"cp720"sv, static_cast<CodePage>(720u) },
|
||||
{ u8"cp737"sv, static_cast<CodePage>(737u) },
|
||||
{ u8"cp775"sv, static_cast<CodePage>(775u) },
|
||||
{ u8"cp850"sv, static_cast<CodePage>(850u) },
|
||||
{ u8"cp852"sv, static_cast<CodePage>(852u) },
|
||||
{ u8"cp855"sv, static_cast<CodePage>(855u) },
|
||||
{ u8"cp857"sv, static_cast<CodePage>(857u) },
|
||||
{ u8"cp858"sv, static_cast<CodePage>(858u) },
|
||||
{ u8"cp860"sv, static_cast<CodePage>(860u) },
|
||||
{ u8"cp861"sv, static_cast<CodePage>(861u) },
|
||||
{ u8"cp862"sv, static_cast<CodePage>(862u) },
|
||||
{ u8"cp863"sv, static_cast<CodePage>(863u) },
|
||||
{ u8"cp864"sv, static_cast<CodePage>(864u) },
|
||||
{ u8"cp865"sv, static_cast<CodePage>(865u) },
|
||||
{ u8"cp866"sv, static_cast<CodePage>(866u) },
|
||||
{ u8"cp869"sv, static_cast<CodePage>(869u) },
|
||||
{ u8"cp874"sv, static_cast<CodePage>(874u) },
|
||||
{ u8"cp875"sv, static_cast<CodePage>(875u) },
|
||||
{ u8"cp932"sv, static_cast<CodePage>(932u) },
|
||||
{ u8"cp949"sv, static_cast<CodePage>(949u) },
|
||||
{ u8"cp950"sv, static_cast<CodePage>(950u) },
|
||||
{ u8"cp1026"sv, static_cast<CodePage>(1026u) },
|
||||
{ u8"cp1140"sv, static_cast<CodePage>(1140u) },
|
||||
{ u8"cp1250"sv, static_cast<CodePage>(1250u) },
|
||||
{ u8"cp1251"sv, static_cast<CodePage>(1251u) },
|
||||
{ u8"cp1252"sv, static_cast<CodePage>(1252u) },
|
||||
{ u8"cp1253"sv, static_cast<CodePage>(1253u) },
|
||||
{ u8"cp1254"sv, static_cast<CodePage>(1254u) },
|
||||
{ u8"cp1255"sv, static_cast<CodePage>(1255u) },
|
||||
{ u8"cp1256"sv, static_cast<CodePage>(1256u) },
|
||||
{ u8"cp1257"sv, static_cast<CodePage>(1257u) },
|
||||
{ u8"cp1258"sv, static_cast<CodePage>(1258u) },
|
||||
{ u8"euc_jp"sv, static_cast<CodePage>(20932u) },
|
||||
{ u8"euc_kr"sv, static_cast<CodePage>(51949u) },
|
||||
{ u8"gb2312"sv, static_cast<CodePage>(936u) },
|
||||
{ u8"gbk"sv, static_cast<CodePage>(936u) },
|
||||
{ u8"gb18030"sv, static_cast<CodePage>(54936u) },
|
||||
{ u8"hz"sv, static_cast<CodePage>(52936u) },
|
||||
{ u8"iso2022_jp"sv, static_cast<CodePage>(50220u) },
|
||||
{ u8"iso2022_kr"sv, static_cast<CodePage>(50225u) },
|
||||
{ u8"latin_1"sv, static_cast<CodePage>(28591u) },
|
||||
{ u8"iso8859_2"sv, static_cast<CodePage>(28592u) },
|
||||
{ u8"iso8859_3"sv, static_cast<CodePage>(28593u) },
|
||||
{ u8"iso8859_4"sv, static_cast<CodePage>(28594u) },
|
||||
{ u8"iso8859_5"sv, static_cast<CodePage>(28595u) },
|
||||
{ u8"iso8859_6"sv, static_cast<CodePage>(28596u) },
|
||||
{ u8"iso8859_7"sv, static_cast<CodePage>(28597u) },
|
||||
{ u8"iso8859_8"sv, static_cast<CodePage>(28598u) },
|
||||
{ u8"iso8859_9"sv, static_cast<CodePage>(28599u) },
|
||||
{ u8"iso8859_13"sv, static_cast<CodePage>(28603u) },
|
||||
{ u8"iso8859_15"sv, static_cast<CodePage>(28605u) },
|
||||
{ u8"johab"sv, static_cast<CodePage>(1361u) },
|
||||
{ u8"mac_cyrillic"sv, static_cast<CodePage>(10007u) },
|
||||
{ u8"mac_greek"sv, static_cast<CodePage>(10006u) },
|
||||
{ u8"mac_iceland"sv, static_cast<CodePage>(10079u) },
|
||||
{ u8"mac_turkish"sv, static_cast<CodePage>(10081u) },
|
||||
{ u8"shift_jis"sv, static_cast<CodePage>(932u) },
|
||||
{ u8"utf_7"sv, static_cast<CodePage>(65000u) },
|
||||
{ u8"utf_8"sv, static_cast<CodePage>(65001u) },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static bool fetch_code_page(const std::u8string_view& enc_name, CodePage& out_cp) {
|
||||
static FetchResult<CodePage> fetch_code_page(const std::u8string_view& enc_name) {
|
||||
// resolve alias
|
||||
std::u8string resolved_name = resolve_encoding_alias(enc_name);
|
||||
// find code page
|
||||
op::lower(resolved_name);
|
||||
auto finder = WINCP_MAP.find(resolved_name);
|
||||
if (finder == WINCP_MAP.end()) return false;
|
||||
if (finder == WINCP_MAP.end()) return std::unexpected(FetchError::NoSuchName);
|
||||
// okey, we found it.
|
||||
out_cp = finder->second;
|
||||
return true;
|
||||
return finder->second;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// clang-format off
|
||||
static const std::map<std::u8string_view, std::string_view> ICONV_MAP{
|
||||
{u8"ascii"sv, "ASCII"sv},
|
||||
{u8"big5"sv, "BIG5"sv},
|
||||
@ -351,17 +396,17 @@ namespace yycc::encoding::pycodec {
|
||||
{u8"utf_7"sv, "UTF-7"sv},
|
||||
{u8"utf_8"sv, "UTF-8"sv},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static bool fetch_iconv_name(const std::u8string_view& enc_name, std::string& out_code) {
|
||||
static FetchResult<std::string_view> fetch_iconv_name(const std::u8string_view& enc_name) {
|
||||
// resolve alias
|
||||
std::u8string resolved_name = resolve_encoding_alias(enc_name);
|
||||
// find code page
|
||||
op::lower(resolved_name);
|
||||
auto finder = ICONV_MAP.find(resolved_name);
|
||||
if (finder == ICONV_MAP.end()) return false;
|
||||
if (finder == ICONV_MAP.end()) return std::unexpected(FetchError::NoSuchName);
|
||||
// okey, we found it.
|
||||
out_code = finder->second;
|
||||
return true;
|
||||
return finder->second;
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -370,103 +415,177 @@ namespace yycc::encoding::pycodec {
|
||||
|
||||
#pragma region Misc
|
||||
|
||||
ConvError::ConvError(const ConvError::Error& err) : inner(err) {}
|
||||
ConvError::ConvError(const ConvBackendError& err) : inner(err) {}
|
||||
|
||||
ConvError::ConvError(const ConvFrontendError& err) : inner(err) {}
|
||||
|
||||
ConvError::ConvError(ConvBackendError&& err) noexcept : inner(std::move(err)) {}
|
||||
|
||||
ConvError::ConvError(ConvFrontendError&& err) noexcept : inner(std::move(err)) {}
|
||||
|
||||
bool is_valid_encoding_name(const EncodingName& name) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
CodePage unused;
|
||||
return fetch_code_page(name, unused);
|
||||
return fetch_code_page(name).has_value();
|
||||
#else
|
||||
std::string unused;
|
||||
return fetch_iconv_name(name, unused);
|
||||
return fetch_iconv_name(name).has_value();
|
||||
#endif
|
||||
}
|
||||
|
||||
// YYC MARK:
|
||||
// Define a macro for following class ctor
|
||||
// We only need initialize member if we are in Iconv environment.
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
#define CTOR_INITLIST_TYPE1
|
||||
#else
|
||||
#define CTOR_INITLIST_TYPE1 : inner()
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Char -> UTF8
|
||||
|
||||
CharToUtf8::CharToUtf8(const EncodingName& name) :
|
||||
CharToUtf8::CharToUtf8(const EncodingName& name) : inner(std::nullopt) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
code_page(fetch)
|
||||
auto rv = fetch_code_page(name);
|
||||
if (rv.has_value()) inner = rv.value();
|
||||
#else
|
||||
inner(fetch_iconv_name())
|
||||
auto rv = fetch_iconv_name(name);
|
||||
if (rv.has_value()) inner = YYCC_PYCODEC_BACKEND_NS::CharToUtf8(rv.value());
|
||||
#endif
|
||||
{}
|
||||
}
|
||||
|
||||
CharToUtf8::~CharToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> CharToUtf8::to_utf8(const std::string_view& src) {
|
||||
if (!inner.has_value()) return std::unexpected(ConvFrontendError::NoSuchName);
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src, inner.value());
|
||||
#else
|
||||
return inner.value().to_utf8(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
#pragma region UTF8 -> Char
|
||||
|
||||
Utf8ToChar::Utf8ToChar(const EncodingName& name) : inner(std::nullopt) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
auto rv = fetch_code_page(name);
|
||||
if (rv.has_value()) inner = rv.value();
|
||||
#else
|
||||
auto rv = fetch_iconv_name(name);
|
||||
if (rv.has_value()) inner = YYCC_PYCODEC_BACKEND_NS::Utf8ToChar(rv.value());
|
||||
#endif
|
||||
}
|
||||
|
||||
Utf8ToChar::~Utf8ToChar() {}
|
||||
|
||||
ConvResult<std::string> Utf8ToChar::to_char(const std::u8string_view& src) {
|
||||
if (!inner.has_value()) return std::unexpected(ConvFrontendError::NoSuchName);
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_char(src, inner.value());
|
||||
#else
|
||||
return inner.value().to_char(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
#pragma region WChar -> UTF8
|
||||
|
||||
WcharToUtf8::WcharToUtf8() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
WcharToUtf8::~WcharToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> WcharToUtf8::to_utf8(const std::wstring_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
|
||||
#else
|
||||
return inner.to_utf8(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
#pragma region UTF8 -> WChar
|
||||
|
||||
Utf8ToWchar::Utf8ToWchar() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf8ToWchar::~Utf8ToWchar() {}
|
||||
|
||||
ConvResult<std::wstring> Utf8ToWchar::to_wchar(const std::u8string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_wchar(src);
|
||||
#else
|
||||
return inner.to_wchar(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
#pragma region UTF8 -> UTF16
|
||||
|
||||
Utf8ToUtf16::Utf8ToUtf16() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf8ToUtf16::~Utf8ToUtf16() {}
|
||||
|
||||
ConvResult<std::u16string> Utf8ToUtf16::to_utf16(const std::u8string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf16(src);
|
||||
#else
|
||||
return inner.to_utf16(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
#pragma region UTF16 -> UTF8
|
||||
|
||||
Utf16ToUtf8::Utf16ToUtf8() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf16ToUtf8::~Utf16ToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> Utf16ToUtf8::to_utf8(const std::u16string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
|
||||
#else
|
||||
return inner.to_utf8(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
#pragma region UTF8 -> UTF32
|
||||
|
||||
Utf8ToUtf32::Utf8ToUtf32() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf8ToUtf32::~Utf8ToUtf32() {}
|
||||
|
||||
ConvResult<std::u32string> Utf8ToUtf32::to_utf32(const std::u8string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf32(src);
|
||||
#else
|
||||
return inner.to_utf32(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
#pragma region UTF32 -> UTF8
|
||||
|
||||
Utf32ToUtf8::Utf32ToUtf8() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf32ToUtf8::~Utf32ToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> Utf32ToUtf8::to_utf8(const std::u32string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
|
||||
#else
|
||||
return inner.to_utf8(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::encoding::pycodec
|
||||
} // namespace yycc::carton::pycodec
|
@ -4,17 +4,19 @@
|
||||
#include "../macro/class_copy_move.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <expected>
|
||||
|
||||
// Choose the backend of PyCodec module
|
||||
#if defined(YYCC_FEAT_ICONV)
|
||||
// We try Iconv first in any cases.
|
||||
#include "iconv.hpp"
|
||||
#include "../encoding/iconv.hpp"
|
||||
#define YYCC_PYCODEC_ICONV_BACKEND
|
||||
#define YYCC_PYCODEC_BACKEND_NS ::yycc::encoding::iconv
|
||||
#elif defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
// If we can not use Iconv, we try to fallback to Windows implementation.
|
||||
#include "windows.hpp"
|
||||
#include "../encoding/windows.hpp"
|
||||
#define YYCC_PYCODEC_WIN32_BACKEND
|
||||
#define YYCC_PYCODEC_BACKEND_NS ::yycc::encoding::windows
|
||||
#else
|
||||
@ -22,20 +24,30 @@
|
||||
#error "Can not find viable encoding convertion solution in current environment for PyCodec module."
|
||||
#endif
|
||||
|
||||
namespace yycc::encoding::pycodec {
|
||||
namespace yycc::carton::pycodec {
|
||||
|
||||
/// @brief The universal name of encoding.
|
||||
using EncodingName = std::u8string_view;
|
||||
|
||||
/// @brief The alias to error type of backend.
|
||||
using ConvBackendError = YYCC_PYCODEC_BACKEND_NS::ConvError;
|
||||
|
||||
/// @brief The error occurs in this module self.
|
||||
enum class ConvFrontendError {
|
||||
NoSuchName, ///< Can not find suitable backend token for given encoding name.
|
||||
};
|
||||
|
||||
/// @brief The possible error occurs in this module.
|
||||
class ConvError {
|
||||
public:
|
||||
using Error = YYCC_PYCODEC_BACKEND_NS::ConvError;
|
||||
ConvError(const Error& err);
|
||||
ConvError(const ConvBackendError& err);
|
||||
ConvError(const ConvFrontendError& err);
|
||||
ConvError(ConvBackendError&& err) noexcept;
|
||||
ConvError(ConvFrontendError&& err) noexcept;
|
||||
YYCC_DEFAULT_COPY_MOVE(ConvError)
|
||||
|
||||
private:
|
||||
Error inner;
|
||||
std::variant<ConvBackendError, ConvFrontendError> inner;
|
||||
};
|
||||
|
||||
/// @brief The result type of this module.
|
||||
@ -62,9 +74,9 @@ namespace yycc::encoding::pycodec {
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
YYCC_PYCODEC_BACKEND_NS::CodePage code_page;
|
||||
std::optional<YYCC_PYCODEC_BACKEND_NS::CodePage> inner;
|
||||
#else
|
||||
YYCC_PYCODEC_BACKEND_NS::CharToUtf8 inner;
|
||||
std::optional<YYCC_PYCODEC_BACKEND_NS::CharToUtf8> inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -81,9 +93,9 @@ namespace yycc::encoding::pycodec {
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
YYCC_PYCODEC_BACKEND_NS::CodePage code_page;
|
||||
std::optional<YYCC_PYCODEC_BACKEND_NS::CodePage> inner;
|
||||
#else
|
||||
YYCC_PYCODEC_BACKEND_NS::Utf8ToChar inner;
|
||||
std::optional<YYCC_PYCODEC_BACKEND_NS::Utf8ToChar> inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
@ -189,4 +201,4 @@ namespace yycc::encoding::pycodec {
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace yycc::encoding::pycodec
|
||||
} // namespace yycc::carton::pycodec
|
192
src/yycc/carton/tabulate.cpp
Normal file
192
src/yycc/carton/tabulate.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include "tabulate.hpp"
|
||||
#include "wcwidth.hpp"
|
||||
#include "../num/safe_op.hpp"
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <stdexcept>
|
||||
#include <ranges>
|
||||
|
||||
#define WCWIDTH ::yycc::carton::wcwidth
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
#define SAFEOP ::yycc::num::safe_op
|
||||
|
||||
namespace yycc::carton::tabulate {
|
||||
|
||||
#pragma region Tabulate Width
|
||||
|
||||
TabulateWidth::TabulateWidth(size_t n) : widths(n, 0u) {}
|
||||
|
||||
TabulateWidth::~TabulateWidth() {}
|
||||
|
||||
size_t TabulateWidth::get_column_count() const {
|
||||
return widths.size();
|
||||
}
|
||||
|
||||
size_t TabulateWidth::get_column_width(size_t column_index) const {
|
||||
return widths.at(column_index);
|
||||
}
|
||||
|
||||
void TabulateWidth::update_column_width(size_t column_index, size_t new_size) {
|
||||
auto& width = widths.at(column_index);
|
||||
width = std::max(width, new_size);
|
||||
}
|
||||
|
||||
void TabulateWidth::clear() {
|
||||
std::fill(widths.begin(), widths.end(), 0u);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Tabulate Cell
|
||||
|
||||
TabulateCell::TabulateCell(const std::u8string_view& text) : text(text), text_width(WCWIDTH::wcswidth(text).value_or(0u)) {}
|
||||
|
||||
TabulateCell::~TabulateCell() {}
|
||||
|
||||
const std::u8string& TabulateCell::get_text() const {
|
||||
return text;
|
||||
}
|
||||
|
||||
size_t TabulateCell::get_text_width() const {
|
||||
return text_width;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Tabulate
|
||||
|
||||
/// @brief Default separator literal for Tabulate.
|
||||
static constexpr char8_t SEPARATOR_BAR[] = u8"---";
|
||||
/// @brief A stupid size_t ZERO literal to trigger template type deduce for std::views::iota.
|
||||
static constexpr size_t ZERO = 0;
|
||||
|
||||
Tabulate::Tabulate(size_t n) :
|
||||
n(n), header_display(true), bar_display(true), prefix_string(), rows_widths(n), header_widths(n), header(n, TabulateCell(u8"")),
|
||||
bar(SEPARATOR_BAR), rows() {}
|
||||
|
||||
Tabulate::~Tabulate() {}
|
||||
|
||||
void Tabulate::print(std::ostream& dst) const {
|
||||
// Get column count
|
||||
auto n = this->get_column_count();
|
||||
|
||||
// Create width recorder for final printing
|
||||
// according to whether we show table header and separator bar.
|
||||
auto widths = this->rows_widths;
|
||||
if (this->header_display) {
|
||||
for (auto index : std::views::iota(ZERO, n)) {
|
||||
widths.update_column_width(index, this->header_widths.get_column_width(index));
|
||||
}
|
||||
}
|
||||
if (this->bar_display) {
|
||||
auto bar_width = this->bar.get_text_width();
|
||||
for (auto index : std::views::iota(ZERO, n)) {
|
||||
widths.update_column_width(index, bar_width);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the maximum space char count to build a string filled with spaces,
|
||||
// for the convenient about following printing.
|
||||
size_t max_space = 1;
|
||||
for (auto index : std::views::iota(ZERO, n)) {
|
||||
max_space = std::max(max_space, widths.get_column_width(index));
|
||||
}
|
||||
std::u8string spaces(max_space, u8' ');
|
||||
std::u8string_view spaces_view(spaces);
|
||||
|
||||
// Print table
|
||||
// Define a convenient macro
|
||||
#define CVT(data) REINTERPRET::as_ordinary_view(data)
|
||||
// Show header
|
||||
if (this->header_display) {
|
||||
dst << CVT(this->prefix_string);
|
||||
for (const auto [index, item] : std::views::enumerate(header)) {
|
||||
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), item.get_text_width());
|
||||
dst << CVT(item.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
|
||||
}
|
||||
dst << std::endl;
|
||||
}
|
||||
// Show bar
|
||||
if (this->bar_display) {
|
||||
dst << CVT(this->prefix_string);
|
||||
auto bar_width = this->bar.get_text_width();
|
||||
for (auto index : std::views::iota(ZERO, n)) {
|
||||
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), bar_width);
|
||||
dst << CVT(this->bar.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
|
||||
}
|
||||
dst << std::endl;
|
||||
}
|
||||
// Show data
|
||||
for (const auto& row : this->rows) {
|
||||
dst << CVT(this->prefix_string);
|
||||
for (const auto [index, item] : std::views::enumerate(row)) {
|
||||
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), item.get_text_width());
|
||||
dst << CVT(item.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
|
||||
}
|
||||
dst << std::endl;
|
||||
}
|
||||
// Undef macro
|
||||
#undef CVT
|
||||
}
|
||||
|
||||
size_t Tabulate::get_column_count() const {
|
||||
return this->n;
|
||||
}
|
||||
|
||||
void Tabulate::show_header(bool show_header) {
|
||||
this->header_display = show_header;
|
||||
}
|
||||
|
||||
void Tabulate::show_bar(bool show_bar) {
|
||||
this->bar_display = show_bar;
|
||||
}
|
||||
|
||||
void Tabulate::set_prefix(const std::u8string_view& prefix) {
|
||||
this->prefix_string = prefix;
|
||||
}
|
||||
|
||||
void Tabulate::set_header(std::initializer_list<std::u8string_view> hdr) {
|
||||
// Check data size.
|
||||
if (hdr.size() != get_column_count()) {
|
||||
throw std::invalid_argument("the size of given header is not equal to column count");
|
||||
}
|
||||
|
||||
// Change header data and update header width recorder.
|
||||
header.clear();
|
||||
header_widths.clear();
|
||||
for (const auto [index, item] : std::views::enumerate(hdr)) {
|
||||
auto cell = header.emplace_back(item);
|
||||
header_widths.update_column_width(index, cell.get_text_width());
|
||||
}
|
||||
}
|
||||
|
||||
void Tabulate::set_bar(const std::u8string_view& bar) {
|
||||
this->bar = bar;
|
||||
}
|
||||
|
||||
void Tabulate::add_row(std::initializer_list<std::u8string_view> row) {
|
||||
// Check data size.
|
||||
if (row.size() != get_column_count()) {
|
||||
throw std::invalid_argument("the size of given row is not equal to column count");
|
||||
}
|
||||
|
||||
// Prepare inserted row, and update data width recorder.
|
||||
Row inserted_row;
|
||||
inserted_row.reserve(row.size());
|
||||
for (const auto [index, item] : std::views::enumerate(row)) {
|
||||
auto cell = inserted_row.emplace_back(item);
|
||||
rows_widths.update_column_width(index, cell.get_text_width());
|
||||
}
|
||||
|
||||
// Insert row
|
||||
rows.emplace_back(std::move(inserted_row));
|
||||
}
|
||||
|
||||
void Tabulate::clear() {
|
||||
// Clear data and data width recorder.
|
||||
rows.clear();
|
||||
rows_widths.clear();
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::carton::tabulate
|
198
src/yycc/carton/tabulate.hpp
Normal file
198
src/yycc/carton/tabulate.hpp
Normal file
@ -0,0 +1,198 @@
|
||||
#pragma once
|
||||
#include "../macro/class_copy_move.hpp"
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace yycc::carton::tabulate {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Assistant class recording column width.
|
||||
*/
|
||||
class TabulateWidth {
|
||||
public:
|
||||
/**
|
||||
* @brief Create width recorder with given column count.
|
||||
* @param[in] n Column count.
|
||||
*/
|
||||
TabulateWidth(size_t n);
|
||||
~TabulateWidth();
|
||||
YYCC_DEFAULT_COPY_MOVE(TabulateWidth)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get column count.
|
||||
* @return Column count.
|
||||
*/
|
||||
size_t get_column_count() const;
|
||||
/**
|
||||
* @brief Get column width of given index.
|
||||
* @param[in] column_index Column index for fetching width.
|
||||
* @return Column width of given index.
|
||||
*/
|
||||
size_t get_column_width(size_t column_index) const;
|
||||
/**
|
||||
* @brief Update column width of given index with given value.
|
||||
* @details The width of column will be updated to the maximum between given value and old value.
|
||||
* @param[in] column_index Column index for updating width.
|
||||
* @param[in] new_size New width value.
|
||||
*/
|
||||
void update_column_width(size_t column_index, size_t new_size);
|
||||
/**
|
||||
* @brief Clear all width data
|
||||
* @details All width data will be reset to zero.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
std::vector<size_t> widths;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Assistant class holding table cell data.
|
||||
* @details
|
||||
* This class holds the data of table cell.
|
||||
* Also make a cache for the width this cell's text occupied in console,
|
||||
* to avoid duplicated calculation for occupied width.
|
||||
*/
|
||||
class TabulateCell {
|
||||
public:
|
||||
/**
|
||||
* @brief Build cell with given text.
|
||||
* @param[in] text Data of cell.
|
||||
*/
|
||||
TabulateCell(const std::u8string_view& text);
|
||||
~TabulateCell();
|
||||
YYCC_DEFAULT_COPY_MOVE(TabulateCell)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the text of cell.
|
||||
* @return The text of cell.
|
||||
*/
|
||||
const std::u8string& get_text() const;
|
||||
/**
|
||||
* @brief Get width this cell's text occupied in console.
|
||||
* @return The width this cell occupied.
|
||||
*/
|
||||
size_t get_text_width() const;
|
||||
|
||||
private:
|
||||
/// @brief The data of cell.
|
||||
std::u8string text;
|
||||
/// @brief The width cache of this data occupied in console.
|
||||
size_t text_width;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The type representing one row of data in table.
|
||||
*/
|
||||
using Row = std::vector<TabulateCell>;
|
||||
/**
|
||||
* @private
|
||||
* @brief The type representing row collection in table.
|
||||
*/
|
||||
using Rows = std::vector<Row>;
|
||||
|
||||
/**
|
||||
* @brief Main class of Tabulate
|
||||
*/
|
||||
class Tabulate {
|
||||
public:
|
||||
/**
|
||||
* @brief Create Tabulate class with given column count.
|
||||
* @details
|
||||
* In default, the separator bar of table is 3 dash.
|
||||
* Header and separator bar are also shown in default.
|
||||
* @param[in] n Column count of table.
|
||||
*/
|
||||
Tabulate(size_t n);
|
||||
~Tabulate();
|
||||
YYCC_DELETE_COPY_MOVE(Tabulate)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Print table into given stream.
|
||||
* @details In default, stream is \c stdout.
|
||||
* @param[in] dst The stream printed into.
|
||||
*/
|
||||
void print(std::ostream& dst = std::cout) const;
|
||||
|
||||
/**
|
||||
* @brief Get the column count of table.
|
||||
* @return Column count of table.
|
||||
*/
|
||||
size_t get_column_count() const;
|
||||
/**
|
||||
* @brief Change whether show table header when printing.
|
||||
* @param[in] show_header True for showing, otherwise false.
|
||||
*/
|
||||
void show_header(bool show_header);
|
||||
/**
|
||||
* @brief Change whether show separator bar when printing.
|
||||
* @param[in] show_bar True for showing, otherwise false.
|
||||
*/
|
||||
void show_bar(bool show_bar);
|
||||
/**
|
||||
* @brief Modify the prefix string of table.
|
||||
* @details
|
||||
* The prefix string of table is the string
|
||||
* which will be printed before each lines of output.
|
||||
* It is usually used for indent.
|
||||
* @param[in] prefix The prefix string.
|
||||
*/
|
||||
void set_prefix(const std::u8string_view& prefix);
|
||||
/**
|
||||
* @brief Modify the header of table.
|
||||
* @param[in] hdr An initializer list holding header texts one by one.
|
||||
* @exception std::invalid_argument The size of given header is mismatch with column count.
|
||||
*/
|
||||
void set_header(std::initializer_list<std::u8string_view> hdr);
|
||||
/**
|
||||
* @brief Modify separator bar string of table.
|
||||
* @param[in] bar New separator bar string.
|
||||
*/
|
||||
void set_bar(const std::u8string_view& bar);
|
||||
/**
|
||||
* @brief Add one data row into table.
|
||||
* @param[in] row An initializer list holding row texts one by one.
|
||||
* @exception std::invalid_argument The size of given header is mismatch with column count.
|
||||
*/
|
||||
void add_row(std::initializer_list<std::u8string_view> row);
|
||||
/**
|
||||
* @brief Clear all data rows of table.
|
||||
* @details Table header and separator bar will not be changed.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
|
||||
/// @brief The column count of table.
|
||||
size_t n;
|
||||
|
||||
/// @brief Whether showing table header.
|
||||
bool header_display;
|
||||
/// @brief Whether showing table separator bar between header and data.
|
||||
bool bar_display;
|
||||
/// @brief The prefix string presented in each lines of output table.
|
||||
std::u8string prefix_string;
|
||||
|
||||
/// @brief Width recorder for header.
|
||||
TabulateWidth header_widths;
|
||||
/// @brief Width recorder for data.
|
||||
TabulateWidth rows_widths;
|
||||
|
||||
/// @brief The header of table.
|
||||
Row header;
|
||||
/// @brief The separator bar of table.
|
||||
TabulateCell bar;
|
||||
/// @brief The data of table.
|
||||
Rows rows;
|
||||
};
|
||||
|
||||
}
|
234
src/yycc/carton/termcolor.cpp
Normal file
234
src/yycc/carton/termcolor.cpp
Normal file
@ -0,0 +1,234 @@
|
||||
#include "termcolor.hpp"
|
||||
#include "../flag_enum.hpp"
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <stdexcept>
|
||||
#include <bit>
|
||||
|
||||
#define FLAG_ENUM ::yycc::flag_enum
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
namespace yycc::carton::termcolor {
|
||||
|
||||
#pragma region Lowlevel Functions
|
||||
|
||||
const std::u8string_view foreground(Color color) {
|
||||
switch (color) {
|
||||
case Color::Default:
|
||||
return u8""sv;
|
||||
case Color::Black:
|
||||
return u8"\033[30m"sv;
|
||||
case Color::Red:
|
||||
return u8"\033[31m"sv;
|
||||
case Color::Green:
|
||||
return u8"\033[32m"sv;
|
||||
case Color::Yellow:
|
||||
return u8"\033[33m"sv;
|
||||
case Color::Blue:
|
||||
return u8"\033[34m"sv;
|
||||
case Color::Magenta:
|
||||
return u8"\033[35m"sv;
|
||||
case Color::Cyan:
|
||||
return u8"\033[36m"sv;
|
||||
case Color::White:
|
||||
return u8"\033[37m"sv;
|
||||
case Color::LightBlack:
|
||||
return u8"\033[90m"sv;
|
||||
case Color::LightRed:
|
||||
return u8"\033[91m"sv;
|
||||
case Color::LightGreen:
|
||||
return u8"\033[92m"sv;
|
||||
case Color::LightYellow:
|
||||
return u8"\033[93m"sv;
|
||||
case Color::LightBlue:
|
||||
return u8"\033[94m"sv;
|
||||
case Color::LightMagenta:
|
||||
return u8"\033[95m"sv;
|
||||
case Color::LightCyan:
|
||||
return u8"\033[96m"sv;
|
||||
case Color::LightWhite:
|
||||
return u8"\033[97m"sv;
|
||||
default:
|
||||
throw std::invalid_argument("invalid color kind");
|
||||
}
|
||||
}
|
||||
|
||||
const std::u8string_view background(Color color) {
|
||||
switch (color) {
|
||||
case Color::Default:
|
||||
return u8""sv;
|
||||
case Color::Black:
|
||||
return u8"\033[40m"sv;
|
||||
case Color::Red:
|
||||
return u8"\033[41m"sv;
|
||||
case Color::Green:
|
||||
return u8"\033[42m"sv;
|
||||
case Color::Yellow:
|
||||
return u8"\033[43m"sv;
|
||||
case Color::Blue:
|
||||
return u8"\033[44m"sv;
|
||||
case Color::Magenta:
|
||||
return u8"\033[45m"sv;
|
||||
case Color::Cyan:
|
||||
return u8"\033[46m"sv;
|
||||
case Color::White:
|
||||
return u8"\033[47m"sv;
|
||||
case Color::LightBlack:
|
||||
return u8"\033[100m"sv;
|
||||
case Color::LightRed:
|
||||
return u8"\033[101m"sv;
|
||||
case Color::LightGreen:
|
||||
return u8"\033[102m"sv;
|
||||
case Color::LightYellow:
|
||||
return u8"\033[103m"sv;
|
||||
case Color::LightBlue:
|
||||
return u8"\033[104m"sv;
|
||||
case Color::LightMagenta:
|
||||
return u8"\033[105m"sv;
|
||||
case Color::LightCyan:
|
||||
return u8"\033[106m"sv;
|
||||
case Color::LightWhite:
|
||||
return u8"\033[107m"sv;
|
||||
default:
|
||||
throw std::invalid_argument("invalid color kind");
|
||||
}
|
||||
}
|
||||
|
||||
const std::u8string_view style(Attribute attr) {
|
||||
// Return for Default first because it can not pass following test
|
||||
if (attr == Attribute::Default) {
|
||||
return u8""sv;
|
||||
}
|
||||
|
||||
// Check whether it only has one flag
|
||||
if (!std::has_single_bit(FLAG_ENUM::integer(attr))) {
|
||||
throw std::invalid_argument("style() only accept single flag attribute");
|
||||
}
|
||||
|
||||
// Return result
|
||||
switch (attr) {
|
||||
case Attribute::Bold:
|
||||
return u8"\033[1m"sv;
|
||||
case Attribute::Dark:
|
||||
return u8"\033[2m"sv;
|
||||
case Attribute::Italic:
|
||||
return u8"\033[3m"sv;
|
||||
case Attribute::Underline:
|
||||
return u8"\033[4m"sv;
|
||||
case Attribute::Blink:
|
||||
return u8"\033[5m"sv;
|
||||
case Attribute::Reverse:
|
||||
return u8"\033[6m"sv;
|
||||
case Attribute::Concealed:
|
||||
return u8"\033[7m"sv;
|
||||
default:
|
||||
throw std::invalid_argument("invalid attribute kind");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The possible maximum length of ANSI Escape Sequence used in this module.
|
||||
* @details This const value is used for computing reserved size of final built string.
|
||||
*/
|
||||
static constexpr size_t ANSI_ESC_LEN = sizeof(u8"\033[000m") - 1;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Count how many single flags combine given attributes.
|
||||
* @details
|
||||
* For function styles() involving multiple font style ANSI Escape Sequence,
|
||||
* this function may be useful for computing desired size of final result,
|
||||
* to reduce useless memory re-allocation.
|
||||
* @param[in] attrs Attributes for counting.
|
||||
* @return The count of single flag.
|
||||
*/
|
||||
static size_t count_attribute_flags(Attribute attrs) {
|
||||
return static_cast<size_t>(std::popcount(FLAG_ENUM::integer(attrs)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Append multiple font styles into given string.
|
||||
* @details
|
||||
* This function will decompose given font styles into single flag.
|
||||
* And append its components one by one into given string.
|
||||
* If there is enough reserved space in given string,
|
||||
* there is no memory re-allocation happened.
|
||||
* @remarks
|
||||
* This function is served for styles() and colored().
|
||||
* @param[in] s The string to be appended.
|
||||
* @param[in] attrs The attributes for writting.
|
||||
*/
|
||||
static void append_styles(std::u8string& s, Attribute attrs) {
|
||||
#define CHECK_ATTR(probe) \
|
||||
if (FLAG_ENUM::has(attrs, probe)) s.append(termcolor::style(probe));
|
||||
|
||||
if (attrs != Attribute::Default) {
|
||||
CHECK_ATTR(Attribute::Bold);
|
||||
CHECK_ATTR(Attribute::Dark);
|
||||
CHECK_ATTR(Attribute::Italic);
|
||||
CHECK_ATTR(Attribute::Blink);
|
||||
CHECK_ATTR(Attribute::Reverse);
|
||||
CHECK_ATTR(Attribute::Concealed);
|
||||
}
|
||||
|
||||
#undef CHECK_ATTR
|
||||
}
|
||||
|
||||
std::u8string styles(Attribute attrs) {
|
||||
// Prepare the result string
|
||||
std::u8string rv;
|
||||
rv.reserve(count_attribute_flags(attrs) * ANSI_ESC_LEN);
|
||||
// Append styles and return
|
||||
append_styles(rv, attrs);
|
||||
return rv;
|
||||
}
|
||||
|
||||
const std::u8string_view reset() {
|
||||
return u8"\033[0m"sv;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Highlevel Functions
|
||||
|
||||
std::u8string colored(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
|
||||
// Calculate the expected size of result string.
|
||||
// final count = styles count + 1 (foreground) + 1 (background) + 1 (reset)
|
||||
std::u8string rv;
|
||||
size_t ansi_esc_count = count_attribute_flags(styles) + 3;
|
||||
rv.reserve(ansi_esc_count * ANSI_ESC_LEN + words.size());
|
||||
|
||||
// Append data one by one
|
||||
rv.append(termcolor::foreground(foreground));
|
||||
rv.append(termcolor::background(background));
|
||||
append_styles(rv, styles);
|
||||
rv.append(words);
|
||||
rv.append(termcolor::reset());
|
||||
|
||||
// Return result
|
||||
return rv;
|
||||
}
|
||||
|
||||
void cprint(const std::u8string_view& words, Color foreground, Color background, Attribute styles, std::ostream& dst) {
|
||||
dst << REINTERPRET::as_ordinary_view(colored(words, foreground, background, styles));
|
||||
}
|
||||
|
||||
void ecprint(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
|
||||
cprint(words, foreground, background, styles, std::cerr);
|
||||
}
|
||||
|
||||
void cprintln(const std::u8string_view& words, Color foreground, Color background, Attribute styles, std::ostream& dst) {
|
||||
cprint(words, foreground, background, styles, dst);
|
||||
dst << std::endl;
|
||||
}
|
||||
|
||||
void ecprintln(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
|
||||
cprintln(words, foreground, background, styles, std::cerr);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::carton::termcolor
|
166
src/yycc/carton/termcolor.hpp
Normal file
166
src/yycc/carton/termcolor.hpp
Normal file
@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* @brief The namespace for terminal font color and style.
|
||||
* @details
|
||||
* This namespace provides functions to generate ANSI escape sequence for terminal font color and style.
|
||||
* It also provides functions to add color and style for given string with ANSI Escape Sequence.
|
||||
*
|
||||
* This namespace is basically the immitation of the Python package with same name.
|
||||
*/
|
||||
namespace yycc::carton::termcolor {
|
||||
|
||||
#pragma region Lowlevel Functions
|
||||
|
||||
/**
|
||||
* @brief The color of font.
|
||||
*/
|
||||
enum class Color {
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
LightBlack,
|
||||
LightRed,
|
||||
LightGreen,
|
||||
LightYellow,
|
||||
LightBlue,
|
||||
LightMagenta,
|
||||
LightCyan,
|
||||
LightWhite,
|
||||
Default
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get ANSI escape sequence for foreground color
|
||||
* @param[in] color The color to generate sequence for
|
||||
* @return Gotten ANSI escape sequence
|
||||
*/
|
||||
const std::u8string_view foreground(Color color);
|
||||
/**
|
||||
* @brief Get ANSI escape sequence for background color
|
||||
* @param[in] color The color to generate sequence for
|
||||
* @return Gotten ANSI escape sequence
|
||||
*/
|
||||
const std::u8string_view background(Color color);
|
||||
|
||||
/**
|
||||
* @brief The attribute of font
|
||||
* @remarks We define this enum as unsigned integral, so that we can use \c std::has_single_bit.
|
||||
*/
|
||||
enum class Attribute : uint32_t {
|
||||
Default = 0,
|
||||
Bold = 1 << 0,
|
||||
Dark = 1 << 1,
|
||||
Italic = 1 << 2,
|
||||
Underline = 1 << 3,
|
||||
Blink = 1 << 4,
|
||||
Reverse = 1 << 5,
|
||||
Concealed = 1 << 6
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get ANSI escape sequence for text style
|
||||
* @details
|
||||
* Please note that this function only support single attribute flag.
|
||||
* If you want to use multiple attributes, please use styles() instead.
|
||||
*
|
||||
* However, the difference between this function and styles() is that
|
||||
* there is no memory allocation in this function.
|
||||
* It may have better performance that styles().
|
||||
* @param[in] attr Single attribute to generate sequence for
|
||||
* @return Gotten ANSI escape sequence
|
||||
* @throws std::invalid_argument if attribute is not a single flag
|
||||
*/
|
||||
const std::u8string_view style(Attribute attr);
|
||||
/**
|
||||
* @brief Generates ANSI escape sequence for multiple text styles
|
||||
* @param[in] attrs Combination of attributes to generate sequences for
|
||||
* @return Generated ANSI escape sequence
|
||||
*/
|
||||
std::u8string styles(Attribute attrs);
|
||||
/**
|
||||
* @brief Get ANSI escape sequence for reset style
|
||||
* @return Gotten ANSI reset sequence
|
||||
*/
|
||||
const std::u8string_view reset();
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Highlevel Functions
|
||||
|
||||
/**
|
||||
* @brief Add color and style for given string with ANSI Escape Sequence.
|
||||
* @param[in] words The words to be decorated.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
* @return Decorated words.
|
||||
*/
|
||||
std::u8string colored(const std::u8string_view& words,
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default);
|
||||
|
||||
/**
|
||||
* @brief Print words into stream with given styles.
|
||||
* @param[in] words The words to be printed.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
* @param[in] dst The stream written into. \c stdout in default.
|
||||
*/
|
||||
void cprint(const std::u8string_view& words = std::u8string_view(u8""),
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default,
|
||||
std::ostream& dst = std::cout);
|
||||
|
||||
/**
|
||||
* @brief Print words into \c stderr with given styles.
|
||||
* @param[in] words The words to be printed.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
*/
|
||||
void ceprint(const std::u8string_view& words = std::u8string_view(u8""),
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default);
|
||||
|
||||
/**
|
||||
* @brief Print words into stream with given styles and break line.
|
||||
* @param[in] words The words to be printed.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
* @param[in] dst The stream written into. \c stdout in default.
|
||||
*/
|
||||
void cprintln(const std::u8string_view& words = std::u8string_view(u8""),
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default,
|
||||
std::ostream& dst = std::cout);
|
||||
|
||||
/**
|
||||
* @brief Print words into \c stderr with given styles and break line.
|
||||
* @param[in] words The words to be printed.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
*/
|
||||
void ceprintln(const std::u8string_view& words = std::u8string_view(u8""),
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::carton::termcolor
|
478
src/yycc/carton/wcwidth.cpp
Normal file
478
src/yycc/carton/wcwidth.cpp
Normal file
@ -0,0 +1,478 @@
|
||||
#include "wcwidth.hpp"
|
||||
#include "../encoding/stl.hpp"
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
#define ENC ::yycc::encoding::stl
|
||||
|
||||
namespace yycc::carton::wcwidth {
|
||||
|
||||
using Boundary = std::pair<char32_t, char32_t>;
|
||||
using BoundaryVector = std::vector<Boundary>;
|
||||
|
||||
// YYC MARK:
|
||||
// Following table and code are copied from Python package "wcwidth".
|
||||
// Although the code of this package are also copied from the original "wcwidth" C implementation.
|
||||
//
|
||||
// I do not need so much exact measurement.
|
||||
// I just want a "it works" wcwdith in all platforms.
|
||||
// So these tables are coming from the table with lowest UNICODE version
|
||||
// (original package provides different tables for different UNICODE versions).
|
||||
|
||||
// clang-format off
|
||||
static const BoundaryVector ZERO_WIDTH{
|
||||
{U'\x00000', U'\x00000'}, // (nil)
|
||||
{U'\x000ad', U'\x000ad'}, // Soft Hyphen
|
||||
{U'\x00300', U'\x0036f'}, // Combining Grave Accent ..Combining Latin Small Le
|
||||
{U'\x00483', U'\x00486'}, // Combining Cyrillic Titlo..Combining Cyrillic Psili
|
||||
{U'\x00488', U'\x00489'}, // Combining Cyrillic Hundr..Combining Cyrillic Milli
|
||||
{U'\x00591', U'\x005b9'}, // Hebrew Accent Etnahta ..Hebrew Point Holam
|
||||
{U'\x005bb', U'\x005bd'}, // Hebrew Point Qubuts ..Hebrew Point Meteg
|
||||
{U'\x005bf', U'\x005bf'}, // Hebrew Point Rafe
|
||||
{U'\x005c1', U'\x005c2'}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
|
||||
{U'\x005c4', U'\x005c5'}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
|
||||
{U'\x005c7', U'\x005c7'}, // Hebrew Point Qamats Qatan
|
||||
{U'\x00600', U'\x00603'}, // Arabic Number Sign ..Arabic Sign Safha
|
||||
{U'\x00610', U'\x00615'}, // Arabic Sign Sallallahou ..Arabic Small High Tah
|
||||
{U'\x0064b', U'\x0065e'}, // Arabic Fathatan ..Arabic Fatha With Two Do
|
||||
{U'\x00670', U'\x00670'}, // Arabic Letter Superscript Alef
|
||||
{U'\x006d6', U'\x006e4'}, // Arabic Small High Ligatu..Arabic Small High Madda
|
||||
{U'\x006e7', U'\x006e8'}, // Arabic Small High Yeh ..Arabic Small High Noon
|
||||
{U'\x006ea', U'\x006ed'}, // Arabic Empty Centre Low ..Arabic Small Low Meem
|
||||
{U'\x0070f', U'\x0070f'}, // Syriac Abbreviation Mark
|
||||
{U'\x00711', U'\x00711'}, // Syriac Letter Superscript Alaph
|
||||
{U'\x00730', U'\x0074a'}, // Syriac Pthaha Above ..Syriac Barrekh
|
||||
{U'\x007a6', U'\x007b0'}, // Thaana Abafili ..Thaana Sukun
|
||||
{U'\x00901', U'\x00903'}, // Devanagari Sign Candrabi..Devanagari Sign Visarga
|
||||
{U'\x0093c', U'\x0093c'}, // Devanagari Sign Nukta
|
||||
{U'\x0093e', U'\x0094d'}, // Devanagari Vowel Sign Aa..Devanagari Sign Virama
|
||||
{U'\x00951', U'\x00954'}, // Devanagari Stress Sign U..Devanagari Acute Accent
|
||||
{U'\x00962', U'\x00963'}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
|
||||
{U'\x00981', U'\x00983'}, // Bengali Sign Candrabindu..Bengali Sign Visarga
|
||||
{U'\x009bc', U'\x009bc'}, // Bengali Sign Nukta
|
||||
{U'\x009be', U'\x009c4'}, // Bengali Vowel Sign Aa ..Bengali Vowel Sign Vocal
|
||||
{U'\x009c7', U'\x009c8'}, // Bengali Vowel Sign E ..Bengali Vowel Sign Ai
|
||||
{U'\x009cb', U'\x009cd'}, // Bengali Vowel Sign O ..Bengali Sign Virama
|
||||
{U'\x009d7', U'\x009d7'}, // Bengali Au Length Mark
|
||||
{U'\x009e2', U'\x009e3'}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
|
||||
{U'\x00a01', U'\x00a03'}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Visarga
|
||||
{U'\x00a3c', U'\x00a3c'}, // Gurmukhi Sign Nukta
|
||||
{U'\x00a3e', U'\x00a42'}, // Gurmukhi Vowel Sign Aa ..Gurmukhi Vowel Sign Uu
|
||||
{U'\x00a47', U'\x00a48'}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
|
||||
{U'\x00a4b', U'\x00a4d'}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
|
||||
{U'\x00a70', U'\x00a71'}, // Gurmukhi Tippi ..Gurmukhi Addak
|
||||
{U'\x00a81', U'\x00a83'}, // Gujarati Sign Candrabind..Gujarati Sign Visarga
|
||||
{U'\x00abc', U'\x00abc'}, // Gujarati Sign Nukta
|
||||
{U'\x00abe', U'\x00ac5'}, // Gujarati Vowel Sign Aa ..Gujarati Vowel Sign Cand
|
||||
{U'\x00ac7', U'\x00ac9'}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Cand
|
||||
{U'\x00acb', U'\x00acd'}, // Gujarati Vowel Sign O ..Gujarati Sign Virama
|
||||
{U'\x00ae2', U'\x00ae3'}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca
|
||||
{U'\x00b01', U'\x00b03'}, // Oriya Sign Candrabindu ..Oriya Sign Visarga
|
||||
{U'\x00b3c', U'\x00b3c'}, // Oriya Sign Nukta
|
||||
{U'\x00b3e', U'\x00b43'}, // Oriya Vowel Sign Aa ..Oriya Vowel Sign Vocalic
|
||||
{U'\x00b47', U'\x00b48'}, // Oriya Vowel Sign E ..Oriya Vowel Sign Ai
|
||||
{U'\x00b4b', U'\x00b4d'}, // Oriya Vowel Sign O ..Oriya Sign Virama
|
||||
{U'\x00b56', U'\x00b57'}, // Oriya Ai Length Mark ..Oriya Au Length Mark
|
||||
{U'\x00b82', U'\x00b82'}, // Tamil Sign Anusvara
|
||||
{U'\x00bbe', U'\x00bc2'}, // Tamil Vowel Sign Aa ..Tamil Vowel Sign Uu
|
||||
{U'\x00bc6', U'\x00bc8'}, // Tamil Vowel Sign E ..Tamil Vowel Sign Ai
|
||||
{U'\x00bca', U'\x00bcd'}, // Tamil Vowel Sign O ..Tamil Sign Virama
|
||||
{U'\x00bd7', U'\x00bd7'}, // Tamil Au Length Mark
|
||||
{U'\x00c01', U'\x00c03'}, // Telugu Sign Candrabindu ..Telugu Sign Visarga
|
||||
{U'\x00c3e', U'\x00c44'}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Vocali
|
||||
{U'\x00c46', U'\x00c48'}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
|
||||
{U'\x00c4a', U'\x00c4d'}, // Telugu Vowel Sign O ..Telugu Sign Virama
|
||||
{U'\x00c55', U'\x00c56'}, // Telugu Length Mark ..Telugu Ai Length Mark
|
||||
{U'\x00c82', U'\x00c83'}, // Kannada Sign Anusvara ..Kannada Sign Visarga
|
||||
{U'\x00cbc', U'\x00cbc'}, // Kannada Sign Nukta
|
||||
{U'\x00cbe', U'\x00cc4'}, // Kannada Vowel Sign Aa ..Kannada Vowel Sign Vocal
|
||||
{U'\x00cc6', U'\x00cc8'}, // Kannada Vowel Sign E ..Kannada Vowel Sign Ai
|
||||
{U'\x00cca', U'\x00ccd'}, // Kannada Vowel Sign O ..Kannada Sign Virama
|
||||
{U'\x00cd5', U'\x00cd6'}, // Kannada Length Mark ..Kannada Ai Length Mark
|
||||
{U'\x00d02', U'\x00d03'}, // Malayalam Sign Anusvara ..Malayalam Sign Visarga
|
||||
{U'\x00d3e', U'\x00d43'}, // Malayalam Vowel Sign Aa ..Malayalam Vowel Sign Voc
|
||||
{U'\x00d46', U'\x00d48'}, // Malayalam Vowel Sign E ..Malayalam Vowel Sign Ai
|
||||
{U'\x00d4a', U'\x00d4d'}, // Malayalam Vowel Sign O ..Malayalam Sign Virama
|
||||
{U'\x00d57', U'\x00d57'}, // Malayalam Au Length Mark
|
||||
{U'\x00d82', U'\x00d83'}, // Sinhala Sign Anusvaraya ..Sinhala Sign Visargaya
|
||||
{U'\x00dca', U'\x00dca'}, // Sinhala Sign Al-lakuna
|
||||
{U'\x00dcf', U'\x00dd4'}, // Sinhala Vowel Sign Aela-..Sinhala Vowel Sign Ketti
|
||||
{U'\x00dd6', U'\x00dd6'}, // Sinhala Vowel Sign Diga Paa-pilla
|
||||
{U'\x00dd8', U'\x00ddf'}, // Sinhala Vowel Sign Gaett..Sinhala Vowel Sign Gayan
|
||||
{U'\x00df2', U'\x00df3'}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
|
||||
{U'\x00e31', U'\x00e31'}, // Thai Character Mai Han-akat
|
||||
{U'\x00e34', U'\x00e3a'}, // Thai Character Sara I ..Thai Character Phinthu
|
||||
{U'\x00e47', U'\x00e4e'}, // Thai Character Maitaikhu..Thai Character Yamakkan
|
||||
{U'\x00eb1', U'\x00eb1'}, // Lao Vowel Sign Mai Kan
|
||||
{U'\x00eb4', U'\x00eb9'}, // Lao Vowel Sign I ..Lao Vowel Sign Uu
|
||||
{U'\x00ebb', U'\x00ebc'}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo
|
||||
{U'\x00ec8', U'\x00ecd'}, // Lao Tone Mai Ek ..Lao Niggahita
|
||||
{U'\x00f18', U'\x00f19'}, // Tibetan Astrological Sig..Tibetan Astrological Sig
|
||||
{U'\x00f35', U'\x00f35'}, // Tibetan Mark Ngas Bzung Nyi Zla
|
||||
{U'\x00f37', U'\x00f37'}, // Tibetan Mark Ngas Bzung Sgor Rtags
|
||||
{U'\x00f39', U'\x00f39'}, // Tibetan Mark Tsa -phru
|
||||
{U'\x00f3e', U'\x00f3f'}, // Tibetan Sign Yar Tshes ..Tibetan Sign Mar Tshes
|
||||
{U'\x00f71', U'\x00f84'}, // Tibetan Vowel Sign Aa ..Tibetan Mark Halanta
|
||||
{U'\x00f86', U'\x00f87'}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags
|
||||
{U'\x00f90', U'\x00f97'}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
|
||||
{U'\x00f99', U'\x00fbc'}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
|
||||
{U'\x00fc6', U'\x00fc6'}, // Tibetan Symbol Padma Gdan
|
||||
{U'\x0102c', U'\x01032'}, // Myanmar Vowel Sign Aa ..Myanmar Vowel Sign Ai
|
||||
{U'\x01036', U'\x01039'}, // Myanmar Sign Anusvara ..Myanmar Sign Virama
|
||||
{U'\x01056', U'\x01059'}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal
|
||||
{U'\x01160', U'\x011ff'}, // Hangul Jungseong Filler ..Hangul Jongseong Ssangni
|
||||
{U'\x0135f', U'\x0135f'}, // Ethiopic Combining Gemination Mark
|
||||
{U'\x01712', U'\x01714'}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
|
||||
{U'\x01732', U'\x01734'}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
|
||||
{U'\x01752', U'\x01753'}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
|
||||
{U'\x01772', U'\x01773'}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
|
||||
{U'\x017b4', U'\x017d3'}, // Khmer Vowel Inherent Aq ..Khmer Sign Bathamasat
|
||||
{U'\x017dd', U'\x017dd'}, // Khmer Sign Atthacan
|
||||
{U'\x0180b', U'\x0180d'}, // Mongolian Free Variation..Mongolian Free Variation
|
||||
{U'\x018a9', U'\x018a9'}, // Mongolian Letter Ali Gali Dagalga
|
||||
{U'\x01920', U'\x0192b'}, // Limbu Vowel Sign A ..Limbu Subjoined Letter W
|
||||
{U'\x01930', U'\x0193b'}, // Limbu Small Letter Ka ..Limbu Sign Sa-i
|
||||
{U'\x019b0', U'\x019c0'}, // New Tai Lue Vowel Sign V..New Tai Lue Vowel Sign I
|
||||
{U'\x019c8', U'\x019c9'}, // New Tai Lue Tone Mark-1 ..New Tai Lue Tone Mark-2
|
||||
{U'\x01a17', U'\x01a1b'}, // Buginese Vowel Sign I ..Buginese Vowel Sign Ae
|
||||
{U'\x01dc0', U'\x01dc3'}, // Combining Dotted Grave A..Combining Suspension Mar
|
||||
{U'\x0200b', U'\x0200f'}, // Zero Width Space ..Right-to-left Mark
|
||||
{U'\x02028', U'\x0202e'}, // Line Separator ..Right-to-left Override
|
||||
{U'\x02060', U'\x02063'}, // Word Joiner ..Invisible Separator
|
||||
{U'\x0206a', U'\x0206f'}, // Inhibit Symmetric Swappi..Nominal Digit Shapes
|
||||
{U'\x020d0', U'\x020eb'}, // Combining Left Harpoon A..Combining Long Double So
|
||||
{U'\x0302a', U'\x0302f'}, // Ideographic Level Tone M..Hangul Double Dot Tone M
|
||||
{U'\x03099', U'\x0309a'}, // Combining Katakana-hirag..Combining Katakana-hirag
|
||||
{U'\x0a802', U'\x0a802'}, // Syloti Nagri Sign Dvisvara
|
||||
{U'\x0a806', U'\x0a806'}, // Syloti Nagri Sign Hasanta
|
||||
{U'\x0a80b', U'\x0a80b'}, // Syloti Nagri Sign Anusvara
|
||||
{U'\x0a823', U'\x0a827'}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
|
||||
{U'\x0d7b0', U'\x0d7ff'}, // Hangul Jungseong O-yeo ..(nil)
|
||||
{U'\x0fb1e', U'\x0fb1e'}, // Hebrew Point Judeo-spanish Varika
|
||||
{U'\x0fe00', U'\x0fe0f'}, // Variation Selector-1 ..Variation Selector-16
|
||||
{U'\x0fe20', U'\x0fe23'}, // Combining Ligature Left ..Combining Double Tilde R
|
||||
{U'\x0feff', U'\x0feff'}, // Zero Width No-break Space
|
||||
{U'\x0fff9', U'\x0fffb'}, // Interlinear Annotation A..Interlinear Annotation T
|
||||
{U'\x10a01', U'\x10a03'}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo
|
||||
{U'\x10a05', U'\x10a06'}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O
|
||||
{U'\x10a0c', U'\x10a0f'}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga
|
||||
{U'\x10a38', U'\x10a3a'}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo
|
||||
{U'\x10a3f', U'\x10a3f'}, // Kharoshthi Virama
|
||||
{U'\x1d165', U'\x1d169'}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{U'\x1d16d', U'\x1d182'}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{U'\x1d185', U'\x1d18b'}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{U'\x1d1aa', U'\x1d1ad'}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{U'\x1d242', U'\x1d244'}, // Combining Greek Musical ..Combining Greek Musical
|
||||
{U'\xe0001', U'\xe0001'}, // Language Tag
|
||||
{U'\xe0020', U'\xe007f'}, // Tag Space ..Cancel Tag
|
||||
{U'\xe0100', U'\xe01ef'}, // Variation Selector-17 ..Variation Selector-256
|
||||
};
|
||||
|
||||
static const BoundaryVector WIDE_EAST_ASIAN{
|
||||
{U'\x01100', U'\x01159'}, // Hangul Choseong Kiyeok ..Hangul Choseong Yeorinhi
|
||||
{U'\x0115f', U'\x0115f'}, // Hangul Choseong Filler
|
||||
{U'\x02329', U'\x0232a'}, // Left-pointing Angle Brac..Right-pointing Angle Bra
|
||||
{U'\x02e80', U'\x02e99'}, // Cjk Radical Repeat ..Cjk Radical Rap
|
||||
{U'\x02e9b', U'\x02ef3'}, // Cjk Radical Choke ..Cjk Radical C-simplified
|
||||
{U'\x02f00', U'\x02fd5'}, // Kangxi Radical One ..Kangxi Radical Flute
|
||||
{U'\x02ff0', U'\x02ffb'}, // Ideographic Description ..Ideographic Description
|
||||
{U'\x03000', U'\x03029'}, // Ideographic Space ..Hangzhou Numeral Nine
|
||||
{U'\x03030', U'\x0303e'}, // Wavy Dash ..Ideographic Variation In
|
||||
{U'\x03041', U'\x03096'}, // Hiragana Letter Small A ..Hiragana Letter Small Ke
|
||||
{U'\x0309b', U'\x030ff'}, // Katakana-hiragana Voiced..Katakana Digraph Koto
|
||||
{U'\x03105', U'\x0312c'}, // Bopomofo Letter B ..Bopomofo Letter Gn
|
||||
{U'\x03131', U'\x0318e'}, // Hangul Letter Kiyeok ..Hangul Letter Araeae
|
||||
{U'\x03190', U'\x031b7'}, // Ideographic Annotation L..Bopomofo Final Letter H
|
||||
{U'\x031c0', U'\x031cf'}, // Cjk Stroke T ..Cjk Stroke N
|
||||
{U'\x031f0', U'\x0321e'}, // Katakana Letter Small Ku..Parenthesized Korean Cha
|
||||
{U'\x03220', U'\x03243'}, // Parenthesized Ideograph ..Parenthesized Ideograph
|
||||
{U'\x03250', U'\x032fe'}, // Partnership Sign ..Circled Katakana Wo
|
||||
{U'\x03300', U'\x04db5'}, // Square Apaato ..Cjk Unified Ideograph-4d
|
||||
{U'\x04e00', U'\x09fbb'}, // Cjk Unified Ideograph-4e..Cjk Unified Ideograph-9f
|
||||
{U'\x0a000', U'\x0a48c'}, // Yi Syllable It ..Yi Syllable Yyr
|
||||
{U'\x0a490', U'\x0a4c6'}, // Yi Radical Qot ..Yi Radical Ke
|
||||
{U'\x0ac00', U'\x0d7a3'}, // Hangul Syllable Ga ..Hangul Syllable Hih
|
||||
{U'\x0f900', U'\x0fa2d'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
|
||||
{U'\x0fa30', U'\x0fa6a'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
|
||||
{U'\x0fa70', U'\x0fad9'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
|
||||
{U'\x0fe10', U'\x0fe19'}, // Presentation Form For Ve..Presentation Form For Ve
|
||||
{U'\x0fe30', U'\x0fe52'}, // Presentation Form For Ve..Small Full Stop
|
||||
{U'\x0fe54', U'\x0fe66'}, // Small Semicolon ..Small Equals Sign
|
||||
{U'\x0fe68', U'\x0fe6b'}, // Small Reverse Solidus ..Small Commercial At
|
||||
{U'\x0ff01', U'\x0ff60'}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
|
||||
{U'\x0ffe0', U'\x0ffe6'}, // Fullwidth Cent Sign ..Fullwidth Won Sign
|
||||
{U'\x20000', U'\x2fffd'}, // Cjk Unified Ideograph-20..(nil)
|
||||
{U'\x30000', U'\x3fffd'}, // Cjk Unified Ideograph-30..(nil)
|
||||
};
|
||||
|
||||
static const BoundaryVector VS16_NARROW_TO_WIDE{
|
||||
{U'\x00023', U'\x00023'}, // Number Sign
|
||||
{U'\x0002a', U'\x0002a'}, // Asterisk
|
||||
{U'\x00030', U'\x00039'}, // Digit Zero ..Digit Nine
|
||||
{U'\x000a9', U'\x000a9'}, // Copyright Sign
|
||||
{U'\x000ae', U'\x000ae'}, // Registered Sign
|
||||
{U'\x0203c', U'\x0203c'}, // Double Exclamation Mark
|
||||
{U'\x02049', U'\x02049'}, // Exclamation Question Mark
|
||||
{U'\x02122', U'\x02122'}, // Trade Mark Sign
|
||||
{U'\x02139', U'\x02139'}, // Information Source
|
||||
{U'\x02194', U'\x02199'}, // Left Right Arrow ..South West Arrow
|
||||
{U'\x021a9', U'\x021aa'}, // Leftwards Arrow With Hoo..Rightwards Arrow With Ho
|
||||
{U'\x02328', U'\x02328'}, // Keyboard
|
||||
{U'\x023cf', U'\x023cf'}, // Eject Symbol
|
||||
{U'\x023ed', U'\x023ef'}, // Black Right-pointing Dou..Black Right-pointing Tri
|
||||
{U'\x023f1', U'\x023f2'}, // Stopwatch ..Timer Clock
|
||||
{U'\x023f8', U'\x023fa'}, // Double Vertical Bar ..Black Circle For Record
|
||||
{U'\x024c2', U'\x024c2'}, // Circled Latin Capital Letter M
|
||||
{U'\x025aa', U'\x025ab'}, // Black Small Square ..White Small Square
|
||||
{U'\x025b6', U'\x025b6'}, // Black Right-pointing Triangle
|
||||
{U'\x025c0', U'\x025c0'}, // Black Left-pointing Triangle
|
||||
{U'\x025fb', U'\x025fc'}, // White Medium Square ..Black Medium Square
|
||||
{U'\x02600', U'\x02604'}, // Black Sun With Rays ..Comet
|
||||
{U'\x0260e', U'\x0260e'}, // Black Telephone
|
||||
{U'\x02611', U'\x02611'}, // Ballot Box With Check
|
||||
{U'\x02618', U'\x02618'}, // Shamrock
|
||||
{U'\x0261d', U'\x0261d'}, // White Up Pointing Index
|
||||
{U'\x02620', U'\x02620'}, // Skull And Crossbones
|
||||
{U'\x02622', U'\x02623'}, // Radioactive Sign ..Biohazard Sign
|
||||
{U'\x02626', U'\x02626'}, // Orthodox Cross
|
||||
{U'\x0262a', U'\x0262a'}, // Star And Crescent
|
||||
{U'\x0262e', U'\x0262f'}, // Peace Symbol ..Yin Yang
|
||||
{U'\x02638', U'\x0263a'}, // Wheel Of Dharma ..White Smiling Face
|
||||
{U'\x02640', U'\x02640'}, // Female Sign
|
||||
{U'\x02642', U'\x02642'}, // Male Sign
|
||||
{U'\x0265f', U'\x02660'}, // Black Chess Pawn ..Black Spade Suit
|
||||
{U'\x02663', U'\x02663'}, // Black Club Suit
|
||||
{U'\x02665', U'\x02666'}, // Black Heart Suit ..Black Diamond Suit
|
||||
{U'\x02668', U'\x02668'}, // Hot Springs
|
||||
{U'\x0267b', U'\x0267b'}, // Black Universal Recycling Symbol
|
||||
{U'\x0267e', U'\x0267e'}, // Permanent Paper Sign
|
||||
{U'\x02692', U'\x02692'}, // Hammer And Pick
|
||||
{U'\x02694', U'\x02697'}, // Crossed Swords ..Alembic
|
||||
{U'\x02699', U'\x02699'}, // Gear
|
||||
{U'\x0269b', U'\x0269c'}, // Atom Symbol ..Fleur-de-lis
|
||||
{U'\x026a0', U'\x026a0'}, // Warning Sign
|
||||
{U'\x026a7', U'\x026a7'}, // Male With Stroke And Male And Female Sign
|
||||
{U'\x026b0', U'\x026b1'}, // Coffin ..Funeral Urn
|
||||
{U'\x026c8', U'\x026c8'}, // Thunder Cloud And Rain
|
||||
{U'\x026cf', U'\x026cf'}, // Pick
|
||||
{U'\x026d1', U'\x026d1'}, // Helmet With White Cross
|
||||
{U'\x026d3', U'\x026d3'}, // Chains
|
||||
{U'\x026e9', U'\x026e9'}, // Shinto Shrine
|
||||
{U'\x026f0', U'\x026f1'}, // Mountain ..Umbrella On Ground
|
||||
{U'\x026f4', U'\x026f4'}, // Ferry
|
||||
{U'\x026f7', U'\x026f9'}, // Skier ..Person With Ball
|
||||
{U'\x02702', U'\x02702'}, // Black Scissors
|
||||
{U'\x02708', U'\x02709'}, // Airplane ..Envelope
|
||||
{U'\x0270c', U'\x0270d'}, // Victory Hand ..Writing Hand
|
||||
{U'\x0270f', U'\x0270f'}, // Pencil
|
||||
{U'\x02712', U'\x02712'}, // Black Nib
|
||||
{U'\x02714', U'\x02714'}, // Heavy Check Mark
|
||||
{U'\x02716', U'\x02716'}, // Heavy Multiplication X
|
||||
{U'\x0271d', U'\x0271d'}, // Latin Cross
|
||||
{U'\x02721', U'\x02721'}, // Star Of David
|
||||
{U'\x02733', U'\x02734'}, // Eight Spoked Asterisk ..Eight Pointed Black Star
|
||||
{U'\x02744', U'\x02744'}, // Snowflake
|
||||
{U'\x02747', U'\x02747'}, // Sparkle
|
||||
{U'\x02763', U'\x02764'}, // Heavy Heart Exclamation ..Heavy Black Heart
|
||||
{U'\x027a1', U'\x027a1'}, // Black Rightwards Arrow
|
||||
{U'\x02934', U'\x02935'}, // Arrow Pointing Rightward..Arrow Pointing Rightward
|
||||
{U'\x02b05', U'\x02b07'}, // Leftwards Black Arrow ..Downwards Black Arrow
|
||||
{U'\x1f170', U'\x1f171'}, // Negative Squared Latin C..Negative Squared Latin C
|
||||
{U'\x1f17e', U'\x1f17f'}, // Negative Squared Latin C..Negative Squared Latin C
|
||||
{U'\x1f321', U'\x1f321'}, // Thermometer
|
||||
{U'\x1f324', U'\x1f32c'}, // White Sun With Small Clo..Wind Blowing Face
|
||||
{U'\x1f336', U'\x1f336'}, // Hot Pepper
|
||||
{U'\x1f37d', U'\x1f37d'}, // Fork And Knife With Plate
|
||||
{U'\x1f396', U'\x1f397'}, // Military Medal ..Reminder Ribbon
|
||||
{U'\x1f399', U'\x1f39b'}, // Studio Microphone ..Control Knobs
|
||||
{U'\x1f39e', U'\x1f39f'}, // Film Frames ..Admission Tickets
|
||||
{U'\x1f3cb', U'\x1f3ce'}, // Weight Lifter ..Racing Car
|
||||
{U'\x1f3d4', U'\x1f3df'}, // Snow Capped Mountain ..Stadium
|
||||
{U'\x1f3f3', U'\x1f3f3'}, // Waving White Flag
|
||||
{U'\x1f3f5', U'\x1f3f5'}, // Rosette
|
||||
{U'\x1f3f7', U'\x1f3f7'}, // Label
|
||||
{U'\x1f43f', U'\x1f43f'}, // Chipmunk
|
||||
{U'\x1f441', U'\x1f441'}, // Eye
|
||||
{U'\x1f4fd', U'\x1f4fd'}, // Film Projector
|
||||
{U'\x1f549', U'\x1f54a'}, // Om Symbol ..Dove Of Peace
|
||||
{U'\x1f56f', U'\x1f570'}, // Candle ..Mantelpiece Clock
|
||||
{U'\x1f573', U'\x1f579'}, // Hole ..Joystick
|
||||
{U'\x1f587', U'\x1f587'}, // Linked Paperclips
|
||||
{U'\x1f58a', U'\x1f58d'}, // Lower Left Ballpoint Pen..Lower Left Crayon
|
||||
{U'\x1f590', U'\x1f590'}, // Raised Hand With Fingers Splayed
|
||||
{U'\x1f5a5', U'\x1f5a5'}, // Desktop Computer
|
||||
{U'\x1f5a8', U'\x1f5a8'}, // Printer
|
||||
{U'\x1f5b1', U'\x1f5b2'}, // Three Button Mouse ..Trackball
|
||||
{U'\x1f5bc', U'\x1f5bc'}, // Frame With Picture
|
||||
{U'\x1f5c2', U'\x1f5c4'}, // Card Index Dividers ..File Cabinet
|
||||
{U'\x1f5d1', U'\x1f5d3'}, // Wastebasket ..Spiral Calendar Pad
|
||||
{U'\x1f5dc', U'\x1f5de'}, // Compression ..Rolled-up Newspaper
|
||||
{U'\x1f5e1', U'\x1f5e1'}, // Dagger Knife
|
||||
{U'\x1f5e3', U'\x1f5e3'}, // Speaking Head In Silhouette
|
||||
{U'\x1f5e8', U'\x1f5e8'}, // Left Speech Bubble
|
||||
{U'\x1f5ef', U'\x1f5ef'}, // Right Anger Bubble
|
||||
{U'\x1f5f3', U'\x1f5f3'}, // Ballot Box With Ballot
|
||||
{U'\x1f5fa', U'\x1f5fa'}, // World Map
|
||||
{U'\x1f6cb', U'\x1f6cb'}, // Couch And Lamp
|
||||
{U'\x1f6cd', U'\x1f6cf'}, // Shopping Bags ..Bed
|
||||
{U'\x1f6e0', U'\x1f6e5'}, // Hammer And Wrench ..Motor Boat
|
||||
{U'\x1f6e9', U'\x1f6e9'}, // Small Airplane
|
||||
{U'\x1f6f0', U'\x1f6f0'}, // Satellite
|
||||
{U'\x1f6f3', U'\x1f6f3'}, // Passenger Ship
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static size_t bisearch(char32_t ucs, const BoundaryVector& table) {
|
||||
// TODO: Use STD algorithm to optimize this function
|
||||
|
||||
// YYC MARK:
|
||||
// Do not change this "int" to "size_t" casually,
|
||||
// because the result of arithmetic operation may be negative.
|
||||
// Do not change this type before using new algorithm.
|
||||
int lbound = 0, ubound = table.size() - 1;
|
||||
|
||||
if (ucs < table.front().first || ucs > table.back().second) return 0;
|
||||
|
||||
while (ubound >= lbound) {
|
||||
int mid = (lbound + ubound) / 2;
|
||||
if (ucs > table[mid].second) lbound = mid + 1;
|
||||
else if (ucs < table[mid].first) ubound = mid - 1;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t wcwidth(char32_t wc) {
|
||||
// TODO: Add lru_cache(maxsize=1000) for this function
|
||||
|
||||
// Small optimize for ASCII
|
||||
if (U'\x20' <= wc && wc < U'\x7F') [[likely]]
|
||||
return 1;
|
||||
|
||||
// C0/C1 control char
|
||||
// NOTE: Not vanilla implementation. Return 0 instead of 1.
|
||||
if ((wc && wc < L'\x20') || (L'\x7F' <= wc && wc < L'\xA0')) return 0;
|
||||
|
||||
// Zero-width char
|
||||
if (bisearch(wc, ZERO_WIDTH)) return 0;
|
||||
|
||||
// Width 1 or 2
|
||||
return 1 + bisearch(wc, WIDE_EAST_ASIAN);
|
||||
}
|
||||
|
||||
enum class WcswidthState {
|
||||
/// Normal character.
|
||||
Normal,
|
||||
/// Under ZWJ control char.
|
||||
/// Ignore the width of next char.
|
||||
ZeroWidthJoiner,
|
||||
/// Under ANSI Escape Sequence.
|
||||
/// Following chars should be treated as escape char.
|
||||
AnsiEscape,
|
||||
/// Under CSI control sequence, a part of ANSI Escape Sequence.
|
||||
/// No width was accumulated before terminal char.
|
||||
AnsiCsiEscape,
|
||||
};
|
||||
|
||||
struct WcswidthContext {
|
||||
/// Current state.
|
||||
WcswidthState state;
|
||||
/// Tract the last computed char.
|
||||
/// It will be used for VS16 char.
|
||||
std::optional<char32_t> last_measured_char;
|
||||
};
|
||||
|
||||
Result<size_t> wcswidth(const std::u32string_view& rhs) {
|
||||
WcswidthContext ctx{WcswidthState::Normal, std::nullopt};
|
||||
size_t width = 0;
|
||||
|
||||
for (char32_t chr : rhs) {
|
||||
// Match char value
|
||||
switch (ctx.state) {
|
||||
case WcswidthState::Normal: {
|
||||
switch (chr) {
|
||||
case U'\x200D': {
|
||||
// ZWJ control char
|
||||
ctx.state = WcswidthState::ZeroWidthJoiner;
|
||||
break;
|
||||
}
|
||||
case U'\xFE0F': {
|
||||
// VS16 control char
|
||||
// If we have a char which was acknowledged and has width,
|
||||
// analyse it instead of this control char.
|
||||
if (ctx.last_measured_char.has_value()) {
|
||||
width += bisearch(ctx.last_measured_char.value(), VS16_NARROW_TO_WIDE);
|
||||
ctx.last_measured_char = std::nullopt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case U'\x1B': {
|
||||
// ANSI escape sequence
|
||||
ctx.state = WcswidthState::AnsiEscape;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Fetch widht for normal char
|
||||
int wcw = wcwidth(chr);
|
||||
// Tract the final non-zero char for VS16 control char
|
||||
if (wcw > 0) ctx.last_measured_char = wcw;
|
||||
// Accumulate width
|
||||
width += wcw;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WcswidthState::ZeroWidthJoiner: {
|
||||
// Eat this char and back to normal state.
|
||||
// This is what ZWJ does.
|
||||
ctx.state = WcswidthState::Normal;
|
||||
break;
|
||||
}
|
||||
case WcswidthState::AnsiEscape: {
|
||||
// Check the second char of escape sequence.
|
||||
// If it is '[', we enter CSI state,
|
||||
// otherwise we eat it and back to normal state.
|
||||
// Additionally, there is a range requirement for this char (0x40-0x5F).
|
||||
if (chr == U'[') {
|
||||
ctx.state = WcswidthState::AnsiCsiEscape;
|
||||
} else if (chr >= U'\x40' && chr <= U'\x5F') {
|
||||
ctx.state = WcswidthState::Normal;
|
||||
} else {
|
||||
return std::unexpected(Error::BadAnsiEscSeq);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WcswidthState::AnsiCsiEscape: {
|
||||
// CSI sequence is aonsisted by variable Parameter Char (count can be zero),
|
||||
// at least one Middle Char and only one Final Char.
|
||||
// So we eat all chars until we reach the terminal char.
|
||||
if (chr >= U'\x40' && chr <= U'\x7E') {
|
||||
// Final Char. Back to normal state.
|
||||
ctx.state = WcswidthState::Normal;
|
||||
} else if (chr >= U'\x30' && chr <= U'\x3F') {
|
||||
; // Parameter Char. Do nothing
|
||||
} else if (chr >= U'\x20' && chr <= U'\x2F') {
|
||||
; // Middle Char. Do nothing
|
||||
} else {
|
||||
return std::unexpected(Error::BadCsiSeq);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
Result<size_t> wcswidth(const std::u8string_view& rhs) {
|
||||
// Cast encoding
|
||||
auto u32str = ENC::to_utf32(rhs);
|
||||
if (!u32str.has_value()) return std::unexpected(Error::BadEncoding);
|
||||
// Call underlying function
|
||||
return wcswidth(u32str.value());
|
||||
}
|
||||
|
||||
} // namespace yycc::carton::wcwidth
|
47
src/yycc/carton/wcwidth.hpp
Normal file
47
src/yycc/carton/wcwidth.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
/**
|
||||
* @brief The namespace replicating Linux-specialized function, "wcswidth", in all platforms.
|
||||
* @details
|
||||
* "wcswdith" is a specialized function in Linux.
|
||||
* It was not included in POSIX standard and only provided on Linux.
|
||||
* This function can fetch how many space which given string occupied in terminal.
|
||||
* This is essential and useful function in our library.
|
||||
* So I create this namespace to make "wcswidth" be available on all platforms.
|
||||
*
|
||||
* "wcswidth" is based on \c wchar_t. In Linux, \c wchar_t is 4-bytes length.
|
||||
* It can represent any characters without surrogate pair.
|
||||
* However, in Windows, \c wchar_t is 2-bytes length.
|
||||
* There is possible surrogate pair within \c wchar_t string, which is inconvenient for our programming.
|
||||
* So in this homebrew namespace, I forcelt use \c char32_t as the basic char type.
|
||||
*
|
||||
* Due to the requirements of mine, this implementation is slightly different with original one.
|
||||
* These differences are list below:
|
||||
*
|
||||
* \li We do not return negative value for Control Char in "wcwidth",
|
||||
* because we need to support the analyse of ANSI Escape Sequence.
|
||||
* \li Due to the previous change, the type of return value of "wcwidth" and "wcswidth"
|
||||
* are changed from \c int to \c size_t because there is no negative return value.
|
||||
* \li "wcswidth" now support ANSI Escape Sequence (e.g. terminal color).
|
||||
* So it can analyse colorful output with correct space.
|
||||
*/
|
||||
namespace yycc::carton::wcwidth {
|
||||
|
||||
/// @brief Error occurs in this module
|
||||
enum class Error {
|
||||
BadEncoding, ///< Given
|
||||
BadAnsiEscSeq, ///< Bad char when processing ANSI Escape Sequence
|
||||
BadCsiSeq, ///< Bad char when processing CSI Sequence.
|
||||
};
|
||||
|
||||
/// @brief Result type for this module
|
||||
template<typename T>
|
||||
using Result = std::expected<T, Error>;
|
||||
|
||||
size_t wcwidth(char32_t wc);
|
||||
Result<size_t> wcswidth(const std::u32string_view& rhs);
|
||||
Result<size_t> wcswidth(const std::u8string_view& rhs);
|
||||
|
||||
} // namespace yycc::carton::wcwidth
|
@ -35,14 +35,10 @@ namespace yycc::constraint::builder {
|
||||
T default_entry = il.begin()[default_index];
|
||||
std::set<T> entries(il);
|
||||
|
||||
// TODO: modify it as `contain` once we finish patch namespace.
|
||||
auto fn_check = [entries](const T& val) -> bool { return entries.find(val) != entries.end(); };
|
||||
auto fn_check = [entries](const T& val) -> bool { return entries.contains(val); };
|
||||
auto fn_clamp = [entries, default_entry](const T& val) -> T {
|
||||
if (entries.find(val) != entries.end()) {
|
||||
return val;
|
||||
} else {
|
||||
return default_entry;
|
||||
}
|
||||
if (entries.contains(val)) return val;
|
||||
else return default_entry;
|
||||
};
|
||||
return Constraint<T>(std::move(fn_check), fn_clamp);
|
||||
}
|
||||
|
@ -71,11 +71,11 @@ namespace yycc::encoding::iconv {
|
||||
that_iconv_close(this->inner);
|
||||
}
|
||||
}
|
||||
PrivToken(PrivToken&& rhs) : inner(rhs.inner) {
|
||||
PrivToken(PrivToken&& rhs) noexcept : inner(rhs.inner) {
|
||||
// Reset rhs inner
|
||||
rhs.inner = INVALID_ICONV_TOKEN;
|
||||
}
|
||||
PrivToken& operator=(PrivToken&& rhs) {
|
||||
PrivToken& operator=(PrivToken&& rhs) noexcept {
|
||||
// Free self first
|
||||
if (this->inner != INVALID_ICONV_TOKEN) {
|
||||
that_iconv_close(this->inner);
|
||||
@ -99,16 +99,32 @@ namespace yycc::encoding::iconv {
|
||||
|
||||
#pragma region Token
|
||||
|
||||
Token::Token(const CodeName& from_code, const CodeName& to_code) : inner(std::make_unique<PrivToken>(from_code, to_code)) {}
|
||||
Token::Token(const CodeName& from_code, const CodeName& to_code) : inner(nullptr) {
|
||||
this->inner = new PrivToken(from_code, to_code);
|
||||
}
|
||||
|
||||
Token::~Token() {}
|
||||
Token::~Token() {
|
||||
if (this->inner != nullptr) {
|
||||
delete this->inner;
|
||||
}
|
||||
}
|
||||
|
||||
Token::Token(Token&& rhs) noexcept : inner(rhs.inner) {
|
||||
rhs.inner = nullptr;
|
||||
}
|
||||
|
||||
Token& Token::operator=(Token&& rhs) noexcept {
|
||||
this->inner = rhs.inner;
|
||||
rhs.inner = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Token::is_valid() const {
|
||||
return this->inner->is_valid();
|
||||
}
|
||||
|
||||
PrivToken* Token::get_inner() const {
|
||||
return this->inner.get();
|
||||
return this->inner;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
@ -151,7 +167,7 @@ namespace yycc::encoding::iconv {
|
||||
|
||||
// resize for container and its variables
|
||||
str_to.resize(str_to.size() + ICONV_INC_LEN);
|
||||
outbytesleft = str_to.size();
|
||||
outbytesleft += ICONV_INC_LEN;
|
||||
|
||||
// assign new outbuf from failed position
|
||||
outbuf = reinterpret_cast<char*>(str_to.data()) + len;
|
||||
@ -193,15 +209,15 @@ namespace yycc::encoding::iconv {
|
||||
constexpr auto WCHAR_CODENAME_LITERAL = "WCHAR_T"sv;
|
||||
constexpr auto UTF16_CODENAME_LITERAL =
|
||||
#if defined(YYCC_ENDIAN_LITTLE)
|
||||
"UTF16LE"sv;
|
||||
"UTF-16LE"sv;
|
||||
#else
|
||||
"UTF16BE"sv;
|
||||
"UTF-16BE"sv;
|
||||
#endif
|
||||
constexpr auto UTF32_CODENAME_LITERAL =
|
||||
#if defined(YYCC_ENDIAN_LITTLE)
|
||||
"UTF32LE"sv;
|
||||
"UTF-32LE"sv;
|
||||
#else
|
||||
"UTF32BE"sv;
|
||||
"UTF-32BE"sv;
|
||||
#endif
|
||||
|
||||
// TODO:
|
||||
@ -210,7 +226,7 @@ namespace yycc::encoding::iconv {
|
||||
// We call them VecString and VecStringView, and use them in "iconv_kernel" instead of real std::vector.
|
||||
// They exposed interface are std::vector-like but its inner is std::basic_string and std::basic_string_view.
|
||||
#define USER_CONVFN(src_char_type, dst_char_type) \
|
||||
auto rv = iconv_kernel(this->token, reinterpret_cast<const uint8_t*>(src.data()), src.size()); \
|
||||
auto rv = iconv_kernel(this->token, reinterpret_cast<const uint8_t*>(src.data()), src.size() * sizeof(src_char_type)); \
|
||||
if (rv.has_value()) { \
|
||||
const auto& dst = rv.value(); \
|
||||
if constexpr (sizeof(dst_char_type) > 1u) { \
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
|
||||
namespace yycc::encoding::iconv {
|
||||
|
||||
@ -25,14 +24,15 @@ namespace yycc::encoding::iconv {
|
||||
public:
|
||||
Token(const CodeName& from_code, const CodeName& to_code);
|
||||
~Token();
|
||||
Token(Token&& rhs) noexcept;
|
||||
Token& operator=(Token&& rhs) noexcept;
|
||||
YYCC_DELETE_COPY(Token)
|
||||
YYCC_DEFAULT_MOVE(Token)
|
||||
|
||||
bool is_valid() const;
|
||||
PrivToken* get_inner() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<PrivToken> inner;
|
||||
PrivToken* inner;
|
||||
};
|
||||
|
||||
/// @brief The possible error occurs in this module.
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "stlcvt.hpp"
|
||||
#include "stl.hpp"
|
||||
#include <locale>
|
||||
|
||||
namespace yycc::encoding::stlcvt {
|
||||
namespace yycc::encoding::stl {
|
||||
|
||||
#pragma region Generic Converter
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
namespace yycc::encoding::stlcvt {
|
||||
namespace yycc::encoding::stl {
|
||||
|
||||
/// @brief Possible convertion error occurs in this module.
|
||||
struct ConvError {};
|
||||
@ -14,30 +14,30 @@ namespace yycc::encoding::stlcvt {
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> UTF16
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF16 -> UTF8
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> UTF32
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF32 -> UTF8
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> utf8(const std::u32string_view& src);
|
||||
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
|
||||
|
||||
}
|
@ -139,12 +139,29 @@ namespace yycc::encoding::windows {
|
||||
const char* ptr = reinterpret_cast<const char*>(src.data());
|
||||
const char* end = ptr + src.size();
|
||||
|
||||
while (ptr < end) {
|
||||
size_t rc = std::mbrtoc16(&c16, ptr, end - ptr, &state);
|
||||
// YYC MARK:
|
||||
// Due to the shitty design of mbrtoc16, it forcely assume that passed string is null-terminated.
|
||||
// And the third argument should >= 1.
|
||||
// However, our given string is string view which do not have null-terminated guaranteen.
|
||||
//
|
||||
// So we manually check whether we have reach the tail of string and simulate a fake null terminal.
|
||||
// If string is still processing, we pass given string.
|
||||
// If we have reach the tail of string, we pass our homemade NULL_TERMINAL to this function to make it works normally.
|
||||
//
|
||||
// This is a stupid polyfill, however, it I do not do this,
|
||||
// there is a bug that the second part of surrogate pair will be dropped in final string,
|
||||
// if there is a Unicode character located at the tail of string which need surrogate pair to be presented.
|
||||
static const char NULL_TERMINAL = '\0';
|
||||
while (true) {
|
||||
bool not_tail = ptr < end;
|
||||
const char* new_ptr = not_tail ? ptr : &NULL_TERMINAL;
|
||||
size_t new_size = not_tail ? end - ptr : sizeof(NULL_TERMINAL);
|
||||
size_t rc = std::mbrtoc16(&c16, new_ptr, new_size, &state);
|
||||
if (!rc) break;
|
||||
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::EncodeUtf8);
|
||||
else if (rc == (size_t) -2) return std::unexpected(ConvError::IncompleteUtf8);
|
||||
else if (rc != (size_t) -3) dst.push_back(c16); // from earlier surrogate pair
|
||||
else if (rc == (size_t) -3) dst.push_back(c16); // from earlier surrogate pair
|
||||
else {
|
||||
dst.push_back(c16);
|
||||
ptr += rc;
|
||||
@ -160,12 +177,23 @@ namespace yycc::encoding::windows {
|
||||
|
||||
std::mbstate_t state{};
|
||||
char mbout[MB_LEN_MAX]{};
|
||||
size_t rc = 1; // Assign it to ONE to avoid mismatching surrogate pair checker when string is empty.
|
||||
for (char16_t c : src) {
|
||||
size_t rc = std::c16rtomb(mbout, c, &state);
|
||||
rc = std::c16rtomb(mbout, c, &state);
|
||||
|
||||
if (rc != (size_t) -1) dst.append(reinterpret_cast<char8_t*>(mbout), rc);
|
||||
else return std::unexpected(ConvError::InvalidUtf16);
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::InvalidUtf16);
|
||||
else dst.append(reinterpret_cast<char8_t*>(mbout), rc);
|
||||
}
|
||||
|
||||
if (rc == 0) {
|
||||
// YYC MARK:
|
||||
// If rc is zero after processing all chars,
|
||||
// it means that we are aborted when processing an UTF16 surrogate pair.
|
||||
// We should report it as an error.
|
||||
return std::unexpected(ConvError::InvalidUtf16);
|
||||
}
|
||||
|
||||
// Okey, return result.
|
||||
return dst;
|
||||
}
|
||||
|
||||
@ -180,11 +208,14 @@ namespace yycc::encoding::windows {
|
||||
const char* end = ptr + src.size();
|
||||
|
||||
while (ptr < end) {
|
||||
// YYC MARK:
|
||||
// There is no surrogate pair in UTF32,
|
||||
// so we do not need do that stupid things in UTF8 to UTF32 functions.
|
||||
size_t rc = std::mbrtoc32(&c32, ptr, end - ptr, &state);
|
||||
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::EncodeUtf8);
|
||||
else if (rc == (size_t) -2) return std::unexpected(ConvError::IncompleteUtf8);
|
||||
else if (rc != (size_t) -3) throw std::runtime_error("no surrogates in UTF-32");
|
||||
else if (rc == (size_t) -3) throw std::runtime_error("no surrogates in UTF-32");
|
||||
else dst.push_back(c32);
|
||||
|
||||
ptr += rc;
|
||||
@ -202,9 +233,14 @@ namespace yycc::encoding::windows {
|
||||
for (char32_t c : src) {
|
||||
size_t rc = std::c32rtomb(mbout, c, &state);
|
||||
|
||||
if (rc != (size_t) -1) dst.append(reinterpret_cast<char8_t*>(mbout), rc);
|
||||
else return std::unexpected(ConvError::InvalidUtf32);
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::InvalidUtf32);
|
||||
else dst.append(reinterpret_cast<char8_t*>(mbout), rc);
|
||||
}
|
||||
|
||||
// YYC MARK:
|
||||
// There is no surrogate pair for UTF32,
|
||||
// so this "if" statement only presented in UTF16 to UTF8 function.
|
||||
// In this function, we directly return value.
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
@ -30,61 +30,61 @@ namespace yycc::encoding::windows {
|
||||
|
||||
/**
|
||||
* @brief WChar -> Char
|
||||
* @param src
|
||||
* @param code_page
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] code_page The code page of native string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::string> to_char(const std::wstring_view& src, CodePage code_page);
|
||||
|
||||
/**
|
||||
* @brief Char -> WChar
|
||||
* @param src
|
||||
* @param code_page
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] code_page The code page of native string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::wstring> to_wchar(const std::string_view& src, CodePage code_page);
|
||||
|
||||
/**
|
||||
* @brief Char -> Char
|
||||
* @details This is the combination of "WChar -> Char" and "Char -> WChar"
|
||||
* @param src
|
||||
* @param src_code_page
|
||||
* @param dst_code_page
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] src_code_page The code page of source string.
|
||||
* @param[in] dst_code_page The code page of destination string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::string> to_char(const std::string_view& src, CodePage src_code_page, CodePage dst_code_page);
|
||||
|
||||
/**
|
||||
* @brief WChar -> UTF8
|
||||
* @details This is the specialization of "WChar -> Char"
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::wstring_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> WChar
|
||||
* @details This is the specialization of "Char -> WChar"
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::wstring> to_wchar(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief Char -> UTF8
|
||||
* @details This is the specialization of "Char -> Char"
|
||||
* @param src
|
||||
* @param code_page
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] code_page The code page of native string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::string_view& src, CodePage code_page);
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> Char
|
||||
* @details This is the specialization of "Char -> Char"
|
||||
* @param src
|
||||
* @param code_page
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] code_page The code page of native string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::string> to_char(const std::u8string_view& src, CodePage code_page);
|
||||
|
||||
@ -96,29 +96,29 @@ namespace yycc::encoding::windows {
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> UTF16
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF16 -> UTF8
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> UTF32
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF32 -> UTF8
|
||||
* @param src
|
||||
* @return
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
|
||||
|
||||
|
@ -191,4 +191,17 @@ namespace yycc::flag_enum {
|
||||
return static_cast<bool>(static_cast<ut>(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cast given enum flags to its equvalent underlying integer value like performing <TT>static_cast<std::underlying_type_t<T>>(e)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param e The enum flags to be cast.
|
||||
* @return The equvalent integer value of given enum flag.
|
||||
*/
|
||||
template<typename TEnum>
|
||||
requires(std::is_enum_v<TEnum>)
|
||||
constexpr std::underlying_type_t<TEnum> integer(TEnum e) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<ut>(e);
|
||||
}
|
||||
|
||||
} // namespace yycc::flag_enum
|
||||
|
@ -7,8 +7,8 @@
|
||||
|
||||
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = delete; \
|
||||
CLSNAME& operator=(CLSNAME&&) = delete;
|
||||
CLSNAME(CLSNAME&&) noexcept = delete; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept = delete;
|
||||
|
||||
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_COPY_MOVE(CLSNAME) \
|
||||
@ -22,10 +22,41 @@
|
||||
|
||||
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) = default; \
|
||||
CLSNAME& operator=(CLSNAME&&) = default;
|
||||
CLSNAME(CLSNAME&&) noexcept = default; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept = default;
|
||||
|
||||
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DEFAULT_COPY(CLSNAME) \
|
||||
YYCC_DEFAULT_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Make declaration of copy (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&); \
|
||||
CLSNAME& operator=(const CLSNAME&);
|
||||
|
||||
/// @brief Make declaration of move (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) noexcept; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept;
|
||||
|
||||
/// @brief Make declaration of copy and move (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DECL_COPY(CLSNAME) \
|
||||
YYCC_DECL_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Make implementation signature of copy \c constrctor for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_COPY_CTOR(CLSNAME, RHS) \
|
||||
CLSNAME::CLSNAME(const CLSNAME& RHS)
|
||||
|
||||
/// @brief Make implementation signature of copy \c operator\= for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_COPY_OPER(CLSNAME, RHS) \
|
||||
CLSNAME& CLSNAME::operator=(const CLSNAME& RHS)
|
||||
|
||||
/// @brief Make implementation signature of move \c constrctor for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_MOVE_CTOR(CLSNAME, RHS) \
|
||||
CLSNAME::CLSNAME(CLSNAME&& RHS) noexcept
|
||||
|
||||
/// @brief Make implementation signature of move \c operator\= for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_MOVE_OPER(CLSNAME, RHS) \
|
||||
CLSNAME& CLSNAME::operator=(CLSNAME&& RHS) noexcept
|
||||
|
@ -17,9 +17,9 @@ namespace yycc::macro::stl {
|
||||
|
||||
/// @brief The STL implementation kind.
|
||||
enum class StlKind {
|
||||
MSSTL, ///< Microsoft STL
|
||||
GNUSTL, ///< GNU STL
|
||||
CLANGSTL ///< Clang STL
|
||||
MsStl, ///< Microsoft STL
|
||||
GnuStl, ///< GNU STL
|
||||
ClangStl ///< Clang STL
|
||||
};
|
||||
|
||||
/**
|
||||
@ -28,11 +28,11 @@ namespace yycc::macro::stl {
|
||||
*/
|
||||
inline constexpr StlKind get_stl() {
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
return StlKind::MSSTL;
|
||||
return StlKind::MsStl;
|
||||
#elif defined(YYCC_STL_GNUSTL)
|
||||
return StlKind::GNUSTL;
|
||||
return StlKind::GnuStl;
|
||||
#else
|
||||
return StlKind::CLANGSTL;
|
||||
return StlKind::ClangStl;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,7 @@ namespace yycc::num::parse {
|
||||
using ParseResult = std::expected<T, ParseError>;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Internal parsing function for floating point types
|
||||
* @brief Parse given string into floating point types
|
||||
* @tparam T Floating point type (float, double, etc)
|
||||
* @param strl The UTF-8 string view to parse
|
||||
* @param fmt The floating point format to use
|
||||
@ -66,8 +65,7 @@ namespace yycc::num::parse {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Internal parsing function for integral types (except bool)
|
||||
* @brief Parse given string into integral types (except bool)
|
||||
* @tparam T Integral type (int, long, etc)
|
||||
* @param strl The UTF-8 string view to parse
|
||||
* @param base Numeric base (2-36)
|
||||
@ -101,8 +99,7 @@ namespace yycc::num::parse {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Internal parsing function for boolean type
|
||||
* @brief Parse given string into boolean type
|
||||
* @tparam T Must be bool type
|
||||
* @param strl The UTF-8 string view to parse ("true" or "false", case insensitive)
|
||||
* @return ParseResult<bool> containing either the parsed value or a ParseError
|
||||
|
@ -17,6 +17,11 @@
|
||||
|
||||
// Import essential header if we are using Windows function family.
|
||||
#if defined(YYCC_HARDWARE_OVERFLOW_WIN32_FNS)
|
||||
// YYC MARK:
|
||||
// This macro is crucial for including "intsafe.h"
|
||||
// Without this, "intsafe.h" will not enable signed integral operations.
|
||||
#define ENABLE_INTSAFE_SIGNED_FUNCTIONS
|
||||
|
||||
#include "../windows/import_guard_head.hpp"
|
||||
#include <intsafe.h>
|
||||
#include "../windows/import_guard_tail.hpp"
|
||||
@ -486,12 +491,19 @@ namespace yycc::num::safe_op {
|
||||
* @brief The result returned by the overflow function family.
|
||||
* @details
|
||||
* The first item is the operation result.
|
||||
* The second item indicates whether an overflow has occurred. true means overflow, otherwise false.
|
||||
* The second item indicating whether an overflow has occurred. True means overflow, otherwise false.
|
||||
*/
|
||||
template<typename T>
|
||||
requires std::integral<T>
|
||||
using OverflowingPair = std::pair<T, bool>;
|
||||
|
||||
/**
|
||||
* @brief Performs an addition operation with overflow detection.
|
||||
* @tparam T Integer type.
|
||||
* @param[in] a The left operand of the addition.
|
||||
* @param[in] b The right operand of the addition.
|
||||
* @return A pair holding result and overflow flag.
|
||||
*/
|
||||
template<typename T>
|
||||
requires std::integral<T>
|
||||
OverflowingPair<T> overflowing_add(T a, T b) {
|
||||
@ -500,6 +512,13 @@ namespace yycc::num::safe_op {
|
||||
return std::make_pair(result, overflow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs a subtraction operation with overflow detection.
|
||||
* @tparam T Integer type.
|
||||
* @param[in] a The left operand of the subtraction.
|
||||
* @param[in] b The right operand of the subtraction.
|
||||
* @return A pair holding result and overflow flag.
|
||||
*/
|
||||
template<typename T>
|
||||
requires std::integral<T>
|
||||
OverflowingPair<T> overflowing_sub(T a, T b) {
|
||||
@ -508,6 +527,13 @@ namespace yycc::num::safe_op {
|
||||
return std::make_pair(result, overflow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs a multiplication operation with overflow detection.
|
||||
* @tparam T Integer type.
|
||||
* @param[in] a The left operand of the multiplication.
|
||||
* @param[in] b The right operand of the multiplication.
|
||||
* @return A pair holding result and overflow flag.
|
||||
*/
|
||||
template<typename T>
|
||||
requires std::integral<T>
|
||||
OverflowingPair<T> overflowing_mul(T a, T b) {
|
||||
@ -516,6 +542,14 @@ namespace yycc::num::safe_op {
|
||||
return std::make_pair(result, overflow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs a division operation with overflow detection.
|
||||
* @tparam T Integer type.
|
||||
* @param[in] a The left operand of the division.
|
||||
* @param[in] b The right operand of the division.
|
||||
* @return A pair holding result and overflow flag.
|
||||
* @exception std::logic_error If division by zero occurs.
|
||||
*/
|
||||
template<typename T>
|
||||
requires std::integral<T>
|
||||
OverflowingPair<T> overflowing_div(T a, T b) {
|
||||
|
@ -55,6 +55,7 @@ namespace yycc::num::stringify {
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the string representation of given integral value.
|
||||
* @tparam T The type derived from integral type except bool type.
|
||||
@ -82,6 +83,7 @@ namespace yycc::num::stringify {
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the string representation of given bool value.
|
||||
* @tparam T The type derived from bool type.
|
||||
|
@ -1,35 +1,39 @@
|
||||
#include "fopen.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../string/reinterpret.hpp"
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "../windows/import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include "../windows/import_guard_tail.hpp"
|
||||
#endif
|
||||
|
||||
// Include header file including MSVCRT specific function.
|
||||
#include <wchar.h>
|
||||
// Include encoding convertion header
|
||||
#include "../encoding/windows.hpp"
|
||||
// Define namespace macro
|
||||
#define ENC ::yycc::encoding::windows
|
||||
#else
|
||||
// Include reinterpret header and define namespace macro
|
||||
#include "../string/reinterpret.hpp"
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
#endif
|
||||
|
||||
namespace yycc::patch::fopen {
|
||||
|
||||
std::FILE* fopen(const char8_t* u8_filepath, const char8_t* u8_mode) {
|
||||
// TODO: Fix this after finish Windows encoding
|
||||
// #if defined(YYCC_OS_WINDOWS)
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// convert mode and file path to wchar
|
||||
auto wmode = ENC::to_wchar(u8_mode);
|
||||
auto wpath = ENC::to_wchar(u8_filepath);
|
||||
|
||||
// // convert mode and file path to wchar
|
||||
// std::wstring wmode, wpath;
|
||||
// if (!YYCC::EncodingHelper::UTF8ToWchar(u8_mode, wmode))
|
||||
// return nullptr;
|
||||
// if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filepath, wpath))
|
||||
// return nullptr;
|
||||
|
||||
// // call microsoft specified fopen which support wchar as argument.
|
||||
// return _wfopen(wpath.c_str(), wmode.c_str());
|
||||
|
||||
// #else
|
||||
// return std::fopen(REINTERPRET::as_ordinary(u8_filepath), REINTERPRET::as_ordinary(u8_mode));
|
||||
// #endif
|
||||
// check convertion success
|
||||
if (wmode.has_value() && wpath.has_value()) {
|
||||
// Call MSVCRT specified fopen which support wchar as argument.
|
||||
// Reference: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170
|
||||
return _wfopen(wpath.value().c_str(), wmode.value().c_str());
|
||||
} else {
|
||||
// fail to convert encoding
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
return std::fopen(REINTERPRET::as_ordinary(u8_filepath), REINTERPRET::as_ordinary(u8_mode));
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace yycc::patch::fopen
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <cstdio>
|
||||
|
||||
namespace yycc::patch::fopen {
|
||||
|
||||
|
159
src/yycc/rust/env.cpp
Normal file
159
src/yycc/rust/env.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
#include "env.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "../encoding/windows.hpp"
|
||||
#include "../num/safe_op.hpp"
|
||||
#include "../num/safe_cast.hpp"
|
||||
#include <Windows.h>
|
||||
#include <winbase.h>
|
||||
#else
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
#define SAFECAST ::yycc::num::safe_cast
|
||||
#define SAFEOP ::yycc::num::safe_op
|
||||
#define ENC ::yycc::encoding::windows
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycc::rust::env {
|
||||
|
||||
EnvResult<std::u8string> get_var(const std::u8string_view &name) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
|
||||
// Convert to wchar
|
||||
auto wname = ENC::to_wchar(name);
|
||||
if (!wname.has_value()) {
|
||||
return std::unexpected(EnvError::BadEncoding);
|
||||
}
|
||||
|
||||
// Prepare a variable with proper init size for holding value.
|
||||
std::wstring wvalue;
|
||||
wvalue.resize(256);
|
||||
|
||||
// Start to fetch value
|
||||
while (true) {
|
||||
// YYC MARK:
|
||||
// Due to the shitty design of GetEnvironmentVariableW,
|
||||
// the size passed to this function must include NULL terminal.
|
||||
// So we forcely use checked add and sub for this bad behavior.
|
||||
auto fct_size = SAFEOP::checked_add<size_t>(wvalue.size(), 1);
|
||||
if (!fct_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
auto rv = ::GetEnvironmentVariableW(wname.value().c_str(), wvalue.data(), fct_size.value());
|
||||
|
||||
// Check the return value
|
||||
if (rv == 0) {
|
||||
// Function failed. Extract error reason.
|
||||
auto ec = GetLastError();
|
||||
if (ec == ERROR_ENVVAR_NOT_FOUND) return std::unexpected(EnvError::NoSuchName);
|
||||
else return std::unexpected(EnvError::BadCall);
|
||||
} else {
|
||||
// Function okey. Check the size.
|
||||
// Fetch function expected size.
|
||||
auto rv_size = SAFECAST::try_to<size_t>(rv);
|
||||
if (!rv_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
auto exp_size = SAFEOP::checked_sub<size_t>(rv_size.value(), 1);
|
||||
if (!exp_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
|
||||
// YYC MARK:
|
||||
// According to Microsoft, the return value of this function is just a bullshit.
|
||||
// If "wvalue" is big enough, the range of return value is [0, wvalue.size()],
|
||||
// indicating the size of final string, excluding NULL terminal.
|
||||
// otherwise, the range of return value is [wvalue.size() + 1, +inf),
|
||||
// indicating the required size of buffer including NULL terminal.
|
||||
// So we must treat this return value carefully.
|
||||
if (exp_size.value() <= wvalue.size()) {
|
||||
// Okey, shrink to the real size of value and break.
|
||||
wvalue.resize(rv_size.value());
|
||||
break;
|
||||
} else {
|
||||
// We need resize it and try again.
|
||||
wvalue.resize(exp_size.value());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to UTF8 string and return.
|
||||
return ENC::to_utf8(wvalue).transform_error([](auto err) { return EnvError::BadEncoding; });
|
||||
#else
|
||||
// String view is not NULL-terminal-guaranted,
|
||||
// so we solve this when casting its type.
|
||||
auto ordinary_name = REINTERPRET::as_ordinary(name);
|
||||
|
||||
// Fetch variable
|
||||
auto finder = std::getenv(ordinary_name.c_str());
|
||||
if (finder == nullptr) return std::unexpected(EnvError::NoSuchName);
|
||||
else return REINTERPRET::as_utf8(finder);
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvResult<void> set_var(const std::u8string_view &name, const std::u8string_view &value) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
|
||||
|
||||
// Convert to wchar
|
||||
auto wname = ENC::to_wchar(name);
|
||||
auto wvalue = ENC::to_wchar(value);
|
||||
if (!(wname.has_value() && wvalue.has_value())) {
|
||||
return std::unexpected(EnvError::BadEncoding);
|
||||
}
|
||||
|
||||
// Delete variable and check result.
|
||||
auto rv = ::SetEnvironmentVariableW(wname.value().c_str(), wvalue.value().c_str());
|
||||
if (!rv) {
|
||||
return std::unexpected(EnvError::BadCall);
|
||||
}
|
||||
|
||||
return {};
|
||||
#else
|
||||
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html
|
||||
|
||||
// Set variable
|
||||
auto ordinary_name = REINTERPRET::as_ordinary(name);
|
||||
auto ordinary_value = REINTERPRET::as_ordinary(value);
|
||||
auto rv = setenv(ordinary_name.c_str(), ordinary_value.c_str(), 1);
|
||||
if (rv == 0) return {};
|
||||
|
||||
// Check error type
|
||||
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
|
||||
else if (errno == ENOMEM) return std::unexpected(EnvError::NoMemory);
|
||||
else throw std::runtime_error("impossible errno");
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvResult<void> del_var(const std::u8string_view &name) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
|
||||
|
||||
// Convert to wchar
|
||||
auto wname = ENC::to_wchar(name);
|
||||
if (!wname.has_value()) {
|
||||
return std::unexpected(EnvError::BadEncoding);
|
||||
}
|
||||
|
||||
// Delete variable and check result.
|
||||
auto rv = ::SetEnvironmentVariableW(wname.value().c_str(), NULL);
|
||||
if (!rv) {
|
||||
return std::unexpected(EnvError::BadCall);
|
||||
}
|
||||
|
||||
return {};
|
||||
#else
|
||||
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html
|
||||
|
||||
// Delete variable
|
||||
auto ordinary_name = REINTERPRET::as_ordinary(name);
|
||||
auto rv = unsetenv(ordinary_name.c_str());
|
||||
if (rv == 0) return {};
|
||||
|
||||
// Check error type
|
||||
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
|
||||
else throw std::runtime_error("impossible errno");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace yycc::rust::env
|
62
src/yycc/rust/env.hpp
Normal file
62
src/yycc/rust/env.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
/**
|
||||
* @brief The namespace providing environment variable operations.
|
||||
* @details
|
||||
* When I programming with Rust, I was astonished that
|
||||
* Rust standard library have so much robust environment variable operations.
|
||||
* Oppositly, C++ STL still lake in this even in today.
|
||||
*
|
||||
* The functions manipulating environment variable is different in different OS.
|
||||
* I create this namespace inspired from Rust standard library
|
||||
* to glue all these things up and make a uniform interface.
|
||||
*/
|
||||
namespace yycc::rust::env {
|
||||
|
||||
/// @brief The error occurs in this module.
|
||||
enum class EnvError {
|
||||
NoSuchName, ///< The variable with given name is not presented.
|
||||
BadEncoding, ///< Error when performing encoding convertion.
|
||||
BadArithmetic, ///< Error when performing arithmetic operations.
|
||||
BadCall, ///< Error occurs when calling backend functions.
|
||||
BadName, ///< Given name is ill-formated (empty string or has "=" character).
|
||||
NoMemory, ///< No enough memory to finish this operation.
|
||||
};
|
||||
|
||||
/// @brief The result type in this module.
|
||||
template<typename T>
|
||||
using EnvResult = std::expected<T, EnvError>;
|
||||
|
||||
/**
|
||||
* @brief Get the value of given environment variable name.
|
||||
* @param[in] name The name of environment variable
|
||||
* @return Gotten value, or error occurs.
|
||||
*/
|
||||
EnvResult<std::u8string> get_var(const std::u8string_view& name);
|
||||
|
||||
/**
|
||||
* @brief Set the value of given environment variable name.
|
||||
* @details
|
||||
* If there is no such name variable presented in environment,
|
||||
* a new variable will be created,
|
||||
* otherwise, new value will overwrite old value.
|
||||
* @param[in] name The name of environment variable
|
||||
* @param[in] value The value to be written into.
|
||||
* @return Nothing or error occurs.
|
||||
*/
|
||||
EnvResult<void> set_var(const std::u8string_view& name, const std::u8string_view& value);
|
||||
|
||||
/**
|
||||
* @brief Delete environment variable with given name.
|
||||
* @details
|
||||
* If given variable is not presented in environment,
|
||||
* this function will NOT return error.
|
||||
* @param[in] name The name of environment variable
|
||||
* @return Nothing, or error occurs.
|
||||
*/
|
||||
EnvResult<void> del_var(const std::u8string_view& name);
|
||||
|
||||
} // namespace yycc::rust::env
|
@ -1,30 +1,35 @@
|
||||
#include "panic.hpp"
|
||||
|
||||
#include "../carton/termcolor.hpp"
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <stacktrace>
|
||||
|
||||
#define TERMCOLOR ::yycc::carton::termcolor
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycc::rust::panic {
|
||||
|
||||
// TODO:
|
||||
// I sadly remove the stacktrace feature for panic function.
|
||||
// Because in GCC, it has link error (can be fixed by extra link option).
|
||||
// In Clang, it even lack the whole header file.
|
||||
// It seems that STL providers are not ready for this feature. So I decide remove it entirely.
|
||||
// Once every STL probiders have ready for this, I will add it back.
|
||||
|
||||
void panic(const char* file, int line, const std::string_view& msg) {
|
||||
// Output message in stderr.
|
||||
auto& dst = std::cerr;
|
||||
|
||||
// TODO: Fix colorful output when finishing `termcolor` lib.
|
||||
|
||||
// Print error message if we support it.
|
||||
// // Setup color
|
||||
// dst << FOREGROUND<Color::Red>;
|
||||
// Setup color
|
||||
dst << REINTERPRET::as_ordinary_view(TERMCOLOR::foreground(TERMCOLOR::Color::Red));
|
||||
// File name and line number message
|
||||
dst << "program paniked at " << std::quoted(file) << ":Ln" << line << std::endl;
|
||||
// User custom message
|
||||
dst << "note: " << msg << std::endl;
|
||||
// Stacktrace message if we support it.
|
||||
dst << "stacktrace: " << std::endl;
|
||||
dst << std::stacktrace::current() << std::endl;
|
||||
// // Restore color
|
||||
// dst << RESET;
|
||||
// Restore color
|
||||
dst << REINTERPRET::as_ordinary_view(TERMCOLOR::reset());;
|
||||
|
||||
// Make sure all messages are flushed into screen.
|
||||
dst.flush();
|
||||
|
@ -34,7 +34,7 @@ namespace yycc::rust::panic {
|
||||
* The macro parameters are the message to format and its arguments, following \c std::format syntax.
|
||||
* This macro essentially calls \c std::format internally.
|
||||
*/
|
||||
#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg __VA_OPT__(, ) __VA_ARGS__))
|
||||
#define RS_PANICF(msg, ...) RS_PANIC(std::format(msg __VA_OPT__(,) __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* @brief Immediately crashes the entire program like Rust's \c panic! macro.
|
||||
|
23
src/yycc/string/stream.cpp
Normal file
23
src/yycc/string/stream.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "stream.hpp"
|
||||
#include "reinterpret.hpp"
|
||||
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycc::string::stream {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const std::u8string_view& u8str) {
|
||||
os << REINTERPRET::as_ordinary_view(u8str);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const char8_t* u8str) {
|
||||
os << REINTERPRET::as_ordinary(u8str);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, char8_t u8chr) {
|
||||
os << static_cast<char>(u8chr);
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
17
src/yycc/string/stream.hpp
Normal file
17
src/yycc/string/stream.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include <ostream>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* @brief This namespace add UTF8 support for \c std::ostream.
|
||||
* @details
|
||||
* The operator overloads written in this namespace will give \c std::ostream ability to write UTF8 string and its char.
|
||||
* For using this feature, please directly use <TT>using namespace yycc::string:stream;</TT> to import this namespace.
|
||||
*/
|
||||
namespace yycc::string::stream {
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const std::u8string_view& u8str);
|
||||
std::ostream& operator<<(std::ostream& os, const char8_t* u8str);
|
||||
std::ostream& operator<<(std::ostream& os, char8_t u8chr);
|
||||
|
||||
}
|
46
src/yycc/windows/com.cpp
Normal file
46
src/yycc/windows/com.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include "com.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
namespace yycc::windows::com {
|
||||
|
||||
/**
|
||||
* @brief The guard for initialize COM environment.
|
||||
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
|
||||
* and it also will try uninitializing COM environment when destructing.
|
||||
* If initialization failed, uninitialization will not be executed.
|
||||
*/
|
||||
class ComGuard {
|
||||
public:
|
||||
ComGuard() : m_HasInit(false) {
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (SUCCEEDED(hr)) m_HasInit = true;
|
||||
}
|
||||
~ComGuard() {
|
||||
if (m_HasInit) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsInitialized() const { return m_HasInit; }
|
||||
|
||||
protected:
|
||||
bool m_HasInit;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The instance of COM environment guard.
|
||||
* @details Dialog related function need COM environment,
|
||||
* so we need initializing COM environment when loading this module,
|
||||
* and uninitializing COM environment when we no longer use this module.
|
||||
* So we use a static instance in here.
|
||||
* And make it be const so no one can change it.
|
||||
*/
|
||||
static const ComGuard COM_GUARD{};
|
||||
|
||||
bool is_initialized() {
|
||||
return COM_GUARD.IsInitialized();
|
||||
}
|
||||
|
||||
} // namespace yycc::windows::com
|
||||
|
||||
#endif
|
70
src/yycc/windows/com.hpp
Normal file
70
src/yycc/windows/com.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
/**
|
||||
* @brief Windows COM related types and checker.
|
||||
* @details
|
||||
* This namespace is Windows specific.
|
||||
* In other platforms, this whole namespace will be unavailable.
|
||||
*/
|
||||
namespace yycc::windows::com {
|
||||
|
||||
/// @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
|
||||
class ComPtrDeleter {
|
||||
public:
|
||||
ComPtrDeleter() {}
|
||||
void operator()(IUnknown* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
com_ptr->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of \c IFileDialog
|
||||
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IFileOpenDialog
|
||||
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItem
|
||||
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellItemArray
|
||||
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
|
||||
/// @brief Smart unique pointer of \c IShellFolder
|
||||
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
|
||||
|
||||
/// @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
|
||||
class CoTaskMemDeleter {
|
||||
public:
|
||||
CoTaskMemDeleter() {}
|
||||
void operator()(void* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
CoTaskMemFree(com_ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Smart unique pointer of COM created \c WCHAR sequence.
|
||||
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
|
||||
|
||||
/**
|
||||
* @brief Check whether COM environment has been initialized.
|
||||
* @return True if it is, otherwise false.
|
||||
* @remarks
|
||||
* This function will call corresponding function of COM Guard.
|
||||
* Do not remove this function and you must preserve at least one reference to this function in final program.
|
||||
* Some compiler will try to drop COM Guard in final program if no reference to it and it will cause the initialization of COM environment failed.
|
||||
* This is the reason why I order you do the things said above.
|
||||
*/
|
||||
bool is_initialized();
|
||||
|
||||
} // namespace yycc::windows::com
|
||||
|
||||
#endif
|
38
src/yycc/windows/console.cpp
Normal file
38
src/yycc/windows/console.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
#include "console.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include <io.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
namespace yycc::windows::console {
|
||||
|
||||
static ExecResult<void> colorful_fs(FILE* fs) {
|
||||
if (!_isatty(_fileno(fs))) {
|
||||
return std::unexpected(ExecError::NotTty);
|
||||
}
|
||||
|
||||
HANDLE h_output;
|
||||
DWORD dw_mode;
|
||||
|
||||
h_output = (HANDLE) _get_osfhandle(_fileno(fs));
|
||||
if (!GetConsoleMode(h_output, &dw_mode)) {
|
||||
return std::unexpected(ExecError::GetMode);
|
||||
}
|
||||
if (!SetConsoleMode(h_output, dw_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) {
|
||||
return std::unexpected(ExecError::SetMode);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ExecResult<void> colorful_console() {
|
||||
return colorful_fs(stdout).and_then([]() { return colorful_fs(stderr); });
|
||||
}
|
||||
|
||||
} // namespace yycc::windows::console
|
||||
|
||||
#endif
|
33
src/yycc/windows/console.hpp
Normal file
33
src/yycc/windows/console.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include <expected>
|
||||
|
||||
/**
|
||||
* @brief The namespace provide patches for Windows console.
|
||||
*/
|
||||
namespace yycc::windows::console {
|
||||
|
||||
/// @brief Error occurs in this module.
|
||||
enum class ExecError {
|
||||
NotTty, ///< Given stream is not TTY.
|
||||
GetMode, ///< Can not get stream mode.
|
||||
SetMode, ///< Can not set stream mode.
|
||||
};
|
||||
|
||||
/// @brief Result type used in this module.
|
||||
template<typename T>
|
||||
using ExecResult = std::expected<T, ExecError>;
|
||||
|
||||
/**
|
||||
* @brief Enable console color support for Windows.
|
||||
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
|
||||
* @return Nothing or error occurs.
|
||||
*/
|
||||
ExecResult<void> colorful_console();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
620
src/yycc/windows/dialog.cpp
Normal file
620
src/yycc/windows/dialog.cpp
Normal file
@ -0,0 +1,620 @@
|
||||
#include "dialog.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include "../string/op.hpp"
|
||||
#include "../encoding/windows.hpp"
|
||||
#include "../num/safe_cast.hpp"
|
||||
#include "../num/safe_op.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
#define ENC ::yycc::encoding::windows
|
||||
#define OP ::yycc::string::op
|
||||
#define SAFECAST ::yycc::num::safe_cast
|
||||
#define SAFEOP ::yycc::num::safe_op
|
||||
#define WINCOM ::yycc::windows::com
|
||||
|
||||
namespace yycc::windows::dialog {
|
||||
|
||||
#pragma region WinFileFilters
|
||||
|
||||
WinFileFilters::WinFileFilters() : m_WinFilters(), m_WinDataStruct() {}
|
||||
|
||||
WinFileFilters::~WinFileFilters() {}
|
||||
|
||||
YYCC_IMPL_COPY_CTOR(WinFileFilters, rhs) : m_WinFilters(rhs.m_WinFilters), m_WinDataStruct() {
|
||||
// Update data
|
||||
this->update();
|
||||
}
|
||||
|
||||
YYCC_IMPL_COPY_OPER(WinFileFilters, rhs) {
|
||||
// Copy data and mark desync
|
||||
this->m_WinFilters = rhs.m_WinFilters;
|
||||
this->m_WinDataStruct.clear();
|
||||
// Update data
|
||||
this->update();
|
||||
// Return self
|
||||
return *this;
|
||||
}
|
||||
|
||||
YYCC_IMPL_MOVE_CTOR(WinFileFilters, rhs) : m_WinFilters(std::move(rhs.m_WinFilters)), m_WinDataStruct() {
|
||||
// In theory, there is no update should perform,
|
||||
// however we do it because there is no guarantee that no memory allocation during this move.
|
||||
this->update();
|
||||
}
|
||||
|
||||
YYCC_IMPL_MOVE_OPER(WinFileFilters, rhs) {
|
||||
// Move data
|
||||
this->m_WinFilters = std::move(rhs.m_WinFilters);
|
||||
this->m_WinDataStruct.clear();
|
||||
// Same reason for updating
|
||||
this->update();
|
||||
// Return self
|
||||
return *this;
|
||||
}
|
||||
|
||||
UINT WinFileFilters::get_filter_count() const {
|
||||
// We have check this length when building this class,
|
||||
// so we can safely and directly cast it in there.
|
||||
return static_cast<UINT>(m_WinFilters.size());
|
||||
}
|
||||
|
||||
const COMDLG_FILTERSPEC* WinFileFilters::get_filter_specs() const {
|
||||
return m_WinDataStruct.data();
|
||||
}
|
||||
|
||||
void WinFileFilters::update() {
|
||||
// Make sure they have same size
|
||||
auto n = m_WinFilters.size();
|
||||
m_WinDataStruct.resize(n);
|
||||
|
||||
// Assign data one by one
|
||||
for (auto i = 0; i < n; ++i) {
|
||||
// Fetch item
|
||||
const auto& filter = m_WinFilters[i];
|
||||
auto& data_struct = m_WinDataStruct[i];
|
||||
// Assign pointer
|
||||
data_struct.pszName = filter.first.c_str();
|
||||
data_struct.pszSpec = filter.second.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region FileFilters
|
||||
|
||||
FileFilters::FileFilters() : m_Filters() {}
|
||||
|
||||
FileFilters::~FileFilters() {}
|
||||
|
||||
void FileFilters::add_filter(const std::u8string_view& filter_name, std::initializer_list<std::u8string_view> il) {
|
||||
// assign filter name
|
||||
FilterName name(filter_name);
|
||||
// check filter name
|
||||
if (name.empty()) {
|
||||
throw std::invalid_argument("filter name is empty");
|
||||
}
|
||||
|
||||
// assign filter patterns
|
||||
FilterModes modes;
|
||||
for (const auto& item : il) {
|
||||
modes.emplace_back(item);
|
||||
}
|
||||
// check filter patterns
|
||||
if (modes.empty()) {
|
||||
throw std::invalid_argument("filter pattern list is empty");
|
||||
}
|
||||
|
||||
// add into pairs and return
|
||||
m_Filters.emplace_back(std::make_pair(std::move(name), std::move(modes)));
|
||||
}
|
||||
|
||||
size_t FileFilters::get_count() const {
|
||||
return m_Filters.size();
|
||||
}
|
||||
|
||||
void FileFilters::clear() {
|
||||
m_Filters.clear();
|
||||
}
|
||||
|
||||
DialogResult<WinFileFilters> FileFilters::to_windows() const {
|
||||
// prepare return value
|
||||
WinFileFilters rv;
|
||||
|
||||
// check filter size
|
||||
// if it overflow the maximum value, return.
|
||||
size_t count = m_Filters.size();
|
||||
if (!SAFECAST::try_to<UINT>(count).has_value()) {
|
||||
return std::unexpected(DialogError::TooManyFilters);
|
||||
}
|
||||
|
||||
// build new Windows oriented string vector
|
||||
for (const auto& item : m_Filters) {
|
||||
// convert name to wchar
|
||||
auto win_name = ENC::to_wchar(item.first);
|
||||
if (!win_name.has_value()) {
|
||||
return std::unexpected(DialogError::BadEncoding);
|
||||
}
|
||||
|
||||
// join pattern string and convert to wchar
|
||||
const auto& modes = item.second;
|
||||
auto joined_modes = OP::join(modes.begin(), modes.end(), u8";");
|
||||
auto win_modes = ENC::to_wchar(joined_modes);
|
||||
if (!win_modes.has_value()) {
|
||||
return std::unexpected(DialogError::BadEncoding);
|
||||
}
|
||||
|
||||
// append this pair
|
||||
rv.m_WinFilters.emplace_back(std::make_pair(win_name.value(), win_modes.value()));
|
||||
}
|
||||
|
||||
// update data struct
|
||||
rv.update();
|
||||
|
||||
// okey, return value
|
||||
// please note that we do not check whether this list is empty in there.
|
||||
// this was done in generic_file_dialog() function.
|
||||
return rv;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region WinFileDialog
|
||||
|
||||
/**
|
||||
* @brief Assistant function for making copy of IShellItem*.
|
||||
* @param[in] obj The object for making copy.
|
||||
* @return Copied COM object.
|
||||
*/
|
||||
static WINCOM::SmartIShellItem duplicate_shell_item(const WINCOM::SmartIShellItem& obj) {
|
||||
// COM object is actually like a std::shared_ptr.
|
||||
// So we simply copy its raw pointer and increase it reference counter if it is not nullptr.
|
||||
WINCOM::SmartIShellItem rv(obj.get());
|
||||
if (rv) rv->AddRef();
|
||||
// Okey, return copied object.
|
||||
return rv;
|
||||
}
|
||||
|
||||
WinFileDialog::WinFileDialog() :
|
||||
m_WinOwner(NULL), m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u), m_WinTitle(std::nullopt), m_WinInitFileName(std::nullopt),
|
||||
m_WinInitDirectory(nullptr) {}
|
||||
|
||||
WinFileDialog::~WinFileDialog() {}
|
||||
|
||||
YYCC_IMPL_COPY_CTOR(WinFileDialog, rhs) :
|
||||
m_WinOwner(rhs.m_WinOwner), m_WinFileTypes(rhs.m_WinFileTypes), m_WinDefaultFileTypeIndex(rhs.m_WinDefaultFileTypeIndex),
|
||||
m_WinTitle(rhs.m_WinTitle), m_WinInitFileName(rhs.m_WinInitFileName),
|
||||
m_WinInitDirectory(duplicate_shell_item(rhs.m_WinInitDirectory)) {}
|
||||
|
||||
YYCC_IMPL_COPY_OPER(WinFileDialog, rhs) {
|
||||
this->m_WinOwner = rhs.m_WinOwner;
|
||||
this->m_WinFileTypes = rhs.m_WinFileTypes;
|
||||
this->m_WinDefaultFileTypeIndex = rhs.m_WinDefaultFileTypeIndex;
|
||||
this->m_WinTitle = rhs.m_WinTitle;
|
||||
this->m_WinInitFileName = rhs.m_WinInitFileName;
|
||||
this->m_WinInitDirectory = duplicate_shell_item(rhs.m_WinInitDirectory);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool WinFileDialog::has_owner() const {
|
||||
return m_WinOwner != NULL;
|
||||
}
|
||||
|
||||
HWND WinFileDialog::get_owner() const {
|
||||
if (!has_owner()) throw std::logic_error("fetch not set owner");
|
||||
return m_WinOwner;
|
||||
}
|
||||
|
||||
bool WinFileDialog::has_title() const {
|
||||
return m_WinTitle.has_value();
|
||||
}
|
||||
|
||||
const wchar_t* WinFileDialog::get_title() const {
|
||||
if (!has_title()) throw std::logic_error("fetch not set title");
|
||||
return m_WinTitle.value().c_str();
|
||||
}
|
||||
|
||||
bool WinFileDialog::has_init_file_name() const {
|
||||
return m_WinInitFileName.has_value();
|
||||
}
|
||||
|
||||
const wchar_t* WinFileDialog::get_init_file_name() const {
|
||||
if (!has_init_file_name()) throw std::logic_error("fetch not set init file name");
|
||||
return m_WinInitFileName.value().c_str();
|
||||
}
|
||||
|
||||
bool WinFileDialog::has_init_directory() const {
|
||||
return m_WinInitDirectory.get() != nullptr;
|
||||
}
|
||||
|
||||
IShellItem* WinFileDialog::get_init_directory() const {
|
||||
if (!has_init_file_name()) throw std::logic_error("fetch not set init directory");
|
||||
return m_WinInitDirectory.get();
|
||||
}
|
||||
|
||||
const WinFileFilters& WinFileDialog::get_file_types() const {
|
||||
return m_WinFileTypes;
|
||||
}
|
||||
|
||||
UINT WinFileDialog::get_default_file_type_index() const {
|
||||
// This index has been checked so we return it directly.
|
||||
return m_WinDefaultFileTypeIndex;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region FileDialog
|
||||
|
||||
FileDialog::FileDialog() :
|
||||
m_Owner(NULL), m_FileTypes(), m_DefaultFileTypeIndex(0u), m_Title(std::nullopt), m_InitFileName(std::nullopt),
|
||||
m_InitDirectory(std::nullopt) {}
|
||||
|
||||
FileDialog::~FileDialog() {}
|
||||
|
||||
void FileDialog::set_owner(HWND owner) {
|
||||
m_Owner = owner;
|
||||
}
|
||||
|
||||
void FileDialog::set_title(const std::u8string_view& title) {
|
||||
m_Title = title;
|
||||
}
|
||||
|
||||
void FileDialog::unset_title() {
|
||||
m_Title = std::nullopt;
|
||||
}
|
||||
|
||||
void FileDialog::set_init_file_name(const std::u8string_view& init_filename) {
|
||||
m_InitFileName = init_filename;
|
||||
}
|
||||
|
||||
void FileDialog::unset_init_file_name() {
|
||||
m_InitFileName = std::nullopt;
|
||||
}
|
||||
|
||||
void FileDialog::set_init_directory(const std::u8string_view& init_dir) {
|
||||
m_InitDirectory = init_dir;
|
||||
}
|
||||
|
||||
void FileDialog::unset_init_directory() {
|
||||
m_InitDirectory = std::nullopt;
|
||||
}
|
||||
|
||||
FileFilters& FileDialog::configre_file_types() {
|
||||
return m_FileTypes;
|
||||
}
|
||||
|
||||
void FileDialog::set_default_file_type_index(size_t idx) {
|
||||
m_DefaultFileTypeIndex = idx;
|
||||
}
|
||||
|
||||
void FileDialog::clear() {
|
||||
m_Owner = NULL;
|
||||
m_Title = std::nullopt;
|
||||
m_InitFileName = std::nullopt;
|
||||
m_InitDirectory = std::nullopt;
|
||||
m_FileTypes.clear();
|
||||
m_DefaultFileTypeIndex = 0u;
|
||||
}
|
||||
|
||||
DialogResult<WinFileDialog> FileDialog::to_windows() const {
|
||||
// prepare return value
|
||||
WinFileDialog rv;
|
||||
|
||||
// set owner
|
||||
rv.m_WinOwner = m_Owner;
|
||||
|
||||
// build title and init file name
|
||||
if (m_Title.has_value()) {
|
||||
auto win_title = ENC::to_wchar(m_Title.value());
|
||||
if (!win_title.has_value()) return std::unexpected(DialogError::BadEncoding);
|
||||
else rv.m_WinTitle = win_title.value();
|
||||
} else rv.m_WinTitle = std::nullopt;
|
||||
if (m_InitFileName.has_value()) {
|
||||
auto win_init_file_name = ENC::to_wchar(m_InitFileName.value());
|
||||
if (!win_init_file_name.has_value()) return std::unexpected(DialogError::BadEncoding);
|
||||
else rv.m_WinInitFileName = win_init_file_name.value();
|
||||
} else rv.m_WinInitFileName = std::nullopt;
|
||||
|
||||
// fetch init directory
|
||||
if (m_InitDirectory.has_value()) {
|
||||
// convert to wchar path
|
||||
auto w_init_dir = ENC::to_wchar(m_InitDirectory.value());
|
||||
if (!w_init_dir.has_value()) {
|
||||
return std::unexpected(DialogError::BadEncoding);
|
||||
}
|
||||
|
||||
// fetch IShellItem*
|
||||
// Ref: https://stackoverflow.com/questions/76306324/how-to-set-default-folder-for-ifileopendialog-interface
|
||||
IShellItem* init_directory = NULL;
|
||||
HRESULT hr = SHCreateItemFromParsingName(w_init_dir.value().c_str(), NULL, IID_PPV_ARGS(&init_directory));
|
||||
if (FAILED(hr)) return std::unexpected(DialogError::NoSuchDir);
|
||||
|
||||
// assign IShellItem*
|
||||
rv.m_WinInitDirectory.reset(init_directory);
|
||||
} else rv.m_WinInitDirectory.reset();
|
||||
|
||||
// build file filters
|
||||
auto win_file_types = m_FileTypes.to_windows();
|
||||
if (!win_file_types.has_value()) return std::unexpected(win_file_types.error());
|
||||
else rv.m_WinFileTypes = std::move(win_file_types.value());
|
||||
|
||||
// check and assign default file type index
|
||||
// check whether it can be safely casted into UINT.
|
||||
auto win_def_index_base0 = SAFECAST::try_to<UINT>(m_DefaultFileTypeIndex);
|
||||
if (!win_def_index_base0.has_value()) {
|
||||
return std::unexpected(DialogError::IndexOverflow);
|
||||
}
|
||||
// check whether it can safely make addition with 1.
|
||||
auto win_def_index = SAFEOP::checked_add<UINT>(win_def_index_base0.value(), 1);
|
||||
if (!win_def_index.has_value()) {
|
||||
return std::unexpected(DialogError::IndexOverflow);
|
||||
}
|
||||
// okey, assign it.
|
||||
// please note we do not check the relation between this variable and file filters.
|
||||
// this was done in generic_file_dialog() function.
|
||||
rv.m_WinDefaultFileTypeIndex = win_def_index.value();
|
||||
|
||||
// everything is okey
|
||||
return rv;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Exposed Functions
|
||||
|
||||
enum class GenericFileDialogType { OpenFile, OpenFiles, SaveFile, OpenFolder };
|
||||
|
||||
/**
|
||||
* @brief Extract display name from given IShellItem*.
|
||||
* @param[in] item The pointer to IShellItem for extracting.
|
||||
* @return Extract display name, or error occurs.
|
||||
* @remarks This is an assist function of generic_file_dialog().
|
||||
*/
|
||||
static DialogResult<std::u8string> extract_display_name(IShellItem* item) {
|
||||
// fetch display name from IShellItem*
|
||||
LPWSTR display_name_ptr;
|
||||
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &display_name_ptr);
|
||||
if (FAILED(hr)) return std::unexpected(DialogError::BadCOMCall);
|
||||
WINCOM::SmartLPWSTR display_name(display_name_ptr);
|
||||
|
||||
// convert result and return
|
||||
return ENC::to_utf8(display_name.get()).transform_error([](auto err) { return DialogError::BadEncoding; });
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generic file dialog.
|
||||
* @details This function is the real underlying function of all dialog functions.
|
||||
* @param[in] params User specified parameter controlling the behavior of this file dialog, including title, file types and etc.
|
||||
* @return
|
||||
* The full path to user selected files or folders.
|
||||
* For multiple selection, the count of items >= 1. For others, the count of item must be 1.
|
||||
* Or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
template<GenericFileDialogType EDialogType>
|
||||
static DialogResult<DialogOutcome<std::vector<std::u8string>>> generic_file_dialog(const FileDialog& params) {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
|
||||
// prepare result variable
|
||||
HRESULT hr;
|
||||
|
||||
// create a const bad com call unexpected result
|
||||
// because it is widely used in this function.
|
||||
constexpr auto BAD_COM_CALL = std::unexpected(DialogError::BadCOMCall);
|
||||
|
||||
// check whether COM environment has been initialized
|
||||
if (!WINCOM::is_initialized()) return BAD_COM_CALL;
|
||||
|
||||
// create file dialog instance
|
||||
// fetch dialog CLSID first
|
||||
CLSID dialog_clsid;
|
||||
switch (EDialogType) {
|
||||
case GenericFileDialogType::OpenFile:
|
||||
case GenericFileDialogType::OpenFiles:
|
||||
case GenericFileDialogType::OpenFolder:
|
||||
dialog_clsid = CLSID_FileOpenDialog;
|
||||
break;
|
||||
case GenericFileDialogType::SaveFile:
|
||||
dialog_clsid = CLSID_FileSaveDialog;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("unknown dialog type");
|
||||
}
|
||||
// create raw dialog pointer
|
||||
IFileDialog* raw_pfd = nullptr;
|
||||
hr = CoCreateInstance(dialog_clsid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&raw_pfd));
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
// create memory-safe dialog pointer
|
||||
WINCOM::SmartIFileDialog pfd(raw_pfd);
|
||||
|
||||
// set options for dialog
|
||||
// before setting, always get the options first in order.
|
||||
// not to override existing options.
|
||||
DWORD dwFlags;
|
||||
hr = pfd->GetOptions(&dwFlags);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
// modify options
|
||||
switch (EDialogType) {
|
||||
// We want user only can pick file system files: FOS_FORCEFILESYSTEM.
|
||||
// Open dialog default: FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR
|
||||
// Save dialog default: FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR
|
||||
// Pick folder: FOS_PICKFOLDERS
|
||||
case GenericFileDialogType::OpenFile:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
break;
|
||||
case GenericFileDialogType::OpenFiles:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
dwFlags |= FOS_ALLOWMULTISELECT;
|
||||
break;
|
||||
case GenericFileDialogType::SaveFile:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
break;
|
||||
case GenericFileDialogType::OpenFolder:
|
||||
dwFlags |= FOS_FORCEFILESYSTEM;
|
||||
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
|
||||
dwFlags |= FOS_PICKFOLDERS;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("unknown dialog type");
|
||||
}
|
||||
// set folder dialog options
|
||||
hr = pfd->SetOptions(dwFlags);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
|
||||
// build Windows used file dialog parameters
|
||||
auto pending_win_params = params.to_windows();
|
||||
if (!pending_win_params.has_value()) return std::unexpected(pending_win_params.error());
|
||||
WinFileDialog win_params(std::move(pending_win_params.value()));
|
||||
|
||||
// setup title and init file name
|
||||
if (win_params.has_title()) {
|
||||
hr = pfd->SetTitle(win_params.get_title());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
}
|
||||
if (win_params.has_init_file_name()) {
|
||||
hr = pfd->SetFileName(win_params.get_init_file_name());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
}
|
||||
|
||||
// setup init directory
|
||||
if (win_params.has_init_directory()) {
|
||||
hr = pfd->SetFolder(win_params.get_init_directory());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
}
|
||||
|
||||
// set file types and default file index when we picking file
|
||||
if constexpr (EDialogType != GenericFileDialogType::OpenFolder) {
|
||||
// fetch file filters
|
||||
const auto& file_filters = win_params.get_file_types();
|
||||
|
||||
// first, check whether file filters is empty
|
||||
if (file_filters.get_filter_count() == 0) {
|
||||
return std::unexpected(DialogError::EmptyFilters);
|
||||
}
|
||||
// then validate index
|
||||
if (win_params.get_default_file_type_index() > file_filters.get_filter_count()) {
|
||||
return std::unexpected(DialogError::IndexOutOfRange);
|
||||
}
|
||||
|
||||
// set file types list
|
||||
hr = pfd->SetFileTypes(file_filters.get_filter_count(), file_filters.get_filter_specs());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
// set default file type index
|
||||
hr = pfd->SetFileTypeIndex(win_params.get_default_file_type_index());
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
}
|
||||
|
||||
// show the dialog and return if user click "Cancel"
|
||||
hr = pfd->Show(win_params.has_owner() ? win_params.get_owner() : NULL);
|
||||
if (FAILED(hr)) return std::nullopt;
|
||||
|
||||
// prepare return value
|
||||
std::vector<std::u8string> rv;
|
||||
// obtain result when user click "OK" button.
|
||||
switch (EDialogType) {
|
||||
case GenericFileDialogType::OpenFile:
|
||||
case GenericFileDialogType::OpenFolder:
|
||||
case GenericFileDialogType::SaveFile: {
|
||||
// obtain one file entry
|
||||
IShellItem* raw_item;
|
||||
hr = pfd->GetResult(&raw_item);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
WINCOM::SmartIShellItem item(raw_item);
|
||||
|
||||
// extract display name
|
||||
auto display_name = extract_display_name(item.get());
|
||||
if (!display_name.has_value()) return std::unexpected(display_name.error());
|
||||
|
||||
// append result
|
||||
rv.emplace_back(std::move(display_name.value()));
|
||||
} break;
|
||||
case GenericFileDialogType::OpenFiles: {
|
||||
// try casting file dialog to file open dialog
|
||||
// Ref: https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface
|
||||
IFileOpenDialog* raw_pfod = nullptr;
|
||||
hr = pfd->QueryInterface(IID_PPV_ARGS(&raw_pfod));
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
WINCOM::SmartIFileOpenDialog pfod(raw_pfod);
|
||||
|
||||
// obtain multiple file entires
|
||||
IShellItemArray* raw_items;
|
||||
hr = pfod->GetResults(&raw_items);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
WINCOM::SmartIShellItemArray items(raw_items);
|
||||
|
||||
// analyze file entries
|
||||
// get array count first
|
||||
DWORD items_count = 0u;
|
||||
hr = items->GetCount(&items_count);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
// iterate array
|
||||
for (DWORD i = 0u; i < items_count; ++i) {
|
||||
// fetch item by index
|
||||
IShellItem* raw_item;
|
||||
hr = items->GetItemAt(i, &raw_item);
|
||||
if (FAILED(hr)) return BAD_COM_CALL;
|
||||
WINCOM::SmartIShellItem item(raw_item);
|
||||
|
||||
// extract display name
|
||||
auto display_name = extract_display_name(item.get());
|
||||
if (!display_name.has_value()) return std::unexpected(display_name.error());
|
||||
|
||||
// append result
|
||||
rv.emplace_back(std::move(display_name.value()));
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
throw std::invalid_argument("unknown dialog type");
|
||||
}
|
||||
|
||||
// everything is okey
|
||||
return rv;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Wrapper Functions
|
||||
|
||||
/**
|
||||
* @brief Assistant function for extracting item from given value returned by generic file dialog.
|
||||
* @details This function will check whether inner vector only contain one item.
|
||||
* @param[in] rhs The return value to be extracted.
|
||||
* @return Extracted return value.
|
||||
*/
|
||||
static DialogResult<DialogOutcome<std::u8string>> transform_generic_rv(DialogResult<DialogOutcome<std::vector<std::u8string>>>&& rhs) {
|
||||
if (rhs.has_value()) {
|
||||
const auto& inner = rhs.value();
|
||||
if (inner.has_value()) {
|
||||
const auto& vec = inner.value();
|
||||
if (vec.size() != 1u) throw std::logic_error("return value doesn't contain exactly one item");
|
||||
else return vec.front();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
return std::unexpected(rhs.error());
|
||||
}
|
||||
}
|
||||
|
||||
DialogResult<DialogOutcome<std::u8string>> open_file(const FileDialog& params){
|
||||
return transform_generic_rv(generic_file_dialog<GenericFileDialogType::OpenFile>(params));
|
||||
}
|
||||
|
||||
DialogResult<DialogOutcome<std::vector<std::u8string>>> open_files(const FileDialog& params) {
|
||||
return generic_file_dialog<GenericFileDialogType::OpenFiles>(params);
|
||||
}
|
||||
|
||||
DialogResult<DialogOutcome<std::u8string>> save_file(const FileDialog& params) {
|
||||
return transform_generic_rv(generic_file_dialog<GenericFileDialogType::SaveFile>(params));
|
||||
}
|
||||
|
||||
DialogResult<DialogOutcome<std::u8string>> open_folder(const FileDialog& params) {
|
||||
return transform_generic_rv(generic_file_dialog<GenericFileDialogType::OpenFolder>(params));
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
||||
|
||||
#endif
|
317
src/yycc/windows/dialog.hpp
Normal file
317
src/yycc/windows/dialog.hpp
Normal file
@ -0,0 +1,317 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
#include "../macro/class_copy_move.hpp"
|
||||
#include "com.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
#define NS_YYCC_WINDOWS_COM ::yycc::windows::com
|
||||
|
||||
/**
|
||||
* @brief The namespace providing Windows universal dialog features.
|
||||
* @details
|
||||
* This namespace only available on Windows platform.
|
||||
* See also \ref dialog_helper.
|
||||
*/
|
||||
namespace yycc::windows::dialog {
|
||||
|
||||
/// @brief The error occurs in this module.
|
||||
enum class DialogError {
|
||||
BadEncoding, ///< Error occurs when perform encoding convertion.
|
||||
TooManyFilters, ///< The size of file filters list is too large for Windows.
|
||||
IndexOverflow, ///< Default filter index is too large for Windows.
|
||||
EmptyFilters, ///< File filters is empty when picking file.
|
||||
IndexOutOfRange, ///< Default filter index is out of range of filters list.
|
||||
NoSuchDir, ///< Given initial directory path is invalid.
|
||||
BadCOMCall, ///< Some COM function calls failed.
|
||||
};
|
||||
|
||||
/// @brief The result type used in this module.
|
||||
template<typename T>
|
||||
using DialogResult = std::expected<T, DialogError>;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is private and served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileFilters {
|
||||
friend class FileFilters;
|
||||
|
||||
public:
|
||||
using WinFilterModes = std::wstring;
|
||||
using WinFilterName = std::wstring;
|
||||
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
|
||||
|
||||
public:
|
||||
WinFileFilters();
|
||||
~WinFileFilters();
|
||||
YYCC_DECL_COPY_MOVE(WinFileFilters)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the count of available file filters
|
||||
* @return Count of file filters.
|
||||
*/
|
||||
UINT get_filter_count() const;
|
||||
/**
|
||||
* @brief Get pointer to Windows used file filters declarations
|
||||
* @return Pointer for Windows use.
|
||||
*/
|
||||
const COMDLG_FILTERSPEC* get_filter_specs() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Update COMDLG_FILTERSPEC according to file filter list.
|
||||
* @remarks Programmer \b MUST call this function after you modify m_WinFilters.
|
||||
*/
|
||||
void update();
|
||||
|
||||
private:
|
||||
std::vector<WinFilterPair> m_WinFilters;
|
||||
std::vector<COMDLG_FILTERSPEC> m_WinDataStruct;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for programmer using.
|
||||
* But you don't need create it on your own.
|
||||
* You can simply fetch it by FileDialog::ConfigreFileTypes(),
|
||||
* because this class is a part of FileDialog.
|
||||
*/
|
||||
class FileFilters {
|
||||
public:
|
||||
using FilterModes = std::vector<std::u8string>;
|
||||
using FilterName = std::u8string;
|
||||
using FilterPair = std::pair<FilterName, FilterModes>;
|
||||
|
||||
public:
|
||||
FileFilters();
|
||||
~FileFilters();
|
||||
YYCC_DEFAULT_COPY_MOVE(FileFilters)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Add a filter pair in file types list.
|
||||
* @param[in] filter_name The friendly name of the filter.
|
||||
* @param[in] il
|
||||
* A C++ initialize list containing acceptable file filter pattern.
|
||||
* Every entries must be a string representing a single filter pattern.
|
||||
* This list at least should have one pattern.
|
||||
* @return True if added success, otherwise false.
|
||||
* @warning This function will not validate the content of these filter patterns, so please write them carefully.
|
||||
* @exception std::invalid_argument Given filtern name is blank, or filter patterns is empty.
|
||||
*/
|
||||
void add_filter(const std::u8string_view& filter_name, std::initializer_list<std::u8string_view> il);
|
||||
/**
|
||||
* @brief Get the count of added file filters.
|
||||
* @return The count of added file filters.
|
||||
*/
|
||||
size_t get_count() const;
|
||||
/**
|
||||
* @brief Clear filter pairs for following re-use.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Build Windows used file filters struct.
|
||||
* @return Built Windows used struct, or error occurs.
|
||||
*/
|
||||
DialogResult<WinFileFilters> to_windows() const;
|
||||
|
||||
private:
|
||||
std::vector<FilterPair> m_Filters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileDialog {
|
||||
friend class FileDialog;
|
||||
|
||||
public:
|
||||
WinFileDialog();
|
||||
~WinFileDialog();
|
||||
YYCC_DECL_COPY(WinFileDialog)
|
||||
YYCC_DEFAULT_MOVE(WinFileDialog)
|
||||
|
||||
/// @brief Get whether this dialog has owner.
|
||||
bool has_owner() const;
|
||||
/// @brief Get the \c HWND of dialog owner.
|
||||
HWND get_owner() const;
|
||||
/// @brief Get whether dialog has custom title.
|
||||
bool has_title() const;
|
||||
/// @brief Get custom title of dialog.
|
||||
const wchar_t* get_title() const;
|
||||
/// @brief Get whether dialog has custom initial file name.
|
||||
bool has_init_file_name() const;
|
||||
/// @brief Get custom initial file name of dialog
|
||||
const wchar_t* get_init_file_name() const;
|
||||
/// @brief Get whether dialog has custom initial directory.
|
||||
bool has_init_directory() const;
|
||||
/// @brief Get custom initial directory of dialog.
|
||||
IShellItem* get_init_directory() const;
|
||||
/// @brief Get the struct holding Windows used file filters data.
|
||||
const WinFileFilters& get_file_types() const;
|
||||
/// @brief Get the index of default selected file filter.
|
||||
UINT get_default_file_type_index() const;
|
||||
|
||||
private:
|
||||
HWND m_WinOwner;
|
||||
WinFileFilters m_WinFileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* This is 1-based index according to Windows specification.
|
||||
* In other words, when generating this struct from FileDialog to this struct this field should plus 1.
|
||||
* Because the same field located in FileDialog is 0-based index.
|
||||
*/
|
||||
UINT m_WinDefaultFileTypeIndex;
|
||||
std::optional<std::wstring> m_WinTitle, m_WinInitFileName;
|
||||
NS_YYCC_WINDOWS_COM::SmartIShellItem m_WinInitDirectory;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for programming using to describe every aspectes of the dialog.
|
||||
* For how to use this struct, see \ref dialog_helper.
|
||||
*/
|
||||
class FileDialog {
|
||||
public:
|
||||
FileDialog();
|
||||
~FileDialog();
|
||||
YYCC_DEFAULT_COPY_MOVE(FileDialog)
|
||||
|
||||
/**
|
||||
* @brief Set the owner of dialog.
|
||||
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
|
||||
*/
|
||||
void set_owner(HWND owner);
|
||||
/**
|
||||
* @brief Set custom title of dialog
|
||||
* @param[in] title The string pointer to custom title.
|
||||
*/
|
||||
void set_title(const std::u8string_view& title);
|
||||
/**
|
||||
* @brief Remove custom title of dialog (keep system default)
|
||||
*/
|
||||
void unset_title();
|
||||
/**
|
||||
* @brief Set the initial file name of dialog
|
||||
* @details If set, the file name will always be same one when opening dialog.
|
||||
* @param[in] init_filename String pointer to initial file name.
|
||||
*/
|
||||
void set_init_file_name(const std::u8string_view& init_filename);
|
||||
/**
|
||||
* @brief Remove custom initial file name of dialog (keep system default)
|
||||
*/
|
||||
void unset_init_file_name();
|
||||
/**
|
||||
* @brief Set the initial directory of dialog
|
||||
* @details If set, the opended directory will always be the same one when opening dialog
|
||||
* @param[in] init_dir String pointer to initial directory.
|
||||
*/
|
||||
void set_init_directory(const std::u8string_view& init_dir);
|
||||
/**
|
||||
* @brief Remove custom initial directory of dialog (keep system default)
|
||||
*/
|
||||
void unset_init_directory();
|
||||
/**
|
||||
* @brief Fetch the struct describing file filters for future configuration.
|
||||
* @return The reference to the struct describing file filters.
|
||||
*/
|
||||
FileFilters& configre_file_types();
|
||||
/**
|
||||
* @brief Set the index of default selected file filter.
|
||||
* @param[in] idx
|
||||
* The index to default file filter.
|
||||
* This must be a valid index in file filters.
|
||||
*/
|
||||
void set_default_file_type_index(size_t idx);
|
||||
/**
|
||||
* @brief Clear file dialog parameters for following re-use.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Build Windows used dialog info struct.
|
||||
* @return Built Windows used struct, or error occurs.
|
||||
*/
|
||||
DialogResult<WinFileDialog> to_windows() const;
|
||||
|
||||
protected:
|
||||
HWND m_Owner;
|
||||
std::optional<std::u8string> m_Title, m_InitFileName, m_InitDirectory;
|
||||
FileFilters m_FileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* The index Windows used is 1-based index.
|
||||
* But for universal experience, we order this is 0-based index.
|
||||
* And do convertion when generating Windows used struct.
|
||||
*/
|
||||
size_t m_DefaultFileTypeIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The result after user close the dialog.
|
||||
* @details
|
||||
* It is an alias to \c std::optional.
|
||||
* If it do not have value, it means that user click "Cancel" button.
|
||||
* otherwise it contain a value that user selected in dialog.
|
||||
*/
|
||||
template<typename T>
|
||||
using DialogOutcome = std::optional<T>;
|
||||
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @return Full path to user selected file, or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
DialogResult<DialogOutcome<std::u8string>> open_file(const FileDialog& params);
|
||||
/**
|
||||
* @brief Open the dialog which order user select multiple file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @return The list of full path of user selected files, or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
DialogResult<DialogOutcome<std::vector<std::u8string>>> open_files(const FileDialog& params);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to save.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @return Full path to user selected file, or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
DialogResult<DialogOutcome<std::u8string>> save_file(const FileDialog& params);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single directory to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @return Full path to user selected directory, or nothing (click "Cancel"), or error occurs.
|
||||
*/
|
||||
DialogResult<DialogOutcome<std::u8string>> open_folder(const FileDialog& params);
|
||||
|
||||
}
|
||||
|
||||
#undef NS_YYCC_WINDOWS_COM
|
||||
|
||||
#endif
|
158
src/yycc/windows/winfct.cpp
Normal file
158
src/yycc/windows/winfct.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
#include "winfct.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "../encoding/windows.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
#include "com.hpp"
|
||||
#endif
|
||||
|
||||
#define ENC ::yycc::encoding::windows
|
||||
#define COM ::yycc::windows::com
|
||||
|
||||
namespace yycc::windows::winfct {
|
||||
|
||||
WinFctResult<HMODULE> get_current_module() {
|
||||
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
|
||||
HMODULE hModule = NULL;
|
||||
BOOL rv = ::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
|
||||
| GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address but do not inc ref counter.
|
||||
(LPCWSTR) get_current_module,
|
||||
&hModule);
|
||||
if (rv) return hModule;
|
||||
else return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
WinFctResult<std::u8string> get_temp_directory() {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD expected_size;
|
||||
|
||||
// fetch temp folder
|
||||
while (true) {
|
||||
if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
|
||||
// failed, return
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
if (expected_size > static_cast<DWORD>(wpath.size())) {
|
||||
// buffer is too short, need enlarge and do fetching again
|
||||
wpath.resize(expected_size);
|
||||
} else {
|
||||
// ok. shrink to real length. break while
|
||||
wpath.resize(expected_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// convert to utf8 and return
|
||||
return ENC::to_utf8(wpath).transform_error([](auto err) { return WinFctError::Encoding; });
|
||||
}
|
||||
|
||||
WinFctResult<std::u8string> get_module_file_name(HINSTANCE hModule) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD copied_size;
|
||||
|
||||
while (true) {
|
||||
if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
|
||||
// failed, return
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
// check insufficient buffer
|
||||
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
// buffer is not enough, enlarge it and try again.
|
||||
wpath.resize(wpath.size() + MAX_PATH);
|
||||
} else {
|
||||
// ok. shrink to real length. break while
|
||||
wpath.resize(copied_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// convert to utf8 and return
|
||||
return ENC::to_utf8(wpath).transform_error([](auto err) { return WinFctError::Encoding; });
|
||||
}
|
||||
|
||||
bool is_valid_code_page(UINT code_page) {
|
||||
CPINFOEXW cpinfo;
|
||||
return ::GetCPInfoExW(code_page, 0, &cpinfo);
|
||||
}
|
||||
|
||||
WinFctResult<void> copy_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName, BOOL bFailIfExists) {
|
||||
auto wExistingFileName = ENC::to_wchar(lpExistingFileName);
|
||||
auto wNewFileName = ENC::to_wchar(lpNewFileName);
|
||||
if (!(wExistingFileName.has_value() && wNewFileName.has_value())) {
|
||||
return std::unexpected(WinFctError::Encoding);
|
||||
}
|
||||
|
||||
if (!::CopyFileW(wExistingFileName.value().c_str(), wNewFileName.value().c_str(), bFailIfExists)) {
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WinFctResult<void> move_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName) {
|
||||
auto wExistingFileName = ENC::to_wchar(lpExistingFileName);
|
||||
auto wNewFileName = ENC::to_wchar(lpNewFileName);
|
||||
if (!(wExistingFileName.has_value() && wNewFileName.has_value())) {
|
||||
return std::unexpected(WinFctError::Encoding);
|
||||
}
|
||||
|
||||
if (!::MoveFileW(wExistingFileName.value().c_str(), wNewFileName.value().c_str())) {
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
WinFctResult<void> delete_file(const std::u8string_view& lpFileName) {
|
||||
auto wFileName = ENC::to_wchar(lpFileName);
|
||||
if (!wFileName.has_value()) {
|
||||
return std::unexpected(WinFctError::Encoding);
|
||||
}
|
||||
|
||||
if (!::DeleteFileW(wFileName.value().c_str())) {
|
||||
return std::unexpected(WinFctError::Backend);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
WinFctResult<std::u8string> get_known_path(KnownDirectory path_type) {
|
||||
// check whether com initialized
|
||||
if (!COM::is_initialized()) return std::unexpected(WinFctError::NoCom);
|
||||
|
||||
// get folder id according to type
|
||||
const KNOWNFOLDERID* pId;
|
||||
switch (path_type) {
|
||||
case KnownDirectory::LocalAppData:
|
||||
pId = &FOLDERID_LocalAppData;
|
||||
break;
|
||||
case KnownDirectory::AppData:
|
||||
pId = &FOLDERID_RoamingAppData;
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("unknow known directory type");
|
||||
}
|
||||
|
||||
// fetch path
|
||||
LPWSTR raw_known_path;
|
||||
HRESULT hr = SHGetKnownFolderPath(*pId, KF_FLAG_CREATE, NULL, &raw_known_path);
|
||||
if (FAILED(hr)) return std::unexpected(WinFctError::Backend);
|
||||
COM::SmartLPWSTR known_path(raw_known_path);
|
||||
|
||||
// convert to utf8 and return
|
||||
return ENC::to_utf8(known_path.get()).transform_error([](auto err) { return WinFctError::Encoding; });
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace yycc::windows::winfct
|
||||
|
||||
#endif
|
116
src/yycc/windows/winfct.hpp
Normal file
116
src/yycc/windows/winfct.hpp
Normal file
@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
#include "import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include "import_guard_tail.hpp"
|
||||
|
||||
namespace yycc::windows::winfct {
|
||||
|
||||
/// @brief All errors occur in this module.
|
||||
enum class WinFctError {
|
||||
Backend, ///< Error occurs when calling Win32 functions.
|
||||
Encoding, ///< Can not perform encoding convertion.
|
||||
NoCom, ///< No COM environment.
|
||||
};
|
||||
|
||||
/// @brief The result type used in this module.
|
||||
template<typename T>
|
||||
using WinFctResult = std::expected<T, WinFctError>;
|
||||
|
||||
/**
|
||||
* @brief Get Windows used HANDLE for current module.
|
||||
* @details
|
||||
* If your target is EXE, the current module simply is your program self.
|
||||
* However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL.
|
||||
*
|
||||
* This function is frequently used by DLL.
|
||||
* Because some design need the HANDLE of current module, not the host EXE loading your DLL.
|
||||
* For example, you may want to get the path of your built DLL, or fetch resources from your DLL at runtime,
|
||||
* then you should pass current module HANDLE, not NULL or the HANDLE of EXE.
|
||||
* @return A Windows HANDLE pointing to current module, or error occurs.
|
||||
*/
|
||||
WinFctResult<HMODULE> get_current_module();
|
||||
|
||||
/**
|
||||
* @brief Get path to Windows temporary directory.
|
||||
* @details Windows temporary directory usually is the target of \%TEMP\%.
|
||||
* @return Fetched UTF8 encoded path to Windows temporary directory, or error occurs.
|
||||
*/
|
||||
WinFctResult<std::u8string> get_temp_directory();
|
||||
|
||||
/**
|
||||
* @brief Get the file name of given module HANDLE
|
||||
* @param[in] hModule
|
||||
* The HANDLE to the module where you want to get file name.
|
||||
* It is same as the HANDLE parameter of Win32 \c GetModuleFileName.
|
||||
* @param[out] ret The variable receiving UTF8 encoded file name of given module.
|
||||
* @return Fetched UTF8 encoded file name of given module, or error occurs.
|
||||
*/
|
||||
WinFctResult<std::u8string> get_module_file_name(HINSTANCE hModule);
|
||||
|
||||
/**
|
||||
* @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 is_valid_code_page(UINT code_page);
|
||||
|
||||
/**
|
||||
* @brief Copies an existing file to a new file.
|
||||
* @param[in] lpExistingFileName The name of an existing file.
|
||||
* @param[in] lpNewFileName The name of the new file.
|
||||
* @param[in] 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 Nothing or error occurs. If function failed with backend error, caller can call \c GetLastError for more details.
|
||||
* @remarks Same as Windows \c CopyFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
|
||||
*/
|
||||
WinFctResult<void> copy_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName, BOOL bFailIfExists);
|
||||
|
||||
/**
|
||||
* @brief Moves an existing file or a directory, including its children.
|
||||
* @param[in] lpExistingFileName The current name of the file or directory on the local computer.
|
||||
* @param[in] 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 Nothing or error occurs. If function failed with backend error, caller can call \c GetLastError for more details.
|
||||
* @remarks Same as Windows \c MoveFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefilew
|
||||
*/
|
||||
WinFctResult<void> move_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName);
|
||||
|
||||
/**
|
||||
* @brief Deletes an existing file.
|
||||
* @param[in] lpFileName The name of the file to be deleted.
|
||||
* @return Nothing or error occurs. If function failed with backend error, caller can call \c GetLastError for more details.
|
||||
* @remarks Same as Windows \c DeleteFile: https://learn.microsoft.com/e-us/windows/win32/api/winbase/nf-winbase-deletefile
|
||||
*/
|
||||
WinFctResult<void> delete_file(const std::u8string_view& lpFileName);
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
/// @brief The known directory type in Windows.
|
||||
enum class KnownDirectory {
|
||||
LocalAppData, ///< The path \%LOCALAPPDATA\% pointed.
|
||||
AppData, ///< The path \%APPDATA\% pointed.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the path to \%LOCALAPPDATA\%.
|
||||
* @details \%LOCALAPPDATA\% usually was used as putting local app data files
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to LOCALAPPDATA.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
WinFctResult<std::u8string> get_known_path(KnownDirectory path_type);
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -4,24 +4,47 @@ add_executable(YYCCTestbench "")
|
||||
target_sources(YYCCTestbench
|
||||
PRIVATE
|
||||
main.cpp
|
||||
|
||||
shared/literals.cpp
|
||||
|
||||
yycc/macro/version_cmp.cpp
|
||||
yycc/macro/os_detector.cpp
|
||||
yycc/macro/compiler_detector.cpp
|
||||
yycc/macro/endian_detector.cpp
|
||||
yycc/macro/ptr_size_detector.cpp
|
||||
yycc/macro/stl_detector.cpp
|
||||
yycc/flag_enum.cpp
|
||||
yycc/constraint.cpp
|
||||
yycc/constraint/builder.cpp
|
||||
yycc/patch/ptr_pad.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/patch/fopen.cpp
|
||||
yycc/rust/env.cpp
|
||||
yycc/string/reinterpret.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/string/stream.cpp
|
||||
yycc/num/parse.cpp
|
||||
yycc/num/stringify.cpp
|
||||
yycc/num/op.cpp
|
||||
yycc/num/safe_cast.cpp
|
||||
yycc/num/safe_op.cpp
|
||||
yycc/encoding/stl.cpp
|
||||
yycc/encoding/windows.cpp
|
||||
yycc/encoding/iconv.cpp
|
||||
yycc/windows/com.cpp
|
||||
yycc/windows/dialog.cpp
|
||||
yycc/windows/winfct.cpp
|
||||
yycc/windows/console.cpp
|
||||
|
||||
yycc/carton/pycodec.cpp
|
||||
yycc/carton/termcolor.cpp
|
||||
yycc/carton/wcwidth.cpp
|
||||
yycc/carton/tabulate.cpp
|
||||
)
|
||||
target_sources(YYCCTestbench
|
||||
PRIVATE
|
||||
FILE_SET HEADERS
|
||||
FILES
|
||||
yycc/encoding/utf_literal.hpp
|
||||
shared/literals.hpp
|
||||
)
|
||||
# Setup headers
|
||||
target_include_directories(YYCCTestbench
|
||||
|
@ -7,94 +7,6 @@ namespace Console = YYCC::ConsoleHelper;
|
||||
|
||||
namespace YYCCTestbench {
|
||||
|
||||
#pragma region Unicode Test Data
|
||||
|
||||
// UNICODE Test Strings
|
||||
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||
#define TEST_UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
|
||||
#define TEST_UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
|
||||
#define TEST_UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
|
||||
#define TEST_UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
|
||||
#define TEST_UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
|
||||
#define TEST_UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
|
||||
#define TEST_UNICODE_STR_RUSSIA "\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
|
||||
#define TEST_UNICODE_STR_THAILAND "\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
|
||||
#define TEST_UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
|
||||
#define TEST_UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
|
||||
#define TEST_UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
|
||||
#define TEST_UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
|
||||
|
||||
#define CONCAT(prefix, strl) prefix ## strl
|
||||
#define CPP_U8_LITERAL(strl) YYCC_U8(strl)
|
||||
#define CPP_U16_LITERAL(strl) CONCAT(u, strl)
|
||||
#define CPP_U32_LITERAL(strl) CONCAT(U, strl)
|
||||
#define CPP_WSTR_LITERAL(strl) CONCAT(L, strl)
|
||||
|
||||
static std::vector<YYCC::yycc_u8string> c_UTF8TestStrTable {
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U8_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::wstring> c_WStrTestStrTable {
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_WSTR_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u16string> c_UTF16TestStrTable {
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U16_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u32string> c_UTF32TestStrTable {
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_JAPAN),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_CHINA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_KOREA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_ISRAEL),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_EGYPT),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_GREECE),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_RUSSIA),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_THAILAND),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_FRANCE),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_SPAIN),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_MATHMATICS),
|
||||
CPP_U32_LITERAL(TEST_UNICODE_STR_EMOJI),
|
||||
};
|
||||
|
||||
#undef CPP_WSTR_LITERAL
|
||||
#undef CPP_U32_LITERAL
|
||||
#undef CPP_U16_LITERAL
|
||||
#undef CPP_U8_LITERAL
|
||||
#undef CONCAT
|
||||
|
||||
#pragma endregion
|
||||
|
||||
static void Assert(bool condition, const YYCC::yycc_char8_t* description) {
|
||||
if (condition) {
|
||||
Console::FormatLine(YYCC_U8(YYCC_COLOR_LIGHT_GREEN("OK: %s")), description);
|
||||
@ -105,24 +17,6 @@ namespace YYCCTestbench {
|
||||
}
|
||||
|
||||
static void ConsoleTestbench() {
|
||||
// Color Test
|
||||
Console::EnableColorfulConsole();
|
||||
Console::WriteLine(YYCC_U8("Color Test:"));
|
||||
|
||||
#define TEST_MACRO(col) Console::WriteLine(YYCC_U8("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col ));
|
||||
// U+2588 is full block
|
||||
|
||||
TEST_MACRO(BLACK);
|
||||
TEST_MACRO(RED);
|
||||
TEST_MACRO(GREEN);
|
||||
TEST_MACRO(YELLOW);
|
||||
TEST_MACRO(BLUE);
|
||||
TEST_MACRO(MAGENTA);
|
||||
TEST_MACRO(CYAN);
|
||||
TEST_MACRO(WHITE);
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// UTF8 Output Test
|
||||
Console::WriteLine(YYCC_U8("UTF8 Output Test:"));
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
@ -142,203 +36,6 @@ namespace YYCCTestbench {
|
||||
|
||||
}
|
||||
|
||||
static void EncodingTestbench() {
|
||||
// get test tuple size
|
||||
size_t count = c_UTF8TestStrTable.size();
|
||||
|
||||
// check the convertion between given string
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
// get item
|
||||
const auto& u8str = c_UTF8TestStrTable[i];
|
||||
const auto& u16str = c_UTF16TestStrTable[i];
|
||||
const auto& u32str = c_UTF32TestStrTable[i];
|
||||
|
||||
// create cache variables
|
||||
YYCC::yycc_u8string u8cache;
|
||||
std::u16string u16cache;
|
||||
std::u32string u32cache;
|
||||
|
||||
// do convertion check
|
||||
Assert(YYCC::EncodingHelper::UTF8ToUTF16(u8str, u16cache) && u16cache == u16str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF16"));
|
||||
Assert(YYCC::EncodingHelper::UTF8ToUTF32(u8str, u32cache) && u32cache == u32str, YYCC_U8("YYCC::EncodingHelper::UTF8ToUTF32"));
|
||||
|
||||
Assert(YYCC::EncodingHelper::UTF16ToUTF8(u16str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF16ToUTF8"));
|
||||
Assert(YYCC::EncodingHelper::UTF32ToUTF8(u32str, u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::UTF32ToUTF8"));
|
||||
}
|
||||
|
||||
// check wstring convertion on windows
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
for (size_t i = 0u; i < count; ++i) {
|
||||
// get item
|
||||
const auto& u8str = c_UTF8TestStrTable[i];
|
||||
const auto& wstr = c_WStrTestStrTable[i];
|
||||
|
||||
// create cache variables
|
||||
YYCC::yycc_u8string u8cache;
|
||||
std::wstring wcache;
|
||||
|
||||
// do convertion check
|
||||
Assert(YYCC::EncodingHelper::UTF8ToWchar(u8str.c_str(), wcache) && wcache == wstr, YYCC_U8("YYCC::EncodingHelper::UTF8ToWchar"));
|
||||
Assert(YYCC::EncodingHelper::WcharToUTF8(wstr.c_str(), u8cache) && u8cache == u8str, YYCC_U8("YYCC::EncodingHelper::WcharToUTF8"));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
static void StringTestbench() {
|
||||
// Test Printf
|
||||
auto test_printf = YYCC::StringHelper::Printf(YYCC_U8("%s == %s"), YYCC_U8("Hello World"), YYCC_U8("Hello, world"));
|
||||
Assert(test_printf == YYCC_U8("Hello World == Hello, world"), YYCC_U8("YYCC::StringHelper::Printf"));
|
||||
|
||||
// Test Replace
|
||||
auto test_replace = YYCC::StringHelper::Replace(YYCC_U8("aabbcc"), YYCC_U8("bb"), YYCC_U8("dd")); // normal case
|
||||
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
|
||||
Assert(test_replace == YYCC_U8("aabbcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
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("aaaabbaa"), YYCC_U8("aa"), YYCC_U8("")); // no replaced string
|
||||
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
|
||||
Assert(test_replace == YYCC_U8("aayxcc"), YYCC_U8("YYCC::StringHelper::Replace"));
|
||||
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 Upper / Lower
|
||||
auto test_lower = YYCC::StringHelper::Lower(YYCC_U8("LOWER"));
|
||||
Assert(test_lower == YYCC_U8("lower"), YYCC_U8("YYCC::StringHelper::Lower"));
|
||||
auto test_upper = YYCC::StringHelper::Upper(YYCC_U8("upper"));
|
||||
Assert(test_upper == YYCC_U8("UPPER"), YYCC_U8("YYCC::StringHelper::Upper"));
|
||||
|
||||
// Test Join
|
||||
std::vector<YYCC::yycc_u8string> test_join_container {
|
||||
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), 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"));
|
||||
|
||||
// Test Split
|
||||
auto test_split = YYCC::StringHelper::Split(YYCC_U8(", 1, 2, "), YYCC_U8(", ")); // normal
|
||||
Assert(test_split.size() == 4u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[1] == YYCC_U8("1"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[2] == YYCC_U8("2"), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[3] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched delimiter
|
||||
Assert(test_split.size() == 1u, 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::yycc_u8string_view()); // empty delimiter
|
||||
Assert(test_split.size() == 1u, 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
|
||||
Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split"));
|
||||
Assert(test_split[0].empty(), YYCC_U8("YYCC::StringHelper::Split"));
|
||||
|
||||
}
|
||||
|
||||
static void ParserTestbench() {
|
||||
|
||||
// Test success TryParse
|
||||
#define TEST_MACRO(type_t, value, string_value, ...) { \
|
||||
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
|
||||
type_t cache; \
|
||||
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(uint8_t, UINT8_C(200), "200");
|
||||
TEST_MACRO(int16_t, INT16_C(6161), "6161");
|
||||
TEST_MACRO(uint16_t, UINT16_C(32800), "32800");
|
||||
TEST_MACRO(int32_t, INT32_C(61616161), "61616161");
|
||||
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
|
||||
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
|
||||
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");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// Test failed TryParse
|
||||
#define TEST_MACRO(type_t, string_value, ...) { \
|
||||
YYCC::yycc_u8string cache_string(YYCC_U8(string_value)); \
|
||||
type_t cache; \
|
||||
Assert(!YYCC::ParserHelper::TryParse<type_t>(cache_string, cache, ##__VA_ARGS__), YYCC_U8("YYCC::StringHelper::TryParse<" #type_t ">")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(int8_t, "6161");
|
||||
TEST_MACRO(uint8_t, "32800");
|
||||
TEST_MACRO(int16_t, "61616161");
|
||||
TEST_MACRO(uint16_t, "4294967293");
|
||||
TEST_MACRO(int32_t, "616161616161");
|
||||
TEST_MACRO(uint32_t, "9223372036854775807");
|
||||
TEST_MACRO(int64_t, "616161616161616161616161");
|
||||
TEST_MACRO(uint64_t, "92233720368547758079223372036854775807");
|
||||
TEST_MACRO(float, "1e40");
|
||||
TEST_MACRO(double, "1e114514");
|
||||
TEST_MACRO(bool, "hello, world!");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
// Test ToString
|
||||
#define TEST_MACRO(type_t, value, string_value, ...) { \
|
||||
type_t cache = value; \
|
||||
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 ">")); \
|
||||
}
|
||||
|
||||
TEST_MACRO(int8_t, INT8_C(-61), "-61");
|
||||
TEST_MACRO(uint8_t, UINT8_C(200), "200");
|
||||
TEST_MACRO(int16_t, INT16_C(6161), "6161");
|
||||
TEST_MACRO(uint16_t, UINT16_C(32800), "32800");
|
||||
TEST_MACRO(int32_t, INT32_C(61616161), "61616161");
|
||||
TEST_MACRO(uint32_t, UINT32_C(4294967293), "4294967293");
|
||||
TEST_MACRO(int64_t, INT64_C(616161616161), "616161616161");
|
||||
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");
|
||||
|
||||
#undef TEST_MACRO
|
||||
|
||||
}
|
||||
|
||||
static void DialogTestbench() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
YYCC::yycc_u8string ret;
|
||||
std::vector<YYCC::yycc_u8string> rets;
|
||||
|
||||
YYCC::DialogHelper::FileDialog params;
|
||||
auto& filters = params.ConfigreFileTypes();
|
||||
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
|
||||
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
|
||||
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
|
||||
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
|
||||
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
|
||||
params.SetDefaultFileTypeIndex(0u);
|
||||
if (YYCC::DialogHelper::OpenFileDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Open File: %s"), ret.c_str());
|
||||
}
|
||||
if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) {
|
||||
Console::WriteLine(YYCC_U8("Open Multiple Files:"));
|
||||
for (const auto& item : rets) {
|
||||
Console::FormatLine(YYCC_U8("\t%s"), item.c_str());
|
||||
}
|
||||
}
|
||||
if (YYCC::DialogHelper::SaveFileDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Save File: %s"), ret.c_str());
|
||||
}
|
||||
params.Clear();
|
||||
if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) {
|
||||
Console::FormatLine(YYCC_U8("Open Folder: %s"), ret.c_str());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ExceptionTestbench() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
@ -373,128 +70,6 @@ namespace YYCCTestbench {
|
||||
#endif
|
||||
}
|
||||
|
||||
static void WinFctTestbench() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
HMODULE test_current_module;
|
||||
Assert((test_current_module = YYCC::WinFctHelper::GetCurrentModule()) != nullptr, YYCC_U8("YYCC::WinFctHelper::GetCurrentModule"));
|
||||
Console::FormatLine(YYCC_U8("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR), test_current_module);
|
||||
|
||||
YYCC::yycc_u8string test_temp;
|
||||
Assert(YYCC::WinFctHelper::GetTempDirectory(test_temp), YYCC_U8("YYCC::WinFctHelper::GetTempDirectory"));
|
||||
Console::FormatLine(YYCC_U8("Temp Directory: %s"), test_temp.c_str());
|
||||
|
||||
YYCC::yycc_u8string test_module_name;
|
||||
Assert(YYCC::WinFctHelper::GetModuleFileName(YYCC::WinFctHelper::GetCurrentModule(), test_module_name), YYCC_U8("YYCC::WinFctHelper::GetModuleFileName"));
|
||||
Console::FormatLine(YYCC_U8("Current Module File Name: %s"), test_module_name.c_str());
|
||||
|
||||
YYCC::yycc_u8string test_localappdata_path;
|
||||
Assert(YYCC::WinFctHelper::GetLocalAppData(test_localappdata_path), YYCC_U8("YYCC::WinFctHelper::GetLocalAppData"));
|
||||
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
|
||||
}
|
||||
|
||||
static void StdPatchTestbench() {
|
||||
|
||||
// Std Path
|
||||
|
||||
std::filesystem::path test_path;
|
||||
for (const auto& strl : c_UTF8TestStrTable) {
|
||||
test_path /= YYCC::StdPatch::ToStdPath(strl);
|
||||
}
|
||||
YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path));
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
std::wstring wdelimiter(1u, std::filesystem::path::preferred_separator);
|
||||
YYCC::yycc_u8string delimiter(YYCC::EncodingHelper::WcharToUTF8(wdelimiter));
|
||||
#else
|
||||
YYCC::yycc_u8string delimiter(1u, std::filesystem::path::preferred_separator);
|
||||
#endif
|
||||
YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), delimiter));
|
||||
|
||||
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 TestFlagEnum : uint8_t {
|
||||
Test1 = 0b00000000,
|
||||
Test2 = 0b00000001,
|
||||
Test3 = 0b00000010,
|
||||
Test4 = 0b00000100,
|
||||
Test5 = 0b00001000,
|
||||
Test6 = 0b00010000,
|
||||
Test7 = 0b00100000,
|
||||
Test8 = 0b01000000,
|
||||
Test9 = 0b10000000,
|
||||
Inverted = 0b01111111,
|
||||
Merged = Test3 + Test5,
|
||||
};
|
||||
|
||||
static void EnumHelperTestbench() {
|
||||
TestFlagEnum val;
|
||||
|
||||
Assert(YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5) == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Merge"));
|
||||
|
||||
Assert(YYCC::EnumHelper::Invert(TestFlagEnum::Test9) == TestFlagEnum::Inverted, YYCC_U8("YYCC::EnumHelper::Invert"));
|
||||
|
||||
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
|
||||
YYCC::EnumHelper::Mask(val, TestFlagEnum::Test3);
|
||||
Assert(YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
|
||||
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
|
||||
YYCC::EnumHelper::Mask(val, TestFlagEnum::Test4);
|
||||
Assert(!YYCC::EnumHelper::Bool(val), YYCC_U8("YYCC::EnumHelper::Mask"));
|
||||
|
||||
val = TestFlagEnum::Test3;
|
||||
YYCC::EnumHelper::Add(val, TestFlagEnum::Test5);
|
||||
Assert(val == TestFlagEnum::Merged, YYCC_U8("YYCC::EnumHelper::Add"));
|
||||
|
||||
val = TestFlagEnum::Merged;
|
||||
YYCC::EnumHelper::Remove(val, TestFlagEnum::Test5);
|
||||
Assert(val == TestFlagEnum::Test3, YYCC_U8("YYCC::EnumHelper::Remove"));
|
||||
|
||||
val = YYCC::EnumHelper::Merge(TestFlagEnum::Test3, TestFlagEnum::Test5);
|
||||
Assert(YYCC::EnumHelper::Has(val, TestFlagEnum::Test3), YYCC_U8("YYCC::EnumHelper::Has"));
|
||||
Assert(!YYCC::EnumHelper::Has(val, TestFlagEnum::Test4), YYCC_U8("YYCC::EnumHelper::Has"));
|
||||
|
||||
Assert(!YYCC::EnumHelper::Bool(TestFlagEnum::Test1), YYCC_U8("YYCC::EnumHelper::Bool"));
|
||||
Assert(YYCC::EnumHelper::Bool(TestFlagEnum::Test2), YYCC_U8("YYCC::EnumHelper::Bool"));
|
||||
|
||||
}
|
||||
|
||||
static void VersionMacroTestbench() {
|
||||
Assert(YYCC_VERCMP_E(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_E"));
|
||||
Assert(!YYCC_VERCMP_NE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NE"));
|
||||
Assert(YYCC_VERCMP_G(1, 2, 3, 0, 2, 5), YYCC_U8("YYCC_VERCMP_G"));
|
||||
Assert(YYCC_VERCMP_GE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_GE"));
|
||||
Assert(YYCC_VERCMP_NL(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NL"));
|
||||
Assert(YYCC_VERCMP_L(0, 2, 5, 1, 2, 3), YYCC_U8("YYCC_VERCMP_L"));
|
||||
Assert(YYCC_VERCMP_LE(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_LE"));
|
||||
Assert(YYCC_VERCMP_NG(1, 2, 3, 1, 2, 3), YYCC_U8("YYCC_VERCMP_NG"));
|
||||
}
|
||||
|
||||
enum class TestEnum : int8_t {
|
||||
Test1, Test2, Test3
|
||||
};
|
||||
|
203
testbench/shared/literals.cpp
Normal file
203
testbench/shared/literals.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
#include "literals.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace yyccshared::literals {
|
||||
|
||||
#define CONCAT(prefix, strl) prefix##strl
|
||||
#define U8_LITERAL(strl) CONCAT(u8, strl)
|
||||
#define U16_LITERAL(strl) CONCAT(u, strl)
|
||||
#define U32_LITERAL(strl) CONCAT(U, strl)
|
||||
#define WSTR_LITERAL(strl) CONCAT(L, strl)
|
||||
|
||||
#pragma region UtfLiterals Data
|
||||
|
||||
// UNICODE Test Strings
|
||||
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||
|
||||
#define UNICODE_STR_JAPAN "\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8"
|
||||
#define UNICODE_STR_CHINA "\u7B80\u4F53\u4E2D\u6587"
|
||||
#define UNICODE_STR_KOREA "\uD06C\uB85C\uC2A4 \uD50C\uB7AB\uD3FC\uC73C\uB85C"
|
||||
#define UNICODE_STR_ISRAEL "\u05DE\u05D3\u05D5\u05E8\u05D9\u05DD \u05DE\u05D1\u05D5\u05E7\u05E9\u05D9\u05DD"
|
||||
#define UNICODE_STR_EGYPT "\u0623\u0641\u0636\u0644 \u0627\u0644\u0628\u062D\u0648\u062B"
|
||||
#define UNICODE_STR_GREECE "\u03A3\u1F72 \u03B3\u03BD\u03C9\u03C1\u03AF\u03B6\u03C9 \u1F00\u03C0\u1F78"
|
||||
#define UNICODE_STR_RUSSIA \
|
||||
"\u0414\u0435\u0441\u044F\u0442\u0443\u044E \u041C\u0435\u0436\u0434\u0443\u043D\u0430\u0440\u043E\u0434\u043D\u0443\u044E"
|
||||
#define UNICODE_STR_THAILAND \
|
||||
"\u0E41\u0E1C\u0E48\u0E19\u0E14\u0E34\u0E19\u0E2E\u0E31\u0E48\u0E19\u0E40\u0E2A\u0E37\u0E48\u0E2D\u0E21\u0E42\u0E17\u0E23\u0E21\u0E41\u0E2A\u0E19\u0E2A\u0E31\u0E07\u0E40\u0E27\u0E0A"
|
||||
#define UNICODE_STR_FRANCE "fran\u00E7ais langue \u00E9trang\u00E8re"
|
||||
#define UNICODE_STR_SPAIN "ma\u00F1ana ol\u00E9"
|
||||
#define UNICODE_STR_MATHMATICS "\u222E E\u22C5da = Q, n \u2192 \u221E, \u2211 f(i) = \u220F g(i)"
|
||||
#define UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
|
||||
|
||||
static std::vector<std::u8string> UTFLIT_U8STR_VEC{
|
||||
U8_LITERAL(UNICODE_STR_JAPAN),
|
||||
U8_LITERAL(UNICODE_STR_CHINA),
|
||||
U8_LITERAL(UNICODE_STR_KOREA),
|
||||
U8_LITERAL(UNICODE_STR_ISRAEL),
|
||||
U8_LITERAL(UNICODE_STR_EGYPT),
|
||||
U8_LITERAL(UNICODE_STR_GREECE),
|
||||
U8_LITERAL(UNICODE_STR_RUSSIA),
|
||||
U8_LITERAL(UNICODE_STR_THAILAND),
|
||||
U8_LITERAL(UNICODE_STR_FRANCE),
|
||||
U8_LITERAL(UNICODE_STR_SPAIN),
|
||||
U8_LITERAL(UNICODE_STR_MATHMATICS),
|
||||
U8_LITERAL(UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::wstring> UTFLIT_WSTR_VEC{
|
||||
WSTR_LITERAL(UNICODE_STR_JAPAN),
|
||||
WSTR_LITERAL(UNICODE_STR_CHINA),
|
||||
WSTR_LITERAL(UNICODE_STR_KOREA),
|
||||
WSTR_LITERAL(UNICODE_STR_ISRAEL),
|
||||
WSTR_LITERAL(UNICODE_STR_EGYPT),
|
||||
WSTR_LITERAL(UNICODE_STR_GREECE),
|
||||
WSTR_LITERAL(UNICODE_STR_RUSSIA),
|
||||
WSTR_LITERAL(UNICODE_STR_THAILAND),
|
||||
WSTR_LITERAL(UNICODE_STR_FRANCE),
|
||||
WSTR_LITERAL(UNICODE_STR_SPAIN),
|
||||
WSTR_LITERAL(UNICODE_STR_MATHMATICS),
|
||||
WSTR_LITERAL(UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u16string> UTFLIT_U16STR_VEC{
|
||||
U16_LITERAL(UNICODE_STR_JAPAN),
|
||||
U16_LITERAL(UNICODE_STR_CHINA),
|
||||
U16_LITERAL(UNICODE_STR_KOREA),
|
||||
U16_LITERAL(UNICODE_STR_ISRAEL),
|
||||
U16_LITERAL(UNICODE_STR_EGYPT),
|
||||
U16_LITERAL(UNICODE_STR_GREECE),
|
||||
U16_LITERAL(UNICODE_STR_RUSSIA),
|
||||
U16_LITERAL(UNICODE_STR_THAILAND),
|
||||
U16_LITERAL(UNICODE_STR_FRANCE),
|
||||
U16_LITERAL(UNICODE_STR_SPAIN),
|
||||
U16_LITERAL(UNICODE_STR_MATHMATICS),
|
||||
U16_LITERAL(UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u32string> UTFLIT_U32STR_VEC{
|
||||
U32_LITERAL(UNICODE_STR_JAPAN),
|
||||
U32_LITERAL(UNICODE_STR_CHINA),
|
||||
U32_LITERAL(UNICODE_STR_KOREA),
|
||||
U32_LITERAL(UNICODE_STR_ISRAEL),
|
||||
U32_LITERAL(UNICODE_STR_EGYPT),
|
||||
U32_LITERAL(UNICODE_STR_GREECE),
|
||||
U32_LITERAL(UNICODE_STR_RUSSIA),
|
||||
U32_LITERAL(UNICODE_STR_THAILAND),
|
||||
U32_LITERAL(UNICODE_STR_FRANCE),
|
||||
U32_LITERAL(UNICODE_STR_SPAIN),
|
||||
U32_LITERAL(UNICODE_STR_MATHMATICS),
|
||||
U32_LITERAL(UNICODE_STR_EMOJI),
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UtfLiterals
|
||||
|
||||
UtfLiterals::UtfLiterals() :
|
||||
u8str_vec(UTFLIT_U8STR_VEC), u16str_vec(UTFLIT_U16STR_VEC), u32str_vec(UTFLIT_U32STR_VEC), wstr_vec(UTFLIT_WSTR_VEC) {
|
||||
// Check whether each vector has same size.
|
||||
bool okey = true;
|
||||
size_t exp_size = this->u8str_vec.size();
|
||||
|
||||
if (this->u16str_vec.size() != exp_size) okey = false;
|
||||
if (this->u32str_vec.size() != exp_size) okey = false;
|
||||
if (this->wstr_vec.size() != exp_size) okey = false;
|
||||
|
||||
if (!okey) throw std::logic_error("utf literal vector have different size");
|
||||
}
|
||||
|
||||
UtfLiterals::~UtfLiterals() {}
|
||||
|
||||
size_t UtfLiterals::get_size() const {
|
||||
// We have checked the size of each vector in ctor.
|
||||
// So we simply return the length of one of them.
|
||||
return this->u8str_vec.size();
|
||||
}
|
||||
|
||||
const std::vector<std::u8string> &UtfLiterals::get_u8str_vec() const {
|
||||
return this->u8str_vec;
|
||||
}
|
||||
|
||||
const std::vector<std::u16string> &UtfLiterals::get_u16str_vec() const {
|
||||
return this->u16str_vec;
|
||||
}
|
||||
|
||||
const std::vector<std::u32string> &UtfLiterals::get_u32str_vec() const {
|
||||
return this->u32str_vec;
|
||||
}
|
||||
|
||||
const std::vector<std::wstring> &UtfLiterals::get_wstr_vec() const {
|
||||
return this->wstr_vec;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region OtherLiteral
|
||||
|
||||
OtherLiteral::OtherLiteral(std::string &&other_str, uint32_t windows_ident, std::string &&iconv_ident, std::u8string &&pycodec_ident) :
|
||||
other_str(std::move(other_str)), windows_ident(windows_ident), iconv_ident(std::move(iconv_ident)),
|
||||
pycodec_ident(std::move(pycodec_ident)) {}
|
||||
|
||||
OtherLiteral::~OtherLiteral() {}
|
||||
|
||||
const std::string &OtherLiteral::get_other_str() const {
|
||||
return this->other_str;
|
||||
}
|
||||
|
||||
uint32_t OtherLiteral::get_windows_ident() const {
|
||||
return this->windows_ident;
|
||||
}
|
||||
|
||||
const std::string &OtherLiteral::get_iconv_ident() const {
|
||||
return this->iconv_ident;
|
||||
}
|
||||
|
||||
const std::u8string &OtherLiteral::get_pycodec_ident() const {
|
||||
return this->pycodec_ident;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region OtherLiterals Data
|
||||
|
||||
static std::vector<OtherLiteral> OTHERLIT_OTHERSTR_VEC{{"\xC4\xE3\xBA\xC3\xD6\xD0\xB9\xFA", UINT32_C(936), "GBK", u8"gbk"}};
|
||||
|
||||
#define OTHER_STR_GBK "\u4f60\u597d\u4e2d\u56fd"
|
||||
|
||||
static std::vector<std::u8string> OTHERLIT_U8STR_VEC{U8_LITERAL(OTHER_STR_GBK)};
|
||||
static std::vector<std::wstring> OTHERLIT_WSTR_VEC{WSTR_LITERAL(OTHER_STR_GBK)};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region OtherLiterals
|
||||
|
||||
OtherLiterals::OtherLiterals() : other_str_vec(OTHERLIT_OTHERSTR_VEC), u8str_vec(OTHERLIT_U8STR_VEC), wstr_vec(OTHERLIT_WSTR_VEC) {
|
||||
// Check whether each vector has same size.
|
||||
bool okey = true;
|
||||
size_t exp_size = this->other_str_vec.size();
|
||||
|
||||
if (this->u8str_vec.size() != exp_size) okey = false;
|
||||
if (this->wstr_vec.size() != exp_size) okey = false;
|
||||
|
||||
if (!okey) throw std::logic_error("utf literal vector have different size");
|
||||
}
|
||||
|
||||
OtherLiterals::~OtherLiterals() {}
|
||||
|
||||
size_t OtherLiterals::get_size() const {
|
||||
// We have checked the size, return size directly.
|
||||
return this->other_str_vec.size();
|
||||
}
|
||||
|
||||
const std::vector<OtherLiteral> &OtherLiterals::get_other_str_vec() const {
|
||||
return this->other_str_vec;
|
||||
}
|
||||
|
||||
const std::vector<std::u8string> &OtherLiterals::get_u8str_vec() const {
|
||||
return this->u8str_vec;
|
||||
}
|
||||
|
||||
const std::vector<std::wstring> &OtherLiterals::get_wstr_vec() const {
|
||||
return this->wstr_vec;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yyccshared::literals
|
64
testbench/shared/literals.hpp
Normal file
64
testbench/shared/literals.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/macro/class_copy_move.hpp>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace yyccshared::literals {
|
||||
|
||||
class UtfLiterals {
|
||||
public:
|
||||
UtfLiterals();
|
||||
~UtfLiterals();
|
||||
YYCC_DELETE_COPY_MOVE(UtfLiterals)
|
||||
|
||||
size_t get_size() const;
|
||||
const std::vector<std::u8string>& get_u8str_vec() const;
|
||||
const std::vector<std::u16string>& get_u16str_vec() const;
|
||||
const std::vector<std::u32string>& get_u32str_vec() const;
|
||||
const std::vector<std::wstring>& get_wstr_vec() const;
|
||||
|
||||
private:
|
||||
const std::vector<std::u8string>& u8str_vec;
|
||||
const std::vector<std::u16string>& u16str_vec;
|
||||
const std::vector<std::u32string>& u32str_vec;
|
||||
const std::vector<std::wstring>& wstr_vec;
|
||||
};
|
||||
|
||||
class OtherLiteral {
|
||||
public:
|
||||
OtherLiteral(std::string&& other_str, uint32_t windows_ident, std::string&& iconv_ident, std::u8string&& pycodec_ident);
|
||||
~OtherLiteral();
|
||||
YYCC_DEFAULT_COPY_MOVE(OtherLiteral)
|
||||
|
||||
const std::string& get_other_str() const;
|
||||
uint32_t get_windows_ident() const;
|
||||
const std::string& get_iconv_ident() const;
|
||||
const std::u8string& get_pycodec_ident() const;
|
||||
|
||||
private:
|
||||
std::string other_str;
|
||||
uint32_t windows_ident;
|
||||
std::string iconv_ident;
|
||||
std::u8string pycodec_ident;
|
||||
};
|
||||
|
||||
class OtherLiterals {
|
||||
public:
|
||||
OtherLiterals();
|
||||
~OtherLiterals();
|
||||
YYCC_DELETE_COPY_MOVE(OtherLiterals)
|
||||
|
||||
size_t get_size() const;
|
||||
const std::vector<OtherLiteral>& get_other_str_vec() const;
|
||||
const std::vector<std::u8string>& get_u8str_vec() const;
|
||||
const std::vector<std::wstring>& get_wstr_vec() const;
|
||||
|
||||
private:
|
||||
const std::vector<OtherLiteral>& other_str_vec;
|
||||
const std::vector<std::u8string>& u8str_vec;
|
||||
const std::vector<std::wstring>& wstr_vec;
|
||||
};
|
||||
|
||||
} // namespace yyccshared::literals
|
121
testbench/yycc/carton/pycodec.cpp
Normal file
121
testbench/yycc/carton/pycodec.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/carton/pycodec.hpp>
|
||||
#include "../../shared/literals.hpp"
|
||||
|
||||
#define ENC ::yycc::carton::pycodec
|
||||
|
||||
namespace yycctest::carton::pycodec {
|
||||
|
||||
static auto UTF_LITERALS = ::yyccshared::literals::UtfLiterals();
|
||||
static auto OTHER_LITERALS = ::yyccshared::literals::OtherLiterals();
|
||||
|
||||
TEST(CartonPycodec, ValidateName) {
|
||||
EXPECT_TRUE(ENC::is_valid_encoding_name(u8"utf-8"));
|
||||
EXPECT_TRUE(ENC::is_valid_encoding_name(u8"gb2312"));
|
||||
EXPECT_TRUE(ENC::is_valid_encoding_name(u8"cp1252"));
|
||||
|
||||
EXPECT_FALSE(ENC::is_valid_encoding_name(u8"this must not be a valid encoding name"));
|
||||
}
|
||||
|
||||
TEST(CartonPycodec, CharToUtf8) {
|
||||
const auto& other_str_literals = OTHER_LITERALS.get_other_str_vec();
|
||||
const auto& u8str_literals = OTHER_LITERALS.get_u8str_vec();
|
||||
|
||||
for (size_t i = 0; i < OTHER_LITERALS.get_size(); ++i) {
|
||||
const auto& other_str_literal = other_str_literals[i];
|
||||
ENC::CharToUtf8 cv(other_str_literal.get_pycodec_ident());
|
||||
|
||||
auto rv = cv.to_utf8(other_str_literal.get_other_str());
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CartonPycodec, Utf8ToChar) {
|
||||
const auto& other_str_literals = OTHER_LITERALS.get_other_str_vec();
|
||||
const auto& u8str_literals = OTHER_LITERALS.get_u8str_vec();
|
||||
|
||||
for (size_t i = 0; i < OTHER_LITERALS.get_size(); ++i) {
|
||||
const auto& other_str_literal = other_str_literals[i];
|
||||
ENC::Utf8ToChar cv(other_str_literal.get_pycodec_ident());
|
||||
|
||||
auto rv = cv.to_char(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), other_str_literal.get_other_str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CartonPycodec, Utf8ToWchar) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& wstr_literals = UTF_LITERALS.get_wstr_vec();
|
||||
|
||||
ENC::Utf8ToWchar cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_wchar(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), wstr_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CartonPycodec, WcharToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& wstr_literals = UTF_LITERALS.get_wstr_vec();
|
||||
|
||||
ENC::WcharToUtf8 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf8(wstr_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CartonPycodec, Utf8ToUtf16) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u16str_literals = UTF_LITERALS.get_u16str_vec();
|
||||
|
||||
ENC::Utf8ToUtf16 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf16(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u16str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CartonPycodec, Utf16ToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u16str_literals = UTF_LITERALS.get_u16str_vec();
|
||||
|
||||
ENC::Utf16ToUtf8 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf8(u16str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CartonPycodec, Utf8ToUtf32) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u32str_literals = UTF_LITERALS.get_u32str_vec();
|
||||
|
||||
ENC::Utf8ToUtf32 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf32(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u32str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CartonPycodec, Utf32ToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u32str_literals = UTF_LITERALS.get_u32str_vec();
|
||||
|
||||
ENC::Utf32ToUtf8 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf8(u32str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
61
testbench/yycc/carton/tabulate.cpp
Normal file
61
testbench/yycc/carton/tabulate.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/carton/tabulate.hpp>
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#define TABULATE ::yycc::carton::tabulate
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycctest::carton::tabulate {
|
||||
|
||||
class CartonTabulate : public ::testing::Test {
|
||||
protected:
|
||||
CartonTabulate() : table(3u), ss() {
|
||||
// setup basic data
|
||||
table.set_prefix(u8"# ");
|
||||
table.set_header({u8"中文1", u8"中文2", u8"中文3"});
|
||||
table.set_bar(u8"===");
|
||||
table.add_row({u8"a", u8"b", u8"c"});
|
||||
}
|
||||
~CartonTabulate() override = default;
|
||||
|
||||
void expected_print(const std::u8string_view& exp) {
|
||||
ss.str("");
|
||||
table.print(ss);
|
||||
EXPECT_EQ(REINTERPRET::as_utf8_view(ss.view()), exp);
|
||||
}
|
||||
|
||||
TABULATE::Tabulate table;
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
TEST_F(CartonTabulate, Full) {
|
||||
table.show_header(true);
|
||||
table.show_bar(true);
|
||||
expected_print(u8"# 中文1 中文2 中文3 \n"
|
||||
u8"# === === === \n"
|
||||
u8"# a b c \n");
|
||||
}
|
||||
|
||||
TEST_F(CartonTabulate, NoHeader) {
|
||||
table.show_header(false);
|
||||
table.show_bar(true);
|
||||
expected_print(u8"# === === === \n"
|
||||
u8"# a b c \n");
|
||||
}
|
||||
|
||||
TEST_F(CartonTabulate, NoBar) {
|
||||
table.show_header(true);
|
||||
table.show_bar(false);
|
||||
expected_print(u8"# 中文1 中文2 中文3 \n"
|
||||
u8"# a b c \n");
|
||||
}
|
||||
|
||||
TEST_F(CartonTabulate, OnlyData) {
|
||||
table.show_header(false);
|
||||
table.show_bar(false);
|
||||
expected_print(u8"# a b c \n");
|
||||
}
|
||||
|
||||
} // namespace yycctest::carton::tabulate
|
44
testbench/yycc/carton/termcolor.cpp
Normal file
44
testbench/yycc/carton/termcolor.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/carton/termcolor.hpp>
|
||||
#include <yycc/flag_enum.hpp>
|
||||
|
||||
#define TERMCOLOR ::yycc::carton::termcolor
|
||||
#define FLAG_ENUM ::yycc::flag_enum
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
using Color = TERMCOLOR::Color;
|
||||
using Attribute = TERMCOLOR::Attribute;
|
||||
|
||||
namespace yycctest::carton::termcolor {
|
||||
|
||||
TEST(CartonTermcolor, Lowlevel) {
|
||||
EXPECT_EQ(TERMCOLOR::foreground(Color::Default), u8"");
|
||||
EXPECT_EQ(TERMCOLOR::foreground(Color::Red), u8"\033[31m");
|
||||
EXPECT_EQ(TERMCOLOR::foreground(Color::LightRed), u8"\033[91m");
|
||||
|
||||
EXPECT_EQ(TERMCOLOR::background(Color::Default), u8"");
|
||||
EXPECT_EQ(TERMCOLOR::background(Color::Red), u8"\033[41m");
|
||||
EXPECT_EQ(TERMCOLOR::background(Color::LightRed), u8"\033[101m");
|
||||
|
||||
EXPECT_EQ(TERMCOLOR::style(Attribute::Default), u8"");
|
||||
EXPECT_EQ(TERMCOLOR::style(Attribute::Italic), u8"\033[3m");
|
||||
EXPECT_EQ(TERMCOLOR::styles(FLAG_ENUM::merge(Attribute::Italic, Attribute::Bold)),
|
||||
u8"\033[1m"
|
||||
"\033[3m");
|
||||
|
||||
EXPECT_EQ(TERMCOLOR::reset(), u8"\033[0m"sv);
|
||||
}
|
||||
|
||||
TEST(CartonTermcolor, Highlevel) {
|
||||
EXPECT_EQ(TERMCOLOR::colored(u8"hello"sv), u8"hello\033[0m");
|
||||
EXPECT_EQ(TERMCOLOR::colored(u8"hello"sv, Color::LightWhite, Color::Red, FLAG_ENUM::merge(Attribute::Italic, Attribute::Bold)),
|
||||
u8"\033[97m"
|
||||
"\033[41m"
|
||||
"\033[1m"
|
||||
"\033[3m"
|
||||
"hello"
|
||||
"\033[0m");
|
||||
}
|
||||
|
||||
} // namespace yycctest::carton::termcolor
|
54
testbench/yycc/carton/wcwidth.cpp
Normal file
54
testbench/yycc/carton/wcwidth.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/carton/wcwidth.hpp>
|
||||
#include <yycc/carton/termcolor.hpp>
|
||||
|
||||
#define WCWDITH ::yycc::carton::wcwidth
|
||||
#define TERMCOLOR ::yycc::carton::termcolor
|
||||
|
||||
namespace yycctest::carton::wcwidth {
|
||||
|
||||
#define TEST_SUCCESS(strl, len) \
|
||||
{ \
|
||||
auto rv = WCWDITH::wcswidth(strl); \
|
||||
ASSERT_TRUE(rv.has_value()); \
|
||||
EXPECT_EQ(rv.value(), len); \
|
||||
}
|
||||
|
||||
#define TEST_FAIL(strl) \
|
||||
{ \
|
||||
auto rv = WCWDITH::wcswidth(strl); \
|
||||
EXPECT_FALSE(rv.has_value()); \
|
||||
}
|
||||
|
||||
TEST(CartonWcwdith, BadAnsi) {
|
||||
TEST_FAIL(u8"\033?");
|
||||
}
|
||||
|
||||
TEST(CartonWcwdith, BadCsi) {
|
||||
TEST_FAIL(u8"\033[\t");
|
||||
}
|
||||
|
||||
TEST(CartonWcwdith, English) {
|
||||
TEST_SUCCESS(u8"abc", 3);
|
||||
}
|
||||
|
||||
TEST(CartonWcwdith, Chinese) {
|
||||
TEST_SUCCESS(u8"中文", 4);
|
||||
TEST_SUCCESS(u8"中a文", 5);
|
||||
}
|
||||
|
||||
TEST(CartonWcwdith, Japanese) {
|
||||
TEST_SUCCESS(u8"ありがとう", 10);
|
||||
TEST_SUCCESS(u8"アリガトウ", 10);
|
||||
TEST_SUCCESS(u8"アリガトウ", 6);
|
||||
}
|
||||
|
||||
TEST(CartonWcwdith, Termcolor) {
|
||||
using Color = TERMCOLOR::Color;
|
||||
TEST_SUCCESS(TERMCOLOR::colored(u8"abc", Color::Red), 3);
|
||||
TEST_SUCCESS(TERMCOLOR::colored(u8"中文", Color::Red), 4);
|
||||
TEST_SUCCESS(TERMCOLOR::colored(u8"ありがとう", Color::Red), 10);
|
||||
}
|
||||
|
||||
} // namespace yycctest::carton::wcwidth
|
115
testbench/yycc/encoding/iconv.cpp
Normal file
115
testbench/yycc/encoding/iconv.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/encoding/iconv.hpp>
|
||||
#include "../../shared/literals.hpp"
|
||||
|
||||
#define ENC ::yycc::encoding::iconv
|
||||
|
||||
namespace yycctest::encoding::iconv {
|
||||
#if defined(YYCC_FEAT_ICONV)
|
||||
|
||||
static auto UTF_LITERALS = ::yyccshared::literals::UtfLiterals();
|
||||
static auto OTHER_LITERALS = ::yyccshared::literals::OtherLiterals();
|
||||
|
||||
TEST(EncodingIconv, CharToUtf8) {
|
||||
const auto& other_str_literals = OTHER_LITERALS.get_other_str_vec();
|
||||
const auto& u8str_literals = OTHER_LITERALS.get_u8str_vec();
|
||||
|
||||
for (size_t i = 0; i < OTHER_LITERALS.get_size(); ++i) {
|
||||
const auto& other_str_literal = other_str_literals[i];
|
||||
ENC::CharToUtf8 cv(other_str_literal.get_iconv_ident());
|
||||
|
||||
auto rv = cv.to_utf8(other_str_literal.get_other_str());
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingIconv, Utf8ToChar) {
|
||||
const auto& other_str_literals = OTHER_LITERALS.get_other_str_vec();
|
||||
const auto& u8str_literals = OTHER_LITERALS.get_u8str_vec();
|
||||
|
||||
for (size_t i = 0; i < OTHER_LITERALS.get_size(); ++i) {
|
||||
const auto& other_str_literal = other_str_literals[i];
|
||||
ENC::Utf8ToChar cv(other_str_literal.get_iconv_ident());
|
||||
|
||||
auto rv = cv.to_char(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), other_str_literal.get_other_str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingIconv, Utf8ToWchar) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& wstr_literals = UTF_LITERALS.get_wstr_vec();
|
||||
|
||||
ENC::Utf8ToWchar cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_wchar(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), wstr_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingIconv, WcharToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& wstr_literals = UTF_LITERALS.get_wstr_vec();
|
||||
|
||||
ENC::WcharToUtf8 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf8(wstr_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingIconv, Utf8ToUtf16) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u16str_literals = UTF_LITERALS.get_u16str_vec();
|
||||
|
||||
ENC::Utf8ToUtf16 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf16(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u16str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingIconv, Utf16ToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u16str_literals = UTF_LITERALS.get_u16str_vec();
|
||||
|
||||
ENC::Utf16ToUtf8 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf8(u16str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingIconv, Utf8ToUtf32) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u32str_literals = UTF_LITERALS.get_u32str_vec();
|
||||
|
||||
ENC::Utf8ToUtf32 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf32(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u32str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingIconv, Utf32ToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u32str_literals = UTF_LITERALS.get_u32str_vec();
|
||||
|
||||
ENC::Utf32ToUtf8 cv;
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = cv.to_utf8(u32str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace yycctest::encoding::iconv
|
56
testbench/yycc/encoding/stl.cpp
Normal file
56
testbench/yycc/encoding/stl.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/encoding/stl.hpp>
|
||||
#include "../../shared/literals.hpp"
|
||||
|
||||
#define ENC ::yycc::encoding::stl
|
||||
|
||||
namespace yycctest::encoding::stl {
|
||||
|
||||
static auto UTF_LITERALS = ::yyccshared::literals::UtfLiterals();
|
||||
|
||||
TEST(EncodingStl, Utf8ToUtf16) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u16str_literals = UTF_LITERALS.get_u16str_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf16(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u16str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingStl, Utf16ToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u16str_literals = UTF_LITERALS.get_u16str_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf8(u16str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingStl, Utf8ToUtf32) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u32str_literals = UTF_LITERALS.get_u32str_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf32(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u32str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingStl, Utf32ToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u32str_literals = UTF_LITERALS.get_u32str_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf8(u32str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace yycctest::encoding::stl
|
137
testbench/yycc/encoding/windows.cpp
Normal file
137
testbench/yycc/encoding/windows.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/encoding/windows.hpp>
|
||||
#include "../../shared/literals.hpp"
|
||||
|
||||
#define ENC ::yycc::encoding::windows
|
||||
|
||||
namespace yycctest::encoding::windows {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
static auto UTF_LITERALS = ::yyccshared::literals::UtfLiterals();
|
||||
static auto OTHER_LITERALS = ::yyccshared::literals::OtherLiterals();
|
||||
|
||||
TEST(EncodingWindows, CharToWchar) {
|
||||
const auto& other_str_literals = OTHER_LITERALS.get_other_str_vec();
|
||||
const auto& wstr_literals = OTHER_LITERALS.get_wstr_vec();
|
||||
|
||||
for (size_t i = 0; i < OTHER_LITERALS.get_size(); ++i) {
|
||||
const auto& other_str_literal = other_str_literals[i];
|
||||
|
||||
auto rv = ENC::to_wchar(other_str_literal.get_other_str(), other_str_literal.get_windows_ident());
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), wstr_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingWindows, WcharToChar) {
|
||||
const auto& other_str_literals = OTHER_LITERALS.get_other_str_vec();
|
||||
const auto& wstr_literals = OTHER_LITERALS.get_wstr_vec();
|
||||
|
||||
for (size_t i = 0; i < OTHER_LITERALS.get_size(); ++i) {
|
||||
const auto& other_str_literal = other_str_literals[i];
|
||||
|
||||
auto rv = ENC::to_char(wstr_literals[i], other_str_literal.get_windows_ident());
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), other_str_literal.get_other_str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingWindows, CharToUtf8) {
|
||||
const auto& other_str_literals = OTHER_LITERALS.get_other_str_vec();
|
||||
const auto& u8str_literals = OTHER_LITERALS.get_u8str_vec();
|
||||
|
||||
for (size_t i = 0; i < OTHER_LITERALS.get_size(); ++i) {
|
||||
const auto& other_str_literal = other_str_literals[i];
|
||||
|
||||
auto rv = ENC::to_utf8(other_str_literal.get_other_str(), other_str_literal.get_windows_ident());
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingWindows, Utf8ToChar) {
|
||||
const auto& other_str_literals = OTHER_LITERALS.get_other_str_vec();
|
||||
const auto& u8str_literals = OTHER_LITERALS.get_u8str_vec();
|
||||
|
||||
for (size_t i = 0; i < OTHER_LITERALS.get_size(); ++i) {
|
||||
const auto& other_str_literal = other_str_literals[i];
|
||||
|
||||
auto rv = ENC::to_char(u8str_literals[i], other_str_literal.get_windows_ident());
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), other_str_literal.get_other_str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingWindows, Utf8ToWchar) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& wstr_literals = UTF_LITERALS.get_wstr_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_wchar(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), wstr_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingWindows, WcharToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& wstr_literals = UTF_LITERALS.get_wstr_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf8(wstr_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(EncodingWindows, Utf8ToUtf16) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u16str_literals = UTF_LITERALS.get_u16str_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf16(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u16str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingWindows, Utf16ToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u16str_literals = UTF_LITERALS.get_u16str_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf8(u16str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingWindows, Utf8ToUtf32) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u32str_literals = UTF_LITERALS.get_u32str_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf32(u8str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u32str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodingWindows, Utf32ToUtf8) {
|
||||
const auto& u8str_literals = UTF_LITERALS.get_u8str_vec();
|
||||
const auto& u32str_literals = UTF_LITERALS.get_u32str_vec();
|
||||
|
||||
for (size_t i = 0; i < UTF_LITERALS.get_size(); ++i) {
|
||||
auto rv = ENC::to_utf8(u32str_literals[i]);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), u8str_literals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
} // namespace yycctest::encoding::windows
|
@ -1,6 +1,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/flag_enum.hpp>
|
||||
#include <cinttypes>
|
||||
|
||||
#include <yycc/rust/prelude.hpp>
|
||||
|
||||
@ -74,4 +75,9 @@ namespace yycctest::flag_enum {
|
||||
EXPECT_TRUE(FLAG_ENUM::boolean(TestEnum::MergedBit247));
|
||||
}
|
||||
|
||||
TEST(FlagEnum, Integer) {
|
||||
EXPECT_EQ(FLAG_ENUM::integer(TestEnum::Empty), UINT8_C(0));
|
||||
EXPECT_EQ(FLAG_ENUM::integer(TestEnum::Bit1), UINT8_C(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
20
testbench/yycc/macro/compiler_detector.cpp
Normal file
20
testbench/yycc/macro/compiler_detector.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/macro/compiler_detector.hpp>
|
||||
|
||||
#define COMPILER ::yycc::macro::compiler
|
||||
|
||||
namespace yycctest::macro::compiler {
|
||||
|
||||
TEST(MacroCompiler, Main) {
|
||||
auto rv = COMPILER::get_compiler();
|
||||
#if defined(YYCC_CC_MSVC)
|
||||
EXPECT_EQ(rv, COMPILER::CompilerKind::Msvc);
|
||||
#elif defined(YYCC_CC_GCC)
|
||||
EXPECT_EQ(rv, COMPILER::CompilerKind::Gcc);
|
||||
#else
|
||||
EXPECT_EQ(rv, COMPILER::CompilerKind::Clang);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace yycctest::macro::compiler
|
18
testbench/yycc/macro/endian_detector.cpp
Normal file
18
testbench/yycc/macro/endian_detector.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/macro/endian_detector.hpp>
|
||||
|
||||
#define ENDIAN ::yycc::macro::endian
|
||||
|
||||
namespace yycctest::macro::endian {
|
||||
|
||||
TEST(MacroEndian, Main) {
|
||||
auto rv = ENDIAN::get_endian();
|
||||
#if defined(YYCC_ENDIAN_LITTLE)
|
||||
EXPECT_EQ(rv, ENDIAN::EndianKind::Little);
|
||||
#else
|
||||
EXPECT_EQ(rv, ENDIAN::EndianKind::Big);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace yycctest::macro::endian
|
20
testbench/yycc/macro/os_detector.cpp
Normal file
20
testbench/yycc/macro/os_detector.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/macro/os_detector.hpp>
|
||||
|
||||
#define OS ::yycc::macro::os
|
||||
|
||||
namespace yycctest::macro::os {
|
||||
|
||||
TEST(MacroOs, Main) {
|
||||
auto rv = OS::get_os();
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
EXPECT_EQ(rv, OS::OsKind::Windows);
|
||||
#elif defined(YYCC_OS_LINUX)
|
||||
EXPECT_EQ(rv, OS::OsKind::Linux);
|
||||
#else
|
||||
EXPECT_EQ(rv, OS::OsKind::MacOs);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace yycctest::macro::os
|
18
testbench/yycc/macro/ptr_size_detector.cpp
Normal file
18
testbench/yycc/macro/ptr_size_detector.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/macro/ptr_size_detector.hpp>
|
||||
|
||||
#define PTR_SIZE ::yycc::macro::ptr_size
|
||||
|
||||
namespace yycctest::macro::ptr_size {
|
||||
|
||||
TEST(MacroPtrSize, Main) {
|
||||
auto rv = PTR_SIZE::get_ptr_size();
|
||||
#if defined(YYCC_PTRSIZE_32)
|
||||
EXPECT_EQ(rv, PTR_SIZE::PtrSizeKind::Bits32);
|
||||
#else
|
||||
EXPECT_EQ(rv, PTR_SIZE::PtrSizeKind::Bits64);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace yycctest::macro::ptr_size
|
20
testbench/yycc/macro/stl_detector.cpp
Normal file
20
testbench/yycc/macro/stl_detector.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/macro/stl_detector.hpp>
|
||||
|
||||
#define STL ::yycc::macro::stl
|
||||
|
||||
namespace yycctest::macro::stl {
|
||||
|
||||
TEST(MacroStl, Main) {
|
||||
auto rv = STL::get_stl();
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
EXPECT_EQ(rv, STL::StlKind::MsStl);
|
||||
#elif defined(YYCC_STL_GNUSTL)
|
||||
EXPECT_EQ(rv, STL::StlKind::GnuStl);
|
||||
#else
|
||||
EXPECT_EQ(rv, STL::StlKind::ClangStl);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace yycctest::macro::stl
|
@ -14,7 +14,7 @@ namespace yycctest::num::parse {
|
||||
#define TEST_SUCCESS(type_t, expected_value, string_value, ...) \
|
||||
{ \
|
||||
std::u8string cache_string(string_value); \
|
||||
auto rv = PARSE::parse<type_t>(cache_string __VA_OPT__(, ) __VA_ARGS__); \
|
||||
auto rv = PARSE::parse<type_t>(cache_string __VA_OPT__(,) __VA_ARGS__); \
|
||||
ASSERT_TRUE(rv.has_value()); \
|
||||
EXPECT_EQ(rv.value(), expected_value); \
|
||||
}
|
||||
@ -22,7 +22,7 @@ namespace yycctest::num::parse {
|
||||
#define TEST_FAIL(type_t, string_value, ...) \
|
||||
{ \
|
||||
std::u8string cache_string(string_value); \
|
||||
auto rv = PARSE::parse<type_t>(cache_string __VA_OPT__(, ) __VA_ARGS__); \
|
||||
auto rv = PARSE::parse<type_t>(cache_string __VA_OPT__(,) __VA_ARGS__); \
|
||||
EXPECT_FALSE(rv.has_value()); \
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ namespace yycctest::num::stringify {
|
||||
#define TEST_SUCCESS(type_t, value, string_value, ...) \
|
||||
{ \
|
||||
type_t cache = value; \
|
||||
std::u8string ret = STRINGIFY::stringify<type_t>(cache __VA_OPT__(, ) __VA_ARGS__); \
|
||||
std::u8string ret = STRINGIFY::stringify<type_t>(cache __VA_OPT__(,) __VA_ARGS__); \
|
||||
EXPECT_EQ(ret, string_value); \
|
||||
}
|
||||
|
||||
|
24
testbench/yycc/patch/fopen.cpp
Normal file
24
testbench/yycc/patch/fopen.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/patch/fopen.hpp>
|
||||
|
||||
#define FOPEN ::yycc::patch::fopen
|
||||
|
||||
namespace yycctest::patch::fopen {
|
||||
|
||||
TEST(PatchFopen, Normal) {
|
||||
FILE* handle;
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// In Windows, we can always visit NUL device.
|
||||
handle = FOPEN::fopen(u8"NUL", u8"wb");
|
||||
#else
|
||||
// In other system following UNIX design, we can visit /dev/null device.
|
||||
handle = FOPEN::fopen(u8"/dev/null", u8"wb");
|
||||
#endif
|
||||
|
||||
ASSERT_TRUE(handle != nullptr);
|
||||
std::fclose(handle);
|
||||
}
|
||||
|
||||
} // namespace yycctest::patch::fopen
|
45
testbench/yycc/rust/env.cpp
Normal file
45
testbench/yycc/rust/env.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/rust/env.hpp>
|
||||
|
||||
#define ENV ::yycc::rust::env
|
||||
|
||||
namespace yycctest::rust::env {
|
||||
|
||||
constexpr char8_t VAR_NAME[] = u8"HOMER";
|
||||
constexpr char8_t VAR_VALUE[] = u8"doh";
|
||||
|
||||
TEST(RustEnv, All) {
|
||||
// Write a new variable should okey
|
||||
{
|
||||
auto rv = ENV::set_var(VAR_NAME, VAR_VALUE);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
// After writing, we can fetch it and check its value.
|
||||
{
|
||||
auto rv = ENV::get_var(VAR_NAME);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
EXPECT_EQ(rv.value(), VAR_VALUE);
|
||||
}
|
||||
|
||||
// The we can delete it.
|
||||
{
|
||||
auto rv = ENV::del_var(VAR_NAME);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
// Delete inexisting variable also should be okey
|
||||
{
|
||||
auto rv = ENV::del_var(VAR_NAME);
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
// After deleting, we can not fetch it anymore.
|
||||
{
|
||||
auto rv = ENV::get_var(VAR_NAME);
|
||||
ASSERT_FALSE(rv.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
31
testbench/yycc/string/stream.cpp
Normal file
31
testbench/yycc/string/stream.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/string/stream.hpp>
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
using namespace std::literals::string_view_literals;
|
||||
using namespace ::yycc::string::stream;
|
||||
|
||||
namespace yycctest::string::stream {
|
||||
|
||||
TEST(StringStream, StringView) {
|
||||
std::stringstream ss;
|
||||
ss << u8"hello"sv;
|
||||
EXPECT_EQ(REINTERPRET::as_utf8_view(ss.view()), u8"hello"sv);
|
||||
}
|
||||
|
||||
TEST(StringStream, CStrPtr) {
|
||||
std::stringstream ss;
|
||||
ss << u8"hello";
|
||||
EXPECT_EQ(REINTERPRET::as_utf8_view(ss.view()), u8"hello");
|
||||
}
|
||||
|
||||
TEST(StringStream, Character) {
|
||||
std::stringstream ss;
|
||||
ss << u8'y';
|
||||
EXPECT_EQ(REINTERPRET::as_utf8_view(ss.view()), u8"y");
|
||||
}
|
||||
|
||||
}
|
16
testbench/yycc/windows/com.cpp
Normal file
16
testbench/yycc/windows/com.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/windows/com.hpp>
|
||||
|
||||
#define COM ::yycc::windows::com
|
||||
|
||||
namespace yycctest::windows::com {
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(WindowsCom, IsInitialized) {
|
||||
// COM environment should always be ready.
|
||||
EXPECT_TRUE(COM::is_initialized());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
17
testbench/yycc/windows/console.cpp
Normal file
17
testbench/yycc/windows/console.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/windows/console.hpp>
|
||||
|
||||
#define CONSOLE ::yycc::windows::console
|
||||
|
||||
namespace yycctest::windows::console {
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(WindowsConsole, ColorfulConsole) {
|
||||
// Set colorful console should always be success.
|
||||
auto rv = CONSOLE::colorful_console();
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
55
testbench/yycc/windows/dialog.cpp
Normal file
55
testbench/yycc/windows/dialog.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/windows/dialog.hpp>
|
||||
|
||||
#define DIALOG ::yycc::windows::dialog
|
||||
|
||||
namespace yycctest::windows::dialog {
|
||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(WindowsDialog, Normal) {
|
||||
// TODO:
|
||||
// I temporaryly disable all dialog open functions in this function after testing them.
|
||||
// Because they need human to operate them to finish the test.
|
||||
// Once I find a better way to do automatic test (maybe send message to these dialogs to close them?)
|
||||
// I will add them back.
|
||||
|
||||
// Prepare parameters
|
||||
DIALOG::FileDialog params;
|
||||
auto& filters = params.configre_file_types();
|
||||
filters.add_filter(u8"Microsoft Word (*.docx; *.doc)", {u8"*.docx", u8"*.doc"});
|
||||
filters.add_filter(u8"Microsoft Excel (*.xlsx; *.xls)", {u8"*.xlsx", u8"*.xls"});
|
||||
filters.add_filter(u8"Microsoft PowerPoint (*.pptx; *.ppt)", {u8"*.pptx", u8"*.ppt"});
|
||||
filters.add_filter(u8"Text File (*.txt)", {u8"*.txt"});
|
||||
filters.add_filter(u8"All Files (*.*)", {u8"*.*"});
|
||||
params.set_default_file_type_index(1u);
|
||||
|
||||
//// Open file
|
||||
//{
|
||||
// auto rv = DIALOG::open_file(params);
|
||||
// EXPECT_TRUE(rv.has_value());
|
||||
//}
|
||||
//// Open files
|
||||
//{
|
||||
// auto rv = DIALOG::open_files(params);
|
||||
// EXPECT_TRUE(rv.has_value());
|
||||
//}
|
||||
//// Save file
|
||||
//{
|
||||
// auto rv = DIALOG::save_file(params);
|
||||
// EXPECT_TRUE(rv.has_value());
|
||||
//}
|
||||
|
||||
// Clear file filters for following operations
|
||||
params.clear();
|
||||
params.set_default_file_type_index(0u);
|
||||
|
||||
//// Open folder
|
||||
//{
|
||||
// auto rv = DIALOG::open_folder(params);
|
||||
// EXPECT_TRUE(rv.has_value());
|
||||
//}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
49
testbench/yycc/windows/winfct.cpp
Normal file
49
testbench/yycc/windows/winfct.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/windows/winfct.hpp>
|
||||
|
||||
#define WINFCT ::yycc::windows::winfct
|
||||
|
||||
namespace yycctest::windows::winfct {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
TEST(WindowsWinFct, GetCurrentModule) {
|
||||
auto rv = WINFCT::get_current_module();
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
TEST(WindowsWinFct, GetTempDirectory) {
|
||||
auto rv = WINFCT::get_temp_directory();
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
TEST(WindowsWinFct, GetModuleFileName) {
|
||||
auto handle = WINFCT::get_current_module();
|
||||
ASSERT_TRUE(handle.has_value());
|
||||
|
||||
auto rv = WINFCT::get_module_file_name(handle.value());
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
TEST(WindowsWinFct, IsValidCodePage) {
|
||||
// Test valid code page
|
||||
EXPECT_TRUE(WINFCT::is_valid_code_page(437));
|
||||
EXPECT_TRUE(WINFCT::is_valid_code_page(65001));
|
||||
// This code page must be invalid
|
||||
EXPECT_FALSE(WINFCT::is_valid_code_page(6161));
|
||||
}
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
TEST(WindowsWinFct, GetKnownPath) {
|
||||
auto rv = WINFCT::get_known_path(WINFCT::KnownDirectory::LocalAppData);
|
||||
EXPECT_TRUE(rv.has_value());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// YYC MARK:
|
||||
// I can't test CopyFile, MoveFile and DeleteFile.
|
||||
|
||||
#endif
|
||||
} // namespace yycctest::windows::winfct
|
Reference in New Issue
Block a user