Compare commits
30 Commits
2b6ac98f27
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 422aa152ff | |||
| 92ce0e29cb | |||
| a0f032c28b | |||
| b51ded2101 | |||
| 09f07d99f7 | |||
| c19561cb54 | |||
| 9ad199073a | |||
| 908d48a7c1 | |||
| aa6dd3031a | |||
| 19df293463 | |||
| 71eb0741f6 | |||
| 09fea7e0a3 | |||
| aecf9bb8cc | |||
| 6449ae1977 | |||
| 1c1e709ed1 | |||
| fe4193efa7 | |||
| 746d20a835 | |||
| 8989e909ad | |||
| 718fe426bf | |||
| 1a4074fd98 | |||
| 74027e7297 | |||
| 044c04aa07 | |||
| e161dafac5 | |||
| 4d9487813b | |||
| 7a34057836 | |||
| 17053f4ebf | |||
| de2b927a14 | |||
| a50233ab6e | |||
| 6dfd957ce9 | |||
| 215a8ce8b8 |
4
.github/scripts/README.md
vendored
Normal file
4
.github/scripts/README.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# GitHub Action Scripts
|
||||
|
||||
These script files are only used for GitHub Action.
|
||||
These script files should only be executed in their root directory respectively.
|
||||
17
.github/scripts/gbenchmark/linux.sh
vendored
Normal file
17
.github/scripts/gbenchmark/linux.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Create build and install directory
|
||||
mkdir build install
|
||||
|
||||
# Build project
|
||||
cd build
|
||||
cmake -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake --build .
|
||||
cmake --install . --prefix=../install
|
||||
cd ..
|
||||
|
||||
# Record install directory
|
||||
cd install
|
||||
export benchmark_ROOT=$(pwd)
|
||||
cd ..
|
||||
17
.github/scripts/gbenchmark/macos.sh
vendored
Normal file
17
.github/scripts/gbenchmark/macos.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Create build and install directory
|
||||
mkdir build install
|
||||
|
||||
# Build project
|
||||
cd build
|
||||
cmake -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake --build .
|
||||
cmake --install . --prefix=../install
|
||||
cd ..
|
||||
|
||||
# Record install directory
|
||||
cd install
|
||||
export benchmark_ROOT=$(pwd)
|
||||
cd ..
|
||||
17
.github/scripts/gbenchmark/windows.bat
vendored
Normal file
17
.github/scripts/gbenchmark/windows.bat
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
@ECHO OFF
|
||||
|
||||
:: Create build and install directory
|
||||
MKDIR build
|
||||
MKDIR install
|
||||
|
||||
:: Build project
|
||||
CD build
|
||||
cmake -A x64 -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF ..
|
||||
cmake --build . --config Release
|
||||
cmake --install . --prefix=../install --config Release
|
||||
CD ..
|
||||
|
||||
:: Record install directory
|
||||
CD install
|
||||
SET benchmark_ROOT=%CD%
|
||||
CD ..
|
||||
17
.github/scripts/gtest/linux.sh
vendored
Normal file
17
.github/scripts/gtest/linux.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Create build and install directory
|
||||
mkdir build install
|
||||
|
||||
# Build project
|
||||
cd build
|
||||
cmake -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake --build .
|
||||
cmake --install . --prefix=../install
|
||||
cd ..
|
||||
|
||||
# Record install directory
|
||||
cd install
|
||||
export GTest_ROOT=$(pwd)
|
||||
cd ..
|
||||
17
.github/scripts/gtest/macos.sh
vendored
Normal file
17
.github/scripts/gtest/macos.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Create build and install directory
|
||||
mkdir build install
|
||||
|
||||
# Build project
|
||||
cd build
|
||||
cmake -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
cmake --build .
|
||||
cmake --install . --prefix=../install
|
||||
cd ..
|
||||
|
||||
# Record install directory
|
||||
cd install
|
||||
export GTest_ROOT=$(pwd)
|
||||
cd ..
|
||||
17
.github/scripts/gtest/windows.bat
vendored
Normal file
17
.github/scripts/gtest/windows.bat
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
@ECHO OFF
|
||||
|
||||
:: Create build and install directory
|
||||
MKDIR build
|
||||
MKDIR install
|
||||
|
||||
:: Build project
|
||||
CD build
|
||||
cmake -A x64 -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON ..
|
||||
cmake --build . --config Release
|
||||
cmake --install . --prefix=../install --config Release
|
||||
CD ..
|
||||
|
||||
:: Record install directory
|
||||
CD install
|
||||
SET GTest_ROOT=%CD%
|
||||
CD ..
|
||||
19
.github/scripts/linux.sh
vendored
Normal file
19
.github/scripts/linux.sh
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Create build directory and enter it
|
||||
mkdir bin
|
||||
cd bin
|
||||
# Create internal build and install directory, then enter it
|
||||
mkdir build
|
||||
mkdir install
|
||||
|
||||
# Build in Release mode
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DYYCC_BUILD_TEST=ON -DGTest_ROOT=$GTest_ROOT -DYYCC_BUILD_BENCHMARK=ON -Dbenchmark_ROOT=$benchmark_ROOT ../..
|
||||
cmake --build .
|
||||
cmake --install . --prefix=../install
|
||||
cd ..
|
||||
|
||||
# Back to root directory
|
||||
cd ..
|
||||
19
.github/scripts/macos.sh
vendored
Normal file
19
.github/scripts/macos.sh
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Create build directory and enter it
|
||||
mkdir bin
|
||||
cd bin
|
||||
# Create internal build and install directory, then enter it
|
||||
mkdir build
|
||||
mkdir install
|
||||
|
||||
# Build in Release mode
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DYYCC_BUILD_TEST=ON -DGTest_ROOT=$GTest_ROOT -DYYCC_BUILD_BENCHMARK=ON -Dbenchmark_ROOT=$benchmark_ROOT ../..
|
||||
cmake --build .
|
||||
cmake --install . --prefix=../install
|
||||
cd ..
|
||||
|
||||
# Back to root directory
|
||||
cd ..
|
||||
18
.github/scripts/windows.bat
vendored
Normal file
18
.github/scripts/windows.bat
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
@ECHO OFF
|
||||
|
||||
:: Create build directory and enter it
|
||||
MKDIR bin
|
||||
CD bin
|
||||
:: Create internal build and install directory, then enter it
|
||||
MKDIR build
|
||||
MKDIR install
|
||||
|
||||
:: Build with x64 architecture in Release mode
|
||||
CD build
|
||||
cmake -A x64 -DYYCC_BUILD_TEST=ON -DGTest_ROOT=%GTest_ROOT% -DYYCC_BUILD_BENCHMARK=ON -Dbenchmark_ROOT=%benchmark_ROOT% ../..
|
||||
cmake --build . --config Release
|
||||
cmake --install . --prefix=../install --config Release
|
||||
CD ..
|
||||
|
||||
:: Back to root directory
|
||||
CD ..
|
||||
69
.github/workflows/linux.yml
vendored
69
.github/workflows/linux.yml
vendored
@@ -10,35 +10,54 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y build-essential cmake git
|
||||
# - name: Setup Google Test and Google Benchmark
|
||||
# run: |
|
||||
# # Setup Google Test
|
||||
# git clone -b v1.17.0 https://github.com/google/googletest.git external/googletest
|
||||
# cd external/googletest
|
||||
# mkdir build install
|
||||
# cd build
|
||||
# cmake -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
# cmake --build .
|
||||
# cmake --install . --prefix=../install
|
||||
# cd ../..
|
||||
# # Setup Google Benchmark
|
||||
# git clone -b v1.9.4 https://github.com/google/benchmark.git external/benchmark
|
||||
# cd external/benchmark
|
||||
# # Create symlink to googletest as required by benchmark
|
||||
# ln -s ../googletest googletest
|
||||
# mkdir build install
|
||||
# cd build
|
||||
# cmake -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF -DCMAKE_BUILD_TYPE=Release ..
|
||||
# cmake --build .
|
||||
# cmake --install . --prefix=../install
|
||||
# cd ../..
|
||||
- name: Build YYCC
|
||||
- name: Fetch Google Test
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'google/googletest'
|
||||
ref: 'v1.17.0'
|
||||
path: 'extern/googletest'
|
||||
- name: Build Google Test
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x ./.github/linux_build.sh
|
||||
./.github/linux_build.sh
|
||||
cd extern/googletest
|
||||
# Build Google Test
|
||||
source ../../.github/scripts/gtest/linux.sh
|
||||
# Record environment variable
|
||||
echo "GTest_ROOT=$GTest_ROOT" >> "$GITHUB_ENV"
|
||||
cd ../..
|
||||
- name: Fetch Google Benchmark
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'google/benchmark'
|
||||
ref: 'v1.9.4'
|
||||
path: 'extern/benchmark'
|
||||
- name: Build Google Benchmark
|
||||
shell: bash
|
||||
run: |
|
||||
cd extern/benchmark
|
||||
# Create symlink to googletest as required by benchmark
|
||||
ln -s ../googletest googletest
|
||||
# Build Google Benchmark
|
||||
source ../../.github/scripts/gbenchmark/linux.sh
|
||||
# Record environment variable
|
||||
echo "benchmark_ROOT=$benchmark_ROOT" >> "$GITHUB_ENV"
|
||||
cd ../..
|
||||
- name: Build YYCC
|
||||
shell: bash
|
||||
run: |
|
||||
source ./.github/scripts/linux.sh
|
||||
- name: Run YYCC Test
|
||||
shell: bash
|
||||
run: |
|
||||
./bin/install/bin/YYCCTest
|
||||
- name: Run YYCC Benchmark
|
||||
shell: bash
|
||||
run: |
|
||||
./bin/install/bin/YYCCBenchmark
|
||||
- name: Upload Built Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
68
.github/workflows/macos.yml
vendored
68
.github/workflows/macos.yml
vendored
@@ -9,32 +9,50 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
# - name: Setup Google Test and Google Benchmark
|
||||
# run: |
|
||||
# # Setup Google Test
|
||||
# git clone -b v1.17.0 https://github.com/google/googletest.git external/googletest
|
||||
# cd external/googletest
|
||||
# mkdir build install
|
||||
# cd build
|
||||
# cmake -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
# cmake --build .
|
||||
# cmake --install . --prefix=../install
|
||||
# cd ../..
|
||||
# # Setup Google Benchmark
|
||||
# git clone -b v1.9.4 https://github.com/google/benchmark.git external/benchmark
|
||||
# cd external/benchmark
|
||||
# # Create symlink to googletest as required by benchmark
|
||||
# ln -s ../googletest googletest
|
||||
# mkdir build install
|
||||
# cd build
|
||||
# cmake -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF -DCMAKE_BUILD_TYPE=Release ..
|
||||
# cmake --build .
|
||||
# cmake --install . --prefix=../install
|
||||
# cd ../..
|
||||
- name: Build YYCC
|
||||
- name: Fetch Google Test
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'google/googletest'
|
||||
ref: 'v1.17.0'
|
||||
path: 'extern/googletest'
|
||||
- name: Build Google Test
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x ./.github/macos_build.sh
|
||||
./.github/macos_build.sh
|
||||
cd extern/googletest
|
||||
# Build Google Test
|
||||
source ../../.github/scripts/gtest/macos.sh
|
||||
# Record environment variable
|
||||
echo "GTest_ROOT=$GTest_ROOT" >> "$GITHUB_ENV"
|
||||
cd ../..
|
||||
- name: Fetch Google Benchmark
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'google/benchmark'
|
||||
ref: 'v1.9.4'
|
||||
path: 'extern/benchmark'
|
||||
- name: Build Google Benchmark
|
||||
shell: bash
|
||||
run: |
|
||||
cd extern/benchmark
|
||||
# Create symlink to googletest as required by benchmark
|
||||
ln -s ../googletest googletest
|
||||
# Build Google Benchmark
|
||||
source ../../.github/scripts/gbenchmark/macos.sh
|
||||
# Record environment variable
|
||||
echo "benchmark_ROOT=$benchmark_ROOT" >> "$GITHUB_ENV"
|
||||
cd ../..
|
||||
- name: Build YYCC
|
||||
shell: bash
|
||||
run: |
|
||||
source ./.github/scripts/macos.sh
|
||||
- name: Run YYCC Test
|
||||
shell: bash
|
||||
run: |
|
||||
./bin/install/bin/YYCCTest
|
||||
- name: Run YYCC Benchmark
|
||||
shell: bash
|
||||
run: |
|
||||
./bin/install/bin/YYCCBenchmark
|
||||
- name: Upload Built Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
69
.github/workflows/windows.yml
vendored
69
.github/workflows/windows.yml
vendored
@@ -16,36 +16,59 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
# - name: Setup Google Test and Google Benchmark
|
||||
# run: |
|
||||
# # Setup Google Test
|
||||
# git clone -b v1.17.0 https://github.com/google/googletest.git external/googletest
|
||||
# cd external/googletest
|
||||
# mkdir build install
|
||||
# cd build
|
||||
# cmake -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||
# cmake --build . --config Release
|
||||
# cmake --install . --prefix=../install --config Release
|
||||
# cd ../..
|
||||
# # Setup Google Benchmark
|
||||
# git clone -b v1.9.4 https://github.com/google/benchmark.git external/benchmark
|
||||
# cd external/benchmark
|
||||
# # Create symlink to googletest as required by benchmark
|
||||
# mklink /D googletest ../googletest
|
||||
# mkdir build install
|
||||
# cd build
|
||||
# cmake -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF -DCMAKE_BUILD_TYPE=Release ..
|
||||
# cmake --build . --config Release
|
||||
# cmake --install . --prefix=../install --config Release
|
||||
# cd ../..
|
||||
- name: Fetch Google Test
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'google/googletest'
|
||||
ref: 'v1.17.0'
|
||||
path: 'extern/googletest'
|
||||
- name: Build Google Test
|
||||
shell: cmd
|
||||
run: |
|
||||
CD extern\googletest
|
||||
:: Build Google Test
|
||||
CALL ..\..\.github\scripts\gtest\windows.bat
|
||||
:: Idk why I can't use $GITHUB_ENV, so I use this stupid way to do this.
|
||||
:: This is first entry so we override it.
|
||||
ECHO SET GTest_ROOT=%GTest_ROOT% > ..\envs.bat
|
||||
CD ..\..
|
||||
- name: Fetch Google Benchmark
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'google/benchmark'
|
||||
ref: 'v1.9.4'
|
||||
path: 'extern/benchmark'
|
||||
- name: Build Google Benchmark
|
||||
shell: cmd
|
||||
run: |
|
||||
CD extern\benchmark
|
||||
:: Create symlink to googletest as required by benchmark
|
||||
mklink /D googletest ..\googletest
|
||||
:: Build Google Benchmark
|
||||
CALL ..\..\.github\scripts\gbenchmark\windows.bat
|
||||
:: This is second entry so we append it.
|
||||
ECHO SET benchmark_ROOT=%benchmark_ROOT% >> ..\envs.bat
|
||||
CD ..\..
|
||||
- name: Build YYCC
|
||||
shell: cmd
|
||||
run: |
|
||||
:: Prepare Visual Studio
|
||||
set VS=${{ matrix.vs }}
|
||||
set VCVARS="C:\Program Files (x86)\Microsoft Visual Studio\%VS%\Enterprise\VC\Auxiliary\Build\vcvarsall.bat"
|
||||
if not exist %VCVARS% set VCVARS="C:\Program Files\Microsoft Visual Studio\%VS%\Enterprise\VC\Auxiliary\Build\vcvarsall.bat"
|
||||
call %VCVARS% ${{ matrix.msvc_arch }}
|
||||
.\.github\windows_build.bat
|
||||
:: Extract saved environment variables
|
||||
CALL .\extern\envs.bat
|
||||
:: Build Project
|
||||
CALL .\.github\scripts\windows.bat
|
||||
- name: Run YYCC Test
|
||||
shell: cmd
|
||||
run: |
|
||||
.\bin\install\bin\YYCCTest.exe
|
||||
- name: Run YYCC Benchmark
|
||||
shell: cmd
|
||||
run: |
|
||||
.\bin\install\bin\YYCCBenchmark.exe
|
||||
- name: Upload Built Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
out/
|
||||
build/
|
||||
install/
|
||||
extern/
|
||||
|
||||
# Ignore CMake generated stuff
|
||||
src/yycc/version.hpp
|
||||
|
||||
@@ -26,6 +26,9 @@ set(YYCC_INSTALL_BIN_PATH ${CMAKE_INSTALL_BINDIR} CACHE PATH
|
||||
set(YYCC_INSTALL_DOC_PATH ${CMAKE_INSTALL_DOCDIR} CACHE PATH
|
||||
"Non-arch doc install path relative to CMAKE_INSTALL_PREFIX unless set to an absolute path.")
|
||||
|
||||
# Test charconv support due to shitty clang's libcxx.
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/check_charconv.cmake)
|
||||
|
||||
# Include dependency.
|
||||
# GTest is required if we build test
|
||||
if (YYCC_BUILD_TEST)
|
||||
@@ -37,6 +40,10 @@ endif ()
|
||||
if (YYCC_BUILD_BENCHMARK)
|
||||
find_package(benchmark REQUIRED)
|
||||
endif ()
|
||||
# Doxygen is required if we build doc
|
||||
if (YYCC_BUILD_DOC)
|
||||
find_package(Doxygen REQUIRED)
|
||||
endif ()
|
||||
# Iconv is required if we are not in Windows or user request it
|
||||
if (YYCC_ENFORCE_ICONV OR (NOT WIN32))
|
||||
find_package(Iconv REQUIRED)
|
||||
|
||||
22
COMPILE.md
22
COMPILE.md
@@ -27,10 +27,16 @@ So you actually do not need Google Test, Google Benchmark and Doxygen.
|
||||
### Compiler
|
||||
|
||||
> [!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.
|
||||
> A possible solution is that use GCC and libstdc++ on macOS instead of default Clang and libc++.
|
||||
> Build issue may be resolved until libc++ finish these features: `std::stacktrace` and `std::views::enumerate`.
|
||||
> You may face some issues when building on macOS with Apple Clang. That's not your fault.
|
||||
> Clang and Apple Clang used libc++ library lacks some essential features used by this project.
|
||||
> This is especially not good for Apple Clang because Apple Clang is usually behind Clang a bunch of versions.
|
||||
>
|
||||
> For resolving this issue, I have written a series of patch header files for libcxx and you can find them in include directory.
|
||||
> This project should be compiled on macOS but everything has exception.
|
||||
> If you really have this issue, a possible solution is that use GCC and libstdc++ on macOS instead of default Clang and libc++.
|
||||
>
|
||||
> Build issue may be resolved until libc++ finish these features: complete `std::from_chars` and `std::to_chars`,
|
||||
> `std::stacktrace` and `std::views::enumerate`.
|
||||
|
||||
### Google Test
|
||||
|
||||
@@ -106,7 +112,13 @@ If you are a developer (developer of this project, or use this project as depend
|
||||
|
||||
"User Build" is basically how GitHub Action build this project.
|
||||
|
||||
Execute `.github/windows_build.bat` on Windows or `.github/linux_build.sh` on POSIX-like OS (Linux and macOS) under **the root directory** of this project. The final built artifact is under `bin/install` directory.
|
||||
Under **the root directory** of this project, execute:
|
||||
|
||||
- `script/windows_build.bat` on Windows
|
||||
- or `script/linux_build.sh` on Linux
|
||||
- or `script/macos_build.sh` on macOS
|
||||
|
||||
The final built artifact is under `bin/install` directory.
|
||||
|
||||
### Developer Build
|
||||
|
||||
|
||||
0
script/.gitignore → asset/.gitignore
vendored
0
script/.gitignore → asset/.gitignore
vendored
0
script/uv.lock → asset/uv.lock
generated
0
script/uv.lock → asset/uv.lock
generated
@@ -26,3 +26,8 @@ PRIVATE
|
||||
YYCCommonplace
|
||||
benchmark::benchmark
|
||||
)
|
||||
|
||||
# Install binary
|
||||
install(TARGETS YYCCBenchmark
|
||||
RUNTIME DESTINATION ${YYCC_INSTALL_BIN_PATH}
|
||||
)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
# Find Iconv if we have found it.
|
||||
if ("@Iconv_FOUND@")
|
||||
find_package(Iconv REQUIRED)
|
||||
endif ()
|
||||
|
||||
# Include targets file
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/YYCCommonplaceTargets.cmake")
|
||||
|
||||
|
||||
30
cmake/check_charconv.cmake
Normal file
30
cmake/check_charconv.cmake
Normal file
@@ -0,0 +1,30 @@
|
||||
message(STATUS "Checking charconv implementation...")
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/chars_format.cpp" TEST_CODE_SNIPPET)
|
||||
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_CHARS_FORMAT)
|
||||
message(STATUS "Support std::chars_format: ${YYCC_CHARCONV_HAS_CHARS_FORMAT}")
|
||||
|
||||
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/from_chars_result.cpp" TEST_CODE_SNIPPET)
|
||||
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_FROM_CHARS_RESULT)
|
||||
message(STATUS "Support std::from_chars_result: ${YYCC_CHARCONV_HAS_FROM_CHARS_RESULT}")
|
||||
|
||||
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/to_chars_result.cpp" TEST_CODE_SNIPPET)
|
||||
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_TO_CHARS_RESULT)
|
||||
message(STATUS "Support std::to_chars_result: ${YYCC_CHARCONV_HAS_TO_CHARS_RESULT}")
|
||||
|
||||
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/from_chars_int.cpp" TEST_CODE_SNIPPET)
|
||||
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_FROM_CHARS_INT)
|
||||
message(STATUS "Support std::from_chars with integral type: ${YYCC_CHARCONV_HAS_FROM_CHARS_INT}")
|
||||
|
||||
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/from_chars_float.cpp" TEST_CODE_SNIPPET)
|
||||
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT)
|
||||
message(STATUS "Suppoer std::from_chars with float point type: ${YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT}")
|
||||
|
||||
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/to_chars_int.cpp" TEST_CODE_SNIPPET)
|
||||
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_TO_CHARS_INT)
|
||||
message(STATUS "Support std::to_chars with integral type: ${YYCC_CHARCONV_HAS_TO_CHARS_INT}")
|
||||
|
||||
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/to_chars_float.cpp" TEST_CODE_SNIPPET)
|
||||
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_TO_CHARS_FLOAT)
|
||||
message(STATUS "Support std::to_chars with float point type: ${YYCC_CHARCONV_HAS_TO_CHARS_FLOAT}")
|
||||
8
cmake/check_charconv/chars_format.cpp
Normal file
8
cmake/check_charconv/chars_format.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <charconv>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
auto scientific = std::chars_format::scientific;
|
||||
auto fixed = std::chars_format::fixed;
|
||||
auto general = std::chars_format::general;
|
||||
auto hex = std::chars_format::hex;
|
||||
}
|
||||
16
cmake/check_charconv/from_chars_float.cpp
Normal file
16
cmake/check_charconv/from_chars_float.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include <charconv>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char probe[] = "0.0";
|
||||
const char* first = probe;
|
||||
const char* last = first + sizeof(probe);
|
||||
|
||||
{
|
||||
float value;
|
||||
auto rv = std::from_chars(first, last, value, std::chars_format::general);
|
||||
}
|
||||
{
|
||||
double value;
|
||||
auto rv = std::from_chars(first, last, value, std::chars_format::general);
|
||||
}
|
||||
}
|
||||
41
cmake/check_charconv/from_chars_int.cpp
Normal file
41
cmake/check_charconv/from_chars_int.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char probe[] = "0";
|
||||
const char* first = probe;
|
||||
const char* last = first + sizeof(probe);
|
||||
|
||||
{
|
||||
std::int8_t value;
|
||||
auto rv = std::from_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::int16_t value;
|
||||
auto rv = std::from_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::int32_t value;
|
||||
auto rv = std::from_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::int64_t value;
|
||||
auto rv = std::from_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::uint8_t value;
|
||||
auto rv = std::from_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::uint16_t value;
|
||||
auto rv = std::from_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::uint32_t value;
|
||||
auto rv = std::from_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::uint64_t value;
|
||||
auto rv = std::from_chars(first, last, value, 10);
|
||||
}
|
||||
}
|
||||
9
cmake/check_charconv/from_chars_result.cpp
Normal file
9
cmake/check_charconv/from_chars_result.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include <charconv>
|
||||
#include <system_error>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::from_chars_result result {
|
||||
.ptr = nullptr,
|
||||
.ec = std::errc{},
|
||||
};
|
||||
}
|
||||
16
cmake/check_charconv/to_chars_float.cpp
Normal file
16
cmake/check_charconv/to_chars_float.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include <charconv>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
char buffer[1024];
|
||||
char* first = buffer;
|
||||
char* last = first + sizeof(buffer);
|
||||
|
||||
{
|
||||
float value = 0;
|
||||
auto rv = std::to_chars(first, last, value, std::chars_format::general, 6);
|
||||
}
|
||||
{
|
||||
double value = 0;
|
||||
auto rv = std::to_chars(first, last, value, std::chars_format::general, 6);
|
||||
}
|
||||
}
|
||||
41
cmake/check_charconv/to_chars_int.cpp
Normal file
41
cmake/check_charconv/to_chars_int.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
char buffer[1024];
|
||||
char* first = buffer;
|
||||
char* last = first + sizeof(buffer);
|
||||
|
||||
{
|
||||
std::int8_t value = 0;
|
||||
auto rv = std::to_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::int16_t value = 0;
|
||||
auto rv = std::to_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::int32_t value = 0;
|
||||
auto rv = std::to_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::int64_t value = 0;
|
||||
auto rv = std::to_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::uint8_t value = 0;
|
||||
auto rv = std::to_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::uint16_t value = 0;
|
||||
auto rv = std::to_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::uint32_t value = 0;
|
||||
auto rv = std::to_chars(first, last, value, 10);
|
||||
}
|
||||
{
|
||||
std::uint64_t value = 0;
|
||||
auto rv = std::to_chars(first, last, value, 10);
|
||||
}
|
||||
}
|
||||
9
cmake/check_charconv/to_chars_result.cpp
Normal file
9
cmake/check_charconv/to_chars_result.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include <charconv>
|
||||
#include <system_error>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
std::to_chars_result result {
|
||||
.ptr = nullptr,
|
||||
.ec = std::errc{},
|
||||
};
|
||||
}
|
||||
@@ -1,19 +1,36 @@
|
||||
# Configure Doxygen config file
|
||||
# Extract all public macros defined in YYCC
|
||||
# However, you should note that these extratcted macros have generator expressions.
|
||||
get_target_property(YYCC_COMPILE_DEFINITIONS YYCCommonplace COMPILE_DEFINITIONS)
|
||||
if (YYCC_COMPILE_DEFINITIONS STREQUAL "YYCC_COMPILE_DEFINITIONS-NOTFOUND")
|
||||
message(FATAL_ERROR "Cannot extract compile definitions from YYCCommonplace.")
|
||||
endif ()
|
||||
# Convert list to string for expanding in future.
|
||||
list(JOIN YYCC_COMPILE_DEFINITIONS " " YYCC_MACRO_GENERATOR_EXPRESSIONS)
|
||||
|
||||
# We simply configure Doxygen config file first.
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/Doxyfile.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Add custom target
|
||||
# Then we use "file GENERATE" syntax to generate per-config truely Doxyfile used by Doxygen.
|
||||
# Because there is no "$<>" syntax in Doxyfile, so we can safely use it.
|
||||
# Please note that the generation of "file GENERATE" syntax will be postponed until the build stage.
|
||||
file(GENERATE
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/Doxyfile"
|
||||
INPUT "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
|
||||
TARGET YYCCommonplace
|
||||
)
|
||||
|
||||
# Add custom target using per-config Doxyfile
|
||||
add_custom_target (YYCCDocumentation
|
||||
doxygen ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
||||
Doxygen::doxygen "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/Doxyfile"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating documentation" VERBATIM
|
||||
)
|
||||
|
||||
# Install built documentation
|
||||
install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
|
||||
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
|
||||
DESTINATION ${YYCC_INSTALL_DOC_PATH}
|
||||
)
|
||||
|
||||
@@ -2306,7 +2306,7 @@ PERLMOD_MAKEVAR_PREFIX =
|
||||
# C-preprocessor directives found in the sources and include files.
|
||||
# The default value is: YES.
|
||||
|
||||
ENABLE_PREPROCESSING = NO
|
||||
ENABLE_PREPROCESSING = YES
|
||||
|
||||
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
|
||||
# in the source code. If set to NO, only conditional compilation will be
|
||||
@@ -2356,7 +2356,7 @@ INCLUDE_FILE_PATTERNS =
|
||||
# recursively expanded use the := operator instead of the = operator.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
PREDEFINED = YYCC_DOXYGEN
|
||||
PREDEFINED = @YYCC_MACRO_GENERATOR_EXPRESSIONS@
|
||||
|
||||
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
|
||||
# tag can be used to specify a list of macro names that should be expanded. The
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
namespace YYCC::ArgParser {
|
||||
/**
|
||||
|
||||
\page arg_parser Universal Argument Parser
|
||||
|
||||
YYCC::ArgParser provides an universal way to parsing command line arguments.
|
||||
|
||||
Universal argument parser has similar design with universal config manager,
|
||||
it is highly recommand that read \ref config_manager chapter first,
|
||||
because you will have a clear understanding of this namespace after reading universal config manager chapter.
|
||||
|
||||
There is an example about how to use universal argument parser.
|
||||
In following content, we will describe it in detail.
|
||||
|
||||
\code{.cpp}
|
||||
class TestArgParser {
|
||||
public:
|
||||
TestArgParser() :
|
||||
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
|
||||
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
|
||||
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
|
||||
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
|
||||
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the test of argument parser."), {
|
||||
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
|
||||
&m_BoolArgument, &m_ClampedFloatArgument
|
||||
}) {}
|
||||
~TestArgParser() {}
|
||||
|
||||
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
|
||||
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
|
||||
YYCC::ArgParser::StringArgument m_StringArgument;
|
||||
|
||||
YYCC::ArgParser::SwitchArgument m_BoolArgument;
|
||||
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
|
||||
|
||||
YYCC::ArgParser::OptionContext m_OptionContext;
|
||||
};
|
||||
|
||||
// Initialize argument parser.
|
||||
TestArgParser test;
|
||||
// Get argument list for parsing from standard C main function.
|
||||
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
|
||||
// Start parsing
|
||||
test.Parse(al);
|
||||
// Get captured string argument
|
||||
if (test.m_StringArgument.IsCaptured())
|
||||
auto val = test.m_StringArgument.Get();
|
||||
\endcode
|
||||
|
||||
These code can resolve following command line:
|
||||
|
||||
\code{.sh}
|
||||
exec -i 114514 -f 2.0 --string fuck -b --clamped-float 0.5
|
||||
\endcode
|
||||
|
||||
For convenience, we define following terms used in this article.
|
||||
|
||||
\li Every items in command line: Argument.
|
||||
\li \c -i, \c --clamped-float: \b Switch / \b Option. the argument starts with dash or double dash.
|
||||
\li \c 114514: \b Value. the value of switch.
|
||||
|
||||
\section arg_parser__argument Argument
|
||||
|
||||
Argument is the leaf of argument parser.
|
||||
It has the same position as setting in universal config manager.
|
||||
|
||||
\subsection arg_parser__argument__presets Argument Presets
|
||||
|
||||
Like setting in universal config manager,
|
||||
we also provide various common used argument presets.
|
||||
Current'y we support following argument presets:
|
||||
|
||||
\li NumberArgument: The argument storing arithmetic type (except \c bool) inside. Such as <TT>-i 114514</TT> in example.
|
||||
\li StringArgument: The argument storing string inside. Such as <TT>--string fuck</TT> in example.
|
||||
\li SwitchArgument: The argument storing nothing. It is just a simple switch. Such as <TT>-b</TT> in example.
|
||||
|
||||
When constructing these argument,
|
||||
you need provide one from long name or short name, or both of them.
|
||||
Short name is the argument starting with dash and long name starts with double dash.
|
||||
You don't need add dash or double dash prefix when providing these names.
|
||||
Please note only ASCII characters, which can be displayed on screen, can be used in these names.
|
||||
|
||||
Optionally, you can provide description when constructing,
|
||||
which will tell user how this switch does and more infomation about this switch.
|
||||
And, you can add an example to tell user which value is valid.
|
||||
|
||||
Next, you can specify an argument to be optional.
|
||||
Optional argument can be absent in command line.
|
||||
Oppositely, non-optional argument must be presented in command line,
|
||||
otherwise parser will return false to indicate an error.
|
||||
For checking whether an optional argument is specified,
|
||||
please call AbstractArgument::IsCaptured().
|
||||
|
||||
Last, you can optionally assign a constraint to it,
|
||||
to help argument limit its value.
|
||||
|
||||
However SwitchArgument must be optional argument.
|
||||
Because it is true if user specify it explicit it,
|
||||
and will be false if user do not give this flag.
|
||||
SwitchArgument doesn't have constraint features,
|
||||
because it doesn't store any value inside.
|
||||
Thus no need to limit this.
|
||||
|
||||
\subsection arg_parser__argument__custom Custom Argument
|
||||
|
||||
In most cases, the combination use of argument presets and constraints is enough.
|
||||
However, if you still are urge to create your personal argument,
|
||||
please inherit AbstractArgument and implement essential class functions.
|
||||
For the class functions you need to implement,
|
||||
please refer to our argument presets.
|
||||
|
||||
\section arg_parser__argument_list Argument List
|
||||
|
||||
Argument list is a struct used by parser for parsing.
|
||||
It is a higher wrapper of a simple list containing argument items.
|
||||
We provide 2 ways to get argument list.
|
||||
|
||||
\li ArgumentList::CreateFromStd: Create argument list from standard C main function parameters.
|
||||
\li ArgumentList::CreateFromWin32: Create argument list from Win32 functions in Windows.
|
||||
You should use this function in Windows instead of ArgumentList::CreateFromStd.
|
||||
Because the command line passed in standard C main function has encoding issue in Windows.
|
||||
Use this function you will fetch correct argument list especially command including non-ASCII characters.
|
||||
|
||||
Please note the first argument in given command line will be stripped.
|
||||
Because in most cases it point to the executable self,
|
||||
and should not be seen as the part of argument list.
|
||||
|
||||
\section arg_parser__option_context Option Context
|
||||
|
||||
Please note any unknow argument will let the parser return false.
|
||||
This is different with other argument parsers.
|
||||
In other common argument parsers,
|
||||
they will collect all unknow argument as positional argument,
|
||||
or just simply ignore them.
|
||||
|
||||
OptionContext also will not add \c -h or \c --help switch automatically.
|
||||
This is also differnent with other parsers.
|
||||
You should manually add it.
|
||||
However, OptionContext provide a universal help print function, OptionContext::Help.
|
||||
You can directly call it to output help text if you needed (fail to parse or user order help).
|
||||
|
||||
\section arg_parser__limitation Limitation
|
||||
|
||||
This universal argument parser is a tiny parser.
|
||||
It only just fulfill my personal requirements.
|
||||
So it only accepts limited command line syntax.
|
||||
In following content I will tell you some syntaxes which this parser \b not accept.
|
||||
|
||||
\subsection arg_parser__limitation__flag_combination Flag Combination
|
||||
|
||||
\code{.sh}
|
||||
exec -l -s -h
|
||||
exec -lsh
|
||||
\endcode
|
||||
|
||||
Parser accept first line but not accept the second line.
|
||||
You must write these flags independently.
|
||||
|
||||
\subsection arg_parser__limitation__equal_symbol Equal Symbol
|
||||
|
||||
\code{.sh}
|
||||
exec --value 114514
|
||||
exec --value=114514
|
||||
exec --value:114514
|
||||
\endcode
|
||||
|
||||
Parser only accept first line command.
|
||||
You can not use equal symbol or any other symbol to assign value for specified argument.
|
||||
You must write value after the argument immediately please.
|
||||
|
||||
\subsection arg_parser__limitation__variable_argument Variable Argument
|
||||
|
||||
\code{.sh}
|
||||
exec -DSOME_VARABLE=SOME_VALUE
|
||||
exec -D SOME_VARIABLE=SOME_VALUE
|
||||
\endcode
|
||||
|
||||
Parser only accept second line.
|
||||
However you nned to write a custom argument or constraint to holding this value.
|
||||
|
||||
\subsection arg_parser__limitation__switch_dependency Switch Dependency
|
||||
|
||||
\code{.sh}
|
||||
exec --action-a --action-b
|
||||
\endcode
|
||||
|
||||
For command line written above,
|
||||
if you hope \c --action-a and \c --action-b is exclusive,
|
||||
or \c --action-b only be valid if \c --action-a specified,
|
||||
you should manually implement this.
|
||||
Parser don't have such features to process this switch dependency.
|
||||
|
||||
The thing you need to do is set these switches are \b not optional.
|
||||
And after parser do a success parsing,
|
||||
manually calling AbstractArgument::IsCaptured to fetch whether corresponding switches are captured,
|
||||
then do your personal dependency check.
|
||||
|
||||
*/
|
||||
}
|
||||
208
doc/src/carton/binstore.dox
Normal file
208
doc/src/carton/binstore.dox
Normal file
@@ -0,0 +1,208 @@
|
||||
namespace yycc::carton::binstore {
|
||||
/**
|
||||
|
||||
\page binstore Binary Settings Storage (Binstore)
|
||||
|
||||
The binstore module provides a binary settings storage system that allows
|
||||
applications to persistently store and retrieve configuration settings in
|
||||
a binary format. It includes functionality for type-safe serialization and deserialization,
|
||||
setting management with unique tokens for access control, version control with migration strategies,
|
||||
and comprehensive error handling.
|
||||
|
||||
\section binstore__overview Overview
|
||||
|
||||
The binstore module consists of several key components:
|
||||
|
||||
\li types: Basic types and error handling for the module
|
||||
\li serdes: Serialization/deserialization functionality for different data types
|
||||
\li setting: Management of settings with name-based lookup and token-based access
|
||||
\li configuration: Version and settings collection management
|
||||
\li storage: Main storage class for loading/saving settings to/from files or streams
|
||||
|
||||
\section binstore__example Example Usage
|
||||
|
||||
Here is a complete example showing how to use the binstore module:
|
||||
|
||||
\code{.cpp}
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/carton/binstore.hpp>
|
||||
#include <yycc/patch/stream.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace yycc::carton::binstore;
|
||||
using namespace yycc::patch::stream;
|
||||
|
||||
enum class LogLevel : uint8_t { Debug, Info, Warning, Error };
|
||||
|
||||
int main() {
|
||||
// Create settings collection
|
||||
auto settings = setting::SettingCollection();
|
||||
auto int_setting_token = settings.add_setting(setting::Setting(u8"max_connections"));
|
||||
auto float_setting_token = settings.add_setting(setting::Setting(u8"timeout"));
|
||||
auto string_setting_token = settings.add_setting(setting::Setting(u8"server_address"));
|
||||
auto bool_setting_token = settings.add_setting(setting::Setting(u8"enable_logging"));
|
||||
auto enum_setting_token = settings.add_setting(setting::Setting(u8"log_level"));
|
||||
|
||||
// Create configuration with version 1
|
||||
auto config = configuration::Configuration(1, std::move(settings));
|
||||
|
||||
// Create storage with the configuration
|
||||
auto storage = storage::Storage(std::move(config));
|
||||
|
||||
// Using appropriate SerDes types for different data types
|
||||
using IntSerDes = serdes::IntegralSerDes<int32_t>;
|
||||
using FloatSerDes = serdes::FloatingPointSerDes<float>;
|
||||
using StringSerDes = serdes::StringSerDes;
|
||||
using BoolSerDes = serdes::BoolSerDes<true>; // true as default value
|
||||
using EnumSerDes = serdes::EnumSerDes<LogLevel, LogLevel::Info>;
|
||||
|
||||
// Set values
|
||||
storage.set_value<IntSerDes>(int_setting_token, 100);
|
||||
storage.set_value<FloatSerDes>(float_setting_token, 2.5f);
|
||||
storage.set_value<StringSerDes>(string_setting_token, u8"localhost");
|
||||
storage.set_value<BoolSerDes>(bool_setting_token, true);
|
||||
storage.set_value<EnumSerDes>(enum_setting_token, LogLevel::Debug);
|
||||
|
||||
// Save to file
|
||||
if (auto result = storage.save_into_file("config.bin"); result.has_value()) {
|
||||
std::cout << "Configuration saved successfully" << std::endl;
|
||||
} else {
|
||||
std::cout << "Failed to save configuration" << std::endl;
|
||||
}
|
||||
|
||||
// Load from file
|
||||
auto new_config = configuration::Configuration(1, setting::SettingCollection());
|
||||
auto new_storage = storage::Storage(std::move(new_config));
|
||||
|
||||
if (auto result = new_storage.load_from_file("config.bin", storage::LoadStrategy::MigrateOld); result.has_value()) {
|
||||
std::cout << "Configuration loaded successfully" << std::endl;
|
||||
|
||||
// Get values
|
||||
int32_t max_conn = new_storage.get_value<IntSerDes>(int_setting_token);
|
||||
float timeout = new_storage.get_value<FloatSerDes>(float_setting_token);
|
||||
std::u8string addr = new_storage.get_value<StringSerDes>(string_setting_token);
|
||||
bool logging = new_storage.get_value<BoolSerDes>(bool_setting_token);
|
||||
LogLevel level = new_storage.get_value<EnumSerDes>(enum_setting_token);
|
||||
|
||||
std::cout << "Max connections: " << max_conn << std::endl;
|
||||
std::cout << "Timeout: " << timeout << std::endl;
|
||||
std::cout << "Server address: " << addr << std::endl;
|
||||
std::cout << "Logging enabled: " << (logging ? "yes" : "no") << std::endl;
|
||||
std::cout << "Log level: " << static_cast<int>(level) << std::endl;
|
||||
} else {
|
||||
std::cout << "Failed to load configuration" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section binstore__components Components
|
||||
|
||||
\subsection binstore__settings Settings Management
|
||||
|
||||
Settings are identified by unique names and accessed via tokens. The [SettingCollection](\ref setting::SettingCollection)
|
||||
manages a collection of settings and ensures no duplicates.
|
||||
|
||||
\subsection binstore__configuration Configuration
|
||||
|
||||
The [Configuration](\ref configuration::Configuration) class holds the version identifier and the collection of settings.
|
||||
Version control is crucial for handling configuration migration between application versions.
|
||||
|
||||
\subsection binstore__storage Storage
|
||||
|
||||
The [Storage](\ref storage::Storage) class is the main interface for setting/getting values and loading/saving configurations.
|
||||
It provides methods for both file-based and stream-based operations.
|
||||
|
||||
\subsection binstore__serdes Serialization/Deserialization
|
||||
|
||||
SerDes (Serializer/Deserializer) classes handle type-safe conversion between values and their binary representation.
|
||||
Built-in SerDes types include:
|
||||
|
||||
\li Integral types ([IntegralSerDes](\ref serdes::IntegralSerDes))
|
||||
\li Floating-point types ([FloatingPointSerDes](\ref serdes::FloatingPointSerDes))
|
||||
\li String types ([StringSerDes](\ref serdes::StringSerDes))
|
||||
\li Boolean types ([BoolSerDes](\ref serdes::BoolSerDes))
|
||||
\li Enum types ([EnumSerDes](\ref serdes::EnumSerDes))
|
||||
|
||||
For some of them, you can specify value range and default value via template parameters.
|
||||
|
||||
\section binstore__load_strategies Load Strategies
|
||||
|
||||
The binstore module provides different strategies for handling version mismatches:
|
||||
|
||||
\li [OnlyCurrent](\ref storage::LoadStrategy::OnlyCurrent): Only accept configurations with matching version
|
||||
\li [MigrateOld](\ref storage::LoadStrategy::MigrateOld): Accept matching and older versions, reject newer versions
|
||||
\li [AcceptAll](\ref storage::LoadStrategy::AcceptAll): Accept all versions (not recommended for production)
|
||||
|
||||
\section binstore__custom_serdes Custom SerDes
|
||||
|
||||
Custom SerDes (Serializer/Deserializer) can be created by implementing the \c SerDes concept.
|
||||
A valid SerDes must satisfy the following requirements:
|
||||
|
||||
\li Have a type alias called \c ValueType indicating the corresponding setting type
|
||||
\li Have a member function called \c serialize that accepts a const reference of the setting data and returns \c ByteArray
|
||||
or \c std::nullopt if serialization fails.
|
||||
\li Have a member function called \c deserialize that converts \c ByteArray to the desired type
|
||||
or returns \c std::nullopt if deserialization fails.
|
||||
\li Have a member function called \c reset that returns a default \c ByteArray value.
|
||||
|
||||
Here is an example of a custom SerDes for storing IPv4 addresses:
|
||||
|
||||
\code{.cpp}
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
struct IPv4Address {
|
||||
std::uint8_t octets[4];
|
||||
|
||||
IPv4Address() : octets{0, 0, 0, 0} {}
|
||||
IPv4Address(std::uint8_t a, std::uint8_t b, std::uint8_t c, std::uint8_t d) {
|
||||
octets[0] = a; octets[1] = b; octets[2] = c; octets[3] = d;
|
||||
}
|
||||
};
|
||||
|
||||
struct IPv4SerDes {
|
||||
using ValueType = IPv4Address;
|
||||
static constexpr size_t VALUE_SIZE = sizeof(IPv4Address); // 4 octets
|
||||
|
||||
std::optional<types::ByteArray> serialize(const ValueType& value) const {
|
||||
types::ByteArray ba;
|
||||
ba.resize_data(VALUE_SIZE);
|
||||
std::memcpy(ba.get_data_ptr(), value.octets, VALUE_SIZE);
|
||||
return ba;
|
||||
}
|
||||
|
||||
std::optional<ValueType> deserialize(const types::ByteArray& ba) const {
|
||||
if (ba.get_data_size() != VALUE_SIZE) return std::nullopt;
|
||||
|
||||
ValueType value;
|
||||
std::memcpy(value.octets, ba.get_data_ptr(), VALUE_SIZE);
|
||||
return value;
|
||||
}
|
||||
|
||||
types::ByteArray reset() const {
|
||||
// Reset to local address
|
||||
ValueType default_value(127, 0, 0, 1);
|
||||
return this->serialize(default_value).value();
|
||||
}
|
||||
};
|
||||
\endcode
|
||||
|
||||
To use the custom SerDes:
|
||||
|
||||
\code{.cpp}
|
||||
// Add setting to collection
|
||||
auto ip_setting_token = settings.add_setting(setting::Setting(u8"server_ip"));
|
||||
|
||||
// Use custom SerDes
|
||||
IPv4SerDes ip_serdes;
|
||||
storage.set_value<IPv4SerDes>(ip_setting_token, IPv4Address(192, 168, 1, 1));
|
||||
|
||||
// Retrieve value
|
||||
IPv4Address ip_addr = storage.get_value<IPv4SerDes>(ip_setting_token);
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
187
doc/src/carton/clap.dox
Normal file
187
doc/src/carton/clap.dox
Normal file
@@ -0,0 +1,187 @@
|
||||
namespace yycc::carton::clap {
|
||||
/**
|
||||
|
||||
\page clap Command Line Argument Parser (CLAP)
|
||||
|
||||
Command Line Argument Parser (CLAP) module for handling command line arguments and environment variables.
|
||||
This module provides a comprehensive system for defining, parsing, and validating command line
|
||||
arguments and environment variables. It includes components for defining application metadata,
|
||||
command line options, variables, and utilities for parsing and validation.
|
||||
|
||||
\section clap__overview Overview
|
||||
|
||||
The CLAP module consists of several key components:
|
||||
|
||||
\li Types: Error types and result types used throughout the module
|
||||
\li Validator: Type-safe validation for command line argument values
|
||||
\li Option: Command line options with short and long names
|
||||
\li Variable: Environment variables that can be captured
|
||||
\li Summary: Application metadata (name, version, author, description)
|
||||
\li Application: Complete application definition with options and variables
|
||||
\li Manual: Help and version information generation
|
||||
\li Parser: Command line argument parsing functionality
|
||||
\li Resolver: Environment variable resolution functionality
|
||||
|
||||
\section clap__example Example Usage
|
||||
|
||||
Here is a complete example showing how to use the CLAP module:
|
||||
|
||||
\code{.cpp}
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/carton/clap.hpp>
|
||||
#include <yycc/patch/stream.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace yycc::carton::clap;
|
||||
using namespace yycc::patch::stream;
|
||||
|
||||
// Define an application with options and variables
|
||||
int main(int argc, char* argv[]) {
|
||||
// Create application summary
|
||||
auto summary = summary::Summary(u8"MyApp", u8"author", u8"1.0.0", u8"A sample application");
|
||||
|
||||
// Create options collection
|
||||
auto options = option::OptionCollection();
|
||||
auto int_opt = options.add_option(option::Option(u8"i", u8"int", u8"NUM", u8"integral argument"));
|
||||
auto float_opt = options.add_option(option::Option(u8"f", std::nullopt, u8"NUM", u8"floating point argument"));
|
||||
auto string_opt = options.add_option(option::Option(std::nullopt, u8"string", u8"STR", u8"string argument"));
|
||||
auto flag_opt = options.add_option(option::Option(u8"v", std::nullopt, std::nullopt, u8"verbose mode"));
|
||||
|
||||
// Create variables collection
|
||||
auto variables = variable::VariableCollection();
|
||||
auto env_var = variables.add_variable(variable::Variable(u8"ENV_VAR", u8"Environment variable description", true));
|
||||
|
||||
// Create the application and manual
|
||||
auto app = application::Application(std::move(summary), std::move(options), std::move(variables));
|
||||
auto manual = manual::Manual(app);
|
||||
|
||||
// Parse command line arguments
|
||||
auto result = parser::Parser::from_system(app);
|
||||
if (result.has_value()) {
|
||||
auto parser = std::move(result.value());
|
||||
|
||||
// Get values using validators
|
||||
using IntValidator = validator::IntegralValidator<int>;
|
||||
using FloatValidator = validator::FloatingPointValidator<float>;
|
||||
using StringValidator = validator::StringValidator;
|
||||
|
||||
// Check and get integer option
|
||||
if (auto int_val = parser.get_value_option<IntValidator>(int_opt); int_val.has_value()) {
|
||||
std::cout << "Integer value: " << int_val.value() << std::endl;
|
||||
}
|
||||
|
||||
// Check and get float option
|
||||
if (auto float_val = parser.get_value_option<FloatValidator>(float_opt); float_val.has_value()) {
|
||||
std::cout << "Float value: " << float_val.value() << std::endl;
|
||||
}
|
||||
|
||||
// Check and get string option
|
||||
if (auto str_val = parser.get_value_option<StringValidator>(string_opt); str_val.has_value()) {
|
||||
std::cout << "String value: " << str_val.value() << std::endl;
|
||||
}
|
||||
|
||||
// Check flag option
|
||||
if (auto flag_val = parser.get_flag_option(flag_opt); flag_val.has_value() && flag_val.value()) {
|
||||
std::cout << "Verbose mode enabled" << std::endl;
|
||||
}
|
||||
} else {
|
||||
// Print help if parsing failed
|
||||
manual.print_help(std::cout);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
\endcode
|
||||
|
||||
This code handles command lines like:
|
||||
\code{.sh}
|
||||
./myapp -i 123 -f 2.5 --string "hello world" -v
|
||||
\endcode
|
||||
|
||||
\section clap__components Components
|
||||
|
||||
\subsection clap__application Application Definition
|
||||
|
||||
The [Application](\ref application::Application) class represents a complete command line application with its summary, options, and environment variables.
|
||||
It combines the application metadata, command line options, and environment variables into a single unit.
|
||||
|
||||
\subsection clap__options Options
|
||||
|
||||
[Option](\ref option::Option) is command line arguments that can accept values or act as flags.
|
||||
They can have both short names (single character)
|
||||
and long names (full text). The [OptionCollection](\ref option::OptionCollection) manages a collection of options and ensures no duplicates.
|
||||
|
||||
\subsection clap__variables Variables
|
||||
|
||||
[Variable](\ref variable::Variable) represent environment variables that can be captured and validated. The [VariableCollection](\ref variable::VariableCollection)
|
||||
manages a collection of environment variables and ensures no duplicates.
|
||||
|
||||
\subsection clap__parsing Parsing
|
||||
|
||||
The [Parser](\ref parser::Parser) class handles command line argument parsing. It can be created from user-provided arguments
|
||||
or from system arguments (argc/argv). Values are retrieved using type-safe validators.
|
||||
|
||||
\subsection clap__validation Validation
|
||||
|
||||
Validators ensure type-safe validation of command line argument values.
|
||||
The module provides built-in validators for:
|
||||
|
||||
\li Integral types ([IntegralValidator](\ref validator::IntegralValidator))
|
||||
\li Floating-point types ([FloatingPointValidator](\ref validator::FloatingPointValidator))
|
||||
\li String types ([StringValidator](\ref validator::StringValidator))
|
||||
|
||||
For some of them, you also can specify value range via template arguments.
|
||||
|
||||
\section clap__custom_validators Custom Validator
|
||||
|
||||
Custom validators can be created by implementing the \c Validator concept.
|
||||
A valid validator must satisfy the following requirements:
|
||||
|
||||
\li Have a type alias called \c ReturnType indicating the return value type
|
||||
\li Have a member function called \c validate that receives <TT>const std::u8string_view&</TT> as its only argument
|
||||
and returns validated \c ReturnType or \c std::nullopt if validation fails
|
||||
|
||||
Here is an example of a custom validator that validates email addresses:
|
||||
|
||||
\code{.cpp}
|
||||
#include <yycc/string/reinterpret.hpp>
|
||||
#include <regex>
|
||||
|
||||
struct EmailValidator {
|
||||
using ReturnType = std::u8string;
|
||||
|
||||
std::optional<ReturnType> validate(const std::u8string_view& sv) const {
|
||||
// Simple email validation using regex
|
||||
static const std::regex email_regex(
|
||||
R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
|
||||
|
||||
auto email_str = yycc::string::reinterpret::as_ordinary_view(sv);
|
||||
if (std::regex_match(email_str, email_regex)) {
|
||||
return sv;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
\endcode
|
||||
|
||||
To use the custom validator:
|
||||
|
||||
\code{.cpp}
|
||||
// Add option to application
|
||||
auto email_opt = options.add_option(option::Option(std::nullopt, u8"email", u8"EMAIL", u8"Email address"));
|
||||
|
||||
// Use custom validator
|
||||
if (auto email_val = parser.get_value_option<EmailValidator>(email_opt); email_val.has_value()) {
|
||||
std::cout << yycc::patch::format(u8"Valid email: {}", email_val); << std::endl;
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section clap__limitations Limitations
|
||||
|
||||
Due to the limitations of implementation,
|
||||
CLAP now only allow only zero or one associated value for single option.
|
||||
More than one assocciated value for single option is not supported.
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
namespace YYCC::ConfigManager {
|
||||
/**
|
||||
|
||||
\page config_manager Universal Config Manager
|
||||
|
||||
YYCC::ConfigManager give programmer an universal way to manage its program settings.
|
||||
There is an example about how to use universal config manager.
|
||||
In following content, we will describe it in detail.
|
||||
|
||||
\code
|
||||
class TestConfigManager {
|
||||
public:
|
||||
enum class TestEnum : int8_t {
|
||||
Test1, Test2, Test3
|
||||
};
|
||||
|
||||
TestConfigManager() :
|
||||
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
|
||||
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
|
||||
m_FloatSetting(YYCC_U8("float-setting"), 0.0f, YYCC::Constraints::GetNumberRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
|
||||
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
|
||||
&m_IntSetting, &m_StringSetting, &m_FloatSetting, &m_EnumSetting
|
||||
})
|
||||
{}
|
||||
~TestConfigManager() {}
|
||||
|
||||
YYCC::ConfigManager::NumberSetting<int32_t> m_IntSetting;
|
||||
YYCC::ConfigManager::StringSetting m_StringSetting;
|
||||
YYCC::ConfigManager::NumberSetting<float> m_FloatSetting;
|
||||
YYCC::ConfigManager::NumberSetting<TestEnum> m_EnumSetting;
|
||||
|
||||
YYCC::ConfigManager::CoreManager m_CoreManager;
|
||||
};
|
||||
|
||||
// Initialize config manager
|
||||
TestConfigManager test;
|
||||
// Load settings.
|
||||
test.m_CoreManager.Load()
|
||||
// Get string setting value.
|
||||
auto val = test.m_StringSetting.Get();
|
||||
\endcode
|
||||
|
||||
\section config_manager__setting Setting
|
||||
|
||||
Setting can be seen as the leaf of the config tree.
|
||||
Each setting describe a single configuration entry.
|
||||
|
||||
\subsection config_manager__setting__presets Setting Presets
|
||||
|
||||
We currently provide 2 setting preset classes which you can directly use.
|
||||
|
||||
\li NumberSetting: The setting storing a number inside.
|
||||
It is a template class. Support all arithmetic and enum types (integral, floating point, bool, enum).
|
||||
\li StringSetting: The setting storing a string inside.
|
||||
|
||||
When constructing these settings,
|
||||
you need to provide its unique name which will be used when saving to file or reading from file.
|
||||
Also you need to provide a default value for it.
|
||||
It will be used when fail to read file or initializing itself.
|
||||
|
||||
Optionally, you also can provide a constraint to setting.
|
||||
Constraint is the struct instructing library to limit value in specified range.
|
||||
It usually is used for making sure the setting stored value is valid.
|
||||
See \ref constraints chapters to know how we provide constraints.
|
||||
|
||||
\subsection config_manager__setting__custom Custom Setting
|
||||
|
||||
In most cases, the combination use of setting presets and constraints is enough.
|
||||
However, if you still are urge to create your personal setting,
|
||||
please inherit AbstractSetting and implement essential class functions.
|
||||
For the class functions you need to implement,
|
||||
please refer to our setting presets, NumberSetting and StringSetting.
|
||||
|
||||
\section config_manager__core_manager Core Manager
|
||||
|
||||
CoreManager manage a collection of settings.
|
||||
And have responsibility to reading and writing config file.
|
||||
|
||||
We highly suggest that you create a personal config manager class like example does.
|
||||
Then put essential settings and core manager inside it.
|
||||
Please note you must place core manager after all settings.
|
||||
Because the order of C++ initializing its class member is the order you declared them.
|
||||
The constructor of core manager need the pointer to all it managed settings,
|
||||
so it must be initialized after initializing all settings.
|
||||
|
||||
When initializing core manager, you need assign config file path first.
|
||||
Then you need specify a version number.
|
||||
Version number is important.
|
||||
It will be used when reading config file and only can be increased if needed (version can not downgrade).
|
||||
The last argument is an initializer list which contain the \b pointer to all settings this manager managed.
|
||||
|
||||
When executing YYCC::ConfigManager::CoreManager::Load to load configs, it will perform following steps one by one:
|
||||
|
||||
<UL>
|
||||
<LI>
|
||||
Open given config file.
|
||||
<UL>
|
||||
<LI>
|
||||
If given file is not existing, loading function will simply return and all configs will be reset to its default value.
|
||||
</LI>
|
||||
<LI>
|
||||
Success to open file, go to next step.
|
||||
</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
|
||||
<LI>
|
||||
Fetch version number from file.
|
||||
<UL>
|
||||
<LI>
|
||||
If fail to read version number from file, loading function will simply return and all configs will be reset to its default value.
|
||||
</LI>
|
||||
<LI>
|
||||
If the version of config file is higher than your specified version number when constructing this class,
|
||||
core manager will assume you are trying to read a config file created by a higher version program,
|
||||
and will reject reading and use default value for all settings.
|
||||
</LI>
|
||||
<LI>
|
||||
If the version of config file is lower than your specified version number,
|
||||
core manager will try to read config file and do proper migration (set default value for configs which do not existing) if possible.
|
||||
</LI>
|
||||
<LI>
|
||||
If the version of config file is equal than your specified version number,
|
||||
core manager will read config file normally.
|
||||
</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
|
||||
<LI>
|
||||
Read config file body.
|
||||
<UL>
|
||||
<LI>
|
||||
If any IO error occurs when reading, loading function will simply return.
|
||||
All read config will keep their read value and all configs which has not been read will keep their default value.
|
||||
</LI>
|
||||
<LI>
|
||||
If some config can not parse binary data to its type,
|
||||
this config will be skipped and core manager will process next config.
|
||||
This config will keep its default value.
|
||||
</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
All of these scenarios can be found by the return value of loading function.
|
||||
The return type of loading function, ConfigLoadResult is a flag enum.
|
||||
You can find whether loading process happend specified issue by using bitwise operation on it.
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
namespace YYCC::ConsoleHelper {
|
||||
/**
|
||||
|
||||
\page console_helper Console Helper
|
||||
|
||||
This helper provide console related stuff.
|
||||
This helper includes 2 parts.
|
||||
First part is console color.
|
||||
It was constituted by a bunch of macros.
|
||||
The second part is universal console IO function because Windows is lacking in UTF8 console IO.
|
||||
All of these parts will be introduced in following content.
|
||||
|
||||
\section console_helper__color Console Color
|
||||
|
||||
YYCC::ConsoleHelper provide a bunch of macros which can allow you output colorful text in terminal.
|
||||
Supported color is limited in 16 colors,
|
||||
because these color is implemented by ASCII Escape Code: https://en.wikipedia.org/wiki/ANSI_escape_code .
|
||||
So if your terminal do not support this, such as default Windows terminal, or teletypewriter,
|
||||
you will see some unrecognised characters surrounding with your output.
|
||||
That's ASCII Escape Code.
|
||||
|
||||
\subsection console_helper__color__enable_win_color Enable Color in Windows Console
|
||||
|
||||
As we introduced in above,
|
||||
you may know Windows console does not support ASCII Escape Code color in default.
|
||||
However #EnableColorfulConsole can fix this issue.
|
||||
|
||||
#EnableColorfulConsole will forcely enable ASCII Escape Code support in Windows console if possible.
|
||||
Thus you can write colorful text in Windows console freely.
|
||||
We suggest you to call this function at the beginning of program.
|
||||
|
||||
Considering most Linux console supports ASCII Escape Code very well,
|
||||
this function does nothing in non-Windows platform.
|
||||
So it is not essential that brack this function calling with Windows-only \c \#if.
|
||||
|
||||
\subsection console_helper__color__common Common Usage
|
||||
|
||||
For common scenarios, you can use macro like this:
|
||||
|
||||
\code
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("Light Red Text")));
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("I am " YYCC_COLOR_LIGHT_RED("Light Red")));
|
||||
\endcode
|
||||
|
||||
In first line, it will make <TT>"Light Red Text"</TT> to be shown in light red color.
|
||||
And for second line, it will make <TT>"Light Red"</TT> to be shown in light red color,
|
||||
but <TT>"I am "</TT> will keep default console font color.
|
||||
|
||||
You also may notice this macro is used with YYCC_U8 macro.
|
||||
Because #WriteLine only accept UTF8 argument.
|
||||
So please note if you use console color macro with YYCC_U8,
|
||||
please make YYCC_U8 always is located the outside.
|
||||
Otherwise, YYCC_U8 will fail to make the whole become UTF8 stirng as we introduced in \ref library_encoding.
|
||||
Because console color macro is implemented by string literal concatenation internally.
|
||||
|
||||
YYCC_COLOR_LIGHT_RED is a member in YYCC_COLOR macro family.
|
||||
YYCC_COLOR macro family has 16 members for 16 different colors:
|
||||
|
||||
\li YYCC_COLOR_BLACK
|
||||
\li YYCC_COLOR_RED
|
||||
\li YYCC_COLOR_GREEN
|
||||
\li YYCC_COLOR_YELLOW
|
||||
\li YYCC_COLOR_BLUE
|
||||
\li YYCC_COLOR_MAGENTA
|
||||
\li YYCC_COLOR_CYAN
|
||||
\li YYCC_COLOR_WHITE
|
||||
\li YYCC_COLOR_LIGHT_BLACK
|
||||
\li YYCC_COLOR_LIGHT_RED
|
||||
\li YYCC_COLOR_LIGHT_GREEN
|
||||
\li YYCC_COLOR_LIGHT_YELLOW
|
||||
\li YYCC_COLOR_LIGHT_BLUE
|
||||
\li YYCC_COLOR_LIGHT_MAGENTA
|
||||
\li YYCC_COLOR_LIGHT_CYAN
|
||||
\li YYCC_COLOR_LIGHT_WHITE
|
||||
|
||||
\subsection console_helper__color__embedded Embedded Usgae
|
||||
|
||||
In some cases, you want change console at some time point and reset it in another time point.
|
||||
You can use color macros like following example:
|
||||
|
||||
\code
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLORHDR_LIGHT_BLUE));
|
||||
|
||||
// Write as much as you liked
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("some string"));
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("another string"));
|
||||
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLORTAIL));
|
||||
\endcode
|
||||
|
||||
At first line, we output YYCC_COLORHDR_LIGHT_BLUE which is in YYCC_COLORHDR macro family.
|
||||
It is colorful text ASCII Escape Code head.
|
||||
It will make all following output become light blue color,
|
||||
until the last line we output YYCC_COLORTAIL to reset console color to original color.
|
||||
|
||||
Same as YYCC_COLOR macro family,
|
||||
YYCC_COLORHDR macro family also has 16 members for 16 different colors:
|
||||
|
||||
\li YYCC_COLORHDR_BLACK
|
||||
\li YYCC_COLORHDR_RED
|
||||
\li YYCC_COLORHDR_GREEN
|
||||
\li YYCC_COLORHDR_YELLOW
|
||||
\li YYCC_COLORHDR_BLUE
|
||||
\li YYCC_COLORHDR_MAGENTA
|
||||
\li YYCC_COLORHDR_CYAN
|
||||
\li YYCC_COLORHDR_WHITE
|
||||
\li YYCC_COLORHDR_LIGHT_BLACK
|
||||
\li YYCC_COLORHDR_LIGHT_RED
|
||||
\li YYCC_COLORHDR_LIGHT_GREEN
|
||||
\li YYCC_COLORHDR_LIGHT_YELLOW
|
||||
\li YYCC_COLORHDR_LIGHT_BLUE
|
||||
\li YYCC_COLORHDR_LIGHT_MAGENTA
|
||||
\li YYCC_COLORHDR_LIGHT_CYAN
|
||||
\li YYCC_COLORHDR_LIGHT_WHITE
|
||||
|
||||
However YYCC_COLORTAIL is YYCC_COLORTAIL.
|
||||
There is no other variant for different colors.
|
||||
Because all tail of colorful ASCII Escape Code is same.
|
||||
|
||||
\section console_helper__universal_io Universal IO Function
|
||||
|
||||
\subsection console_helper__universal_io__why Why?
|
||||
|
||||
Windows console doesn't support UTF8 very well.
|
||||
The standard input output functions can not work properly with UTF8 on Windows.
|
||||
So we create this namespace and provide various console-related functions
|
||||
to patch Windows console and let it more like the console in other platforms.
|
||||
|
||||
The function provided in this function can be called in any platforms.
|
||||
In Windows, the implementation will use Windows native function,
|
||||
and in other platform, the implementation will redirect request to standard C function like \c std::fputs and etc.
|
||||
So the programmer do not need to be worried about which function should they use,
|
||||
and don't need to use macro to use different IO function in different platforms.
|
||||
It is just enough that fully use the functions provided in this namespace.
|
||||
|
||||
All IO functions this namespace provided are UTF8-based.
|
||||
It also means that input output string should always be UTF8 encoded.
|
||||
|
||||
\subsection console_helper__universal_io__input Input Functions
|
||||
|
||||
Please note that EOL will automatically converted into LF on Windows platform, not CRLF.
|
||||
This action actually is removing all CR chars in result string.
|
||||
This behavior affect nothing in most cases but it still is possible break something in some special case.
|
||||
|
||||
Due to implementation, if you decide to use this function,
|
||||
you should give up using any other function to read stdin stream,
|
||||
such as \c std::gets() and \c std::cin.
|
||||
Because this function may read chars which is more than needed.
|
||||
These extra chars will be stored in this function and can be used next calling.
|
||||
But these chars can not be visited by stdin again.
|
||||
This behavior may cause bug.
|
||||
So if you decide using this function, stick on it and do not change.
|
||||
|
||||
Due to implementation, this function do not support hot switch of stdin.
|
||||
It means that stdin can be redirected before first calling of this function,
|
||||
but it should not be redirected during program running.
|
||||
The reason is the same one introduced above.
|
||||
|
||||
\subsection console_helper__universal_io__output Output Functions
|
||||
|
||||
In current implementation, EOL will not be converted automatically to CRLF.
|
||||
This is different with other stream read functions provided in this namespace.
|
||||
|
||||
Comparing with other stream read functions provided in this namespace,
|
||||
stream write function support hot switch of stdout and stderr.
|
||||
Because they do not have internal buffer storing something.
|
||||
|
||||
In this namespace, there are various stream write function.
|
||||
There is a list telling you how to choose one from them for using:
|
||||
|
||||
\li Functions with leading "Err" will write data into stderr,
|
||||
otherwise they will write data into stdout.
|
||||
\li Functions with embedded "Format" are output functions with format feature
|
||||
like \c std::fprintf(), otherwise the functions with embedded "Write" will
|
||||
only write plain string like \c std::fputs().
|
||||
\li Functions with trailing "Line" will write extra EOL to break current line.
|
||||
This is commonly used, otherwise functions will only write the text provided by arguments,
|
||||
without adding something.
|
||||
|
||||
*/
|
||||
}
|
||||
74
doc/src/carton/csconsole.dox
Normal file
74
doc/src/carton/csconsole.dox
Normal file
@@ -0,0 +1,74 @@
|
||||
namespace yycc::carton::csconsole {
|
||||
/**
|
||||
|
||||
\page csconsole Universal IO Function
|
||||
|
||||
This namespace provide universal console IO function which is more like C\# provided,
|
||||
because Windows is lacking in UTF8 console IO.
|
||||
|
||||
\section csconsole__deprecation Deprecation Notes
|
||||
|
||||
This namespace, or this module is deprecated.
|
||||
Its provided functions are too aggressive and can not cover all use scenarios.
|
||||
So it is suggested not to use this namespace.
|
||||
Programmers should handle Windows UTF8 issues on their own.
|
||||
|
||||
\section csconsole__why Why?
|
||||
|
||||
Windows console doesn't support UTF8 very well.
|
||||
The standard input output functions can not work properly with UTF8 on Windows.
|
||||
So we create this namespace and provide various console-related functions
|
||||
to patch Windows console and let it more like the console in other platforms.
|
||||
|
||||
The function provided in this function can be called in any platforms.
|
||||
In Windows, the implementation will use Windows native function,
|
||||
and in other platform, the implementation will redirect request to standard C function like \c std::fputs and etc.
|
||||
So the programmer do not need to be worried about which function should they use,
|
||||
and don't need to use macro to use different IO function in different platforms.
|
||||
It is just enough that fully use the functions provided in this namespace.
|
||||
|
||||
All IO functions this namespace provided are UTF8-based.
|
||||
It also means that input output string should always be UTF8 encoded.
|
||||
|
||||
\section csconsole__input Input Functions
|
||||
|
||||
Please note that EOL will automatically converted into LF on Windows platform, not CRLF.
|
||||
This action actually is removing all CR chars in result string.
|
||||
This behavior affect nothing in most cases but it still is possible break something in some special case.
|
||||
|
||||
Due to implementation, if you decide to use this function,
|
||||
you should give up using any other function to read stdin stream,
|
||||
such as \c std::gets() and \c std::cin.
|
||||
Because this function may read chars which is more than needed.
|
||||
These extra chars will be stored in this function and can be used next calling.
|
||||
But these chars can not be visited by stdin again.
|
||||
This behavior may cause bug.
|
||||
So if you decide using this function, stick on it and do not change.
|
||||
|
||||
Due to implementation, this function do not support hot switch of stdin.
|
||||
It means that stdin can be redirected before first calling of this function,
|
||||
but it should not be redirected during program running.
|
||||
The reason is the same one introduced above.
|
||||
|
||||
\section csconsole__output Output Functions
|
||||
|
||||
In current implementation, EOL will not be converted automatically to CRLF.
|
||||
This is different with other stream read functions provided in this namespace.
|
||||
|
||||
Comparing with other stream read functions provided in this namespace,
|
||||
stream write function support hot switch of stdout and stderr.
|
||||
Because they do not have internal buffer storing something.
|
||||
|
||||
In this namespace, there are various stream write function.
|
||||
There is a list telling you how to choose one from them for using:
|
||||
|
||||
\li Functions with leading "e" (like eformat, ewrite) will write data into stderr,
|
||||
otherwise they will write data into stdout.
|
||||
\li Functions with embedded "format" (format, format_line, eformat, eformat_line) are output functions with format feature like \c std::fprintf(),
|
||||
otherwise the functions with embedded "write" in the name (write, write_line, ewrite, ewrite_line) will only write plain string like \c std::fputs().
|
||||
\li Functions with trailing "line" (format_line, write_line, eformat_line, ewrite_line) will write extra EOL to break current line.
|
||||
This is commonly used, otherwise functions will only write the text provided by arguments,
|
||||
without adding something.
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
namespace YYCC::EncodingHelper {
|
||||
/**
|
||||
|
||||
\page encoding_helper Encoding Helper
|
||||
|
||||
YYCC::EncodingHelper namespace include all encoding related functions:
|
||||
|
||||
\li The convertion between ordinary string and UTF8 string which has been introduced in chapter \ref library_encoding.
|
||||
\li Windows specific convertion between \c WCHAR, UTF8 string and string encoded by other encoding.
|
||||
\li The convertion among UTF8, UTF16 and UTF32.
|
||||
|
||||
\section encoding_helper__ordinary_utf8_conv Ordinary & UTF8 Convertion
|
||||
|
||||
These convertion functions have been introduced in previous page.
|
||||
See \ref library_encoding for more infomation.
|
||||
|
||||
YYCC supports following convertions:
|
||||
|
||||
\li #ToUTF8: Convert ordinary string to UTF8 string.
|
||||
\li #ToUTF8View: Same as ToUTF8, but return string view instead.
|
||||
\li #ToOrdinary: Convert UTF8 string to ordinary string.
|
||||
\li #ToOrdinaryView: Same as ToOrdinary, but return string view instead.
|
||||
|
||||
\section encoding_helper__win_conv Windows Specific Convertion
|
||||
|
||||
During Windows programming, the convertion between Microsoft specified \c wchar_t and \c char is an essential operation.
|
||||
Because Windows has 2 different function system, the functions ended with A and the functions ended with W.
|
||||
(Microsoft specified \c wchar_t is \c 2 bytes long. It's different with Linux defined common 4 bytes long).
|
||||
Thus YYCC provides these convertion functions in Windows to help programmer have better programming experience.
|
||||
|
||||
These functions are Windows specific, so they will be invisible in other platforms.
|
||||
Please use them carefully (make sure that you are using them only in Windows environment).
|
||||
|
||||
YYCC supports following convertions:
|
||||
|
||||
\li #WcharToChar: Convert \c wchar_t string to code page specified string.
|
||||
\li #CharToWchar: The reversed convertion of WcharToChar.
|
||||
\li #CharToChar: Convert string between 2 different code pages. It's a shortcut of calling CharToWchar and WcharToChar successively.
|
||||
\li #WcharToUTF8: Convert \c wchar_t string to UTF8 string.
|
||||
\li #UTF8ToWchar: The reversed convertion of WcharToUTF8.
|
||||
\li #CharToUTF8: Convert code page specified string to UTF8 string.
|
||||
\li #UTF8ToChar: The reversed convertion of CharToUTF8.
|
||||
|
||||
Code Page is a Windows concept.
|
||||
If you don't understand it, please view corresponding Microsoft documentation.
|
||||
|
||||
\section encoding_helper__utf_conv UTF8 UTF16 UTF32 Convertion
|
||||
|
||||
The convertion between UTF8, UTF16 and UTF32 is not common but essential.
|
||||
These convertions can be achieved by standard library functions and classes.
|
||||
(they are actually done by standard library functions in our implementation)
|
||||
But we provided functions are easy to use and have clear interface.
|
||||
|
||||
These functions are different with the functions introduced above.
|
||||
They can be used in any platform, not confined in Windows platforms.
|
||||
|
||||
YYCC supports following convertions:
|
||||
|
||||
\li #UTF8ToUTF16: Convert UTF8 string to UTF16 string.
|
||||
\li #UTF16ToUTF8: The reversed convertion of UTF8ToUTF16.
|
||||
\li #UTF8ToUTF32: Convert UTF8 string to UTF32 string.
|
||||
\li #UTF32ToUTF8: The reversed convertion of UTF8ToUTF32.
|
||||
|
||||
\section encoding_helper__overloads Function Overloads
|
||||
|
||||
Every encoding convertion functions (except the convertion between UTF8 and ordinary string) have 4 different overloads for different scenarios.
|
||||
Take #WcharToChar for example.
|
||||
There are following 4 overloads:
|
||||
|
||||
\code
|
||||
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page);
|
||||
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page);
|
||||
std::string WcharToChar(const std::wstring_view& src, UINT code_page);
|
||||
std::string WcharToChar(const wchar_t* src, UINT code_page);
|
||||
\endcode
|
||||
|
||||
\subsection encoding_helper__overloads_destination Destination String
|
||||
|
||||
According to the return value, these 4 overload can be divided into 2 types.
|
||||
The first type returns bool. The second type returns \c std::string instance.
|
||||
|
||||
For the first type, it always return bool to indicate whether the convertion is success.
|
||||
Due to this, the function must require an argument for holding the result string.
|
||||
So you can see the functions belonging to this type always require a reference to \c std::string in argument.
|
||||
|
||||
Oppositely, the second directly returns result by return value.
|
||||
It doesn't care the success of convertion and will return empty string if convertion failed.
|
||||
Programmer can more naturally use it because the retuen value itself is the result.
|
||||
There is no need to declare a variable before calling convertion function for holding result.
|
||||
|
||||
All in all, the first type overload should be used in strict scope.
|
||||
The success of convertion will massively affect the behavior of your following code.
|
||||
For example, the convertion code is delivered to some system function and it should not be empty and etc.
|
||||
The second type overload usually is used in lossen scenarios.
|
||||
For exmaple, this overload usually is used in console output because it usually doesn't matter.
|
||||
There is no risk even if the convertion failed (just output a blank string).
|
||||
|
||||
For the first type, please note that there is \b NO guarantee that the argument holding return value is not changed.
|
||||
Even the convertion is failed, the argument holding return value may still be changed by function itself.
|
||||
|
||||
In this case, the type of result is \c std::string because this is function required.
|
||||
In other functions, such as #WcharToUTF8, the type of result can be \c yycc_u8string or etc.
|
||||
So please note the type of result is decided by convertion function itself, not only \c std::string.
|
||||
|
||||
\subsection encoding_helper__overloads__source Source String
|
||||
|
||||
According to the way providing source string,
|
||||
these 4 overload also can be divided into 2 types.
|
||||
The first type take a reference to constant \c std::wstring_view.
|
||||
The second type take a pointer to constant \c wchar_t.
|
||||
|
||||
For first type, it will take the whole string for convertion, including \b embedded NUL terminal.
|
||||
Please note we use string view as argument.
|
||||
It is compatible with corresponding raw string pointer and string container.
|
||||
So it is safe to directly pass \c std::wstring for this function.
|
||||
|
||||
For second type, it will assume that you passed argument is a NUL terminated string and send it for convertion.
|
||||
|
||||
The result is clear.
|
||||
If you want to process string with \b embedded NUL terminal, please choose first type overload.
|
||||
Otherwise the second type overload is enough.
|
||||
|
||||
Same as destination string, the type of source is also decided by the convertion function itself.
|
||||
For exmaple, the type of source in #UTF8ToWchar is \c yycc_u8string_view and \c yycc_char8_t,
|
||||
not \c std::wstring and \c wchar_t.
|
||||
|
||||
\subsection encoding_helper__overloads__extra Extra Argument
|
||||
|
||||
There is an extra argument called \c code_page for #WcharToChar.
|
||||
It indicates the code page of destination string,
|
||||
because this function will convert \c wchar_t string to the string with specified code page encoding.
|
||||
|
||||
Some convertion functions have extra argument like this,
|
||||
because they need more infomations to decide what they need to do.
|
||||
Some convertion functions don't have extra argument.
|
||||
For exmaple, the convertion between \c wchar_t string and UTF8 string.
|
||||
Because both source string and destination string are concrete.
|
||||
There is no need to provide any more infomations.
|
||||
|
||||
\subsection encoding_helper__overloads__conclusion Conclusion
|
||||
|
||||
Mixing 2 types of source string and 2 types of destination string,
|
||||
we have 4 different overload as we illustrated before.
|
||||
Programmer can use them freely according to your requirements.
|
||||
And don't forget to provide extra argument if function required.
|
||||
|
||||
*/
|
||||
}
|
||||
93
doc/src/carton/fft.dox
Normal file
93
doc/src/carton/fft.dox
Normal file
@@ -0,0 +1,93 @@
|
||||
namespace yycc::carton::fft {
|
||||
/**
|
||||
|
||||
\page fft Homemade FFT
|
||||
|
||||
This namespace provides a fast Fourier transform (FFT) implementation for signal processing applications.
|
||||
It includes classes for performing FFT computations on complex and real-valued signals, along with
|
||||
window functions to reduce spectral leakage.
|
||||
|
||||
\section fft__basic_usage Basic Usage
|
||||
|
||||
To use the FFT functionality for general purposes, use the FriendlyFft class:
|
||||
|
||||
\code
|
||||
#include "yycc/carton/fft.hpp"
|
||||
using namespace yycc::carton::fft;
|
||||
|
||||
// Create FFT instance for 8-point transform
|
||||
FriendlyFft<size_t, float, 8u> fft;
|
||||
|
||||
// Prepare input data (must be power of 2 in length)
|
||||
float time_scope[8] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
|
||||
float freq_scope[4]; // Output is half the input size
|
||||
|
||||
// Create window function to reduce spectral leakage
|
||||
Window<size_t, float, 8u> window(WindowType::HanningWindow);
|
||||
|
||||
// Perform FFT transformation
|
||||
fft.easy_compute(time_scope, freq_scope, window);
|
||||
|
||||
// freq_scope now contains frequency domain data
|
||||
\endcode
|
||||
|
||||
\section fft__window_functions Window Functions
|
||||
|
||||
The library provides window functions to reduce spectral leakage:
|
||||
|
||||
\code
|
||||
// Create a Hanning window for 16-point data
|
||||
Window<size_t, float, 16u> hanning_window(WindowType::HanningWindow);
|
||||
|
||||
// Apply window to your data
|
||||
float data[16];
|
||||
// ... initialize data ...
|
||||
hanning_window.apply_window(data);
|
||||
\endcode
|
||||
|
||||
\section fft__direct_fft Direct FFT Computation
|
||||
|
||||
For more control over the FFT computation, use the core Fft class:
|
||||
|
||||
\code
|
||||
#include "yycc/carton/fft.hpp"
|
||||
|
||||
// Create FFT instance for 16-point transform
|
||||
Fft<size_t, double, 16u> fft;
|
||||
|
||||
// Prepare complex input data
|
||||
std::complex<double> data[16];
|
||||
// ... initialize complex data ...
|
||||
|
||||
// Perform FFT transformation
|
||||
fft.compute(data);
|
||||
// data now contains transformed values
|
||||
\endcode
|
||||
|
||||
\section fft__predefined_types Predefined Types
|
||||
|
||||
The library provides commonly used FFT types for convenience:
|
||||
|
||||
\code
|
||||
// Float precision FFTs
|
||||
Fft4F fft4f; // 4-point float FFT
|
||||
Fft8F fft8f; // 8-point float FFT
|
||||
Fft16F fft16f; // 16-point float FFT
|
||||
Fft256F fft256f; // 256-point float FFT
|
||||
|
||||
// Double precision FFTs
|
||||
Fft4 fft4; // 4-point double FFT
|
||||
Fft8 fft8; // 8-point double FFT
|
||||
Fft16 fft16; // 16-point double FFT
|
||||
Fft256 fft256; // 256-point double FFT
|
||||
\endcode
|
||||
|
||||
\section fft__requirements Requirements
|
||||
|
||||
- Template parameters must satisfy certain constraints:
|
||||
- \c TIndex: The index type used by FFT which must be an unsigned integral type.
|
||||
- \c TFloat: The float point type used by FFT.
|
||||
- \c VN: The point of FFT which must be a power of 2 and >= 2.
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -1,30 +1,30 @@
|
||||
namespace YYCC::ExceptionHelper {
|
||||
namespace yycc::carton::ironpad {
|
||||
/**
|
||||
|
||||
\page exception_helper Unhandled Exception Handler
|
||||
\page ironpad Unhandled Exception Handler
|
||||
|
||||
Most Linux users are familiar with core dump.
|
||||
However core dump is a tough work on Windows especially most Windows users are naive for getting core dump.
|
||||
So it is essential to make an easy-to-visit core dump Feature for Windows program.
|
||||
YYCC provides this feature in YYCC::ExceptionHelper.
|
||||
Most Linux users are familiar with using core dump to find bugs.
|
||||
However finding bugs is a tough work on Windows especially most Windows users are naive for getting core dump.
|
||||
So it is essential to make an easy-to-visit core dump feature for Windows program.
|
||||
This is the reason why I create this module, yycc::carton::ironpad.
|
||||
|
||||
You may know Google also has a similar and universal project called Crashpad used by Google Chrome.
|
||||
That's right. But it is too heavy.
|
||||
I just want to implement a tiny but worked core dump feature on Windows.
|
||||
|
||||
This module is Windows specific.
|
||||
It will be invisible on other platforms.
|
||||
It still be available on other operating systems but all of its functions are do nothing.
|
||||
|
||||
\section exception_helper__usage Usage
|
||||
\section ironpad__usage Usage
|
||||
|
||||
\subsection exception_helper__usage__code Register Code
|
||||
\subsection ironpad__usage__code Register Code
|
||||
|
||||
In most scenarios, programmer only need call #Register when program started or module loaded.
|
||||
And call #Unregister when program exited or module unloaded.
|
||||
In most scenarios, programmer only need call #startup when program started or module loaded.
|
||||
And call #shutdown when program exited or module unloaded.
|
||||
All details are hidden by these 2 feature.
|
||||
Programmer do not need worried about the implementation of unhandled exception handler.
|
||||
|
||||
Optionally, you can provide a function pointer during calling #Register as a callback.
|
||||
Optionally, you can provide a function pointer during calling #startup as a callback.
|
||||
The prototype of this function pointer is #ExceptionCallback.
|
||||
This callback will be called if any unhandled exception happened.
|
||||
It provides 2 pathes to log file and core dump file respectively.
|
||||
@@ -35,21 +35,21 @@ However, please note the pathes provided by callback may be empty.
|
||||
In this case, it means that handler fail to create corresponding log files.
|
||||
Also, if you trying to register unhandled exception handler on the same process in different module with different callback,
|
||||
only the callback provided in first success registering will be called when unhandled exception happened,
|
||||
due to \ref exception_helper__notes__singleton design.
|
||||
due to \ref ironpad__notes__singleton design.
|
||||
|
||||
\subsection exception_helper__usage__location Location
|
||||
\subsection ironpad__usage__location Location
|
||||
|
||||
When unhandled exception occurs,
|
||||
unhandled exception handler will try to record error log and core dump in following path:
|
||||
|
||||
\li Error Log: <TT>\%LOCALAPPDATA\%\\CrashDumps\\<I>program.exe</I>.<I>pid</I>.log</TT>
|
||||
\li Core Dump: <TT>\%LOCALAPPDATA\%\\CrashDumps\\<I>program.exe</I>.<I>pid</I>.dmp</TT>
|
||||
\li Error Log: <TT>\%LOCALAPPDATA\%\\IronPad\\<I>program.exe</I>.<I>pid</I>.log</TT>
|
||||
\li Core Dump: <TT>\%LOCALAPPDATA\%\\IronPad\\<I>program.exe</I>.<I>pid</I>.dmp</TT>
|
||||
|
||||
The italic characters <I>program.exe</I> and <I>pid</I> will be replaced by program name and process ID respectively at runtime.
|
||||
Directory <TT>\%LOCALAPPDATA\%\\CrashDumps</TT> also is Windows used crash dump directory.
|
||||
So you may see some other core dumps done by Windows in it.
|
||||
Directory <TT>\%LOCALAPPDATA\%\\IronPad</TT> is the dedicated directory for this module.
|
||||
So you may see the generated logs and dumps in it.
|
||||
|
||||
\subsection exception_helper__usage__last_remedy Last Remedy
|
||||
\subsection ironpad__usage__last_remedy Last Remedy
|
||||
|
||||
If unhandled exception handler occurs error, these stuff may not be generated correctly.
|
||||
The end user may not find them and send them to you.
|
||||
@@ -65,40 +65,40 @@ Also please note the last remedy may still have a little bit possibility to occu
|
||||
especially the error occurs in back trace function.
|
||||
There is no guaranteen that unhandled exception handler must generate error log and core dump.
|
||||
|
||||
\section exception_helper__notes Notes
|
||||
\section ironpad__notes Notes
|
||||
|
||||
\subsection exception_helper__notes__thread_safe Thread Safe
|
||||
\subsection ironpad__notes__thread_safe Thread Safe
|
||||
|
||||
All exposed functions in YYCC::ExceptionHelper are thread safe.
|
||||
The implementation uses \c std:mutex to ensure this.
|
||||
All exposed functions in this namespace are thread safe.
|
||||
The implementation uses \c std::mutex to ensure this.
|
||||
|
||||
\subsection exception_helper__notes__singleton Singleton Handler
|
||||
\subsection ironpad__notes__singleton Singleton Handler
|
||||
|
||||
YYCC::ExceptionHelper also have a mechanism that make sure the same unhandled exception handler implementation only appear once in the same process.
|
||||
This namespace also have a mechanism that make sure the same unhandled exception handler implementation only appear once in the same process.
|
||||
For example, you have an executable program A.exe, and 2 dynamic libraries B.dll and C.dll.
|
||||
A.exe and B.dll use YYCC unhandled exception handler feature but C.dll not.
|
||||
A.exe will load B.dll and C.dll at runtime.
|
||||
Although both A.exe and B.dll call #Register,
|
||||
Although both A.exe and B.dll call #startup,
|
||||
when unhandled exception occurs, there is only one error report output,
|
||||
which may be generated by A.exe or B.dll accoridng to their order of loading.
|
||||
|
||||
The core purpose of this is making sure the program will not output too many error report for the same unhandled exception,
|
||||
no matter how many modules calling #Register are loaded.
|
||||
no matter how many modules calling #startup are loaded.
|
||||
Only one error report is enough.
|
||||
|
||||
More precisely, we use \c CreateMutexW to create an unique mutex in Windows global scope,
|
||||
to make sure #Register only run once in the same process.
|
||||
to make sure #startup only run once in the same process.
|
||||
It is very like the implementation of singleton application.
|
||||
|
||||
\subsection exception_helper__notes__recursive_calling Recursive Calling
|
||||
\subsection ironpad__notes__recursive_calling Recursive Calling
|
||||
|
||||
The implementation of unhandled exception handler may also will throw exception.
|
||||
This will cause infinite recursive calling.
|
||||
YYCC::ExceptionHelper has internal mechanism to prevent this bad case.
|
||||
This namespace has internal mechanism to prevent this bad case.
|
||||
If this really happened, the handler will quit silent and will not cause any issue.
|
||||
Programmer don't need to worry about this.
|
||||
|
||||
\subsection exception_helper__notes__user_callback The Timing of User Callback
|
||||
\subsection ironpad__notes__user_callback The Timing of User Callback
|
||||
|
||||
The timing of calling user callback is the tail of unhandled exception handler.
|
||||
It means that all log and coredump have been written if possible before calling callback.
|
||||
102
doc/src/carton/lexer61.dox
Normal file
102
doc/src/carton/lexer61.dox
Normal file
@@ -0,0 +1,102 @@
|
||||
namespace yycc::carton::lexer61 {
|
||||
/**
|
||||
|
||||
\page lexer61 Homemade Command Line Lexer
|
||||
|
||||
This namespace provides a lexer for parsing command-line arguments, supporting various quoting mechanisms,
|
||||
escape sequences, and Unicode characters. It follows the standard shell parsing rules for handling
|
||||
arguments containing spaces and special characters.
|
||||
|
||||
\section lexer61__basic_usage Basic Usage
|
||||
|
||||
To parse command line arguments, create a Lexer61 instance and call the Lexer61::lex() method:
|
||||
|
||||
\code
|
||||
#include "yycc/carton/lexer61.hpp"
|
||||
using namespace yycc::carton::lexer61;
|
||||
|
||||
Lexer61 lexer;
|
||||
auto result = lexer.lex(u8"program arg1 arg2 arg3");
|
||||
|
||||
if (result.has_value()) {
|
||||
auto args = std::move(result.value());
|
||||
// args contains: [u8"program", u8"arg1", u8"arg2", u8"arg3"]
|
||||
for (const auto& arg : args) {
|
||||
std::wcout << reinterpret_cast<const wchar_t*>(arg.c_str()) << std::endl;
|
||||
}
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section lexer61__quoting_support Quoting Support
|
||||
|
||||
The lexer supports both single and double quotes for grouping arguments with spaces:
|
||||
|
||||
\code
|
||||
Lexer61 lexer;
|
||||
|
||||
// Double quotes
|
||||
auto result1 = lexer.lex(u8R"(program "argument with spaces" end)");
|
||||
// Result: [u8"program", u8"argument with spaces", u8"end"]
|
||||
|
||||
// Single quotes
|
||||
auto result2 = lexer.lex(u8"program 'another argument' end");
|
||||
// Result: [u8"program", u8"another argument", u8"end"]
|
||||
|
||||
// Mixed quotes
|
||||
auto result3 = lexer.lex(u8R"(program "double quoted 'single inside'" 'single quoted "double inside"')");
|
||||
// Result: [u8"program", u8"double quoted 'single inside'", u8"single quoted \"double inside\""]
|
||||
\endcode
|
||||
|
||||
\section lexer61__escape_sequences Escape Sequences
|
||||
|
||||
The lexer supports escape sequences for including special characters:
|
||||
|
||||
\code
|
||||
Lexer61 lexer;
|
||||
auto result = lexer.lex(u8R"(program escaped\ space "quoted with \" quote" 'single with \' quote')");
|
||||
|
||||
// Result: [u8"program", u8"escaped space", u8"quoted with \" quote", u8"single with \' quote"]
|
||||
\endcode
|
||||
|
||||
\section lexer61__unicode_support Unicode Support
|
||||
|
||||
The lexer fully supports Unicode characters in command line arguments:
|
||||
|
||||
\code
|
||||
Lexer61 lexer;
|
||||
auto result = lexer.lex(u8"程序 中文 参数");
|
||||
// Result: [u8"程序", u8"中文", u8"参数"]
|
||||
|
||||
// With quotes
|
||||
auto result2 = lexer.lex(u8R"(程序 "中文 参数" '另一个"引号"参数')");
|
||||
// Result: [u8"程序", u8"中文 参数", u8"另一个\"引号\"参数"]
|
||||
\endcode
|
||||
|
||||
\section lexer61__empty_arguments Empty Arguments
|
||||
|
||||
Empty arguments can be represented with empty quotes:
|
||||
|
||||
\code
|
||||
Lexer61 lexer;
|
||||
auto result = lexer.lex(u8R"(program "" '')");
|
||||
|
||||
// Result: [u8"program", u8"", u8""]
|
||||
\endcode
|
||||
|
||||
\section lexer61__error_handling Error Handling
|
||||
|
||||
The lexer uses \c std::expected for error handling:
|
||||
|
||||
\code
|
||||
Lexer61 lexer;
|
||||
auto result = lexer.lex(u8R"(program "unclosed quote)");
|
||||
|
||||
if (!result.has_value()) {
|
||||
// Handle error - in this case, unclosed quote
|
||||
std::cerr << "Error: unclosed quote" << std::endl;
|
||||
std::abort();
|
||||
}
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
202
doc/src/carton/pycodec.dox
Normal file
202
doc/src/carton/pycodec.dox
Normal file
@@ -0,0 +1,202 @@
|
||||
namespace yycc::carton::pycodec {
|
||||
/**
|
||||
\page pycodec Unified Codec (Python-like Codec)
|
||||
|
||||
\section pycodec__overview Overview
|
||||
|
||||
The unified encoding conversion module provides a consistent interface for character encoding conversion across different platforms.
|
||||
It automatically selects the appropriate backend implementation based on the platform and available features.
|
||||
|
||||
\section pycodec__classes Available Classes
|
||||
|
||||
\subsection pycodec__classes__char Character to/from UTF-8 Conversion
|
||||
|
||||
Convert between named encodings and UTF-8 using a unified interface:
|
||||
|
||||
\code
|
||||
#include <yycc/carton/pycodec.hpp>
|
||||
|
||||
// Example: Converting from a named encoding to UTF-8
|
||||
CharToUtf8 converter("GBK"); // or "ISO-8859-1", "SHIFT-JIS", etc.
|
||||
|
||||
std::string gbk_text = "你好,世界!";
|
||||
auto result = converter.to_utf8(gbk_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting from UTF-8 to a named encoding
|
||||
Utf8ToChar converter("GBK");
|
||||
|
||||
std::u8string utf8_text = u8"Hello, 世界!";
|
||||
auto result = converter.to_char(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::string gbk_text = result.value();
|
||||
// Use gbk_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\subsection pycodec__classes__wchar Wide Character to/from UTF-8 Conversion
|
||||
|
||||
Convert between wide character strings and UTF-8:
|
||||
|
||||
\code
|
||||
#include <yycc/carton/pycodec.hpp>
|
||||
|
||||
// Example: Converting wide character to UTF-8
|
||||
WcharToUtf8 converter;
|
||||
|
||||
std::wstring wide_text = L"Hello, 世界!";
|
||||
auto result = converter.to_utf8(wide_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-8 to wide character
|
||||
Utf8ToWchar converter;
|
||||
|
||||
std::u8string utf8_text = u8"Hello, 世界!";
|
||||
auto result = converter.to_wchar(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::wstring wide_text = result.value();
|
||||
// Use wide_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\subsection pycodec__classes__utf16_utf32 UTF-8 to/from UTF-16/UTF-32 Conversion
|
||||
|
||||
Convert between UTF encodings:
|
||||
|
||||
\code
|
||||
#include <yycc/carton/pycodec.hpp>
|
||||
|
||||
// Example: Converting UTF-8 to UTF-16
|
||||
Utf8ToUtf16 converter;
|
||||
|
||||
std::u8string utf8_text = u8"Hello, 世界!";
|
||||
auto result = converter.to_utf16(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::u16string utf16_text = result.value();
|
||||
// Use utf16_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-16 to UTF-8
|
||||
Utf16ToUtf8 converter;
|
||||
|
||||
std::u16string utf16_text = u"Hello, 世界!";
|
||||
auto result = converter.to_utf8(utf16_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-8 to UTF-32
|
||||
Utf8ToUtf32 converter;
|
||||
|
||||
std::u8string utf8_text = u8"Hello, 世界! 🌍";
|
||||
auto result = converter.to_utf32(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::u32string utf32_text = result.value();
|
||||
// Use utf32_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-32 to UTF-8
|
||||
Utf32ToUtf8 converter;
|
||||
|
||||
std::u32string utf32_text = U"Hello, 世界! 🌍";
|
||||
auto result = converter.to_utf8(utf32_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section pycodec__utility Utility Functions
|
||||
|
||||
\subsection pycodec__utility__validation Encoding Name Validation
|
||||
|
||||
Check if an encoding name is valid in the current environment:
|
||||
|
||||
\code
|
||||
#include <yycc/carton/pycodec.hpp>
|
||||
|
||||
// Example: Validating an encoding name
|
||||
bool is_valid = is_valid_encoding_name(u8"UTF-8");
|
||||
if (is_valid) {
|
||||
std::cout << "UTF-8 is a valid encoding name\n";
|
||||
} else {
|
||||
std::cout << "UTF-8 is not supported\n";
|
||||
}
|
||||
|
||||
// Test another encoding
|
||||
is_valid = is_valid_encoding_name(u8"GBK");
|
||||
\endcode
|
||||
|
||||
\section pycodec__error_handling Error Handling
|
||||
|
||||
All functions in this module return a result containing either
|
||||
a ConvError struct represents conversion errors, or the final converted string.
|
||||
|
||||
\code
|
||||
#include <yycc/carton/pycodec.hpp>
|
||||
|
||||
CharToUtf8 converter("INVALID_ENCODING_NAME");
|
||||
std::string text = "Hello";
|
||||
|
||||
auto result = converter.to_utf8(text);
|
||||
|
||||
if (result.has_value()) {
|
||||
std::u8string converted = result.value();
|
||||
// Process successfully converted string
|
||||
} else {
|
||||
// Handle conversion failure
|
||||
std::cout << "Conversion failed\n";
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section pycodec__backend_specifics Platform-Specific Backends
|
||||
|
||||
For detailed information about the specific platform backends, see:
|
||||
|
||||
\li \ref encoding__windows : Windows-specific implementation using Win32 APIs
|
||||
\li \ref encoding__iconv : Iconv-based implementation for POSIX-like systems
|
||||
|
||||
\section pycodec__notes Notes
|
||||
|
||||
For all supported encoding names and their aliases,
|
||||
please browse code written in <TT>script/pycodec</TT> located in our source code.
|
||||
|
||||
Please also note that not all encoding name has implementation for all platforms.
|
||||
Some uncommon encoding names are not supported on some backend due to the limitations of the corresponding baskend.
|
||||
These also can be found in that directory introduced above.
|
||||
|
||||
*/
|
||||
}
|
||||
107
doc/src/carton/tabulate.dox
Normal file
107
doc/src/carton/tabulate.dox
Normal file
@@ -0,0 +1,107 @@
|
||||
namespace yycc::carton::tabulate {
|
||||
/**
|
||||
|
||||
\page tabulate Tabulate Utilities
|
||||
|
||||
This namespace provides utilities for creating formatted tables in console output.
|
||||
It supports Unicode text, automatic column width calculation, customizable headers,
|
||||
and flexible display options.
|
||||
|
||||
\section tabulate__basic_usage Basic Usage
|
||||
|
||||
To create a simple table with headers and data rows:
|
||||
|
||||
\code
|
||||
#include "yycc/carton/tabulate.hpp"
|
||||
using namespace yycc::carton::tabulate;
|
||||
|
||||
// Create a table with 3 columns
|
||||
Tabulate table(3);
|
||||
|
||||
// Set the header
|
||||
table.set_header({u8"Name", u8"Age", u8"City"});
|
||||
|
||||
// Add data rows
|
||||
table.add_row({u8"Alice", u8"30", u8"New York"});
|
||||
table.add_row({u8"Bob", u8"25", u8"Los Angeles"});
|
||||
table.add_row({u8"Charlie", u8"35", u8"Chicago"});
|
||||
|
||||
// Print the table
|
||||
table.print();
|
||||
\endcode
|
||||
|
||||
This will output:
|
||||
\verbatim
|
||||
Name Age City
|
||||
----- --- ---------
|
||||
Alice 30 New York
|
||||
Bob 25 Los Angeles
|
||||
Charlie 35 Chicago
|
||||
\endverbatim
|
||||
|
||||
\section tabulate__customization Customization Options
|
||||
|
||||
You can customize various aspects of the table:
|
||||
|
||||
\code
|
||||
Tabulate table(3);
|
||||
|
||||
// Set a custom separator bar
|
||||
table.set_bar(u8"===");
|
||||
|
||||
// Add a prefix (useful for indentation)
|
||||
table.set_prefix(u8"> ");
|
||||
|
||||
// Control header and bar visibility
|
||||
table.show_header(false); // Hide header
|
||||
table.show_bar(true); // Show separator bar
|
||||
|
||||
// Set header
|
||||
table.set_header({u8"Col1", u8"Col2", u8"Col3"});
|
||||
|
||||
// Add data
|
||||
table.add_row({u8"data1", u8"data2", u8"data3"});
|
||||
|
||||
table.print();
|
||||
\endcode
|
||||
|
||||
\section tabulate__unicode_support Unicode Support
|
||||
|
||||
The library fully supports Unicode text in tables:
|
||||
|
||||
\code
|
||||
Tabulate table(3);
|
||||
|
||||
// Set Unicode header
|
||||
table.set_header({u8"姓名", u8"年龄", u8"城市"});
|
||||
|
||||
// Add Unicode data
|
||||
table.add_row({u8"张三", u8"30", u8"北京"});
|
||||
table.add_row({u8"李四", u8"25", u8"上海"});
|
||||
|
||||
table.print();
|
||||
\endcode
|
||||
|
||||
\section tabulate__stream_output Stream Output
|
||||
|
||||
You can print to any output stream, not just stdout:
|
||||
|
||||
\code
|
||||
#include <fstream>
|
||||
|
||||
Tabulate table(2);
|
||||
table.set_header({u8"Key", u8"Value"});
|
||||
table.add_row({u8"temp", u8"25C"});
|
||||
|
||||
// Print to file
|
||||
std::ofstream file("table.txt");
|
||||
table.print(file);
|
||||
|
||||
// Or print to stringstream
|
||||
std::stringstream ss;
|
||||
table.print(ss);
|
||||
std::string table_str = ss.str();
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
121
doc/src/carton/termcolor.dox
Normal file
121
doc/src/carton/termcolor.dox
Normal file
@@ -0,0 +1,121 @@
|
||||
namespace yycc::carton::termcolor {
|
||||
/**
|
||||
|
||||
\page termcolor Terminal Color Utilities
|
||||
|
||||
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.
|
||||
|
||||
Supported color is limited in 16 colors,
|
||||
because these color is implemented by ASCII Escape Code: https://en.wikipedia.org/wiki/ANSI_escape_code .
|
||||
So if your terminal do not support this, such as default Windows terminal, or teletypewriter,
|
||||
you will see some unrecognised characters surrounding with your output.
|
||||
That's ASCII Escape Code.
|
||||
|
||||
This namespace is basically the imitation of the Python package with same name.
|
||||
|
||||
\section termcolor__basic_colors Basic Colors
|
||||
|
||||
To use basic foreground and background colors:
|
||||
|
||||
\code
|
||||
#include "yycc/carton/termcolor.hpp"
|
||||
using namespace yycc::carton::termcolor;
|
||||
|
||||
// Print red text
|
||||
std::cout << colored(u8"Error message", Color::Red) << std::endl;
|
||||
|
||||
// Print blue text with yellow background
|
||||
cprint(u8"Info message", Color::Blue, Color::Yellow);
|
||||
\endcode
|
||||
|
||||
\section termcolor__light_colors Light Colors
|
||||
|
||||
The namespace provides both standard and light versions of colors:
|
||||
|
||||
\code
|
||||
// Standard green
|
||||
std::cout << colored(u8"Success", Color::Green) << std::endl;
|
||||
|
||||
// Light green (brighter variant)
|
||||
std::cout << colored(u8"Notice", Color::LightGreen) << std::endl;
|
||||
\endcode
|
||||
|
||||
\section termcolor__text_styles Text Styles
|
||||
|
||||
Multiple text styles can be combined using bitwise operations:
|
||||
|
||||
\code
|
||||
// Bold text
|
||||
std::cout << colored(u8"Important", Color::Red, Color::Default, Attribute::Bold) << std::endl;
|
||||
|
||||
// Underlined text
|
||||
std::cout << colored(u8"Underlined", Color::Blue, Color::Default, Attribute::Underline) << std::endl;
|
||||
|
||||
// Combined styles
|
||||
auto combined_style = yycc::cenum::merge(Attribute::Bold, Attribute::Italic);
|
||||
cprint(u8"Bold and italic", Color::Magenta, Color::Default, combined_style);
|
||||
\endcode
|
||||
|
||||
\section termcolor__convenience_functions Convenience Functions
|
||||
|
||||
Several convenience functions are available for direct printing:
|
||||
|
||||
\code
|
||||
// Print to stdout with color
|
||||
cprint(u8"Hello World", Color::Green);
|
||||
|
||||
// Print to stderr with color and style
|
||||
ceprint(u8"Warning", Color::Yellow, Color::Default, Attribute::Bold);
|
||||
|
||||
// Print with line break
|
||||
cprintln(u8"Success", Color::LightGreen);
|
||||
|
||||
// Print to stderr with line break
|
||||
ceprintln(u8"Critical Error", Color::LightRed, Color::Default, Attribute::Blink);
|
||||
\endcode
|
||||
|
||||
\section termcolor__color_enums Available Colors
|
||||
|
||||
Available foreground and background colors include:
|
||||
|
||||
\li Color::Black
|
||||
\li Color::Red
|
||||
\li Color::Green
|
||||
\li Color::Yellow
|
||||
\li Color::Blue
|
||||
\li Color::Magenta
|
||||
\li Color::Cyan
|
||||
\li Color::White
|
||||
\li Color::LightBlack
|
||||
\li Color::LightRed
|
||||
\li Color::LightGreen
|
||||
\li Color::LightYellow
|
||||
\li Color::LightBlue
|
||||
\li Color::LightMagenta
|
||||
\li Color::LightCyan
|
||||
\li Color::LightWhite
|
||||
\li Color::Default
|
||||
|
||||
\section termcolor__style_enums Available Styles
|
||||
|
||||
Available text styles include:
|
||||
|
||||
\li Attribute::Bold
|
||||
\li Attribute::Dark
|
||||
\li Attribute::Italic
|
||||
\li Attribute::Underline
|
||||
\li Attribute::Blink
|
||||
\li Attribute::Reverse
|
||||
\li Attribute::Concealed
|
||||
\li Attribute::Default
|
||||
|
||||
\section termcolor__old_macros Where is Old Macros
|
||||
|
||||
If you have used YYCC 1.x version, you may know that these features are also presented but in a bunch of macros style.
|
||||
These macros is removed since YYCC 2.0.
|
||||
Since YYCC 2.0, we suggest you to use these new provided functions instead,
|
||||
because they are more robust and correspond with our new style of coding by \c std::format and etc.
|
||||
|
||||
*/
|
||||
}
|
||||
103
doc/src/carton/wcwidth.dox
Normal file
103
doc/src/carton/wcwidth.dox
Normal file
@@ -0,0 +1,103 @@
|
||||
namespace yycc::carton::wcwidth {
|
||||
/**
|
||||
|
||||
\page wcwidth Cross-platform Wcwidth
|
||||
|
||||
This namespace provides cross-platform implementations of character width calculation functions,
|
||||
similar to the Linux-specific \c wcswidth function. It supports Unicode text and ANSI escape sequences,
|
||||
making it suitable for calculating display widths of text in terminals across different platforms.
|
||||
|
||||
\section wcwidth__basic_usage Basic Usage
|
||||
|
||||
To calculate the display width of a string in a terminal:
|
||||
|
||||
\code
|
||||
#include <yycc/carton/wcwidth.hpp>
|
||||
using namespace yycc::carton::wcwidth;
|
||||
|
||||
// Calculate width of ASCII text
|
||||
size_t width1 = wcwidth(U'a'); // Returns 1
|
||||
auto width2 = wcswidth(u8"Hello"); // Returns 5
|
||||
|
||||
// Calculate width of Unicode text
|
||||
auto width3 = wcswidth(u8"你好世界"); // Returns 8 (Chinese chars typically take 2 spaces each)
|
||||
auto width4 = wcswidth(u8"ありがとう"); // Returns 10 (Japanese katakana)
|
||||
\endcode
|
||||
|
||||
\section wcwidth__ansi_support ANSI Escape Sequence Support
|
||||
|
||||
The library can handle ANSI escape sequences (like color codes) in text
|
||||
which is not supported by Linux \c wcswidth.
|
||||
|
||||
\code
|
||||
#include "yycc/carton/termcolor.hpp"
|
||||
using namespace yycc::carton::termcolor;
|
||||
|
||||
// Calculate width of colored text
|
||||
auto colored_text = colored(u8"Hello World", Color::Red);
|
||||
auto width = wcswidth(colored_text);
|
||||
// Returns the width of "Hello World" (ignoring the ANSI escape sequences)
|
||||
\endcode
|
||||
|
||||
\section wcwidth__error_handling Error Handling
|
||||
|
||||
The functions use \c std::expected for error handling:
|
||||
|
||||
\code
|
||||
#include "yycc/carton/wcwidth.hpp"
|
||||
|
||||
// Safe way to handle potential errors
|
||||
auto result = wcswidth(u8"\033?"); // Invalid ANSI sequence
|
||||
|
||||
if (result.has_value()) {
|
||||
size_t width = result.value();
|
||||
std::cout << "Width: " << width << std::endl;
|
||||
} else {
|
||||
std::cout << "Invalid string" << std::endl;
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section wcwidth__character_types Supported Character Types
|
||||
|
||||
The library provides two main functions:
|
||||
|
||||
- wcwidth(): Calculate width of a single character.
|
||||
- wcswidth(): Calculate width of a string.
|
||||
|
||||
\code
|
||||
// Using wcwidth for single characters
|
||||
size_t char_width = wcwidth(U'A'); // Returns 1
|
||||
size_t emoji_width = wcwidth(U'😀'); // Returns width of emoji
|
||||
|
||||
// Using wcswidth for strings
|
||||
auto str_width1 = wcswidth(u8"Hello"); // Returns 5
|
||||
auto str_width2 = wcswidth(U"Unicode String"); // Returns string length in display width
|
||||
\endcode
|
||||
|
||||
\section wcwidth__platform_differences Platform Considerations
|
||||
|
||||
This library addresses platform differences in wide character handling:
|
||||
|
||||
\li On Linux, \c whar_t is 4 bytes and can represent any Unicode character
|
||||
\li On Windows, \c whar_t is 2 bytes and may require surrogate pairs for some characters
|
||||
|
||||
So this library uses \c char32_t internally to ensure consistent behavior across platforms,
|
||||
and expose functions with \c char32_t and \c char8_t string container respectively for user.
|
||||
|
||||
\section wcwidth__unicode_support Unicode and East Asian Widths
|
||||
|
||||
The library properly handles East Asian character widths:
|
||||
|
||||
\code
|
||||
// Chinese characters typically occupy 2 terminal spaces
|
||||
auto chinese_width = wcswidth(u8"中文"); // Returns 4 (2 chars × 2 spaces each)
|
||||
|
||||
// Japanese characters
|
||||
auto kana_width = wcswidth(u8"ありがとう"); // Returns 10 (5 kana × 2 spaces each)
|
||||
|
||||
// Mixed text
|
||||
auto mixed_width = wcswidth(u8"Hello 世界"); // Returns 8 (5 ASCII + 1 space + 2 Chinese chars × 2 spaces)
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
166
doc/src/encoding/iconv.dox
Normal file
166
doc/src/encoding/iconv.dox
Normal file
@@ -0,0 +1,166 @@
|
||||
namespace yycc::encoding::iconv {
|
||||
/**
|
||||
\page encoding__iconv Iconv-based Codec
|
||||
|
||||
\section encoding__iconv__overview Overview
|
||||
|
||||
The Iconv-based encoding conversion module provides encoding conversion functionality using the iconv library.
|
||||
This module is available when you are in POSIX system, or enable iconv support manually when configuring the library.
|
||||
|
||||
\section encoding__iconv__classes Available Classes
|
||||
|
||||
\subsection encoding__iconv__classes__char Char to/from UTF-8 Conversion
|
||||
|
||||
Convert between character encodings and UTF-8:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/iconv.hpp>
|
||||
|
||||
// Example: Creating a converter from Latin-1 to UTF-8
|
||||
CharToUtf8 converter("ISO-8859-1");
|
||||
|
||||
std::string latin1_text = "Café résumé naïve";
|
||||
auto result = converter.to_utf8(latin1_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Creating a converter from UTF-8 to Latin-1
|
||||
Utf8ToChar converter("ISO-8859-1");
|
||||
|
||||
std::u8string utf8_text = u8"Café résumé naïve";
|
||||
auto result = converter.to_char(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::string latin1_text = result.value();
|
||||
// Use latin1_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\subsection encoding__iconv__classes__wchar WChar to/from UTF-8 Conversion
|
||||
|
||||
Convert between wide character and UTF-8:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/iconv.hpp>
|
||||
|
||||
// Example: Converting wide character to UTF-8
|
||||
WcharToUtf8 converter;
|
||||
|
||||
std::wstring wide_text = L"Hello, 世界!";
|
||||
auto result = converter.to_utf8(wide_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-8 to wide character
|
||||
Utf8ToWchar converter;
|
||||
|
||||
std::u8string utf8_text = u8"Hello, 世界!";
|
||||
auto result = converter.to_wchar(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::wstring wide_text = result.value();
|
||||
// Use wide_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\subsection encoding__iconv__classes__utf16_utf32 UTF-8 to/from UTF-16/UTF-32 Conversion
|
||||
|
||||
Convert between UTF encodings:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/iconv.hpp>
|
||||
|
||||
// Example: Converting UTF-8 to UTF-16
|
||||
Utf8ToUtf16 converter;
|
||||
|
||||
std::u8string utf8_text = u8"Hello, 世界!";
|
||||
auto result = converter.to_utf16(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::u16string utf16_text = result.value();
|
||||
// Use utf16_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-16 to UTF-8
|
||||
Utf16ToUtf8 converter;
|
||||
|
||||
std::u16string utf16_text = u"Hello, 世界!";
|
||||
auto result = converter.to_utf8(utf16_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-8 to UTF-32
|
||||
Utf8ToUtf32 converter;
|
||||
|
||||
std::u8string utf8_text = u8"Hello, 世界! 🌍";
|
||||
auto result = converter.to_utf32(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::u32string utf32_text = result.value();
|
||||
// Use utf32_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-32 to UTF-8
|
||||
Utf32ToUtf8 converter;
|
||||
|
||||
std::u32string utf32_text = U"Hello, 世界! 🌍";
|
||||
auto result = converter.to_utf8(utf32_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section encoding__iconv__error_handling Error Handling
|
||||
|
||||
All functions in this module return a result containing either
|
||||
a ConvError struct represents conversion errors, or the final converted string.
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/iconv.hpp>
|
||||
|
||||
CharToUtf8 converter("INVALID_ENCODING");
|
||||
// Note: Constructor errors might be detected during conversion
|
||||
|
||||
std::string text = "Hello";
|
||||
auto result = converter.to_utf8(text);
|
||||
|
||||
if (result.has_value()) {
|
||||
std::u8string converted = result.value();
|
||||
// Process successfully converted string
|
||||
} else {
|
||||
// Handle conversion failure
|
||||
std::cout << "Conversion failed\n";
|
||||
}
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
98
doc/src/encoding/stl.dox
Normal file
98
doc/src/encoding/stl.dox
Normal file
@@ -0,0 +1,98 @@
|
||||
namespace yycc::encoding::stl {
|
||||
/**
|
||||
\page encoding__stl STL-based Codec
|
||||
|
||||
\section encoding__stl__overview Overview
|
||||
|
||||
The STL-based encoding conversion module provides cross-platform encoding conversion functionality using the standard library's codecvt facets.
|
||||
This module is designed to handle conversions between UTF-8, UTF-16, and UTF-32 encodings using the standard C++ locale facilities.
|
||||
|
||||
\section encoding__stl__attentions Attentions
|
||||
|
||||
The underlying implementation of this module is deprecated by C++ STL and may be removed in future versions of C++.
|
||||
So please use this module carefully or considering use our \ref pycodec module instead.
|
||||
|
||||
\section encoding__stl__functions Available Functions
|
||||
|
||||
\subsection encoding__stl__functions__utf16 UTF-8 to/from UTF-16 Conversion
|
||||
|
||||
Convert between UTF-8 and UTF-16 encodings using standard library facilities:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/stl.hpp>
|
||||
|
||||
// Example: Converting UTF-8 to UTF-16
|
||||
std::u8string utf8_text = u8"Hello, 世界!";
|
||||
auto result = to_utf16(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::u16string utf16_text = result.value();
|
||||
// Use utf16_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-16 to UTF-8
|
||||
std::u16string utf16_text = u"Hello, 世界!";
|
||||
auto result = to_utf8(utf16_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\subsection encoding__stl__functions__utf32 UTF-8 to/from UTF-32 Conversion
|
||||
|
||||
Convert between UTF-8 and UTF-32 encodings:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/stl.hpp>
|
||||
|
||||
// Example: Converting UTF-8 to UTF-32
|
||||
std::u8string utf8_text = u8"Hello, 世界! 🌍";
|
||||
auto result = to_utf32(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::u32string utf32_text = result.value();
|
||||
// Use utf32_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-32 to UTF-8
|
||||
std::u32string utf32_text = U"Hello, 世界! 🌍";
|
||||
auto result = to_utf8(utf32_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section encoding__stl__error_handling Error Handling
|
||||
|
||||
All functions in this module return a result containing either
|
||||
a ConvError struct represents conversion errors, or the final converted string.
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/stl.hpp>
|
||||
|
||||
std::u8string invalid_utf8 = "\xFF\xFE"; // Invalid UTF-8 sequence
|
||||
auto result = to_utf16(invalid_utf8);
|
||||
|
||||
if (result.has_value()) {
|
||||
std::u16string converted = result.value();
|
||||
// Process successfully converted string
|
||||
} else {
|
||||
// Handle conversion failure
|
||||
std::cout << "Conversion failed\n";
|
||||
}
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
191
doc/src/encoding/windows.dox
Normal file
191
doc/src/encoding/windows.dox
Normal file
@@ -0,0 +1,191 @@
|
||||
namespace yycc::encoding::windows {
|
||||
/**
|
||||
\page encoding__windows Win32-based Codec
|
||||
|
||||
\section encoding__windows__overview Overview
|
||||
|
||||
The Windows-specific encoding conversion module provides encoding conversion functionality
|
||||
using Windows API functions such as `WideCharToMultiByte` and `MultiByteToWideChar`.
|
||||
This module is available only on Windows platforms and offers efficient conversion
|
||||
between various character encodings including wide character, multi-byte, and UTF-8.
|
||||
|
||||
\section encoding__windows__functions Available Functions
|
||||
|
||||
\subsection encoding__windows__functions__wchar Wide Character to/from Multi-byte Conversion
|
||||
|
||||
Convert between wide character strings and multi-byte strings using Windows code pages:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/windows.hpp>
|
||||
|
||||
// Example: Converting wide character string to multi-byte with specific code page
|
||||
std::wstring wide_text = L"Hello, 世界!";
|
||||
auto result = to_char(wide_text, CP_UTF8); // Using UTF-8 code page
|
||||
if (result.has_value()) {
|
||||
std::string multi_byte_text = result.value();
|
||||
// Use multi_byte_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting multi-byte string to wide character with specific code page
|
||||
std::string multi_byte_text = "Hello, 世界!";
|
||||
auto result = to_wchar(multi_byte_text, CP_UTF8);
|
||||
if (result.has_value()) {
|
||||
std::wstring wide_text = result.value();
|
||||
// Use wide_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\subsection encoding__windows__functions__mbcs Multi-byte to/from Multi-byte Conversion
|
||||
|
||||
Convert between different multi-byte encodings by using wide character as an intermediate:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/windows.hpp>
|
||||
|
||||
// Example: Converting between two different code pages
|
||||
std::string source_text = "Hello, world!";
|
||||
auto result = to_char(source_text, CP_ACP, CP_UTF8); // ANSI to UTF-8
|
||||
if (result.has_value()) {
|
||||
std::string utf8_text = result.value();
|
||||
// Use converted UTF-8 text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\subsection encoding__windows__functions__utf8 UTF-8 Specific Conversions
|
||||
|
||||
Specialized functions for UTF-8 conversion without requiring explicit code page specification:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/windows.hpp>
|
||||
|
||||
// Example: Converting wide character to UTF-8
|
||||
std::wstring wide_text = L"Hello, 世界!";
|
||||
auto result = to_utf8(wide_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-8 to wide character
|
||||
std::u8string utf8_text = u8"Hello, 世界!";
|
||||
auto result = to_wchar(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::wstring wide_text = result.value();
|
||||
// Use wide_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting multi-byte to UTF-8
|
||||
std::string multi_byte_text = "Hello, world!";
|
||||
auto result = to_utf8(multi_byte_text, CP_ACP);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-8 to multi-byte
|
||||
std::u8string utf8_text = u8"Hello, world!";
|
||||
auto result = to_char(utf8_text, CP_ACP);
|
||||
if (result.has_value()) {
|
||||
std::string multi_byte_text = result.value();
|
||||
// Use multi_byte_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\subsection encoding__windows__functions__utf16_utf32 UTF-8 to/from UTF-16/UTF-32 Conversion
|
||||
|
||||
Available on Windows with Microsoft STL for conversion between UTF encodings:
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/windows.hpp>
|
||||
|
||||
// Example: Converting UTF-8 to UTF-16
|
||||
std::u8string utf8_text = u8"Hello, 世界!";
|
||||
auto result = to_utf16(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::u16string utf16_text = result.value();
|
||||
// Use utf16_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-16 to UTF-8
|
||||
std::u16string utf16_text = u"Hello, 世界!";
|
||||
auto result = to_utf8(utf16_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-8 to UTF-32
|
||||
std::u8string utf8_text = u8"Hello, 世界! 🌍";
|
||||
auto result = to_utf32(utf8_text);
|
||||
if (result.has_value()) {
|
||||
std::u32string utf32_text = result.value();
|
||||
// Use utf32_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\code
|
||||
// Example: Converting UTF-32 to UTF-8
|
||||
std::u32string utf32_text = U"Hello, 世界! 🌍";
|
||||
auto result = to_utf8(utf32_text);
|
||||
if (result.has_value()) {
|
||||
std::u8string utf8_text = result.value();
|
||||
// Use utf8_text...
|
||||
} else {
|
||||
// Handle conversion error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section encoding__windows__error_handling Error Handling
|
||||
|
||||
All functions in this module return a result containing either
|
||||
a ConvError struct represents conversion errors, or the final converted string.
|
||||
|
||||
\code
|
||||
#include <yycc/encoding/windows.hpp>
|
||||
|
||||
std::wstring invalid_text = /* some problematic string */;
|
||||
auto result = to_char(invalid_text, CP_UTF8);
|
||||
|
||||
if (result.has_value()) {
|
||||
std::string converted = result.value();
|
||||
// Process successfully converted string
|
||||
} else {
|
||||
// Handle conversion failure
|
||||
std::cout << "Conversion failed\n";
|
||||
}
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
85
doc/src/env.dox
Normal file
85
doc/src/env.dox
Normal file
@@ -0,0 +1,85 @@
|
||||
namespace yycc::env {
|
||||
/**
|
||||
|
||||
\page env Environment Operations
|
||||
|
||||
This namespace provide various environment operations inspired by Rust standard library.
|
||||
|
||||
\section env__var Environment Variable Operations
|
||||
|
||||
These functions allow manipulation of environment variables with proper error handling using \c std::expected.
|
||||
|
||||
\li get_var(): Get the value of a given environment variable name
|
||||
\li set_var(): Set the value of a given environment variable name
|
||||
\li del_var(): Delete environment variable with given name
|
||||
\li get_vars(): Get all environment variables as a list of name-value pairs
|
||||
|
||||
There is an example usage of these functions below:
|
||||
|
||||
\code
|
||||
#include <yycc/env.hpp>
|
||||
|
||||
auto result = yycc::env::get_var(u8"PATH");
|
||||
if (result.has_value()) {
|
||||
auto value = result.value();
|
||||
// Use the value...
|
||||
} else {
|
||||
// Handle the error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section env__path Path Operations
|
||||
|
||||
These functions provide access to various important paths in the system environment.
|
||||
|
||||
\li current_dir(): Returns the current working directory
|
||||
\li current_exe(): Returns the path of the current running executable
|
||||
\li home_dir(): Returns the path of the current user's home directory if known
|
||||
\li temp_dir(): Returns the path of a temporary directory
|
||||
|
||||
There is an example usage of these functions below:
|
||||
|
||||
\code
|
||||
#include <yycc/env.hpp>
|
||||
#include <yycc/patch/stream.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace yycc::patch::stream;
|
||||
|
||||
auto cwd_result = yycc::env::current_dir();
|
||||
if (cwd_result.has_value()) {
|
||||
std::cout << "Current directory: " << cwd_result.value() << std::endl;
|
||||
}
|
||||
|
||||
auto exe_result = yycc::env::current_exe();
|
||||
if (exe_result.has_value()) {
|
||||
std::cout << "Executable path: " << exe_result.value() << std::endl;
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section env__arg Command Line Argument Operations
|
||||
|
||||
These functions provide access to command-line arguments passed to the program.
|
||||
|
||||
\li get_args(): Returns the arguments that this program was started with
|
||||
|
||||
There is an example usage of these functions below:
|
||||
|
||||
\code
|
||||
#include <yycc/env.hpp>
|
||||
#include <yycc/patch/stream.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace yycc::patch::stream;
|
||||
|
||||
auto args_result = yycc::env::get_args();
|
||||
if (args_result.has_value()) {
|
||||
auto args = args_result.value();
|
||||
for (auto& arg : args) {
|
||||
std::cout << "Arg: " << arg << std::endl;
|
||||
}
|
||||
}
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -43,28 +43,51 @@
|
||||
|
||||
\li \subpage num__parser
|
||||
|
||||
\li \subpage num__op
|
||||
|
||||
\li \subpage num__safe_cast
|
||||
|
||||
\li \subpage num__safe_op
|
||||
|
||||
\li \subpage patch
|
||||
|
||||
<!--
|
||||
\li \subpage encoding_helper
|
||||
\li \subpage env
|
||||
|
||||
\li \subpage console_helper
|
||||
\li \subpage rust
|
||||
|
||||
-->
|
||||
<B>Text Encoding</B>
|
||||
|
||||
<!--
|
||||
\li \subpage constraints
|
||||
\li \subpage encoding__stl
|
||||
|
||||
\li \subpage config_manager
|
||||
\li \subpage encoding__windows
|
||||
|
||||
\li \subpage arg_parser
|
||||
-->
|
||||
\li \subpage encoding__iconv
|
||||
|
||||
\li \subpage pycodec
|
||||
|
||||
</TD>
|
||||
<TD ALIGN="LEFT" VALIGN="TOP">
|
||||
|
||||
<B>Advanced Features (Carton)</B>
|
||||
|
||||
\li \subpage termcolor
|
||||
|
||||
\li \subpage csconsole
|
||||
|
||||
\li \subpage ironpad
|
||||
|
||||
\li \subpage clap
|
||||
|
||||
\li \subpage binstore
|
||||
|
||||
\li \subpage fft
|
||||
|
||||
\li \subpage lexer61
|
||||
|
||||
\li \subpage wcwidth
|
||||
|
||||
\li \subpage tabulate
|
||||
|
||||
<B>Windows Specific Features</B>
|
||||
|
||||
\li \subpage windows__import_guard
|
||||
@@ -73,11 +96,9 @@
|
||||
|
||||
\li \subpage windows__dialog
|
||||
|
||||
<!--
|
||||
\li \subpage win_fct_helper
|
||||
\li \subpage windows__winfct
|
||||
|
||||
\li \subpage exception_helper
|
||||
-->
|
||||
\li \subpage windows__console
|
||||
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
30
doc/src/num/op.dox
Normal file
30
doc/src/num/op.dox
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace yycc::num::op {
|
||||
/**
|
||||
|
||||
\page num__op Numeric Operations
|
||||
|
||||
Namespace yycc::num::op provides functions for robust numeric operations inspired by Rust's approach to primitive type operations.
|
||||
|
||||
Currently, this namespace only supports unsigned integer ceiling division, though more operations may be added in the future based on demand.
|
||||
|
||||
\section num__op__div_ceil Ceiling Division
|
||||
|
||||
The \c div_ceil function performs division between two unsigned integers and rounds up the result.
|
||||
It uses a safe algorithm that avoids potential overflow issues that could occur with the traditional formula <TT>(lhs + rhs - 1) / rhs</TT>.
|
||||
|
||||
The function computes: <TT>(lhs % rhs == 0) ? (lhs / rhs) : (lhs / rhs) + 1u</TT>
|
||||
The function prevents division by zero by checking the divisor before performing the operation and throwing a std::logic_error if the divisor is zero.
|
||||
|
||||
Here are some examples showing how to use this function:
|
||||
|
||||
\code
|
||||
#include <yycc/num/op.hpp>
|
||||
|
||||
// Ceiling division examples
|
||||
uint32_t result1 = op::div_ceil(uint32_t(10), uint32_t(3)); // Results in 4
|
||||
uint32_t result2 = op::div_ceil(uint32_t(9), uint32_t(3)); // Results in 3
|
||||
uint32_t result3 = op::div_ceil(uint32_t(1), uint32_t(10)); // Results in 1
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
63
doc/src/num/safe_cast.dox
Normal file
63
doc/src/num/safe_cast.dox
Normal file
@@ -0,0 +1,63 @@
|
||||
namespace yycc::num::safe_cast {
|
||||
/**
|
||||
|
||||
\page num__safe_cast Numeric Safe Casting
|
||||
|
||||
Namespace yycc::num::safe_cast provides functions which safely cast numeric value from one type to another.
|
||||
|
||||
\section num__safe_cast__overview Overview
|
||||
|
||||
When writing C++ code, casting between types with different ranges is very important
|
||||
but greatly easy to make mistakes which finally cause fatal errors.
|
||||
Inspired by Rust's approach to type conversion,
|
||||
this namespace provides safe casting functions that handle potential overflow and underflow issues.
|
||||
|
||||
\section num__safe_cast__functions Functions
|
||||
|
||||
The namespace provides two main functions:
|
||||
\li \c to() - Direct conversion for cases where the destination type can definitely hold the source value
|
||||
which means definitely safe conversions (widening conversions).
|
||||
\li \c try_to() - Attempt conversion and return a Result type that includes error information if the conversion fails
|
||||
which means potentially risky conversions (narrowing conversions).
|
||||
|
||||
The \c try_to function returns a \c std::expected.
|
||||
If the conversion succeeds, the result contains the converted value.
|
||||
If it fails, it contains a error info.
|
||||
|
||||
\section num__safe_cast__examples Examples
|
||||
|
||||
Here are some examples showing how to use the safe casting functions:
|
||||
|
||||
\code
|
||||
#include <yycc/num/safe_cast.hpp>
|
||||
|
||||
// Safe conversion using 'to' function
|
||||
uint32_t val1 = safe_cast::to<uint32_t>(static_cast<int16_t>(123));
|
||||
|
||||
// Potentially risky conversion using 'try_to' function
|
||||
auto result = safe_cast::try_to<int16_t>(static_cast<int32_t>(12345));
|
||||
if (result.has_value()) {
|
||||
auto converted = result.value();
|
||||
// Use converted value
|
||||
} else {
|
||||
// Handle error
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section num__safe_cast__notes Notes
|
||||
|
||||
The safety of conversions is determined at compile time using the \c CAN_SAFE_TO meta-programming concept.
|
||||
However, for variable-length data types (like \c size_t ), the safety determination may vary across platforms,
|
||||
which could affect code portability. For this reason, it would be better to use \c try_to for these types
|
||||
for better robust application on different platforms.
|
||||
|
||||
\section num__safe_cast__limitations Limitations
|
||||
|
||||
This namespace supports safe casting between integral types only.
|
||||
Currently unsupported conversions include:
|
||||
|
||||
\li Floating-point to floating-point conversions
|
||||
\li Floating-point to integer conversions
|
||||
|
||||
*/
|
||||
}
|
||||
86
doc/src/num/safe_op.dox
Normal file
86
doc/src/num/safe_op.dox
Normal file
@@ -0,0 +1,86 @@
|
||||
namespace yycc::num::safe_op {
|
||||
/**
|
||||
|
||||
\page num__safe_op Numeric Safe Arithmetic Operations
|
||||
|
||||
Namespace yycc::num::safe_op provides Rust-like safe arithmetic operations
|
||||
for handling overflow, underflow, and other undefined behaviors in C++.
|
||||
|
||||
\section num__safe_op__overview Overview
|
||||
|
||||
Inspired by Rust's rich set of arithmetic operators,
|
||||
this namespace provides safe arithmetic operations that handle potential overflow, underflow, and other undefined behaviors that commonly occur in C++.
|
||||
It offers multiple strategies for handling arithmetic operations including wrapping, checked, overflowing, saturating, and strict operations.
|
||||
|
||||
\section num__safe_op__operation_types Operation Types
|
||||
|
||||
The namespace provides several families of arithmetic operations:
|
||||
|
||||
\li \c wrapping_* operations: Perform arithmetic with wrapping on overflow/underflow (similar to unsigned integer behavior)
|
||||
\li \c checked_* operations: Return std::optional containing the result, or std::nullopt if overflow/underflow occurs
|
||||
\li \c overflowing_* operations: Return a pair with the result and a boolean indicating whether overflow occurred
|
||||
\li \c saturating_* operations: Clamp the result to the min/max value when overflow/underflow occurs
|
||||
\li \c strict_* operations: Throw exceptions when overflow/underflow occurs
|
||||
\li \c ordinary operations (add, sub, mul, div): Alias to wrapping operations for safe default behavior
|
||||
|
||||
\section num__safe_op__arithmetic_functions Arithmetic Functions
|
||||
|
||||
For each operation type, the namespace provides functions for the four basic arithmetic operations:
|
||||
\li \c _add : Addition
|
||||
\li \c _sub : Subtraction
|
||||
\li \c _mul : Multiplication
|
||||
\li \c _div : Division
|
||||
|
||||
For example, for wrapping operations: \c wrapping_add, \c wrapping_sub, \c wrapping_mul, \c wrapping_div.
|
||||
|
||||
\section num__safe_op__examples Examples
|
||||
|
||||
Here are some examples showing how to use the safe arithmetic functions:
|
||||
|
||||
\code
|
||||
#include <yycc/num/safe_op.hpp>
|
||||
#include <iostream>
|
||||
|
||||
// Wrapping addition - wraps around on overflow
|
||||
uint8_t result1 = safe_op::wrapping_add(uint8_t(200), uint8_t(100)); // Results in 44
|
||||
|
||||
// Checked multiplication - returns std::optional
|
||||
auto result2 = safe_op::checked_mul(int32_t(1000000), int32_t(1000000));
|
||||
if (!result2.has_value()) {
|
||||
std::cout << "Multiplication overflowed!" << std::endl;
|
||||
} else {
|
||||
std::cout << "Result: " << result2.value() << std::endl;
|
||||
}
|
||||
|
||||
// Overflowing subtraction - returns pair of result and overflow flag
|
||||
auto [result3, overflowed] = safe_op::overflowing_sub(int32_t(-10), int32_t(INT32_MIN));
|
||||
if (overflowed) {
|
||||
std::cout << "Subtraction overflowed!" << std::endl;
|
||||
}
|
||||
|
||||
// Saturating multiplication - clamps to min/max on overflow
|
||||
int32_t result4 = safe_op::saturating_mul(int32_t(1000000), int32_t(1000000)); // Clamps to INT32_MAX
|
||||
|
||||
// Ordinary operations - safe defaults without undefined behavior
|
||||
int32_t result5 = safe_op::add(int32_t(10), int32_t(20)); // 30
|
||||
\endcode
|
||||
|
||||
\section num__safe_op__undefined_behaviors Handling of Undefined Behaviors
|
||||
|
||||
This namespace handles several undefined behaviors in C++ arithmetic:
|
||||
\li Signed integer overflow and underflow (e.g. INT_MAX + 1)
|
||||
\li Division by zero
|
||||
\li Performing INT_MIN / -1 division (which would result in a value that doesn't fit in the type)
|
||||
|
||||
For division operations, special care is taken to handle these undefined behaviors appropriately depending on the operation type.
|
||||
|
||||
\section num__safe_op__platform_support Platform Support
|
||||
|
||||
The implementation uses hardware-specific overflow detection functions:
|
||||
\li GCC/Clang: Uses built-in functions like __builtin_add_overflow
|
||||
\li Windows: Uses Windows API functions from \c intsafe.h
|
||||
|
||||
This ensures optimal performance across different platforms.
|
||||
|
||||
*/
|
||||
}
|
||||
133
doc/src/rust.dox
Normal file
133
doc/src/rust.dox
Normal file
@@ -0,0 +1,133 @@
|
||||
namespace yycc {
|
||||
/**
|
||||
|
||||
\page rust Rust Facilities in C++
|
||||
|
||||
This collection of following headers brings Rust-style programming facilities to C++.
|
||||
|
||||
\section rust__primitive Primitive Types
|
||||
|
||||
The yycc::primitive namespace provides primitive types similar to Rust's approach.
|
||||
Especially resolve the problem that the names of C++ primitive types are so long.
|
||||
|
||||
There is an example of using primitive types:
|
||||
|
||||
\code
|
||||
#include <yycc/primitive.hpp>
|
||||
using namespace yycc::primitive;
|
||||
|
||||
i32 value = 42;
|
||||
u64 big_number = 1000000ULL;
|
||||
f64 precision = 3.14159265359;
|
||||
\endcode
|
||||
|
||||
\section rust__option Option Type
|
||||
|
||||
The yycc::option namespace reproduces Rust's Option type and its members Some and None in C++.
|
||||
Considering C++ has provide \c std::optional, this namespace provided contents are just an alias to it.
|
||||
|
||||
\li yycc::option::Option - Template alias for std::optional
|
||||
\li yycc::option::Some - Function to create an Option with a value
|
||||
\li yycc::option::None - Function to create an empty Option
|
||||
|
||||
There is an example of using \c Option type:
|
||||
|
||||
\code
|
||||
#include <yycc/option.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace yycc::option;
|
||||
|
||||
Option<int> maybe_value = Some<Option<int>>(42);
|
||||
if (maybe_value.has_value()) {
|
||||
std::cout << "Value: " << maybe_value.value() << std::endl;
|
||||
}
|
||||
|
||||
auto empty_value = None<Option<int>>();
|
||||
if (!empty_value.has_value()) {
|
||||
std::cout << "No value present" << std::endl;
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section rust__result Result Type
|
||||
|
||||
The yycc::result namespace reproduces Rust's Result type and its members Ok and Err in C++.
|
||||
Considering C++ has provide \c std::expected, this namespace provided contents are just an alias to it.
|
||||
|
||||
\li yycc::result::Result - Template alias for std::expected
|
||||
\li yycc::result::Ok - Function to create a Result with a success value
|
||||
\li yycc::result::Err - Function to create a Result with an error value
|
||||
|
||||
There is an example of using \c Result type:
|
||||
|
||||
\code
|
||||
#include <yycc/result.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace yycc::result;
|
||||
|
||||
Result<int, int> divide(int a, int b) {
|
||||
if (b == 0) {
|
||||
return Err<Result<int, int>>(-1); // Error code
|
||||
}
|
||||
return Ok<Result<int, int>>(a / b);
|
||||
}
|
||||
|
||||
auto result = divide(10, 2);
|
||||
if (result.has_value()) {
|
||||
std::cout << "Result: " << result.value() << std::endl;
|
||||
} else {
|
||||
std::cout << "Error occurred: " << result.error() << std::endl;
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section rust__panic Panic Mechanism
|
||||
|
||||
The yycc::panic namespace provides Rust-style panic functionality for immediate program termination on unrecoverable errors.
|
||||
This imitates Rust's panic! macro behavior, allowing the program to immediately exit with error information and stack traces.
|
||||
|
||||
\li RS_PANIC: Macro equivalent to Rust's panic! macro.
|
||||
This macro will help you append all filename, line and function info to the real panic trigger function.
|
||||
\li yycc::panic::panic: The actual function called by the macro.
|
||||
User usually does not need to call this function directly.
|
||||
|
||||
There is an example of using this panic mechanism:
|
||||
|
||||
\code
|
||||
#include <yycc/panic.hpp>
|
||||
|
||||
void critical_function(int err_code) {
|
||||
// Some condition that indicates an unrecoverable error
|
||||
if (err_code != 100) {
|
||||
RS_PANIC("Unrecoverable error in critical function with code {}", err_code);
|
||||
}
|
||||
}
|
||||
\endcode
|
||||
|
||||
\section rust__prelude Prelude
|
||||
|
||||
The yycc::prelude namespace provides a Rust-like prelude for C++. In Rust, types are automatically imported into all files by default.
|
||||
This default-imported set of types is called the "prelude".
|
||||
This namespace provides a similar concept for C++.
|
||||
|
||||
This namespace will extract following content into \b global scope:
|
||||
|
||||
\li All primitive types defined in yycc::primitive.
|
||||
\li Vec: \c std::vector template alias.
|
||||
\li All functionality from yycc::option and yycc::result namespaces.
|
||||
\li Panic mechanism from yycc::panic.
|
||||
|
||||
There is an example of using this header:
|
||||
|
||||
\code
|
||||
#include <yycc/prelude.hpp>
|
||||
|
||||
// Now all Rust-style facilities are available without prefixes:
|
||||
i32 x = 42; // From primitive
|
||||
Vec<i32> numbers = {1, 2, 3}; // Vector of primitive types
|
||||
auto result = Ok<Result<i32, i32>>(x); // Result type
|
||||
RS_PANIC("Something went wrong"); // Panic macro
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -37,7 +37,7 @@ yycc::string::op provide 2 functions for programmer do string replacement:
|
||||
|
||||
\code
|
||||
void replace(std::u8string& strl, const std::u8string_view& from_strl, const std::u8string_view& to_strl);
|
||||
std::u8string replace(const std::u8string_view& strl, const std::u8string_view& from_strl, const std::u8string_view& to_strl);
|
||||
std::u8string to_replace(const std::u8string_view& strl, const std::u8string_view& from_strl, const std::u8string_view& to_strl);
|
||||
\endcode
|
||||
|
||||
The first overload will do replacement in given string container directly.
|
||||
@@ -162,7 +162,7 @@ std::vector<std::u8string> split_owned(const std::u8string_view& strl, const std
|
||||
All these overloads take a string view as the first argument representing the string need to be split.
|
||||
The second argument is a string view representing the delimiter for splitting.
|
||||
|
||||
The first function #lazy_split returns a #LazySplit object that can be used in range-based for loops.
|
||||
The first function #lazy_split returns a LazySplit object that can be used in range-based for loops.
|
||||
This is lazy-computed and memory-efficient for large datasets.
|
||||
The second function #split returns a vector of string views, which is memory-efficient
|
||||
but the views are only valid as long as the original string remains valid.
|
||||
|
||||
23
doc/src/windows/console.dox
Normal file
23
doc/src/windows/console.dox
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace yycc::windows::console {
|
||||
/**
|
||||
|
||||
\page windows__console Windows Console Helper
|
||||
|
||||
Namespace yycc::windows::console is designed to resolve some issue of Windows console
|
||||
which is not corresponding to POSIX system console.
|
||||
This namespace also is only available on Windows platform.
|
||||
|
||||
Currently this namespace only has one function: colorful_console(),
|
||||
which enable colorful console output support for \c stdout and \c stderr in Windows.
|
||||
As we introduced, you may know Windows console does not support ASCII Escape Code color in default.
|
||||
This function can fix this issue.
|
||||
This function will forcely enable ASCII Escape Code support in Windows console if possible.
|
||||
Thus you can write colorful text in Windows console freely.
|
||||
We suggest you to call this function at the beginning of program.
|
||||
|
||||
Considering most Linux console supports ASCII Escape Code very well,
|
||||
this function isn't presented in non-Windows platform.
|
||||
So it is essential that brack this function calling with Windows-only \c \#if.
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -133,10 +133,10 @@ auto result4 = open_folder(params);
|
||||
|
||||
There are 4 file dialogs you can choose:
|
||||
|
||||
\li #open_file: Open single file
|
||||
\li #open_files: Open multiple files
|
||||
\li #save_file: Save single file
|
||||
\li #open_folder: Open single directory
|
||||
\li open_file(): Open single file
|
||||
\li open_files(): Open multiple files
|
||||
\li save_file(): Save single file
|
||||
\li open_folder(): Open single directory
|
||||
|
||||
\subsection windows__dialog__result__arguments Arguments
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
namespace YYCC::WinFctHelper {
|
||||
/**
|
||||
|
||||
\page win_fct_helper Windows Function Helper
|
||||
|
||||
This helper give a more convenient way to call Windows functions.
|
||||
|
||||
This namespace is Windows specific.
|
||||
It will be entirely invisible in other platforms.
|
||||
|
||||
Currently this namespace has following functions:
|
||||
|
||||
\li #GetCurrentModule: Get the handle to current module.
|
||||
\li #GetTempDirectory: Get temporary directory in Windows.
|
||||
\li #GetModuleFileName: Get the path to module in file system by given handle.
|
||||
\li #GetLocalAppData: Get the path inside \%LOCALAPPDATA\%
|
||||
\li #IsValidCodePage: Check whether given code page number is valid.
|
||||
\li #CopyFile: The UTF8 version of Win32 \c CopyFile.
|
||||
\li #MoveFile: The UTF8 version of Win32 \c MoveFile.
|
||||
\li #DeleteFile: The UTF8 version of Win32 \c DeleteFile.
|
||||
|
||||
*/
|
||||
}
|
||||
15
doc/src/windows/winfct.dox
Normal file
15
doc/src/windows/winfct.dox
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace yycc::windows::winfct {
|
||||
/**
|
||||
|
||||
\page windows__winfct Windows Function Helper
|
||||
|
||||
Namespace yycc::windows::winfct gives a more convenient way to call Windows functions.
|
||||
If you want to know how to use these functions, please read the documentation of each function.
|
||||
The return value of most functions is a specific result type.
|
||||
If any error occurs, the result type will be an error, otherwise it will be the true result.
|
||||
|
||||
This namespace is Windows specific.
|
||||
It will be entirely invisible in other platforms.
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -69,6 +69,7 @@ FILES
|
||||
yycc/patch/format.hpp
|
||||
yycc/patch/libcxx/enumerate.hpp
|
||||
yycc/patch/libcxx/stacktrace.hpp
|
||||
yycc/patch/libcxx/charconv.hpp
|
||||
yycc/num/parse.hpp
|
||||
yycc/num/stringify.hpp
|
||||
yycc/num/safe_cast.hpp
|
||||
@@ -166,6 +167,15 @@ PUBLIC
|
||||
# Fix Windows header file shit
|
||||
$<$<BOOL:${WIN32}>:WIN32_LEAN_AND_MEAN>
|
||||
$<$<BOOL:${WIN32}>:NOMINMAX>
|
||||
PUBLIC
|
||||
$<$<BOOL:${YYCC_CHARCONV_HAS_CHARS_FORMAT}>:YYCC_CHARCONV_HAS_CHARS_FORMAT>
|
||||
$<$<BOOL:${YYCC_CHARCONV_HAS_FROM_CHARS_RESULT}>:YYCC_CHARCONV_HAS_FROM_CHARS_RESULT>
|
||||
$<$<BOOL:${YYCC_CHARCONV_HAS_TO_CHARS_RESULT}>:YYCC_CHARCONV_HAS_TO_CHARS_RESULT>
|
||||
$<$<BOOL:${YYCC_CHARCONV_HAS_FROM_CHARS_INT}>:YYCC_CHARCONV_HAS_FROM_CHARS_INT>
|
||||
$<$<BOOL:${YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT}>:YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT>
|
||||
$<$<BOOL:${YYCC_CHARCONV_HAS_TO_CHARS_INT}>:YYCC_CHARCONV_HAS_TO_CHARS_INT>
|
||||
$<$<BOOL:${YYCC_CHARCONV_HAS_TO_CHARS_FLOAT}>:YYCC_CHARCONV_HAS_TO_CHARS_FLOAT>
|
||||
|
||||
)
|
||||
target_compile_options(YYCCommonplace
|
||||
PUBLIC
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "../../patch/libcxx/enumerate.hpp"
|
||||
#include "../../string/op.hpp"
|
||||
#include "../../env.hpp"
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
|
||||
#define CLAP ::yycc::carton::clap
|
||||
@@ -112,8 +113,9 @@ namespace yycc::carton::clap::manual {
|
||||
// only print usage if we can fetch the name of executable
|
||||
auto executable = ENV::current_exe();
|
||||
if (executable.has_value()) {
|
||||
// Get the filename part and print
|
||||
TERMCOLOR::cprintln(trctx.usage_title, TERMCOLOR::Color::Yellow, TERMCOLOR::Color::Default, TERMCOLOR::Attribute::Default, dst);
|
||||
dst << INDENT << FORMAT::format(trctx.usage_body, executable.value().u8string()) << std::endl;
|
||||
dst << INDENT << FORMAT::format(trctx.usage_body, executable.value().filename().u8string()) << std::endl;
|
||||
}
|
||||
|
||||
const auto &variables = app.get_variables();
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace yycc::carton::clap::option {
|
||||
Option::Option(std::optional<std::u8string_view> short_name,
|
||||
std::optional<std::u8string_view> long_name,
|
||||
std::optional<std::u8string_view> value_hint,
|
||||
const std::u8string& description) :
|
||||
const std::u8string_view& description) :
|
||||
short_name(short_name), long_name(long_name), value_hint(value_hint), description(description) {
|
||||
if (!short_name.has_value() && !long_name.has_value()) {
|
||||
throw std::logic_error("must have at least one name, short or long name");
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace yycc::carton::clap::option {
|
||||
Option(std::optional<std::u8string_view> short_name,
|
||||
std::optional<std::u8string_view> long_name,
|
||||
std::optional<std::u8string_view> value_hint,
|
||||
const std::u8string& description);
|
||||
const std::u8string_view& description);
|
||||
~Option();
|
||||
YYCC_DEFAULT_COPY_MOVE(Option)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
* \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.
|
||||
* For how to utilize this functions provided by this namespace, please view \ref csconsole.
|
||||
*
|
||||
* @warning
|
||||
* All functions provided by this namespace are too aggressive.
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace yycc::carton::fft {
|
||||
* @param[out] freq_scope The length of this data must be N / 2.
|
||||
* The first data is 0Hz and the frequency of last data is decided by sample rate which can be computed by get_max_freq() function in this class.
|
||||
* @param[in] window The window instance applied to data.
|
||||
* @warnings
|
||||
* @warning
|
||||
* This function is \b NOT thread-safe.
|
||||
* Please do NOT call this function in different thread for one instance.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* @details
|
||||
* This namespace is currently works on Windows.
|
||||
* On other platforms, this namespace provided functions do nothing.
|
||||
* For how to utilize this namespace, please see \ref exception_helper.
|
||||
* For how to utilize this namespace, please see \ref ironpad.
|
||||
*
|
||||
* This feature is originate from my created Virtools plugin.
|
||||
* Because its user frequently trigger some weird behaviors but I have no idea about the detail of them.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#if defined(YYCC_FEAT_ICONV)
|
||||
|
||||
#include "../macro/endian_detector.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include <cerrno>
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
@@ -202,11 +203,14 @@ namespace yycc::encoding::iconv {
|
||||
// If we use UTF16 or UTF32 code name directly, it will produce a BOM at data head.
|
||||
// That's not what we expected.
|
||||
// So we need manually check runtime endian and explicitly specify endian in code name.
|
||||
//
|
||||
// Also, at the same time, iconv on macOS do not support "WCHAR_T" encoding,
|
||||
// so we need manually set it as UTF32 encoding.
|
||||
// See https://developer.apple.com/forums/thread/811508 for more infos.
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
constexpr auto UTF8_CODENAME_LITERAL = "UTF-8"sv;
|
||||
constexpr auto WCHAR_CODENAME_LITERAL = "WCHAR_T"sv;
|
||||
constexpr auto UTF16_CODENAME_LITERAL =
|
||||
#if defined(YYCC_ENDIAN_LITTLE)
|
||||
"UTF-16LE"sv;
|
||||
@@ -219,6 +223,13 @@ namespace yycc::encoding::iconv {
|
||||
#else
|
||||
"UTF-32BE"sv;
|
||||
#endif
|
||||
constexpr auto WCHAR_CODENAME_LITERAL =
|
||||
#if defined(YYCC_OS_MACOS)
|
||||
UTF32_CODENAME_LITERAL;
|
||||
static_assert(sizeof(wchar_t) == sizeof(char32_t), "unexpected wchar_t size");
|
||||
#else
|
||||
"WCHAR_T"sv;
|
||||
#endif
|
||||
|
||||
// TODO:
|
||||
// There is a memory copy in this function. Consider optimizing it in future.
|
||||
|
||||
126
src/yycc/env.cpp
126
src/yycc/env.cpp
@@ -1,39 +1,42 @@
|
||||
#include "env.hpp"
|
||||
#include "macro/os_detector.hpp"
|
||||
#include "string/reinterpret.hpp"
|
||||
#include "num/safe_op.hpp"
|
||||
#include "num/safe_cast.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <stdexcept>
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Windows headers
|
||||
#include "encoding/windows.hpp"
|
||||
#include "windows/winfct.hpp"
|
||||
#include <memory> // For safely free gotten environment variable and commandline argument.
|
||||
#include <type_traits> // Used by std::unique_ptr to get underlying pointer type.
|
||||
#include "windows/import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include <winbase.h>
|
||||
#include <processenv.h> // For getting environment variables and commandline argument.
|
||||
#include <shellapi.h> // For getting commandline argument.
|
||||
#include "windows/import_guard_tail.hpp"
|
||||
#elif defined(YYCC_OS_LINUX)
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#else
|
||||
// POSIX headers
|
||||
#include "string/reinterpret.hpp"
|
||||
#include <cstdlib> // For POSIX environment variable operations.
|
||||
#include <cerrno> // For POSIX errno.
|
||||
#include <stdexcept> // For re-throw unexpected POSIX errno as STL exception.
|
||||
#if defined(YYCC_OS_LINUX)
|
||||
// Linux-only headers
|
||||
#include <fstream> // For reading commandline argument.
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h> // For reading symlink target.
|
||||
#elif defined(YYCC_OS_MACOS)
|
||||
#include <cstdlib>
|
||||
#include <cerrno>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
// macOS-only headers
|
||||
#include <mach-o/dyld.h> // For getting current exe path.
|
||||
#include <crt_externs.h> // For getting commandline argument.
|
||||
#else
|
||||
#error "Not supported OS"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Extern POSIX environment variables list.
|
||||
#if !defined(YYCC_OS_WINDOWS)
|
||||
extern char** environ;
|
||||
#endif
|
||||
|
||||
#define SAFECAST ::yycc::num::safe_cast
|
||||
#define SAFEOP ::yycc::num::safe_op
|
||||
@@ -159,6 +162,7 @@ namespace yycc::env {
|
||||
}
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
class EnvironmentStringsDeleter {
|
||||
public:
|
||||
EnvironmentStringsDeleter() {}
|
||||
@@ -169,6 +173,7 @@ namespace yycc::env {
|
||||
}
|
||||
};
|
||||
using SmartEnvironmentStrings = std::unique_ptr<std::remove_pointer_t<LPWCH>, EnvironmentStringsDeleter>;
|
||||
|
||||
#endif
|
||||
|
||||
VarResult<std::vector<VarPair>> get_vars() {
|
||||
@@ -241,84 +246,6 @@ namespace yycc::env {
|
||||
return std::filesystem::current_path();
|
||||
}
|
||||
|
||||
#if defined(YYCC_OS_LINUX)
|
||||
|
||||
/// @brief All possible error occurs when reading Linux symlink target.
|
||||
enum class ReadSymlinkTargetError {
|
||||
SysCall, ///< Fail to call system call.
|
||||
Truncated, ///< Expected path to target was truncated.
|
||||
Others, ///< Any other errors
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An utility function for convenient reading symlink target on Linux.
|
||||
* @param[in] link The path to symlink where the target is read.
|
||||
* @return The read path to to target, or error occurs.
|
||||
*/
|
||||
static std::expected<std::u8string, ReadSymlinkTargetError> read_symlink_target(const std::string_view &link) {
|
||||
// Reference: https://www.man7.org/linux/man-pages/man2/readlink.2.html
|
||||
|
||||
// String view is not NUL terminated.
|
||||
// Create an string container for it.
|
||||
std::string path(link);
|
||||
|
||||
// Get the expected size.
|
||||
// Query this symlink info first.
|
||||
struct stat sb;
|
||||
if (lstat(path.c_str(), &sb) != 0) {
|
||||
return std::unexpected(ReadSymlinkTargetError::SysCall);
|
||||
}
|
||||
// Fetch the size of target path in gotten struct.
|
||||
// And cast it into expected type.
|
||||
auto expected_size = SAFECAST::try_to<size_t>(sb.st_size);
|
||||
if (!expected_size.has_value()) {
|
||||
return std::unexpected(ReadSymlinkTargetError::Others);
|
||||
}
|
||||
auto buf_size = expected_size.value();
|
||||
// Some magic symlinks under (for example) /proc and /sys report 'st_size' as zero.
|
||||
// In that case, take PATH_MAX as a "good enough" estimate.
|
||||
if (buf_size == 0) {
|
||||
buf_size = PATH_MAX;
|
||||
}
|
||||
|
||||
// Prepare return value and allocate it with previous gotten size.
|
||||
std::u8string rv(u8'\0', buf_size);
|
||||
|
||||
// Copy data into result value.
|
||||
// Add one to the link size, so that we can determine whether
|
||||
// the buffer returned by readlink() was truncated.
|
||||
// Also, due to the add operation, we need do overflow checks.
|
||||
auto passed_size = SAFEOP::checked_add<size_t>(buf_size, 1);
|
||||
if (!passed_size.has_value()) {
|
||||
return std::unexpected(ReadSymlinkTargetError::Others);
|
||||
}
|
||||
// Read data into result value.
|
||||
ssize_t nbytes = readlink(path.c_str(), REINTERPRET::as_ordinary(rv.data()), passed_size.value());
|
||||
if (nbytes < 0) {
|
||||
return std::unexpected(ReadSymlinkTargetError::SysCall);
|
||||
}
|
||||
|
||||
// Check written size
|
||||
// Cast it type into expected type.
|
||||
auto written_size = SAFECAST::try_to<size_t>(nbytes);
|
||||
if (!written_size.has_value()) {
|
||||
return std::unexpected(ReadSymlinkTargetError::Others);
|
||||
}
|
||||
// If the return value was equal to the buffer size, then
|
||||
// the link target was larger than expected (perhaps because the
|
||||
// target was changed between the call to lstat() and the call to
|
||||
// readlink()). Return error instead.
|
||||
if (written_size.value() != buf_size) {
|
||||
// TODO: There must be a better solution to this truncated issue than simply return error.
|
||||
return std::unexpected(ReadSymlinkTargetError::Truncated);
|
||||
}
|
||||
|
||||
// Everything is okey
|
||||
return rv;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
PathResult<std::filesystem::path> current_exe() {
|
||||
std::u8string rv;
|
||||
|
||||
@@ -328,9 +255,10 @@ namespace yycc::env {
|
||||
else return std::unexpected(PathError::SysCall);
|
||||
#elif defined(YYCC_OS_LINUX)
|
||||
// Reference: https://www.man7.org/linux/man-pages/man5/proc_pid_exe.5.html
|
||||
auto target = read_symlink_target("/proc/self/exe");
|
||||
if (target.has_value()) return rv = std::move(target.value());
|
||||
else return std::unexpected(PathError::SysCall);
|
||||
std::error_code ec;
|
||||
auto target = std::filesystem::read_symlink(std::filesystem::path("/proc/self/exe"), ec);
|
||||
if (ec) return std::unexpected(PathError::SysCall);
|
||||
else rv = REINTERPRET::as_utf8(target.string());
|
||||
#elif defined(YYCC_OS_MACOS)
|
||||
// TODO: This is AI generated and don't have test and reference.
|
||||
std::string buffer(PATH_MAX, '\0');
|
||||
@@ -437,10 +365,10 @@ namespace yycc::env {
|
||||
while (true) {
|
||||
// We use NUL as delimiter
|
||||
std::getline(cmdline, arg, '\0');
|
||||
// Check whether we reach EOF
|
||||
if (cmdline.eof()) break;
|
||||
// Check whether reading is okey.
|
||||
if (!cmdline.good()) return std::unexpected(ArgError::Others);
|
||||
// If return string is empty, it means that we reach the tail.
|
||||
if (arg.empty()) break;
|
||||
if (cmdline.fail()) return std::unexpected(ArgError::Others);
|
||||
|
||||
// Push this argument into result.
|
||||
rv.emplace_back(REINTERPRET::as_utf8(arg));
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <charconv>
|
||||
#include <stdexcept>
|
||||
#include <expected>
|
||||
#include <charconv>
|
||||
|
||||
#if defined(YYCC_STL_CLANGSTL)
|
||||
#include "../patch/libcxx/charconv.hpp"
|
||||
#endif
|
||||
|
||||
#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret
|
||||
#define NS_YYCC_STRING_OP ::yycc::string::op
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
#include <charconv>
|
||||
#include <stdexcept>
|
||||
#include <charconv>
|
||||
|
||||
#if defined(YYCC_STL_CLANGSTL)
|
||||
#include "../patch/libcxx/charconv.hpp"
|
||||
#endif
|
||||
|
||||
#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
|
||||
446
src/yycc/patch/libcxx/charconv.hpp
Normal file
446
src/yycc/patch/libcxx/charconv.hpp
Normal file
@@ -0,0 +1,446 @@
|
||||
#pragma once
|
||||
#include "../../macro/stl_detector.hpp"
|
||||
|
||||
//#if defined(YYCC_STL_CLANGSTL)
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @file This is the polyfill for LLVM libcxx charconv header
|
||||
* which including \c std::from_chars and \c std::to_chars utilities.
|
||||
* These 2 functions are only \b fully implemented in the latest Clang (20+)
|
||||
* and partially implemented in the latest Apple Clang (Xcode 16).
|
||||
* This should be removed once both Clang official and Apple Clang libcxx \b fully provide them.
|
||||
* This polyfill is generated by AI.
|
||||
*/
|
||||
|
||||
#include <charconv>
|
||||
#include <system_error>
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
#include <type_traits>
|
||||
#include <concepts>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
|
||||
namespace std {
|
||||
|
||||
#if !defined(YYCC_CHARCONV_HAS_CHARS_FORMAT)
|
||||
|
||||
enum class chars_format : unsigned int {
|
||||
scientific = 1,
|
||||
fixed = 2,
|
||||
hex = 4,
|
||||
general = fixed | scientific // This should be 6 (fixed|scientific)
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_RESULT)
|
||||
|
||||
struct from_chars_result {
|
||||
const char* ptr;
|
||||
std::errc ec;
|
||||
|
||||
friend bool operator==(const from_chars_result&, const from_chars_result&) = default;
|
||||
constexpr explicit operator bool() const noexcept { return ec == std::errc{}; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_RESULT)
|
||||
|
||||
struct to_chars_result {
|
||||
char* ptr;
|
||||
std::errc ec;
|
||||
|
||||
friend bool operator==(const to_chars_result&, const to_chars_result&) = default;
|
||||
constexpr explicit operator bool() const noexcept { return ec == std::errc{}; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_INT)
|
||||
|
||||
/// @private
|
||||
enum class __integral_type {
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
};
|
||||
|
||||
/// @private
|
||||
enum class __integral_base_type {
|
||||
base8,
|
||||
base10,
|
||||
base16,
|
||||
};
|
||||
|
||||
/// @private
|
||||
template<typename T>
|
||||
constexpr __integral_type __classify_int_type() {
|
||||
if constexpr (std::is_same_v<T, std::uint8_t>) {
|
||||
return __integral_type::u8;
|
||||
} else if constexpr (std::is_same_v<T, std::uint16_t>) {
|
||||
return __integral_type::u16;
|
||||
} else if constexpr (std::is_same_v<T, std::uint32_t>) {
|
||||
return __integral_type::u32;
|
||||
} else if constexpr (std::is_same_v<T, std::uint64_t>) {
|
||||
return __integral_type::u64;
|
||||
} else if constexpr (std::is_same_v<T, std::int8_t>) {
|
||||
return __integral_type::i8;
|
||||
} else if constexpr (std::is_same_v<T, std::int16_t>) {
|
||||
return __integral_type::i16;
|
||||
} else if constexpr (std::is_same_v<T, std::int32_t>) {
|
||||
return __integral_type::i32;
|
||||
} else if constexpr (std::is_same_v<T, std::int64_t>) {
|
||||
return __integral_type::i64;
|
||||
} else {
|
||||
static_cast(false, "Unsupported integral type");
|
||||
}
|
||||
}
|
||||
|
||||
/// @private
|
||||
std::optional<__integral_base_type> __classify_int_base(int base) {
|
||||
if (base == 8) {
|
||||
return __integral_base_type::base8;
|
||||
} else if (base == 10) {
|
||||
return __integral_base_type::base10;
|
||||
} else if (base == 16) {
|
||||
return __integral_base_type::base16;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/// @private
|
||||
/// @brief Helper to get printf format specifier based on type and base
|
||||
template<__integral_type EIntTy>
|
||||
const char* __get_int_format(__integral_base_type base) {
|
||||
if (base == __integral_base_type::base8) {
|
||||
if constexpr (EIntTy == __integral_type::u8) {
|
||||
return "%" PRIo8;
|
||||
} else if constexpr (EIntTy == __integral_type::u16) {
|
||||
return "%" PRIo16;
|
||||
} else if constexpr (EIntTy == __integral_type::u32) {
|
||||
return "%" PRIo32;
|
||||
} else if constexpr (EIntTy == __integral_type::u64) {
|
||||
return "%" PRIo64;
|
||||
} else {
|
||||
static_assert(false, "Unsupported integral type and base");
|
||||
}
|
||||
} else if (base == __integral_base_type::base10) {
|
||||
if constexpr (EIntTy == __integral_type::u8) {
|
||||
return "%" PRIu8;
|
||||
} else if constexpr (EIntTy == __integral_type::u16) {
|
||||
return "%" PRIu16;
|
||||
} else if constexpr (EIntTy == __integral_type::u32) {
|
||||
return "%" PRIu32;
|
||||
} else if constexpr (EIntTy == __integral_type::u64) {
|
||||
return "%" PRIu64;
|
||||
} else if constexpr (EIntTy == __integral_type::i8) {
|
||||
return "%" PRId8;
|
||||
} else if constexpr (EIntTy == __integral_type::i16) {
|
||||
return "%" PRId16;
|
||||
} else if constexpr (EIntTy == __integral_type::i32) {
|
||||
return "%" PRId32;
|
||||
} else if constexpr (EIntTy == __integral_type::i64) {
|
||||
return "%" PRId64;
|
||||
}
|
||||
} else if (base == __integral_base_type::base16) {
|
||||
if constexpr (EIntTy == __integral_type::u8) {
|
||||
return "%" PRIx8;
|
||||
} else if constexpr (EIntTy == __integral_type::u16) {
|
||||
return "%" PRIx16;
|
||||
} else if constexpr (EIntTy == __integral_type::u32) {
|
||||
return "%" PRIx32;
|
||||
} else if constexpr (EIntTy == __integral_type::u64) {
|
||||
return "%" PRIx64;
|
||||
} else {
|
||||
static_assert(false, "Unsupported integral type and base");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Integer to_chars
|
||||
template<typename T>
|
||||
requires std::integral<T> && (!std::same_as<T, bool>)
|
||||
to_chars_result to_chars(char* first, char* last, T value, int base = 10) {
|
||||
if (first >= last) {
|
||||
return {first, std::errc::value_too_large};
|
||||
}
|
||||
|
||||
constexpr auto integral_type = __classify_int_type<T>();
|
||||
const auto opt_integral_base_type = __classify_int_base(base);
|
||||
if (!opt_integral_base_type.has_value()) {
|
||||
return {first, std::errc::invalid_argument};
|
||||
}
|
||||
const auto integral_base_type = std::move(opt_integral_base_type.value());
|
||||
|
||||
// Use snprintf with appropriate format
|
||||
const auto max_buffer_size = static_cast<size_t>(last - first);
|
||||
const char* format_string = __get_int_format<integral_type>(integral_base_type);
|
||||
int written = std::snprintf(first, max_buffer_size, format_string, value);
|
||||
if (written < 0 || static_cast<size_t>(written) >= max_buffer_size) {
|
||||
return {last, std::errc::value_too_large};
|
||||
}
|
||||
return {first + written, std::errc{}};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(YYCC_CHARCONV_HAS_TO_CHARS_FLOAT)
|
||||
|
||||
/// @private
|
||||
enum class __float_type {
|
||||
f32,
|
||||
f64,
|
||||
};
|
||||
|
||||
/// @private
|
||||
template<typename T>
|
||||
constexpr __float_type __classify_float_type() {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
return __float_type::f32;
|
||||
} else if constexpr (std::is_same_v<T, double>) {
|
||||
return __float_type::f64;
|
||||
} else {
|
||||
static_assert(false, "Unsupported floating point type");
|
||||
}
|
||||
}
|
||||
|
||||
/// @private
|
||||
enum class __float_fmt_type { general, scientific, fixed, hex };
|
||||
|
||||
/// @private
|
||||
std::optional<__float_fmt_type> __classify_float_fmt(chars_format fmt) {
|
||||
if (fmt == chars_format::general) {
|
||||
return __float_fmt_type::general;
|
||||
} else if (fmt == chars_format::scientific) {
|
||||
return __float_fmt_type::scientific;
|
||||
} else if (fmt == chars_format::fixed) {
|
||||
return __float_fmt_type::fixed;
|
||||
} else if (fmt == chars_format::hex) {
|
||||
return __float_fmt_type::hex;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/// @private
|
||||
template<__float_type TFpTy>
|
||||
std::optional<const char*> __get_float_format(chars_format fmt) {
|
||||
// Precision is passed by extra argument via ".*" format.
|
||||
if (fmt == chars_format::general) {
|
||||
if constexpr (TFpTy == __float_type::f32) {
|
||||
return "%.*g";
|
||||
} else if constexpr (TFpTy == __float_type::f64) {
|
||||
return "%.*lg";
|
||||
}
|
||||
} else if (fmt == chars_format::scientific) {
|
||||
if constexpr (TFpTy == __float_type::f32) {
|
||||
return "%.*e";
|
||||
} else if constexpr (TFpTy == __float_type::f64) {
|
||||
return "%.*le";
|
||||
}
|
||||
} else if (fmt == chars_format::fixed) {
|
||||
if constexpr (TFpTy == __float_type::f32) {
|
||||
return "%.*f";
|
||||
} else if constexpr (TFpTy == __float_type::f64) {
|
||||
return "%.*lf";
|
||||
}
|
||||
} else if (fmt == chars_format::hex) {
|
||||
if constexpr (TFpTy == __float_type::f32) {
|
||||
return "%.*a";
|
||||
} else if constexpr (TFpTy == __float_type::f64) {
|
||||
return "%.*la";
|
||||
}
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Float to_chars
|
||||
template<typename T>
|
||||
requires std::floating_point<T>
|
||||
to_chars_result to_chars(char* first, char* last, T value, chars_format fmt, int precision = 6) {
|
||||
if (first >= last) {
|
||||
return {first, std::errc::value_too_large};
|
||||
}
|
||||
|
||||
constexpr auto float_type = __classify_float_type<T>();
|
||||
const auto opt_float_fmt_type = __classify_float_fmt(fmt);
|
||||
if (!opt_float_fmt_type.has_value()) {
|
||||
return {first, std::errc::invalid_argument};
|
||||
}
|
||||
const auto float_fmt_type = std::move(opt_float_fmt_type.value());
|
||||
|
||||
const auto max_buffer_size = static_cast<size_t>(last - first);
|
||||
const char* format_string = __get_float_format<float_type>(float_fmt_type);
|
||||
int written = std::snprintf(first, max_buffer_size, format_string, precision, value);
|
||||
if (written < 0 || static_cast<size_t>(written) >= max_buffer_size) {
|
||||
return {last, std::errc::value_too_large};
|
||||
}
|
||||
return {first + written, std::errc{}};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_INT)
|
||||
|
||||
/// @private
|
||||
enum class __strtoi_cluster { tol, toll, toul, toull };
|
||||
|
||||
/// @private
|
||||
template<typename T>
|
||||
constexpr __strtoi_cluster __classify_strtoi_cluster() {
|
||||
if constexpr (std::is_signed_v<T>) {
|
||||
if constexpr (sizeof(T) <= sizeof(long)) {
|
||||
return __strtoi_cluster::tol;
|
||||
} else if constexpr (sizeof(T) <= sizeof(long long)) {
|
||||
return __strtoi_cluster::toll;
|
||||
} else {
|
||||
static_assert(false, "Unsupported signed integral type");
|
||||
}
|
||||
} else {
|
||||
if constexpr (sizeof(T) <= sizeof(unsigned long)) {
|
||||
return __strtoi_cluster::toul;
|
||||
} else if constexpr (sizeof(T) <= sizeof(unsigned long long)) {
|
||||
return __strtoi_cluster::toull;
|
||||
} else {
|
||||
static_assert(false, "Unsupported unsigned integral type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @private
|
||||
template<__strtoi_cluster TFc>
|
||||
auto __execute_strtoi(const char* str, char** str_end, int base) {
|
||||
if constexpr (TFc == __strtoi_cluster::tol) {
|
||||
return std::strtol(str, str_end, base);
|
||||
} else if constexpr (TFc == __strtoi_cluster::toll) {
|
||||
return std::strtoll(str, str_end, base);
|
||||
} else if constexpr (TFc == __strtoi_cluster::toul) {
|
||||
return std::strtoul(str, str_end, base);
|
||||
} else if constexpr (TFc == __strtoi_cluster::toull) {
|
||||
return std::strtoull(str, str_end, base);
|
||||
}
|
||||
}
|
||||
|
||||
// Integer from_chars
|
||||
template<typename T>
|
||||
requires std::integral<T> && (!std::same_as<T, bool>)
|
||||
from_chars_result from_chars(const char* first, const char* last, T& value, int base = 10) {
|
||||
if (first >= last) {
|
||||
return {first, std::errc::invalid_argument};
|
||||
}
|
||||
|
||||
// strtoi function cluster strongly order that given string must be NULL-terminated.
|
||||
// So we must do a heavy copy in there because first-last pair is not NULL-terminated guaranteed.
|
||||
std::string buffer(first, static_cast<size_t>(last - first));
|
||||
|
||||
constexpr auto strtoi_cluster = __classify_strtoi_cluster<T>();
|
||||
|
||||
errno = 0;
|
||||
char* end_ptr = const_cast<char*>(first);
|
||||
auto result = __execute_strtoi<strtoi_cluster>(buffer.data(), &end_ptr, base);
|
||||
if (errno == ERANGE) {
|
||||
return {end_ptr, std::errc::result_out_of_range};
|
||||
}
|
||||
|
||||
using strtoi_cluster_rvtype = decltype(result);
|
||||
// Check if result fits in T
|
||||
if (result < static_cast<strtoi_cluster_rvtype>(std::numeric_limits<T>::min())
|
||||
|| result > static_cast<strtoi_cluster_rvtype>(std::numeric_limits<T>::max())) {
|
||||
return {end_ptr, std::errc::result_out_of_range};
|
||||
}
|
||||
|
||||
if (end_ptr == buffer.data()) {
|
||||
return {first, std::errc::invalid_argument};
|
||||
}
|
||||
|
||||
// Ensure we don't go past 'last'
|
||||
if ((end_ptr - buffer.data()) > (last - first)) {
|
||||
return {const_cast<char*>(last), std::errc::invalid_argument};
|
||||
}
|
||||
|
||||
value = static_cast<T>(result);
|
||||
return {first + (end_ptr - buffer.data()), std::errc{}};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT)
|
||||
|
||||
/// @private
|
||||
enum class __strtof_cluster { tof, tod };
|
||||
|
||||
/// @private
|
||||
template<typename T>
|
||||
constexpr __strtof_cluster __classify_strtof_cluster() {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
return __strtof_cluster::tof;
|
||||
} else if constexpr (std::is_same_v<T, double>) {
|
||||
return __strtof_cluster::tod;
|
||||
} else {
|
||||
static_assert(false, "Unsupported floating point type");
|
||||
}
|
||||
}
|
||||
|
||||
/// @private
|
||||
template<__strtof_cluster TFc>
|
||||
auto __execute_strtof(const char* str, char** str_end) {
|
||||
if constexpr (TFc == __strtof_cluster::tof) {
|
||||
return std::strtof(str, str_end);
|
||||
} else if constexpr (TFc == __strtof_cluster::tod) {
|
||||
return std::strtod(str, str_end);
|
||||
}
|
||||
}
|
||||
|
||||
// Float from_chars
|
||||
template<typename T>
|
||||
requires std::floating_point<T>
|
||||
from_chars_result from_chars(const char* first, const char* last, T& value, chars_format fmt = chars_format::general) {
|
||||
// We ignore "fmt" by design.
|
||||
if (first >= last) {
|
||||
return {first, std::errc::invalid_argument};
|
||||
}
|
||||
|
||||
// strtof function cluster strongly order that given string must be NULL-terminated.
|
||||
// So we must do a heavy copy in there because first-last pair is not NULL-terminated guaranteed.
|
||||
std::string buffer(first, static_cast<size_t>(last - first));
|
||||
|
||||
constexpr auto strtof_cluster = __classify_strtof_cluster<T>();
|
||||
|
||||
errno = 0;
|
||||
char* end_ptr = const_cast<char*>(first);
|
||||
auto result = __execute_strtof<strtof_cluster>(buffer.data(), &end_ptr);
|
||||
if (errno == ERANGE) {
|
||||
return {end_ptr, std::errc::result_out_of_range};
|
||||
}
|
||||
|
||||
if (end_ptr == buffer.data()) {
|
||||
return {first, std::errc::invalid_argument};
|
||||
}
|
||||
|
||||
// Ensure we don't go past 'last'
|
||||
if ((end_ptr - buffer.data()) > (last - first)) {
|
||||
return {const_cast<char*>(last), std::errc::invalid_argument};
|
||||
}
|
||||
|
||||
value = result;
|
||||
return {first + (end_ptr - buffer.data()), std::errc{}};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace std
|
||||
|
||||
//#endif
|
||||
@@ -97,7 +97,7 @@ namespace yycc::string::op {
|
||||
}
|
||||
}
|
||||
|
||||
std::u8string replace(const std::u8string_view& _strl, const std::u8string_view& _from_strl, const std::u8string_view& _to_strl) {
|
||||
std::u8string to_replace(const std::u8string_view& _strl, const std::u8string_view& _from_strl, const std::u8string_view& _to_strl) {
|
||||
// prepare result
|
||||
std::u8string strl(_strl);
|
||||
replace(strl, _from_strl, _to_strl);
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace yycc::string::op {
|
||||
* @param[in] _to_strl The \e new string.
|
||||
* @return The result of replacement.
|
||||
*/
|
||||
std::u8string replace(const std::u8string_view& _strl, const std::u8string_view& _from_strl, const std::u8string_view& _to_strl);
|
||||
std::u8string to_replace(const std::u8string_view& _strl, const std::u8string_view& _from_strl, const std::u8string_view& _to_strl);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* @brief The namespace providing Windows universal dialog features.
|
||||
* @details
|
||||
* This namespace only available on Windows platform.
|
||||
* See also \ref dialog_helper.
|
||||
* See also \ref windows__dialog.
|
||||
*/
|
||||
namespace yycc::windows::dialog {
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace yycc::windows::dialog {
|
||||
* @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.
|
||||
* For how to use this struct, see \ref windows__dialog.
|
||||
*/
|
||||
class FileDialog {
|
||||
public:
|
||||
|
||||
@@ -49,7 +49,6 @@ namespace yycc::windows::winfct {
|
||||
* @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);
|
||||
@@ -101,10 +100,9 @@ namespace yycc::windows::winfct {
|
||||
};
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @brief Get the path to known directory in Windows.
|
||||
* @param[in] path_type The type of known directory.
|
||||
* @return The result type containing either fetched path, or error occurs.
|
||||
*/
|
||||
WinFctResult<std::u8string> get_known_path(KnownDirectory path_type);
|
||||
|
||||
|
||||
@@ -61,6 +61,11 @@ PRIVATE
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
# Install binary
|
||||
install(TARGETS YYCCTest
|
||||
RUNTIME DESTINATION ${YYCC_INSTALL_BIN_PATH}
|
||||
)
|
||||
|
||||
# Discover all test
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(YYCCTest)
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace yyccshared::literals {
|
||||
// UNICODE Test Strings
|
||||
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||
|
||||
#define UNICODE_STR_BLANK ""
|
||||
#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"
|
||||
@@ -30,6 +31,7 @@ namespace yyccshared::literals {
|
||||
#define UNICODE_STR_EMOJI "\U0001F363 \u2716 \U0001F37A" // sushi x beer mug
|
||||
|
||||
static std::vector<std::u8string> UTFLIT_U8STR_VEC{
|
||||
U8_LITERAL(UNICODE_STR_BLANK),
|
||||
U8_LITERAL(UNICODE_STR_JAPAN),
|
||||
U8_LITERAL(UNICODE_STR_CHINA),
|
||||
U8_LITERAL(UNICODE_STR_KOREA),
|
||||
@@ -44,6 +46,7 @@ namespace yyccshared::literals {
|
||||
U8_LITERAL(UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::wstring> UTFLIT_WSTR_VEC{
|
||||
WSTR_LITERAL(UNICODE_STR_BLANK),
|
||||
WSTR_LITERAL(UNICODE_STR_JAPAN),
|
||||
WSTR_LITERAL(UNICODE_STR_CHINA),
|
||||
WSTR_LITERAL(UNICODE_STR_KOREA),
|
||||
@@ -58,6 +61,7 @@ namespace yyccshared::literals {
|
||||
WSTR_LITERAL(UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u16string> UTFLIT_U16STR_VEC{
|
||||
U16_LITERAL(UNICODE_STR_BLANK),
|
||||
U16_LITERAL(UNICODE_STR_JAPAN),
|
||||
U16_LITERAL(UNICODE_STR_CHINA),
|
||||
U16_LITERAL(UNICODE_STR_KOREA),
|
||||
@@ -72,6 +76,7 @@ namespace yyccshared::literals {
|
||||
U16_LITERAL(UNICODE_STR_EMOJI),
|
||||
};
|
||||
static std::vector<std::u32string> UTFLIT_U32STR_VEC{
|
||||
U32_LITERAL(UNICODE_STR_BLANK),
|
||||
U32_LITERAL(UNICODE_STR_JAPAN),
|
||||
U32_LITERAL(UNICODE_STR_CHINA),
|
||||
U32_LITERAL(UNICODE_STR_KOREA),
|
||||
@@ -157,12 +162,22 @@ namespace yyccshared::literals {
|
||||
|
||||
#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"}};
|
||||
static std::vector<OtherLiteral> OTHERLIT_OTHERSTR_VEC{
|
||||
{"", UINT32_C(437), "ASCII", u8"ascii"},
|
||||
{"\xC4\xE3\xBA\xC3\xD6\xD0\xB9\xFA", UINT32_C(936), "GBK", u8"gbk"},
|
||||
};
|
||||
|
||||
#define OTHER_STR_BLANK ""
|
||||
#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)};
|
||||
static std::vector<std::u8string> OTHERLIT_U8STR_VEC{
|
||||
U8_LITERAL(OTHER_STR_BLANK),
|
||||
U8_LITERAL(OTHER_STR_GBK),
|
||||
};
|
||||
static std::vector<std::wstring> OTHERLIT_WSTR_VEC{
|
||||
WSTR_LITERAL(OTHER_STR_BLANK),
|
||||
WSTR_LITERAL(OTHER_STR_GBK),
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
@@ -72,10 +72,10 @@ namespace yycctest::carton::clap {
|
||||
|
||||
// Add options
|
||||
auto options = CLAP::option::OptionCollection();
|
||||
int_option = options.add_option(CLAP::option::Option(u8"i", u8"int", u8"<integer>", u8"integral argument"));
|
||||
float_option = options.add_option(CLAP::option::Option(u8"f", std::nullopt, u8"<float>", u8""));
|
||||
string_option = options.add_option(CLAP::option::Option(std::nullopt, u8"string", u8"<string>", u8""));
|
||||
clamped_float_option = options.add_option(CLAP::option::Option(std::nullopt, u8"clamped-float", u8"<float>", u8""));
|
||||
int_option = options.add_option(CLAP::option::Option(u8"i", u8"int", u8"integer", u8"integral argument"));
|
||||
float_option = options.add_option(CLAP::option::Option(u8"f", std::nullopt, u8"float", u8""));
|
||||
string_option = options.add_option(CLAP::option::Option(std::nullopt, u8"string", u8"string", u8""));
|
||||
clamped_float_option = options.add_option(CLAP::option::Option(std::nullopt, u8"clamped-float", u8"float", u8""));
|
||||
novalue_option = options.add_option(CLAP::option::Option(u8"b", std::nullopt, std::nullopt, u8""));
|
||||
|
||||
// Add variables
|
||||
|
||||
@@ -25,32 +25,32 @@ namespace yycctest::string::op {
|
||||
TEST(StringOp, Replace) {
|
||||
// Normal case
|
||||
{
|
||||
auto rv = OP::replace(u8"aabbcc", u8"bb", u8"dd");
|
||||
auto rv = OP::to_replace(u8"aabbcc", u8"bb", u8"dd");
|
||||
EXPECT_EQ(rv, u8"aaddcc");
|
||||
}
|
||||
// No matched expected string
|
||||
{
|
||||
auto rv = OP::replace(u8"aabbcc", u8"zz", u8"yy");
|
||||
auto rv = OP::to_replace(u8"aabbcc", u8"zz", u8"yy");
|
||||
EXPECT_EQ(rv, u8"aabbcc");
|
||||
}
|
||||
// Empty expected string
|
||||
{
|
||||
auto rv = OP::replace(u8"aabbcc", std::u8string_view(), u8"zz");
|
||||
auto rv = OP::to_replace(u8"aabbcc", std::u8string_view(), u8"zz");
|
||||
EXPECT_EQ(rv, u8"aabbcc");
|
||||
}
|
||||
// Empty replace string
|
||||
{
|
||||
auto rv = OP::replace(u8"aaaabbaa", u8"aa", u8"");
|
||||
auto rv = OP::to_replace(u8"aaaabbaa", u8"aa", u8"");
|
||||
EXPECT_EQ(rv, u8"bb");
|
||||
}
|
||||
// Nested replacing
|
||||
{
|
||||
auto rv = OP::replace(u8"aaxcc", u8"x", u8"yx");
|
||||
auto rv = OP::to_replace(u8"aaxcc", u8"x", u8"yx");
|
||||
EXPECT_EQ(rv, u8"aayxcc");
|
||||
}
|
||||
// Empty source string
|
||||
{
|
||||
auto rv = OP::replace(std::u8string_view(), u8"", u8"xy");
|
||||
auto rv = OP::to_replace(std::u8string_view(), u8"", u8"xy");
|
||||
EXPECT_EQ(rv, u8"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ 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());
|
||||
// Set colorful console should always be success.
|
||||
// Unless it is not a TTY.
|
||||
EXPECT_TRUE(rv.has_value() || rv.error() == CONSOLE::ExecError::NotTty);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user