1
0

1 Commits

Author SHA1 Message Date
7234b31ee0 temp 2025-08-20 21:59:51 +08:00
233 changed files with 7034 additions and 12917 deletions

View File

@@ -1,4 +0,0 @@
# GitHub Action Scripts
These script files are only used for GitHub Action.
These script files should only be executed in their root directory respectively.

View File

@@ -1,17 +0,0 @@
#!/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 ..

View File

@@ -1,17 +0,0 @@
#!/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 ..

View File

@@ -1,17 +0,0 @@
@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 ..

View File

@@ -1,17 +0,0 @@
#!/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 ..

View File

@@ -1,17 +0,0 @@
#!/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 ..

View File

@@ -1,17 +0,0 @@
@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 ..

View File

@@ -1,19 +0,0 @@
#!/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 ..

View File

@@ -1,19 +0,0 @@
#!/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 ..

View File

@@ -1,18 +0,0 @@
@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 ..

View File

@@ -1,66 +0,0 @@
name: YYCC Linux Build
on: [workflow_dispatch]
jobs:
linux-build:
runs-on: ubuntu-latest
steps:
- 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: 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: |
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:
name: YYCC-linux-build
path: bin/install/*
retention-days: 30

View File

@@ -1,61 +0,0 @@
name: YYCC macOS Build
on: [workflow_dispatch]
jobs:
macos-build:
runs-on: macos-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- 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: |
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:
name: YYCC-macos-build
path: bin/install/*
retention-days: 30

35
.github/workflows/nightly.yml.disabled vendored Normal file
View File

@@ -0,0 +1,35 @@
name: YYCC Nightly Build
on:
workflow_dispatch:
push:
branches:
- master
jobs:
msvc-build:
strategy:
matrix:
vs: ['2019']
msvc_arch: ['x86']
runs-on: windows-2019
steps:
- name: Fetching Repository
uses: actions/checkout@v3
- name: Building YYCC
shell: cmd
run: |
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 }}
.\script\build.bat
- name: Uploading Nightly Build
uses: actions/upload-artifact@v3
with:
name: YYCC-windows-nightly
path: bin/install/*
retention-days: 30

View File

@@ -1,78 +0,0 @@
name: YYCC Windows Build
on: [workflow_dispatch]
jobs:
windows-build:
strategy:
matrix:
include:
- vs: '2022'
msvc_arch: 'x64'
runs-on: windows-2022
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- 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 }}
:: 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:
name: YYCC-windows-build
path: bin/install/*
retention-days: 30

1
.gitignore vendored
View File

@@ -3,7 +3,6 @@
out/
build/
install/
extern/
# Ignore CMake generated stuff
src/yycc/version.hpp

View File

@@ -10,8 +10,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Provide options
option(YYCC_BUILD_TEST "Build test of YYCCommonplace." OFF)
option(YYCC_BUILD_BENCHMARK "Build benchmark of YYCCommonplace." OFF)
option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF)
option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF)
option(YYCC_ENFORCE_ICONV "Enforce iconv support for this library (e.g. in MSYS2 environment)." OFF)
@@ -26,36 +25,22 @@ 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)
# GTest is required if we build testbench
if (YYCC_BUILD_TESTBENCH)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
find_package(GTest REQUIRED)
endif ()
# Google Benchmark is required if we build benchmark
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)
endif ()
# Import 4 build targets
# Import 3 build targets
add_subdirectory(src)
if (YYCC_BUILD_TEST)
add_subdirectory(test)
endif ()
if (YYCC_BUILD_BENCHMARK)
add_subdirectory(benchmark)
if (YYCC_BUILD_TESTBENCH)
add_subdirectory(testbench)
endif ()
if (YYCC_BUILD_DOC)
add_subdirectory(doc)

View File

@@ -2,9 +2,6 @@
## Choose Version
This manual is only suit for the version equal or newer than YYCC 2.0.
For old version, please checkout to corresponding tag and browse how to build them.
We suggest that you only use stable version (tagged commit).
The latest commit always present current works.
It means that it is not stable and work in progress.
@@ -14,71 +11,40 @@ It means that it is not stable and work in progress.
* CMake 3.23 at least.
* The common compiler supporting C++ 23 (GCC / Clang / MSVC).
* Iconv (Optional on Windows. Required on other systems).
* [Google Test](https://github.com/google/googletest) (Required if you build test).
* [Google Benchmark](https://github.com/google/benchmark) (Required if you build benchmark).
* [GoogleTest](https://github.com/google/googletest) (Required if you build testbench).
* Doxygen (Required if you build documentation).
* Python and Astral UV (Required if you use "User Build" method)
If you are just want to build this project to make something works, or build other project, rather than code with it,
you commonly do not need build test, benchmark and documentation.
So you actually do not need Google Test, Google Benchmark and Doxygen.
> [!WARNING]
> You may face some issues when building on macOS with Clang. That's not your fault.
> Clang used libc++ library lacks some essential features used by this project.
> You may try other solutions for compiling this project on macOS or with Clang.
## Preparing
### Compiler
### GoogleTest
> [!WARNING]
> 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
Google Test is required if you need to build test.
GoogleTest is required if you need to build testbench.
If you don't need this please skip this chapter.
We use Google Test v1.17.0.
We use GoogleTest v1.17.0.
It would be okey use other versions but I have not test on them.
> [!WARNING]
> When building this project, you may face link error with Google Test, especially on Linux.
> When building this project, you may face link error with GoogleTest, especially on Linux.
> This issue is caused by that the binary provided by your package manager is built in C++17 and its ABI is incompatible with C++23.
> See this [GitHub Issue](https://github.com/google/googletest/issues/4591) for more infomation.
> The solution is that download Google Test source code and build it in C++23 on your own.
> The solution is that download GoogleTest source code and build it in C++23 on your own.
> Following content tell you how to do this.
There are the steps instructing you how to compile Google Test manually.
There are the steps instructing you how to compile GoogleTest manually.
1. Download Google Test source code with given version in GitHub Release page.
1. Download GoogleTest source code with given version in GitHub Release page.
1. Extract it into a directory.
1. Enter this directory and create 2 subdirectory `build` and `install` for CMake build and install respectively.
1. Enter `build` directory and configure CMake with extra `-DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON` parameters.
1. Use CMake to build Google Test
1. Use CMake to install Google Test into previous we created `install` directory.
### Google Benchmark
Google Benchmark is required if you need to build benchmark.
If you don't need this please skip this chapter.
We use Google Benchmark v1.9.4.
It would be okey use other versions but I have not test on them.
There are the steps instructing you how to compile Google Benchmark manually.
1. Download Google Benchmark source code with given version in GitHub Release page.
1. Extract it into a directory.
1. Enter this directory and create link named `googletest` to previous fetched Google Test root directory. This is instructed by official manual because Google Benchmark rely on Google Test. Link can be create by executing `mklink /D googletest <path-to-googletest-root-dir>` on Windows or `ln -s <path-to-googletest-root-dir> googletest` on POSIX-like OS.
1. Keep stay in this directory and create 2 subdirectory `build` and `install` for CMake build and install respectively.
1. Enter `build` directory and configure CMake with extra `-DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF` parameters.
1. Use CMake to build Google Benchmark
1. Use CMake to install Google Benchmark into previous we created `install` directory.
1. Use CMake to build GoogleTest
1. Use CMake to install GoogleTest into previous we created `install` directory.
### Iconv
@@ -105,81 +71,36 @@ So before compiling, you must make sure `doxygen` are presented in your environm
## Build and Install
There are 2 different ways to build this project.
If you are the user of this project (just want this project to make something works, or build other projects), please choose "User Build".
If you are the user of this project (just want this project to make something work), please choose "User Build".
If you are a developer (developer of this project, or use this project as dependency to develop your project), please choose "Developer Build".
### User Build
"User Build" is basically how GitHub Action build this project.
Under **the root directory** of this project, execute:
We use Python 3.11 and UV 0.7.17 to manage our build script generator.
It would be okey use other versions but I have not test on them.
- `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.
TODO...
### Developer Build
#### Configurable Variables
TODO...
First, there is a list listing all variables you may configure during compiling.
There is a list listing all variables you may configure during compiling.
* `YYCC_BUILD_TEST`: Set it to `ON` to build test. `OFF` in default.
* `YYCC_BUILD_TESTBENCH`: Set it to `ON` to build testbench. `OFF` in default.
It is useful for the developer of this project.
It also suit for the user who has runtime issues on their platforms to check whether this project works as expected.
If you are debugging this project to find bug, I suggest that you build this project under Debug mode and use this test project for debugging.
* `YYCC_BUILD_BENCHMARK`: Set it to `ON` to build benchmark. `OFF` in default.
It is useful for the developer of this project to checking the performace for those homemade functions.
It is highly suggested build this project with Release mode to have real benchmark result.
* `YYCC_BUILD_DOC`: Set it to `ON` to build documentation. `OFF` in default.
It may be useful for the developer who firstly use this project in their own projects.
Please note that generated documentation is different in different platforms.
* `YYCC_ENFORCE_ICONV`: Set it to `ON` to enable Iconv feature forcely. `OFF` in default.
The usage of this option has been introduced in previous "Iconv" chapter.
* `GTest_ROOT`: Set to the install path of Google Test
if you have enable `YYCC_BUILD_TEST` and want to use your personal built Google Test.
* `benchmark_ROOT`: Set to the install path of Google Benchmark
if you have enable `YYCC_BUILD_BENCHMARK` and want to use your personal built Google Benchmark.
* `Iconv_ROOT`: The assistant variable for finding Iconv which is exposed by CMake.
You usually do not need set it up.
* `GTest_ROOT`: TODO
* `Iconv_ROOT`: TODO
* `CMAKE_CXX_STANDARD`: Set C++ standard version of project.
`23` in default and this version can not be lower than C++23.
You usually do not need change this.
* `CMAKE_POSITION_INDEPENDENT_CODE`: Set it to `True` to enable PIC.
This is essential for those project which use this project and produce dynamicing library as final artifact.
#### Configure CMake
When configure CMake, you may use different options on different platforms.
Following list may help you.
- On Windows:
* `-A Win32` or `-A x64` to specify architecture.
- On Linux or other POSIX systems:
* `-DCMAKE_BUILD_TYPE=Debug` or `-DCMAKE_BUILD_TYPE=Release` to specify build type.
Additionally, you can attach any variables introduced above with `-D` option during CMake configurations.
#### Build with CMake
After configuration, you can use `cmake --build .` to build project,
with additional options on different platforms.
Following list may help you.
- On Windows:
* `--config Debug` or `--config Release` to specify build type.
- On Linux or other POSIX systems:
* None
#### Install with CMake
After building, you can use `cmake --install . --prefix <path-to-prefix>`
to install project into given path, with additional options on different platforms.
Following list may help you.
- On Windows:
* `--config Debug` or `--config Release` to specify build type.
- On Linux or other POSIX systems:
* None

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2024-2026 yyc12345
Copyright (c) 2024-2024 yyc12345
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,33 +0,0 @@
# Create executable benchmark
add_executable(YYCCBenchmark "")
# Setup test sources
target_sources(YYCCBenchmark
PRIVATE
main.cpp
yycc/string/op.cpp
yycc/carton/fft.cpp
)
# target_sources(YYCCBenchmark
# PRIVATE
# FILE_SET HEADERS
# FILES
# shared/literals.hpp
# )
# Setup headers
target_include_directories(YYCCBenchmark
PUBLIC
"${CMAKE_CURRENT_LIST_DIR}"
)
# Setup libraries
target_link_libraries(YYCCBenchmark
PRIVATE
YYCCommonplace
benchmark::benchmark
)
# Install binary
install(TARGETS YYCCBenchmark
RUNTIME DESTINATION ${YYCC_INSTALL_BIN_PATH}
)

View File

@@ -1,3 +0,0 @@
#include <benchmark/benchmark.h>
BENCHMARK_MAIN();

View File

@@ -1,40 +0,0 @@
#include <benchmark/benchmark.h>
#include <yycc.hpp>
#include <yycc/carton/fft.hpp>
#include <random>
#include <chrono>
#define FFT ::yycc::carton::fft
namespace yyccbench::carton::fft {
using TIndex = size_t;
using TFloat = float;
using TComplex = std::complex<TFloat>;
template<TIndex N>
using TFft = FFT::Fft<TIndex, TFloat, N>;
constexpr TIndex FFT_POINTS = 1024u;
static void BM_FftCompute(benchmark::State& state) {
// prepare random buffer
constexpr TIndex RND_BUF_CNT = 8u;
std::random_device rnd_device;
std::default_random_engine rnd_engine(rnd_device());
std::uniform_real_distribution<TFloat> rnd_dist(0.0f, 1.0f);
std::vector<std::vector<TComplex>> buffer_collection(RND_BUF_CNT);
for (auto& buf : buffer_collection) {
buf.resize(FFT_POINTS);
std::generate(buf.begin(), buf.end(), [&rnd_engine, &rnd_dist]() mutable -> TComplex { return TComplex(rnd_dist(rnd_engine)); });
}
// prepare FFT engine
TFft<FFT_POINTS> fft;
// do benchmark
for (auto _ : state) {
fft.compute(buffer_collection[state.iterations() % RND_BUF_CNT].data());
}
}
BENCHMARK(BM_FftCompute)->Name("FftCompute");
}

View File

@@ -1,28 +0,0 @@
#include <benchmark/benchmark.h>
#include <yycc.hpp>
#include <yycc/string/op.hpp>
#define OP ::yycc::string::op
using namespace std::literals::string_view_literals;
namespace yyccbench::string::op {
static void BM_StringStrip(benchmark::State& state) {
std::u8string_view strl = u8" \thello\r\n"sv, words = u8" \t\r\n"sv;
for (auto _ : state) {
auto rv = OP::strip(strl, words);
benchmark::DoNotOptimize(rv);
}
}
BENCHMARK(BM_StringStrip)->Name("StringStrip");
static void BM_StringTrim(benchmark::State& state) {
std::u8string_view strl = u8" \thello\r\n"sv, words = u8" \t\r\n"sv;
for (auto _ : state) {
auto rv = OP::trim(strl, words);
benchmark::DoNotOptimize(rv);
}
}
BENCHMARK(BM_StringTrim)->Name("StringTrim");
}

View File

@@ -1,12 +1,7 @@
@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")
check_required_components(YYCCommonplace)
check_required_components(YYCCommonplace)

View File

@@ -1,30 +0,0 @@
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}")

View File

@@ -1,8 +0,0 @@
#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;
}

View File

@@ -1,16 +0,0 @@
#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);
}
}

View File

@@ -1,41 +0,0 @@
#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);
}
}

View File

@@ -1,9 +0,0 @@
#include <charconv>
#include <system_error>
int main(int argc, char **argv) {
std::from_chars_result result {
.ptr = nullptr,
.ec = std::errc{},
};
}

View File

@@ -1,16 +0,0 @@
#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);
}
}

View File

@@ -1,41 +0,0 @@
#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);
}
}

View File

@@ -1,9 +0,0 @@
#include <charconv>
#include <system_error>
int main(int argc, char **argv) {
std::to_chars_result result {
.ptr = nullptr,
.ec = std::errc{},
};
}

View File

@@ -1,36 +1,19 @@
# 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 Doxygen config file
configure_file(
${CMAKE_CURRENT_LIST_DIR}/Doxyfile.in
${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
@ONLY
)
# 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
add_custom_target (YYCCDocumentation
Doxygen::doxygen "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/Doxyfile"
doxygen ${CMAKE_CURRENT_BINARY_DIR}/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}
)

View File

@@ -1031,7 +1031,7 @@ EXCLUDE_SYMBOLS =
# that contain example code fragments that are included (see the \include
# command).
EXAMPLE_PATH = @CMAKE_CURRENT_LIST_DIR@/../test
EXAMPLE_PATH = @CMAKE_CURRENT_LIST_DIR@/../testbench
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
@@ -2306,7 +2306,7 @@ PERLMOD_MAKEVAR_PREFIX =
# C-preprocessor directives found in the sources and include files.
# The default value is: YES.
ENABLE_PREPROCESSING = YES
ENABLE_PREPROCESSING = NO
# 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_MACRO_GENERATOR_EXPRESSIONS@
PREDEFINED = YYCC_DOXYGEN
# 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

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

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

View File

@@ -1,208 +0,0 @@
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
*/
}

View File

@@ -1,187 +0,0 @@
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.
*/
}

View File

@@ -1,74 +0,0 @@
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.
*/
}

View File

@@ -1,93 +0,0 @@
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.
*/
}

View File

@@ -1,102 +0,0 @@
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
*/
}

View File

@@ -1,202 +0,0 @@
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.
*/
}

View File

@@ -1,107 +0,0 @@
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
*/
}

View File

@@ -1,121 +0,0 @@
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.
*/
}

View File

@@ -1,103 +0,0 @@
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
*/
}

35
doc/src/com_helper.dox Normal file
View File

@@ -0,0 +1,35 @@
namespace YYCC::COMHelper {
/**
\page com_helper COM Helper
This namespace is Windows specific.
It will be invisible on other platforms.
This namespace is used by internal functions as intended.
They should not be used outside of this library.
But if you compel to use them, it is also okey.
\section com_helper__memory_safe_ptr Memory Safe Pointer Types
This namespace also provided various memory-safe types for interacting with COM functions.
Although Microsoft also has similar smart pointer called \c CComPtr.
But this library is eager to hide all Microsoft-related functions calling.
Using \c CComPtr is not corresponding with the philosophy of this library.
So these standard library based smart pointer types were created.
\section com_helper__com_guard COM Guard
This namespace contain a COM Guard which make sure COM was initialized in current module when loading current module.
It is essential because all calling to COM functions should be under the premise that COM has been initialized.
This guard also will uninitialize COM when unloading this module.
There is only an exposed function called #IsInitialized for user calling.
This function will check whether COM environment is initialized.
If you want YYCC automatically initialize COM environment for you,
you must call this function in your program at least one time.
Otherwise COM Guard code may be unavailable,
because compiler may think they are not essential code and drop them.
*/
}

151
doc/src/config_manager.dox Normal file
View File

@@ -0,0 +1,151 @@
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.
*/
}

181
doc/src/console_helper.dox Normal file
View File

@@ -0,0 +1,181 @@
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.
*/
}

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

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

View File

@@ -1,7 +1,7 @@
namespace yycc::windows::dialog {
namespace YYCC::DialogHelper {
/**
\page windows__dialog Dialog Helper
\page dialog_helper Dialog Helper
Picking files and folders is an important and essential operation under Windows.
However the functions picking files and folders are so complex.
@@ -11,7 +11,7 @@ In following contents we will tell you how to call them.
This helper is Windows specific.
It will be totally invisible if you are in other platforms.
\section windows__dialog__file_dialog Configure File Dialog
\section dialog_helper__file_dialog Configure File Dialog
The first thing is that we should initialize FileDialog,
and configure it according to your requirements.
@@ -20,16 +20,16 @@ This class is the data struct representing all aspects of file dialog.
It also one of the arguments in final dialog function.
\code
FileDialog params;
params.set_owner(owner_getter());
params.set_title(u8"My File Picker");
params.set_init_file_name(u8"test.txt");
params.set_init_directory(initial_directory_getter());
YYCC::DialogHelper::FileDialog params;
params.SetOwner(owner_getter());
params.SetTitle(YYCC_U8("My File Picker"));
params.SetInitFileName(YYCC_U8("test.txt"));
params.SetInitDirectory(initial_directory_getter());
\endcode
\subsection windows__dialog__file_dialog__owner Owner
\subsection dialog_helper__file_dialog__owner Owner
FileDialog::set_owner() will set owner of this dialog.
FileDialog::SetOwner will set owner of this dialog.
It accepts a Microsoft defined \c HWND as argument which should be familiar with Windows programmer.
If you pass \c NULL to it or skip calling this function, it indicate that there is no owner of this dialog.
<I>
@@ -37,9 +37,9 @@ I don't what will happen if there is no owner for it.
But it would be better to have an owner if possible.
</I>
\subsection windows__dialog__file_dialog__title Title
\subsection dialog_helper__file_dialog__title Title
FileDialog::set_title() will set dialog title of this dialog.
FileDialog::SetTitle will set dialog title of this dialog.
If you pass \c nullptr or skip calling it,
the title of dialog will be filled by system and the function type you calling.
For example, the title will be "Open..." if you call open file function,
@@ -48,9 +48,9 @@ At the same time, the language of this title filled by system is system UI depen
It means that you do not need to do any extra I18N work for it.
So I suggest you do not set title except you really want to modify title.
\subsection windows__dialog__file_dialog__init_file_name Initial File Name
\subsection dialog_helper__file_dialog__init_file_name Initial File Name
FileDialog::set_init_file_name() will set the initial file name presented in dialog file name input box.
FileDialog::SetInitFileName will set the initial file name presented in dialog file name input box.
If you pass \c nullptr or skip calling it, the text in dialog file name input box will be empty.
User can modify the name presented in input box later.
@@ -58,9 +58,9 @@ But if you assign this value, the dialog will lose the ability that remember the
In normal case, dialog will try remembering the file name user input in dialog, and represent it in the next calling.
However, if you specify this field, the dialog will always presented your specified value in every calling.
\subsection windows__dialog__file_dialog__init_directory Initial Directory
\subsection dialog_helper__file_dialog__init_directory Initial Directory
FileDialog::set_init_directory() will set the initial directory (startup directory) when opening dialog.
FileDialog::SetInitDirectory will set the initial directory (startup directory) when opening dialog.
In following cases, initial directory will fall back to system behavior:
@@ -72,7 +72,7 @@ The system default behavior of initial directory is similar with initial file na
The dialog will try remembering the last directory you just entering, and will back into it in the next calling.
The directory we meeting in the first launch is system defined.
\section windows__dialog__file_filters Configure File Filters
\section dialog_helper__file_filters Configure File Filters
File filters is a drop down list represented in file dialog which allow user filter files by their extensions.
It is beneficial to let user get the file which they want in a directory including massive different files.
@@ -84,20 +84,20 @@ Directory can not be filtered.
FileFilters takes responsibility for this feature:
\code
auto& filters = params.configure_file_types();
filters.add_filter(u8"Microsoft Word (*.docx; *.doc)", { u8"*.docx", u8"*.doc" });
filters.add_filter(u8"Microsoft Excel (*.xlsx; *.xls)", { u8"*.xlsx", u8"*.xls" });
filters.add_filter(u8"Microsoft PowerPoint (*.pptx; *.ppt)", { u8"*.pptx", u8"*.ppt" });
filters.add_filter(u8"Text File (*.txt)", { u8"*.txt" });
filters.add_filter(u8"All Files (*.*)", { u8"*.*" });
params.set_default_file_type_index(0u);
auto& filters = params.ConfigreFileTypes();
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
params.SetDefaultFileTypeIndex(0u);
\endcode
\subsection windows__dialog__file_filters__setup File Filters
\subsection dialog_helper__file_filters__setup File Filters
We don't need to initialize FileFilters by ourselves.
Oppositely, we fetch it from FileDialog instance by calling FileDialog::configure_file_types().
After fetching, we can call FileFilters::add_filter() to add a filter pair for file filters.
Oppositely, we fetch it from FileDialog instance by calling FileDialog::ConfigreFileTypes.
After fetching, we can call FileFilters::Add to add a filter pair for file filters.
The first argument is the display text which user will see in file filter drop down box.
@@ -107,51 +107,55 @@ It is okey to use multiple wildcard string in list.
This is suit for those file types involving multiple file extensions, such as the old and new file types of Microsoft Office as we illustracted.
Empty list not allowed
FileFilters::add_filter() throws std::invalid_argument if filter name is blank or filter patterns is empty.
Because these errors should be found during developing.
FileFilters::Add also will return a bool to indicate the success of this adding.
It should at least has one file filter in file dialog.
I don't know the consequence if you don't provide any file filter.
\subsection windows__dialog__file_filters__default_filter Default File Type
\subsection dialog_helper__file_filters__default_filter Default File Type
FileDialog::set_default_file_type_index() will set the default selected file filter of this dialog.
FileDialog::SetDefaultFileTypeIndex will set the default selected file filter of this dialog.
It accepts an index pointing to the file filter which you want to show in default for this file dialog.
The index of file filters is the order where you call FileFilters::add_filter() above.
The index of file filters is the order where you call FileFilters::Add above.
If you pass \c NULL to it or skip calling this function, the first one will be default.
\section windows__dialog__result Create Dialog and Get Result
\section dialog_helper__result Create Dialog and Get Result
Finally, we can call file dialog functions by we initialized FileDialog
\code
auto result1 = open_file(params);
auto result2 = open_files(params);
auto result3 = save_file(params);
auto result4 = open_folder(params);
YYCC::yycc_u8string single_selection;
std::vector<YYCC::yycc_u8string> multiple_selection;
YYCC::DialogHelper::OpenFileDialog(params, single_selection);
YYCC::DialogHelper::OpenMultipleFileDialog(params, multiple_selection);
YYCC::DialogHelper::SaveFileDialog(params, single_selection);
YYCC::DialogHelper::OpenFolderDialog(params, single_selection);
\endcode
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 #OpenFileDialog: Open single file
\li #OpenMultipleFileDialog: Open multiple files
\li #SaveFileDialog: Save single file
\li #OpenFolderDialog: Open single directory
\subsection windows__dialog__result__arguments Arguments
\subsection dialog_helper__result__arguments Arguments
Among these 4 functions, the only argument is the reference to FileDialog.
Function will use it to decide what would be shown in this file dialog.
Among these 4 functions, the first argument always is the reference to FileDialog.
Function will use it to decide what should be shown in this file dialog.
\subsection windows__dialog__result__return_value Return Value
The second argument always is the reference to the container receiving the result.
For single selection, the return type is \c yycc_u8string.
For multiple selection, the return type is a list of strings: \c std::vector<yycc_u8string>.
Please note these 4 functions will return a dialog specified result type as their return value.
If this result type is an error, it means that an error occurred during execution.
Otherwise, there is an optional value inside this result type.
If user click Cancel button, this optional value will be empty.
otherwise, this optional value will hold user selected a file or directory.
\subsection dialog_helper__result__return_value Return Value
\section windows__dialog__notes Notes
Please note among these 4 functions will return a bool as its return value to indicate the success of function.
If they return false, it means that the execution of functions are failed or user click Cancel button.
In this case, there is no guaranteen to the content of second argument (the real return value).
\section dialog_helper__notes Notes
You may notice there are various classes which we never introduce.
Because they are intermediate classes and should not be used by programmer.

View File

@@ -1,166 +0,0 @@
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
*/
}

View File

@@ -1,98 +0,0 @@
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
*/
}

View File

@@ -1,191 +0,0 @@
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
*/
}

148
doc/src/encoding_helper.dox Normal file
View File

@@ -0,0 +1,148 @@
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.
*/
}

View File

@@ -1,37 +1,35 @@
namespace yycc::cenum {
namespace YYCC::EnumHelper {
/**
\page cenum Scoped Enum Helper
\page enum_helper Scoped Enum Helper
\section cenum__intro Intro
\section enum_helper__intro Intro
C++ introduce a new enum called scoped enum.
It is better than legacy C enum because it will not leak name into namespace where it locate,
and also can specify an underlying type to it to make sure it is stored as specified size.
However, the shortcoming of it is that it lack bitwise operator comparing with legacy C enum.
Programmer must implement them for scoped enum one by one but it is a hardship and inconvenient.
This is the reason why I invent this class.
And this is the reason why I call this module "cenum"
because it gives scoped enum type with the same abilities of legacy C enum.
Programmer must implement them for scoped enum one by one.
It is a hardship and inconvenient.
This is the reason why I invent this class
\section cenum__Usage Usage
\section enum_helper__Usage Usage
In this namespace, we provide all bitwise functions related to scoped enum type which may be used.
See yycc::cenum for more detail (It is more clear to read function annotation than I introduce in there repeatedly).
See YYCC::EnumHelper for more detail (It is more clear to read function annotation than I introduce in there repeatedly).
\section cenum__why Why not Operator Overload Way
\section enum_helper__why Why not Operator Overload
I have try it (and you even can see the relic of it in source code).
But it need a extra statement written in following to include it, otherwise compiler can not see it.
\code
using namespace yycc::cenum;
using namespace YYCC::EnumHelper;
\endcode
The last and most important reason why I do not use this method is that
Another reason why I do not use this method is that
this overload strategy may be applied to some type which should not be applied by accient, such as non-scoped enum type.
So I gave up this solution.
It is much better that order user explicitly specify when to use them.
*/
}

View File

@@ -1,85 +0,0 @@
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
*/
}

View File

@@ -1,30 +1,30 @@
namespace yycc::carton::ironpad {
namespace YYCC::ExceptionHelper {
/**
\page ironpad Unhandled Exception Handler
\page exception_helper Unhandled Exception Handler
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.
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.
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 still be available on other operating systems but all of its functions are do nothing.
It will be invisible on other platforms.
\section ironpad__usage Usage
\section exception_helper__usage Usage
\subsection ironpad__usage__code Register Code
\subsection exception_helper__usage__code Register Code
In most scenarios, programmer only need call #startup when program started or module loaded.
And call #shutdown when program exited or module unloaded.
In most scenarios, programmer only need call #Register when program started or module loaded.
And call #Unregister 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 #startup as a callback.
Optionally, you can provide a function pointer during calling #Register as a callback.
The prototype of this function pointer is #ExceptionCallback.
This callback will be called if any unhandled exception happened.
It provides 2 pathes to log file and core dump file respectively.
@@ -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 ironpad__notes__singleton design.
due to \ref exception_helper__notes__singleton design.
\subsection ironpad__usage__location Location
\subsection exception_helper__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\%\\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>
\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>
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\%\\IronPad</TT> is the dedicated directory for this module.
So you may see the generated logs and dumps in it.
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.
\subsection ironpad__usage__last_remedy Last Remedy
\subsection exception_helper__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 ironpad__notes Notes
\section exception_helper__notes Notes
\subsection ironpad__notes__thread_safe Thread Safe
\subsection exception_helper__notes__thread_safe Thread Safe
All exposed functions in this namespace are thread safe.
The implementation uses \c std::mutex to ensure this.
All exposed functions in YYCC::ExceptionHelper are thread safe.
The implementation uses \c std:mutex to ensure this.
\subsection ironpad__notes__singleton Singleton Handler
\subsection exception_helper__notes__singleton Singleton Handler
This namespace also have a mechanism that make sure the same unhandled exception handler implementation only appear once in the same process.
YYCC::ExceptionHelper 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 #startup,
Although both A.exe and B.dll call #Register,
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 #startup are loaded.
no matter how many modules calling #Register 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 #startup only run once in the same process.
to make sure #Register only run once in the same process.
It is very like the implementation of singleton application.
\subsection ironpad__notes__recursive_calling Recursive Calling
\subsection exception_helper__notes__recursive_calling Recursive Calling
The implementation of unhandled exception handler may also will throw exception.
This will cause infinite recursive calling.
This namespace has internal mechanism to prevent this bad case.
YYCC::ExceptionHelper has internal mechanism to prevent this bad case.
If this really happened, the handler will quit silent and will not cause any issue.
Programmer don't need to worry about this.
\subsection ironpad__notes__user_callback The Timing of User Callback
\subsection exception_helper__notes__user_callback The Timing of User Callback
The timing of calling user callback is the tail of unhandled exception handler.
It means that all log and coredump have been written if possible before calling callback.

View File

@@ -10,7 +10,7 @@
<TD><CENTER>
<B>YYCCommonplace Programming Manual</B>
Copyright 2024-2026 by yyc12345.
Copyright 2024 by yyc12345.
</CENTER></TD>
</TR>
</TABLE>
@@ -25,80 +25,58 @@
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<B>Overviews</B>
<B>General Features</B>
\li \subpage intro
\li \subpage premise_and_principle
<B>STL Enhancements</B>
<!--
\li \subpage library_macros
\li \subpage macro
\li \subpage library_encoding
\li \subpage cenum
\li \subpage encoding_helper
\li \subpage string__reinterpret
\li \subpage string_helper
\li \subpage string__op
\li \subpage parser_helper
\li \subpage num__parser
\li \subpage console_helper
\li \subpage num__op
\li \subpage io_helper
\li \subpage num__safe_cast
\li \subpage std_patch
\li \subpage num__safe_op
\li \subpage enum_helper
-->
\li \subpage patch
<B>Advanced Features</B>
\li \subpage env
<!--
\li \subpage constraints
\li \subpage rust
\li \subpage config_manager
<B>Text Encoding</B>
\li \subpage encoding__stl
\li \subpage encoding__windows
\li \subpage encoding__iconv
\li \subpage pycodec
\li \subpage arg_parser
-->
</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
<!--
\li \subpage win_import
\li \subpage windows__com
\li \subpage com_helper
\li \subpage windows__dialog
\li \subpage dialog_helper
\li \subpage windows__winfct
\li \subpage win_fct_helper
\li \subpage windows__console
\li \subpage exception_helper
-->
</TD>
</TR>

50
doc/src/io_helper.dox Normal file
View File

@@ -0,0 +1,50 @@
namespace YYCC::IOHelper {
/**
\page io_helper IO Helper
Actually, YYCC::IOHelper includes functions which can not be placed in other place.
\section io_helper__ptr_pri_padding Pointer Print Padding
When printing pointer on screen, programmer usually left-pad zero to make it looks good.
However, the count of zero for padding is different in x86 and x64 architecture (8 for x86 and 16 for x64).
Macro \c PRI_XPTR_LEFT_PADDING will help you to resolve this issue.
Macro \c PRI_XPTR_LEFT_PADDING will be defined to following value according to the target system architecture.
\li \c "08": On x86 system.
\li \c "016": On x64 system.
There is an example for how to use it:
\code
void* raw_ptr = blabla();
std::printf(stdout, "Raw Pointer 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR, raw_ptr);
\endcode
Note \c PRIXPTR is defined by standard library for formatting pointer as hexadecimal style.
\section io_helper__smart_file Smart FILE Pointer
#SmartStdFile use \c std::unique_ptr with custom deleter to implement smart \c FILE*.
It is useful in the cases that you want to automatically free opened file when leaving corresponding scope.
\section io_helper__utf8_fopen UTF8 fopen
In Windows, standard \c std::fopen can not handle UTF8 file name in common environment.
So we create this function to give programmer an universal \c fopen in UTF8 style.
In Windows platform, this function will try to convert its argument to \c wchar_t
and calling Microsoft specific \c _wfopen function to open file.
If encoding convertion or \c _wfopen failed, this function will return \c nullptr like \c std::fopen does.
In other platforms, it will simply redirect calling to \c std::fopen.
There is a simple example:
\code
FILE* fs = YYCC::IOHelper::FOpen(YYCC_U8("/path/to/file"), YYCC_U8("rb"));
\endcode
*/
}

View File

@@ -0,0 +1,228 @@
namespace YYCC {
/**
\page library_encoding Library Encoding
Before using this library, you should know the encoding strategy of this library first.
In short words, this library use UTF8 encoding everywhere except some special cases,
for example, function explicitly order the encoding of input parameters.
In following content of this article, you will know the details about how we use UTF8 in this library.
\section library_encoding__utf8_type UTF8 Type
YYCC uses custom UTF8 char type, string container and string view all over the library, from parameters to return value.
Following content will introduce how we define them.
\subsection library_encoding__utf8_type__char_type Char Type
YYCC library has its own UTF8 char type, \c yycc_char8_t.
This is how we define it:
\code
#if defined(__cpp_char8_t)
using yycc_char8_t = char8_t;
#else
using yycc_char8_t = unsigned char;
#endif
\endcode
If your environment (higher or equal to C++ 20) supports \c char8_t provided by standard library, \c yycc_char8_t is just an alias to \c char8_t,
otherwise (lower than C++ 20, e.g. C++ 17), \c yycc_char8_t will be defined as \c unsigned \c char like C++ 20 does (this can be seen as a polyfill).
This means that if you already have used \c char8_t provided by standard library,
you do not need to do any extra modification before using this library.
Because all types are compatible.
\subsection library_encoding__utf8_type__container_type String Container and View
We define string container and string view like this:
\code
using yycc_u8string = std::basic_string<yycc_char8_t>;
using yycc_u8string_view = std::basic_string_view<yycc_char8_t>;
\endcode
The real code written in library may be slightly different with this but they have same meanings.
In \c char8_t environment, they are just the alias to \c std::u8string and \c std::u8string_view respectively.
So if you have already used them, no need to any modification for your code before using this library.
\subsection library_encoding__utf8_type__why Why?
You may curious why I create a new UTF8 char type, rather than using standard library UTF8 char type directly. There are 2 reasons.
First, It was too late that I notice I can use standard library UTF8 char type.
My UTF8 char type has been used in library everywhere and its tough to fully replace them into standard library UTF8 char type.
Second, UTF8 related content of standard library is \e volatile.
I notice standard library change UTF8 related functions frequently and its API are not stable.
For example, standard library brings \c std::codecvt_utf8 in C++ 11, deprecate it in C++ 17 and even remove it in C++ 26.
That's unacceptable! So I create my own UTF8 type to avoid the scenario that standard library remove \c char8_t in future.
\section library_encoding__concept Concepts
In following content, you may be face with 2 words: ordinary string and UTF8 string.
UTF8 string, as its name, is the string encoded with UTF8.
The char type of it must is \c yycc_char8_t.
(equivalent to \c char8_t after C++ 20.)
Ordinary string means the plain, native string.
The result of C++ string literal without any prefix \c "foo bar" is a rdinary string.
The char type of it is \c char.
Its encoding depends on compiler and environment.
(UTF8 in Linux, or system code page in Windows if UTF8 switch was not enabled in MSVC.)
For more infomation, please browse CppReference:
https://en.cppreference.com/w/cpp/language/string_literal
\section library_encoding__utf8_literal UTF8 Literal
String literal is a C++ concept.
If you are not familar with it, please browse related article first, such as CppReference.
\subsection library_encoding__utf8_literal__single Single Literal
In short words, YYCC allow you declare an UTF8 literal like this:
\code
YYCC_U8("This is UTF8 literal.")
\endcode
YYCC_U8 is macro.
You don't need add extra \c u8 prefix in string given to the macro.
This macro will do this automatically.
In detail, this macro do a \c reinterpret_cast to change the type of given argument to \c const \c yycc_char8_t* forcely.
This ensure that declared UTF8 literal is compatible with YYCC UTF8 types.
\subsection library_encoding__utf8_literal__char Single Char
Same as UTF8 literal, YYCC allow you cast normal \c char into \c yycc_char8_t as following code:
\code
YYCC_U8_CHAR('A')
\endcode
YYCC_U8_CHAR is a macro.
It just simply use \c static_cast to cast given value to \c yycc_char8_t.
It doesn't mean that you can cast non-ASCII characters,
because the space these characters occupied usually more than the maximum value of \c char.
For example, following code is \b invalid:
\code
YYCC_U8_CHAR('文') // INVALID!
\endcode
\subsection library_encoding__utf8_literal__concatenation Literal Concatenation
YYCC_U8 macro also works for string literal concatenation:
\code
YYCC_U8("Error code: " PRIu32 ". Please contact me.");
\endcode
According to C++ standard for string literal concatenation,
<I>"If one of the strings has an encoding prefix and the other does not, the one that does not will be considered to have the same encoding prefix as the other."</I>
At the same time, YYCC_U8 macro will automatically add \c u8 prefix for the first component of this string literal concatenation.
So the whole string will be UTF8 literal.
It also order you should \b not add any prefix for other components of this string literal concatenation.
\subsection library_encoding__utf8_literal__why Why?
You may know that C++ standard allows programmer declare an UTF8 literal explicitly by writing code like this:
\code
u8"foo bar"
\endcode
This is okey. But it may incompatible with YYCC UTF8 char type.
According to C++ standard, this UTF8 literal syntax will only return \c const \c char8_t* if your C++ standard higher or equal to C++ 20,
otherwise it will return \c const \c char*.
This behavior cause that you can not assign this UTF8 literal to \c yycc_u8string if you are in the environment which do not support \c char8_t,
because their types are different.
Thereas you can not use the functions provided by this library because they are all use YYCC defined UTF8 char type.
\section library_encoding__utf8_pointer UTF8 String Pointer
String pointer means the raw pointer pointing to a string, such as \c const \c char*, \c char*, \c char32_t* and etc.
Many legacy code assume \c char* is encoded with UTF8 (the exception is Windows). But \c char* is incompatible with \c yycc_char8_t.
YYCC provides YYCC::EncodingHelper::ToUTF8 to resolve this issue. There is an exmaple:
\code
const char* absolutely_is_utf8 = "I confirm this is encoded with UTF8.";
const yycc_char8_t* converted = YYCC::EncodingHelper::ToUTF8(absolutely_is_utf8);
char* mutable_utf8 = const_cast<char*>(absolutely_is_utf8); // This is not safe. Just for example.
yycc_char8_t* mutable_converted = YYCC::EncodingHelper::ToUTF8(mutable_utf8);
\endcode
YYCC::EncodingHelper::ToUTF8 has 2 overloads which can handle constant and mutable stirng pointer convertion respectively.
YYCC also has ability that convert YYCC UTF8 char type to ordinary char type by YYCC::EncodingHelper::ToOrdinary.
Here is an exmaple:
\code
const yycc_char8_t* yycc_utf8 = YYCC_U8("I am UTF8 string.");
const char* converted = YYCC::EncodingHelper::ToOrdinary(yycc_utf8);
yycc_char8_t* mutable_yycc_utf8 = const_cast<char*>(yycc_utf8); // Not safe. Also just for example.
char* mutable_converted = YYCC::EncodingHelper::ToOrdinary(mutable_yycc_utf8);
\endcode
Same as YYCC::EncodingHelper::ToUTF8, YYCC::EncodingHelper::ToOrdinary also has 2 overloads to handle constant and mutable string pointer.
\section library_encoding__utf8_container UTF8 String Container
String container usually means the standard library string container, such as \c std::string, \c std::wstring, \c std::u32string and etc.
In many personal project, programmer may use \c std::string everywhere because \c std::u8string may not be presented when writing peoject.
How to do convertion between ordinary string container and YYCC UTF8 string container?
It is definitely illegal that directly do force convertion. Because they may have different class layout.
Calm down and I will tell you how to do correct convertion.
YYCC provides YYCC::EncodingHelper::ToUTF8 to convert ordinary string container to YYCC UTF8 string container.
There is an exmaple:
\code
std::string ordinary_string("I am UTF8");
yycc_u8string yycc_string = YYCC::EncodingHelper::ToUTF8(ordinary_string);
auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string);
\endcode
Actually, YYCC::EncodingHelper::ToUTF8 accepts a reference to \c std::string_view as argument.
However, there is a implicit convertion from \c std::string to \c std::string_view,
so you can directly pass a \c std::string instance to it.
String view will reduce unnecessary memory copy.
If you just want to pass ordinary string container to function, and this function accepts \c yycc_u8string_view as its argument,
you can use alternative YYCC::EncodingHelper::ToUTF8View.
\code
std::string ordinary_string("I am UTF8");
yycc_u8string_view yycc_string = YYCC::EncodingHelper::ToUTF8View(ordinary_string);
auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string);
\endcode
Comparing with previous one, this example use less memory.
The reduced memory is the content of \c yycc_string because string view is a view, not the copy of original string.
Same as UTF8 string pointer, we also have YYCC::EncodingHelper::ToOrdinary and YYCC::EncodingHelper::ToOrdinaryView do correspondant reverse convertion.
Try to do your own research and figure out how to use them.
It's pretty easy.
\section library_encoding__windows Warnings to Windows Programmer
Due to the legacy of MSVC, the encoding of \c char* may not be UTF8 in most cases.
If you run the convertion code introduced in this article with the string which is not encoded with UTF8, it may cause undefined behavior.
To enable UTF8 mode of MSVC, please deliver \c /utf-8 switch to MSVC.
Thus you can use the functions introduced in this article safely.
Otherwise, you must guarteen that the argument you provided to these functions is encoded by UTF8 manually.
Linux user do not need care this.
Because almost Linux distro use UTF8 in default.
*/
}

122
doc/src/library_macros.dox Normal file
View File

@@ -0,0 +1,122 @@
namespace YYCC {
/**
\page library_macros Library Macros
In this page we will introduce the macros defined by this library
which can not be grouped in other topic.
\section library_macros__batch_class_copy_move Library Version and Version Comparison
Version is a important things in modern software development, especially for a library.
In YYCC, we use Semantic Versioning as our version standard.
For more infomations about it, please see: https://semver.org/
First, YYCC has its own version and it can be visited by
\c YYCC_VER_MAJOR, \c YYCC_VER_MINOR, and \c YYCC_VER_PATCH.
Each part of Semantic Versioning is provided individually.
YYCC also provide a bunch of macros to compare 2 versions.
It also provides a way to check YYCC version in program using YYCC,
because some of them rely on a specific version of YYCC.
There is a list of these comparison macros.
\li YYCC_VERCMP_E
\li YYCC_VERCMP_NE
\li YYCC_VERCMP_G
\li YYCC_VERCMP_GE
\li YYCC_VERCMP_NL
\li YYCC_VERCMP_L
\li YYCC_VERCMP_LE
\li YYCC_VERCMP_NG
You may notice all of these macros are starts with \c YYCC_VERCMP_,
and their tails are inspired from x86 ASM comparison jump code.
For example, \c E means "equal" and \c NE means "not equal",
\c G means "greater", \c GE means "greater or equal", and \c NG means "not gretaer".
All of these macros take 6 arguments,
for the first 3 arguments, we call them "left version".
From left to right they are the major part, minor part and patch part of semantic version.
And for the last 3 arguments, we call them "right version".
From left to right they are the major part, minor part and patch part of semantic version.
There is a example about checking whether YYCC library version is exactly what we wanted version.
\code
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
#error "Not Matched YYCC Version"
#endif
\endcode
\section library_macros__platform_checker Platform Checker
In many cross platform applications,
programmer usually write code adapted to different platforms in one source file
and enable them respectively by macros representing the target platform.
As a cross platform library,
YYCC also has this feature and you can utilize it if you don't have other ways to so the same things.
\subsection library_macros__platform_checker__values Values
YYCC always define a macro called \c YYCC_OS to indicate the system of target platform.
In implementation, it will check following list from top to bottom to set matched value for it.
\li \c YYCC_OS_WINDOWS: Windows environment. It is done by checking whether environment define \c _WIN32 macro.
\li \c YYCC_OS_LINUX: In current implementation, this means target platform is \b NOT Windows.
\subsection library_macros__platform_checker__usage Usage
Now you know any possible value of \c YYCC_OS.
The next step is how to use it to enable specified code in specific target platform.
We take Windows platform for example.
Assume \c blabla() function is Windows specific.
We have following example code:
\code
#if defined(YYCC_OS_WINDOWS)
blabla();
#endif
\endcode
It's enough and simple that use \c \#if to bracket the Windows specified code.
\section library_macros__batch_class_copy_move Batch Class Copy / Move Functions
YYCC provides 6 macros to batchly remove class copy constructor and move constructor,
or set default class copy constructor and move constructor.
<UL>
<LI>
\c YYCC_DEL_CLS_COPY: Declare following 2 statements which delete copy constrcutor and copy assign operator.
<UL>
<LI><TT>CLSNAME(const CLSNAME&) = delete;</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&) = delete;</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DEL_CLS_MOVE: Declare following 2 statements which delete move constrcutor and move assign operator.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) = delete;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) = delete;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DEL_CLS_COPY_MOVE: The combination of \c YYCC_DEL_CLS_COPY and \c YYCC_DEL_CLS_MOVE.</LI>
<LI>
\c YYCC_DEF_CLS_COPY: Declare following 2 statements which set default copy constrcutor and copy assign operator.
<UL>
<LI><TT>CLSNAME(const CLSNAME&) = default;</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&) = default;</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DEF_CLS_MOVE: Declare following 2 statements which set default move constrcutor and move assign operator.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) = default;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) = default;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DEF_CLS_COPY_MOVE: The combination of \c YYCC_DEF_CLS_COPY and \c YYCC_DEF_CLS_MOVE.</LI>
</UL>
*/
}

View File

@@ -1,323 +0,0 @@
namespace yycc::macro {
/**
\page macro Library Macros
In this page we will introduce the macros defined by this library
which can not be grouped in other topic.
\section macro__version Library Version
Version is a important things in modern software development, especially for a library.
In YYCC, we use Semantic Versioning as our version standard.
For more infomations about it, please see: https://semver.org/
First, YYCC has its own version and it can be visited by
\c YYCC_VER_MAJOR, \c YYCC_VER_MINOR, and \c YYCC_VER_PATCH.
Each part of Semantic Versioning is provided individually.
\section macro__version_cmp Version Comparison
YYCC also provide a bunch of macros to compare 2 versions.
It also provides a way to check YYCC version in program using YYCC,
because some of them rely on a specific version of YYCC.
There is a list of these comparison macros.
\li YYCC_VERCMP_E
\li YYCC_VERCMP_NE
\li YYCC_VERCMP_G
\li YYCC_VERCMP_GE
\li YYCC_VERCMP_NL
\li YYCC_VERCMP_L
\li YYCC_VERCMP_LE
\li YYCC_VERCMP_NG
You may notice all of these macros are all start with \c YYCC_VERCMP_,
and their tails are inspired from x86 ASM comparison jump code.
For example, \c E means "equal" and \c NE means "not equal",
\c G means "greater", \c GE means "greater or equal", and \c NG means "not gretaer".
All of these macros take 6 arguments,
for the first 3 arguments, we call them "left version".
From left to right they are the major part, minor part and patch part of semantic version.
And for the last 3 arguments, we call them "right version".
From left to right they are the major part, minor part and patch part of semantic version.
There is a example about checking whether YYCC library version is exactly what we wanted version.
\code
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
#error "Not Matched YYCC Version"
#endif
\endcode
\section macro__copy_move Class Copy / Move Functions
YYCC provides several macros to manage copy and move constructors and assignment operators for classes.
These include macros to delete, default, declare, and implement copy and move operations.
<UL>
<LI>
\c YYCC_DELETE_COPY(CLSNAME): Explicitly remove copy constructor and copy assignment operator for the given class.
<UL>
<LI><TT>CLSNAME(const CLSNAME&) = delete;</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&) = delete;</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DELETE_MOVE(CLSNAME): Explicitly remove move constructor and move assignment operator for the given class.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) noexcept = delete;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) noexcept = delete;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DELETE_COPY_MOVE(CLSNAME): The combination of \c YYCC_DELETE_COPY and \c YYCC_DELETE_MOVE.</LI>
<LI>
\c YYCC_DEFAULT_COPY(CLSNAME): Explicitly set default copy constructor and copy assignment operator for the given class.
<UL>
<LI><TT>CLSNAME(const CLSNAME&) = default;</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&) = default;</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DEFAULT_MOVE(CLSNAME): Explicitly set default move constructor and move assignment operator for the given class.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) noexcept = default;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) noexcept = default;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DEFAULT_COPY_MOVE(CLSNAME): The combination of \c YYCC_DEFAULT_COPY and \c YYCC_DEFAULT_MOVE.</LI>
<LI>
\c YYCC_DECL_COPY(CLSNAME): Make declaration of copy constructor and assignment operator for the given class to avoid typos.
<UL>
<LI><TT>CLSNAME(const CLSNAME&);</TT></LI>
<LI><TT>CLSNAME& operator=(const CLSNAME&);</TT></LI>
</UL>
</LI>
<LI>
\c YYCC_DECL_MOVE(CLSNAME): Make declaration of move constructor and assignment operator for the given class to avoid typos.
<UL>
<LI><TT>CLSNAME(CLSNAME&&) noexcept;</TT></LI>
<LI><TT>CLSNAME& operator=(CLSNAME&&) noexcept;</TT></LI>
</UL>
</LI>
<LI>\c YYCC_DECL_COPY_MOVE(CLSNAME): The combination of \c YYCC_DECL_COPY and \c YYCC_DECL_MOVE.</LI>
<LI>\c YYCC_IMPL_COPY_CTOR(CLSNAME, RHS): Make implementation signature of copy constructor for the given class with the right operand name to avoid typos.</LI>
<LI>\c YYCC_IMPL_COPY_OPER(CLSNAME, RHS): Make implementation signature of copy assignment operator for the given class with the right operand name to avoid typos.</LI>
<LI>\c YYCC_IMPL_MOVE_CTOR(CLSNAME, RHS): Make implementation signature of move constructor for the given class with the right operand name to avoid typos.</LI>
<LI>\c YYCC_IMPL_MOVE_OPER(CLSNAME, RHS): Make implementation signature of move assignment operator for the given class with the right operand name to avoid typos.</LI>
</UL>
Please note that \c YYCC_DECL_ and \c YYCC_IMPL_ should be used together.
These macros are designed to make sure that you write correct function signatures.
There is an example about how to use it.
In HPP file, you can write:
\code
class Foo {
YYCC_DECL_COPY_MOVE(Foo)
};
\endcode
And in corresponding CPP file, you should write:
\code
YYCC_IMPL_COPY_CTOR(Foo, rhs)
{
// Copy members from rhs
}
YYCC_IMPL_COPY_OPER(Foo, rhs)
{
// Copy members from rhs
return *this;
}
YYCC_IMPL_MOVE_CTOR(Foo, rhs)
{
// Move members from rhs
}
YYCC_IMPL_MOVE_OPER(Foo, rhs)
{
// Move members from rhs
return *this;
}
\endcode
\section macro__platform_checker OS Detector
In many cross platform applications,
programmer usually write code adapted to different platforms in one source file
and enable them respectively by macros representing the target platform.
As a cross platform library,
YYCC also has this feature and you can utilize it if you don't have other ways to so the same things.
\subsection macro__platform_checker__macro Macro
YYCC always define <B>one of following macros</B> to indicate the system of target platform.
\li \c YYCC_OS_WINDOWS: Windows environment.
\li \c YYCC_OS_LINUX: Linux environment.
\li \c YYCC_OS_MACOS: macOS environment.
Assume \c blabla() function is Windows specific.
There is an example about how to use it:
\code
#if defined(YYCC_OS_WINDOWS)
// Code specific to Windows
blabla();
#endif
\endcode
\subsection macro__platform_checker__constexpr_function Constexpr Function
Additionally, YYCC also provides a bunch of constexpr functions to check whether the target platform is what we want.
More precisely, os::get_os() function returns an enum value os::OsKind indicating the target platform.
There is an example about how to use it:
\code
if constexpr (os::get_os() == os::OsKind::Windows) {
// Code specific to Windows
blabla();
}
\endcode
\section macro__compiler_detector Compiler Detector
YYCC provides macros and constexpr functions to detect the compiler being used for compilation.
\subsection macro__compiler_detector__macro Macro
YYCC defines <B>one of following macros</B> to indicate which compiler is being used.
\li \c YYCC_CC_MSVC: MSVC compiler (Microsoft Visual C++)
\li \c YYCC_CC_GCC: GCC compiler (GNU Compiler Collection)
\li \c YYCC_CC_CLANG: Clang compiler
There is an example about how to use it:
\code
#if defined(YYCC_CC_MSVC)
// Code specific to MSVC
blabla();
#endif
\endcode
\subsection macro__compiler_detector__constexpr_function Constexpr Function
YYCC also provides a constexpr function to check which compiler is being used at compile time.
More precisely, compiler::get_compiler() function returns an enum value compiler::CompilerKind indicating the compiler being used.
There is an example about how to use it:
\code
if constexpr (compiler::get_compiler() == compiler::CompilerKind::Msvc) {
// Code specific to MSVC
blabla();
}
\endcode
\section macro__endian_detector Endian Detector
YYCC provides macros and constexpr functions to detect the endianness of the target platform.
\subsection macro__endian_detector__macro Macro
YYCC always defines <B>one of following macros</B> to indicate the endianness of the target platform.
\li \c YYCC_ENDIAN_LITTLE: Little endian system
\li \c YYCC_ENDIAN_BIG: Big endian system
There is an example about how to use it:
\code
#if defined(YYCC_ENDIAN_LITTLE)
// Code specific to little endian systems
blabla();
#endif
\endcode
\subsection macro__endian_detector__constexpr_function Constexpr Function
YYCC also provides a constexpr function to check the endianness of the target platform.
More precisely, endian::get_endian() function returns an enum value endian::EndianKind indicating the endianness.
There is an example about how to use it:
\code
if constexpr (endian::get_endian() == endian::EndianKind::Little) {
// Code specific to little endian systems
blabla();
}
\endcode
\section macro__stl_detector STL Detector
YYCC provides macros to detect which Standard Template Library (STL) implementation is being used.
\subsection macro__stl_detector__macro Macro
YYCC defines <B>one of following macros</B> to indicate which STL implementation is being used.
\li \c YYCC_STL_MSSTL: Microsoft STL
\li \c YYCC_STL_GNUSTL: GNU STL
\li \c YYCC_STL_CLANGSTL: Clang STL
There is an example about how to use it:
\code
#if defined(YYCC_STL_MSSTL)
// Code specific to Microsoft STL
blabla();
#endif
\endcode
\subsection macro__stl_detector__constexpr_function Constexpr Function
YYCC also provides a constexpr function to check which STL implementation is being used at compile time.
More precisely, stl::get_stl() function returns an enum value stl::StlKind indicating the STL implementation.
There is an example about how to use it:
\code
if constexpr (stl::get_stl() == stl::StlKind::MsStl) {
// Code specific to Microsoft STL
blabla();
}
\endcode
\section macro__ptr_size_detector Pointer Size Detector
YYCC provides macros and constexpr functions to detect the pointer size of the target platform.
\subsection macro__ptr_size_detector__macro Macro
YYCC always define <B>one of following macros</B> to indicate the pointer size of target platform.
\li \c YYCC_PTRSIZE_32: 32-bit environment
\li \c YYCC_PTRSIZE_64: 64-bit environment
There is an example about how to use it:
\code
#if defined(YYCC_PTRSIZE_32)
// Code specific to 32-bit environment
blabla();
#endif
\endcode
\subsection macro__ptr_size_detector__constexpr_function Constexpr Function
YYCC also provides a constexpr function to check the pointer size of the target platform.
More precisely, ptr_size::get_ptr_size() function returns an enum value ptr_size::PtrSizeKind indicating the pointer size.
There is an example about how to use it:
\code
if constexpr (ptr_size::get_ptr_size() == ptr_size::PtrSizeKind::Bits32) {
// Code specific to 32-bit environment
blabla();
}
\endcode
*/
}

View File

@@ -1,30 +0,0 @@
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
*/
}

View File

@@ -1,70 +0,0 @@
namespace yycc::num::parse {
/**
\page num__parser Numeric Parser
Namespace yycc::num::parse is served for the convertion from string to number.
\section num__parser__supported_types Supported Types
Functions located in this namespace support the convertion from string to following types:
\li Integral types (except \c bool): \c int, \c uint32_t, \c char and etc.
\li Floating point types: \c float, \c double and etc.
\li \c bool
Please note in C++, \c bool is integral type but we list it individually because parser will treat it specially.
For \c bool type, parser will try doing convertion between it and \c "true" \c "false" string.
(\b case-insensitive. It means that \c "true", \c "True" and \c "TRUE", all of them can be converted into \c true.)
\section num__parser__usage Usage
This namespace provide a uniform parser function #parse with various overloads.
All of them accept an UTF8 string view at first argument,
and following argument is different required by different overloads which may change parser behavior.
For example, for floating point type, this function allows caller to specify extra argument providing the format of given number string (\c std::chars_format).
or for integral type, this function allows caller to specify extra argument providing the base of given number string.
The return value is a result type, containing converted value or error occurs.
There are some examples:
\code
auto rv = parse::parse<uint32_t>(u8"123");
assert(rv.has_value());
auto converted = rv.value();
\endcode
\section num__parser__stringify Stringify
Namespace yycc::num::stringify provide the opposite function of namespace yycc::num::parse.
They convert given number into their string representation.
There is an example:
\code
auto showcase = stringify::stringify<uint32_t>(UINT32_C(114));
\endcode
Same as parse::parse, stringify::stringify also has same overloads and different second arguments.
For floating point type, this function allows caller to specify extra arguments
which provides the format (\c std::chars_format) and precision when getting string representation.
For integral type, this function allows caller to specify extra argument
providing the base of number when getting string representation.
However, the result value of stringify::stringify is just the result, not a result type.
Because it is mostly impossible to occur error in stringify::stringify.
\section num__parser__notes Notes
All functions located in yycc::num::parse and yycc::num::stringify namespace are implementated by standard library functions.
These functions just make a good wrapper for complex standard library functions.
And give you a experience like Rust \c parse functions.
Basically, all functions located in this helper have possibility to throw exception.
But this possibility are more close to the possibility that \c new statement throw \c std::bad_alloc.
So in most cases you can assume these functions will not throw any exception.
All functions are template functions.
The argument of template is the type these functions need to be processed.
Although C++ have \e smart template type deduction,
it would be better to specify template argument manually to explicitly specify your desired type.
*/
}

View File

@@ -1,63 +0,0 @@
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
*/
}

View File

@@ -1,86 +0,0 @@
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.
*/
}

88
doc/src/parser_helper.dox Normal file
View File

@@ -0,0 +1,88 @@
namespace YYCC::ParserHelper {
/**
\page parser_helper Parser Helper
This helper is served for the convertion between number and string.
\section parser_helper_supported_types Supported Types
Functions located in this helper support the convertion between string and following types:
\li Integral types (except \c bool): \c int, \c uint32_t, \c char and etc.
\li Floating point types: \c float, \c double and etc.
\li \c bool
Please note in C++, \c bool is integral type but we list it individually because parser will treat it specially.
For \c bool type, parser will try doing convertion between it and \c "true" \c "false" string.
(\b case-insensitive. It means that \c true can be converted from \c "true", \c "True" or \c "TRUE".)
\section parser_helper__try_parse Try Parse
#TryParse will try to parse string into caller specified type.
All of them accept an UTF8 string view at first argument,
require that you provide a container receiving converted result in the second argument,
and return a bool value to indicate whether the convertion is successful.
There are some examples:
\code
uint32_t val;
YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("123"), val);
YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("7fff"), val, 16);
\endcode
For floating point type, this function allows caller to specify extra argument providing the format of given number string (\c std::chars_format).
For integral type, this function allows caller to specify extra argument providing the base of given number string.
\section parser_helper__parse Parse
#Parse is similar to #TryParse.
But it will not return bool value to indicate success and doesn't have the argument receiving result.
It only accepts an UTF8 string view as the only one argument, and return result directly.
If the convertion failed, the return value is \b undefined (but usually is the default value of given type).
There is an example:
\code
uint32_t val = YYCC::ParserHelper::Parse<uint32_t>(YYCC_U8("123"));
\endcode
For integral and floating point value,
it has same extra argument with #TryParse to provide more number infomation.
Using this function is dangerous if the validation of your input is important.
In this case, please use #TryParse instead.
\section parser_helper__to_string To String
#ToString basically is the reversed operation of #Parse.
It gets the string representation of given type.
The only argument of these functions is the type which need to be converted to its string representation.
And they will return yycc_u8string as result.
There is an example:
\code
auto result = YYCC::ParserHelper::ToString<uint32_t>(UINT32_C(114));
\endcode
For floating point type, this function allows caller to specify extra arguments
which provides the format (\c std::chars_format) and precision when getting string representation.
For integral type, this function allows caller to specify extra argument
providing the base of number when getting string representation.
\section parser_helper__notes Notes
All functions within this helper are implementated by standard library functions.
These functions just make a good wrapper for complex standard library functions.
And give you a experience like C\# parser functions.
Basically, all functions located in this helper have possibility to throw exception.
But this possibility are more close to the possibility that \c new statement throw \c std::bad_alloc.
So in most cases you can assume these functions will not throw any exception.
All functions are template functions.
The argument of template is the type these functions need to be processed.
Although C++ have \e smart template type deduction,
it would be better to specify template argument manually to explicitly specify your desired type.
*/
}

View File

@@ -1,75 +0,0 @@
namespace yycc::patch {
/**
\page patch Other STL Patches
There are some other STL patches in this library which can not be organized in single document file individually.
So I put them together here.
\section patch__ptr_pad Pointer Print Padding
When printing pointer on screen, programmer usually left-pad zero to make it looks good.
However, the count of zero for padding is different in x86 and x64 architecture (8 for x86 and 16 for x64).
Macro \c PRIXPTR_LPAD will help you to resolve this issue.
Macro \c PRIXPTR_LPAD will be expended to one of following value according to the target system architecture.
\li \c "08": On x86 system.
\li \c "016": On x64 system.
There is an example for how to use it:
\code
void* raw_ptr = blabla();
std::printf(stdout, "Raw Pointer 0x%" PRIXPTR_LPAD PRIXPTR, raw_ptr);
\endcode
Note \c PRIXPTR is defined by standard library for formatting pointer as hexadecimal style.
\section patch__smart_file Smart FILE Pointer
fopen::SmartStdFile use \c std::unique_ptr with custom deleter to implement smart \c FILE*.
It is useful in the cases that you want to automatically free opened file when leaving corresponding scope.
\section patch__utf8_fopen UTF8 fopen
In Windows, standard \c std::fopen can not handle UTF8 file name in common environment.
So we create fopen::fopen to give programmer an universal \c fopen in UTF8 style.
In Windows platform, this function will try to convert its argument to \c wchar_t
and calling Microsoft specific \c _wfopen function to open file.
If encoding convertion or \c _wfopen failed, this function will return \c nullptr like \c std::fopen does.
In other platforms, it will simply redirect calling to \c std::fopen.
There is a simple example:
\code
FILE* fs = fopen::fopen(u8"/path/to/file", u8"rb");
\endcode
\section patch__utf8_stream UTF8 Stream Support
The namespace yycc::patch::stream provides UTF8 support for \c std::ostream.
This namespace contains operator overloads that give \c std::ostream the ability to write UTF8 string and its char.
To use this feature, you should include its header file first,
and then directly use <TT>using namespace ::yycc::patch::stream;</TT> to import this namespace.
\section patch__utf8_format UTF8 Format Support
The namespace yycc::patch::format provides a patch for \c std::format to allow UTF8 string as arguments.
As \c std::format only allows \c char and \c wchar_t as its char type in C++ 23 currently,
it's impossible to use UTF8 string for std::format, both as format string and argument.
This namespace gives a patch for this shortcoming.
First, it define a brandnew format::format function, which resolve the issue that we can not use UTF8 as format string.
The implementation of this function is simple. We simply convert given UTF8 format string into ordinary string,
and then delegate it to \c std::vformat, the runtime format function in C++ 23.
So the performance of this function may be a little worse than \c std::format, but it's not a big deal.
We suggest that you use this namespace provided format::format function in your code,
to enable this UTF8 format string feature.
Additionally, this namespace provides \c std::formatter specializations for UTF8 string.
Thus we can safely use UTF8 string as argument in \c std::format, also including our invented brandnew format::format function.
*/
}

View File

@@ -11,7 +11,7 @@ When some functions throw exception, it should cause program paniked, rather tha
This is inspired from Rust, and also the compromise with STL.
Most functions this library provided has Rust-Result-like return value.
It means that programmer can handle error gracefully.
It means that programmer can handle error correctly.
However, this library is based on STL, another library that may throw C++ exception to indicate error.
We can not control this behavior of STL, so I forcely apply this rule.
@@ -19,22 +19,18 @@ We can not control this behavior of STL, so I forcely apply this rule.
This library has special treat with Windows to make it works on Windows.
However, for other operating system, it do not have too much care.
We brutally make a premise that other operating systems are POSIX-compatible and use UTF8 as its encoding.
We brutally make a premise that other operating systems are UNIX-liked and use UTF8 as its encoding.
\section premise_and_principle__string_encoding String Encoding
Before using this library, you should know the encoding strategy of this library first.
After upgrade the whole project into C++23, \c char8_t is the only valid UTF8 char type.
\c std::u8string and \c std::u8string_view are the only valid UTF8 string container and viewer.
And, \c u8 string literal prefix is the only way to create UTF8 string literal.
In brief words, this library use UTF8 encoding everywhere.
However, there are some special cases that use ordinary string instead of UTF8 string list following
(also, not all cases are covered).
In short words, this library use UTF8 encoding everywhere except some special cases list following (not all).
\li Traditional format function in yycc::string::op.
Traditional format function provide some overloads for ordinary string formatting.
That's because this feature is so common to use in some cases.
\li The message of Rust panic in yycc::rust::panic.
Due to the limitation of \c std::format, we only can use ordinary string as its message content.
\li The message of standard library exception.
For the compatibility with C++ standard library exception,
we only can use ordinary string as the message of exception.

View File

@@ -1,133 +0,0 @@
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
*/
}

112
doc/src/std_patch.dox Normal file
View File

@@ -0,0 +1,112 @@
namespace YYCC::StdPatch {
/**
\page std_patch Standard Library Patch
\section std_patch__starts_with_ends_with Starts With & Ends With
\c std::basic_string::starts_with and \c std::basic_string::ends_with (also available in \c std::basic_string_view)
are functions introduced in C++ 20 and unavailable in C++ 17.
YYCC::StdPatch provides a patch for these function in C++ 17 environment.
Please note these implementations are following implementation instruction presented by CppReference website.
And it should have the same performance with vanilla functions because Microsoft STL use the same way to implement.
These implementations will not fallback to vanilla function even they are available.
Because their performance are good.
To use these functions, you just need to call them like corresponding vanilla functions.
Our implementations provide all necessary overloads.
The only thing you need to do is provide the string self as the first argument,
because our implementations can not be inserted as a class member of string.
There is an example:
\code
YYCC::StdPatch::StartsWith(YYCC_U8("aabbcc"), YYCC_U8("aa"));
YYCC::StdPatch::EndsWith(YYCC_U8("aabbcc"), YYCC_U8("cc"));
\endcode
\section std_patch__contains Contains
\c Contains function in standard library ordered and unordered successive container are also introduced in C++ 20.
YYCC::StdPatch provides a patch for this function in C++ 17 environment.
Please note this implementation will fallback to vanilla function if it is available.
Because our implementation is a remedy (there is no way to use public class member to have the same performance of vanilla function).
There is an example about how to use it:
\code
std::set<int> test { 1, 5 };
YYCC::StdPatch::Contains(test, static_cast<int>(5));
\endcode
\section std_patch__fs_path std::filesystem::path Patch
As you know, the underlying char type of \c std::filesystem::path is \c wchar_t on Windows,
and in other platforms, it is simple \c char.
Due to this, if you try to create a \c std::filesystem::path instance by calling constructor with an UTF8 char sequence on Windows,
the library implementation will assume your input is based on current Windows code page, not UTF8.
And the final path stored in \c std::filesystem::path is not what you expcected.
This patch gives you a way to create \c std::filesystem::path
and extract path string stored in \c std::filesystem::path with UTF8 encoding.
This patch namespace always use UTF8 as its argument.
You should use the functions provided by this namespace on any platforms
instead of vanilla \c std::filesystem::path functions.
However, if your C++ standard is higher than C++ 20,
you can directly use UTF8 string pointer and string container in \c std::filesystem::path,
because standard library has supported them.
This patch only just want to provide an uniform programming experience.
This patch is served for Windows but also works on other plaftoms.
If you are in Windows, this patch will perform extra operations to achieve goals,
and in other platforms, they just redirect request to corresponding vanilla C++ functions.
\subsection std_patch__fs_path__from_utf8_path Create Path from UTF8 String
#ToStdPath provides this feature.
It accepts an string pointer to UTF8 string and try to create \c std::filesystem::path from it.
Function will throw exception if encoding convertion or constructor self failed.
There are some example:
\code
auto foobar_path = YYCC::StdPatch::ToStdPath(YYCC_U8("/foo/bar"));
auto slashed_path = foobar_path / YYCC::StdPatch::ToStdPath(YYCC_U8("test"));
auto replaced_ext = foobar_path.replace_extension(YYCC::StdPatch::ToStdPath(YYCC_U8(".txt")));
\endcode
For first line in example, it is obvious that you can create a \c std::filesystem::path from this function.
However, for the second and third line in example, what we want to tell you is
that you should always use this function in other \c std::filesystem::path functions requiring path string.
\c std::filesystem::path is a very \e conservative class.
Most of its functions only accept \c std::filesystem::path self as argument.
For example, \c std::filesystem::path::replace_extension do not accept string as argument.
It accepts a reference to \c std::filesystem::path as argument.
(it still is possible that pass string pointer or string container to it because they can be converted to \c std::filesystem::path implicitly.)
It's great. This is what we expected!
We now can safely deliver the result generated by our function to these functions,
and don't need to worry about the encoding of we provided string.
Because all strings have been converted to \c std::filesystem::path by our function before passing them.
So, the second line will produce \c "/foo/bar/test"
and the third line will produce \c "/foo/bar.txt" in any platforms.
You may notice std::filesystem::u8path.
However it is depracted since C++ 20,
because \c std::filesystem::path directly supports UTF8 by \c char8_t since C++ 20.
Because C++ standard is volatile, we create this function to have an uniform programming experience.
\subsection std_patch__fs_path__to_utf8_path Extract UTF8 Path String from Path
#ToUTF8Path provides this feature.
It basically is the reversed operation of #ToStdPath.
It is usually used when you have done all path work in \c std::filesystem::path
and want to get the result.
There is an example:
\code
auto foobar_path = YYCC::StdPatch::ToStdPath(YYCC_U8("/foo/bar"));
auto result = YYCC::StdPatch::ToUTF8Path(foobar_path / YYCC::StdPatch::ToStdPath(YYCC_U8("test")));
\endcode
*/
}

View File

@@ -1,176 +0,0 @@
namespace yycc::string::op {
/**
\page string__op String Operations
\section string__op__printf Printf VPrintf
yycc::string::op provides 4 functions for formatting string.
These functions are originally provided to programmer who can not use C++ 20 \c std::format feature.
However, when this project was migrated to C++23 standard, \c std::format is finally available.
And we set these functions as the complement to \c std::format feature.
\code
std::u8string printf(const char8_t* format, ...);
std::u8string vprintf(const char8_t* format, va_list argptr);
std::string printf(const char* format, ...);
std::string vprintf(const char* format, va_list argptr);
\endcode
#printf and #vprintf is similar to \c std::sprintf and \c std::vsprintf.
#printf accepts UTF8 format string and variadic arguments specifying data to print.
This is commonly used by programmer.
However, #vprintf also do the same work but its second argument is \c va_list,
the representation of variadic arguments.
It is mostly used by other function which has variadic arguments.
The only difference between these function and standard library functions is
that you don't need to worry about whether the space of given buffer is enough,
because these functions help you to calculate this internally.
Once there are some exceptions occurs, such as, not enough memeory, or the bad syntax of format string,
these functions will throw exception immediately.
\section string__op__replace Replace
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 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.
The second overload will produce a copy of original string and do replacement on the copied string.
These #replace functions have special treatments for boundary scenarios:
\li If given string is empty, the return value will be empty.
\li If the character sequence to be replaced is empty string, no replacement will happen.
\li If the character sequence will be replaced into string is or empty, it will simply delete found character sequence from given string.
\section string__op__join Join
yycc::string::op provide an universal way for joining string and various specialized join functions.
\subsection string__op__join__universal Universal Join Function
Because C++ list types are various.
There is no unique and convenient way to create an universal join function.
So we create #JoinDataProvider to describe join context.
Before using universal join function,
you should setup #JoinDataProvider first, the context of join function.
It actually is an \c std::function object which can be easily fetched by C++ lambda syntax.
This function pointer returns \c std::optional<std::u8string_view>,
which should return \c std::u8string_view for the data to be joined, or \c std::nullopt if there is no more data.
As you noticed, this is similar to Rust iterator.
Then, you can pass the created #JoinDataProvider object to #join function.
And specify delimiter at the same time.
Then you can get the final joined string.
There is an example:
\code
std::vector<std::u8string> data {
u8"", u8"1", u8"2", u8""
};
auto iter = data.cbegin();
auto stop = data.cend();
std::u8string joined_string = yycc::string::op::join(
[&iter, &stop]() -> std::optional<std::u8string_view> {
if (iter == stop) return std::nullopt;
return *iter++;
},
delimiter
);
\endcode
\subsection string__op__join__specialized Specialized Join Function
Despite universal join function,
yycc::string::op also provide a specialized join functions for standard library container.
For example, the code written above can be written in following code by using this specialized overload.
The first two argument is just the begin and end iterator.
However, you must make sure that the iterator can be dereferenced and then implicitly converted to std::u8string_view.
\code
std::vector<std::u8string> data {
u8"", u8"1", u8"2", u8""
};
std::u8string joined_string = yycc::string::op::join(data.begin(), data.end(), delimiter);
\endcode
\section string__op__lower_upper Lower Upper
This namespace provides Python-like string lower and upper function.
\code
void lower(std::u8string& strl);
std::u8string to_lower(const std::u8string_view& strl);
void upper(std::u8string& strl);
std::u8string to_upper(const std::u8string_view& strl);
\endcode
The functions start with "to_" prefix accept a string view as argument
and return a \b copy whose content are all the lower/upper case of original string.
The rest of these functions accept a mutable string container as argument and will modify it in place.
\section string__op__strip_trim Strip and Trim
This namespace provides functions for removing leading and trailing characters.
There are two sets of functions:
\subsection string__op__strip Unicode-aware functions
These functions properly handle Unicode characters when stripping:
\code
std::u8string_view strip(const std::u8string_view& strl, const std::u8string_view& words);
std::u8string_view lstrip(const std::u8string_view& strl, const std::u8string_view& words);
std::u8string_view rstrip(const std::u8string_view& strl, const std::u8string_view& words);
\endcode
The prefix "l" and "r" are for left and right strip respectively like Python.
\subsection string__op__trim ASCII-only functions
These functions treat each byte as an individual character and are faster for ASCII-only scenarios:
\code
std::u8string_view trim(const std::u8string_view& strl, const std::u8string_view& words);
std::u8string_view ltrim(const std::u8string_view& strl, const std::u8string_view& words);
std::u8string_view rtrim(const std::u8string_view& strl, const std::u8string_view& words);
\endcode
The difference of "trim" and "strip" is same as their invented time in Java.
"trim" is inveted at first so its function is confined to ASCII-only strings.
"strip" is introduced later and it should accept more scenarios like Unicode.
Although all of "trim" and "strip" can handle Unicode in Java.
\section string__op__split Split
This namespace provides Python-like string split functions.
It has 3 variants for different use cases:
\code
LazySplit lazy_split(const std::u8string_view& strl, const std::u8string_view& delimiter);
std::vector<std::u8string_view> split(const std::u8string_view& strl, const std::u8string_view& delimiter);
std::vector<std::u8string> split_owned(const std::u8string_view& strl, const std::u8string_view& delimiter);
\endcode
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.
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.
The third function #split_owned returns a vector of strings, which are copies of the original parts.
If the source string (the string need to be split) is empty, or the delimiter is empty,
the result will only has 1 item and this item is source string itself.
There is no way that these methods return an empty list, except the code is buggy.
*/
}

View File

@@ -1,118 +0,0 @@
namespace yycc::string::reinterpret {
/**
\page string__reinterpret String Reinterpret
Now, you have know that we use UTF8 string everywhere in this project
as we introduced in \ref premise_and_principle__string_encoding.
Now it's time to know how to fetch UTF8 string from user or anywhere else.
\section string__reinterpret__concept Concepts
In following content, you may be face with 2 words: ordinary string and UTF8 string.
UTF8 string, as its name, is the string encoded with UTF8.
The char type of it must is \c char8_t.
Ordinary string means the plain, native string.
The result of C++ string literal without any prefix \c "foo bar" is a rdinary string.
The char type of it is \c char.
Its encoding depends on compiler and environment.
(UTF8 in Linux, or system code page in Windows if UTF8 switch was not enabled in MSVC.)
For more infomation, please browse CppReference:
https://en.cppreference.com/w/cpp/language/string_literal
\section string__reinterpret__pointer UTF8 String Pointer
String pointer means the raw pointer pointing to a string, such as \c const \c char*, \c char*, \c char32_t* and etc.
Many legacy code assume \c char* is encoded with UTF8 (the exception is Windows). But \c char* is incompatible with \c char8_t.
YYCC provides as_utf8() to resolve this issue. There is an exmaple:
\code
const char* absolutely_is_utf8 = "I confirm this is encoded with UTF8.";
const char8_t* converted = as_utf8(absolutely_is_utf8);
char* mutable_utf8 = const_cast<char*>(absolutely_is_utf8); // This is not safe. Just for example.
char8_t* mutable_converted = as_utf8(mutable_utf8);
\endcode
as_utf8() has 2 overloads which can handle constant and mutable stirng pointer convertion respectively.
YYCC also has ability that convert UTF8 char type to ordinary char type by as_ordinary().
Here is an exmaple:
\code
const char8_t* utf8 = u8"I am UTF8 string.";
const char* converted = as_ordinary(utf8);
char8_t* mutable_utf8 = const_cast<char*>(utf8); // Not safe. Also just for example.
char* mutable_converted = as_ordinary(mutable_utf8);
\endcode
Same as as_utf8(), as_ordinary() also has 2 overloads to handle constant and mutable string pointer.
\section string__reinterpret__container UTF8 String Container
String container usually means the standard library string container, such as \c std::string, \c std::wstring, \c std::u32string and etc.
In many personal project, programmer may use \c std::string everywhere because \c std::u8string may not be presented when writing peoject.
How to do convertion between ordinary string container and UTF8 string container?
It is definitely illegal that directly do force convertion. Because they may have different class layout.
Calm down and I will tell you how to do correct convertion.
YYCC provides as_utf8() to convert ordinary string container to UTF8 string container.
There is an exmaple:
\code
std::string ordinary_string("I am UTF8");
std::u8string utf8_string = as_utf8(ordinary_string);
\endcode
Actually, as_utf8() accepts a reference to \c std::string_view as argument.
However, there is a implicit convertion from \c std::string to \c std::string_view,
so you can directly pass a \c std::string instance to it.
String view will reduce unnecessary memory copy.
If you just want to pass ordinary string container to function, and this function accepts \c std::u8string_view as its argument,
you can use alternative as_utf8_view().
\code
std::string ordinary_string("I am UTF8");
std::u8string_view utf8_string = as_utf8_view(ordinary_string);
\endcode
Comparing with previous one, this example use less memory.
The reduced memory is the content of \c utf8_string because string view is a view, not the copy of original string.
Same as UTF8 string pointer, we also have as_ordinary() and as_ordinary_view() do correspondant reverse convertion.
Try to do your own research and figure out how to use them.
It's pretty easy.
\section string__reinterpret__clarification Clarification about Usage Scenario
Let we make a clarification for what this chapter are talking about.
In these chapter, what we are talking about the convertion between UTF8 string and ordinary string,
which is originally encoded by UTF-8 but presented by \c char type.
This spot is crucial. If you apply any functions provided by this namespace to any string which is not encoded by UTF-8,
for example, trying converting an CP1252 encoded western europe string to UTF-8 via function given by this namespace,
it must cause <B>undefined behavior</B>.
The correct function for doing these things introduced above is located in yycc::encoding namespace,
or a more generic module located in yycc::carton::pycodec.
This namespace is only suit for the convertion of UTF-8 string which was mis-presented by non-<TT>char8_t</TT> types.
After understand this point, you now can safely use this namespace.
Additionally, due to the legacy of MSVC, the encoding of \c char* may not be UTF8 in most cases.
If you run the convertion code introduced in this article with the string which is not encoded with UTF8,
it may cause undefined behavior.
To enable UTF8 mode of MSVC, please deliver \c /utf-8 switch to MSVC compiler.
Thus you can use the functions introduced in this article safely.
Otherwise, you must guarteen that the argument you provided to these functions is encoded by UTF8 manually.
Linux user do not need care this.
Because almost Linux distro use UTF8 in default.
*/
}

149
doc/src/string_helper.dox Normal file
View File

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

View File

@@ -0,0 +1,23 @@
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.
*/
}

View File

@@ -1,27 +1,25 @@
namespace yycc::windows {
namespace YYCC {
/**
\page windows__import_guard Windows Import Guard
\page win_import Windows Import Guard
Windows is shitty for the programmer who is familiar with UNIX programming.
Due to legacy reason, Windows defines various things which are not compatible with UNIX or standard C++ programming.
\section windows__import_guard__usage Usage
\section win_import__usage Usage
YYCC has a way to solve the issue introduced above.
\code
#include <yycc/macro/os_detector.hpp>
#if defined(YYCC_OS_WINDOWS)
#include <yycc/windows/import_guard_head.hpp>
#include <WinImportPrefix.hpp>
#include <Windows.h>
#include "other_header_depend_on_windows.h"
#include <yycc/windows/import_guard_tail.hpp>
#include <WinImportSuffix.hpp>
#endif
\endcode
The including of import_guard_head.hpp and import_guard_tail.hpp is a pair.
The including of WinImportPrefix.hpp and WinImportSuffix.hpp is a pair.
They just like a guard bracket the include operation of Windows related headers,
to keep all Windows shitty contents will not be leaked outside.
@@ -32,7 +30,7 @@ This guard can solve following issues:
Programmer can not use \c std::max and \c std::min normally.
<UL>
<LI>Windows defines \c MAX and \c MIN as macros for personal use. This is why this happened.</LI>
<LI>This is actually resolved by CMake defined 2 public build macros which tell Windows do not create these 2 macros. But I simply conclude this feature in there.</LI>
<LI>Guard defines some special macros to tell Windows do not create these 2 macros.</LI>
</UL>
</LI>
<LI>
@@ -46,12 +44,12 @@ This guard can solve following issues:
Compiler throw annoy warnings and errors when using specific standard library functions.
<UL>
<LI>MSVC will throw warnings and errors when you are using Microsoft so-called \e depracted or \e unsafe standard library functions.</LI>
<LI>This is also done by CMake public build macros.</LI>
<LI>YYCCInternal.hpp, which has been included by this pair, defines some macros to purge these warnings and errors out.</LI>
</UL>
</LI>
</UL>
\section windows__import_guard__notes Notes
\section win_import__notes Notes
If you have other header files which are strongly depend on Windows header,
you should put them into this bracket at the same time like example did.

View File

@@ -1,31 +0,0 @@
namespace yycc::windows::com {
/**
\page windows__com COM Helper
This namespace is Windows specific.
It will be invisible on other platforms.
\section windows__com__memory_safe_ptr Memory Safe Pointer Types
This namespace provides various memory-safe types for interacting with COM functions.
Although Microsoft also has similar smart pointer called \c CComPtr.
But this library is eager to hide all Microsoft-related functions calling.
Using \c CComPtr is not corresponding with the philosophy of this library.
So these standard library based smart pointer and corresponding deleter types were created.
\section windows__com__com_guard COM Guard
This namespace contains a COM Guard which make sure COM was initialized in current module when loading current module.
It is essential because all calling to COM functions should be under the premise that COM has been initialized.
This guard also will uninitialize COM when unloading this module.
There is only an exposed function called is_initialized() for user calling.
This function will check whether COM environment is initialized.
If you want YYCC automatically initialize COM environment for you,
you must call this function in your program at least one time.
Otherwise COM Guard code may be unavailable,
because compiler may think these code is not referenced by any other code and drop them.
*/
}

View File

@@ -1,23 +0,0 @@
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.
*/
}

View File

@@ -1,15 +0,0 @@
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.
*/
}

View File

@@ -2,6 +2,10 @@
# Exclude VSCode
.vscode/
# Exclude generated files
win_build.bat
linux_build.sh
## ===== Python =====
# Byte-compiled / optimized / DLL files
__pycache__/

106
script/gen_build_script.py Normal file
View File

@@ -0,0 +1,106 @@
import argparse
import typing
import re
import shlex
from pathlib import Path
from dataclasses import dataclass
import jinja2
def validate_cpp_ver(ver: str) -> str:
if re.match(r'^[0-9]+$', ver) is not None: return ver
else: raise argparse.ArgumentTypeError('invalid version of C++ standard.')
def write_line(f: typing.TextIO, val: str) -> None:
f.write(val)
f.write('\n')
# Reference: https://stackoverflow.com/questions/29213106/how-to-securely-escape-command-line-arguments-for-the-cmd-exe-shell-on-windows
def escape_for_cmd_exe(arg):
meta_re = re.compile(r'([()%!^"<>&|])')
return meta_re.sub('^\1', arg)
def escape_cmd_argument(arg):
if not arg or re.search(r'(["\s])', arg):
arg = '"' + arg.replace('"', r'\"') + '"'
return escape_for_cmd_exe(arg)
def escape_sh_argument(arg):
return shlex.quote(arg)
@dataclass(frozen=True)
class ScriptSettings:
pic: bool
cpp_version: str
build_doc: bool
build_testbench: bool
class TemplateRender:
loader: jinja2.BaseLoader
environment: jinja2.Environment
win_template: jinja2.Template
linux_template: jinja2.Template
settings: ScriptSettings
def __init__(self, settings: ScriptSettings) -> None:
self.loader = jinja2.FileSystemLoader(self.__get_dir())
self.environment = jinja2.Environment(loader=self.loader)
self.win_template = self.environment.get_template('win_build.bat.jinja')
self.linux_template = self.environment.get_template('linux_build.sh.jinja')
self.settings = settings
def __get_dir(self) -> Path:
return Path(__file__).resolve().parent
def __escape_path(self, val: str, is_win: bool) -> str:
if is_win: return escape_cmd_argument(val)
else: return escape_sh_argument(val)
def __render(self, template: jinja2.Template, dest_file: str, is_win: bool) -> None:
with open(self.__get_dir() / dest_file, 'w', encoding='utf-8') as f:
f.write(template.render(
repo_root_dir = self.__escape_path(str(self.__get_dir().parent), is_win),
cpp_version = self.settings.cpp_version,
build_doc = self.settings.build_doc,
pic = settings.pic
))
def render_win_script(self) -> None:
self.__render(self.win_template, 'win_build.bat', True)
def render_linux_script(self) -> None:
self.__render(self.linux_template, 'linux_build.sh', False)
if __name__ == '__main__':
# parse argument
parser = argparse.ArgumentParser(
prog='YYCC Windows Build Script Generator',
description='YYCC Windows Build Script Generator'
)
parser.add_argument(
'-c', '--cpp',
action='store', default='17', dest='cpp', type=validate_cpp_ver,
help='The version of C++ standard used when building.'
)
parser.add_argument(
'-d', '--build-doc',
action='store_true', dest='build_doc',
help='Build YYCC with documentation.'
)
parser.add_argument(
'-p', '--pic',
action='store_true', dest='pic',
help='Enable Position Independent Code flag on non-Windows platform. This is crucial for compiling dynamic library using this library.'
)
args = parser.parse_args()
# build settings
settings = ScriptSettings(args.cpp, args.build_doc, args.pic)
# build template render and render result
render = TemplateRender(settings)
render.render_win_script()
render.render_linux_script()

View File

@@ -1,19 +0,0 @@
#!/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
cd build
# Build in Release mode
cmake -DCMAKE_BUILD_TYPE=Release ../..
cmake --build .
cmake --install . --prefix=../install
# Back to root directory
cd ..
cd ..

View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Navigate to project root directory
cd {{ repo_root_dir }}
# Create build and install directory
mkdir build
mkdir install
# Build as release version
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} {{ '-DYYCC_BUILD_DOC=ON' if build_doc }} {{ '-DYYCC_BUILD_TESTBENCH=ON' if build_testbench }} ../.. --fresh
cmake --build .
cmake --install . --prefix ../install
# Exit to original path
cd ..
echo "YYCC Linux CMake build done"

View File

@@ -1,19 +0,0 @@
#!/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
cd build
# Build in Release mode
cmake -DCMAKE_BUILD_TYPE=Release ../..
cmake --build .
cmake --install . --prefix=../install
# Back to root directory
cd ..
cd ..

View File

View File

@@ -0,0 +1,85 @@
@ECHO OFF
:: Navigate to project root directory
CD /d {{ repo_root_dir }}
:: Create build directory and enter it
MKDIR bin
CD bin
MKDIR cpp{{ cpp_version }}
CD cpp{{ cpp_version }}
:: Create internal build directory
MKDIR Win32
MKDIR x64
MKDIR documentation
:: Create internal install directory
MKDIR install
CD install
MKDIR Win32_Debug
MKDIR Win32_Release
MKDIR x64_Debug
MKDIR x64_Release
CD ..
:: Create internal MSVC specific install directory
MKDIR msvc_install
CD msvc_install
MKDIR bin
MKDIR include
MKDIR lib
MKDIR share
CD bin
MKDIR Win32
MKDIR x64
CD ..
CD lib
MKDIR Win32\Debug
MKDIR Win32\Release
MKDIR x64\Debug
MKDIR x64\Release
CD ..
CD ..
:: Build for Win32
CD Win32
cmake -A Win32 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_TESTBENCH=ON ../../..
cmake --build . --config Debug
cmake --install . --prefix=../install/Win32_Debug --config Debug
cmake --build . --config RelWithDebInfo
cmake --install . --prefix=../install/Win32_Release --config RelWithDebInfo
CD ..
:: Build for x64
CD x64
cmake -A x64 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_TESTBENCH=ON ../../..
cmake --build . --config Debug
cmake --install . --prefix=../install/x64_Debug --config Debug
cmake --build . --config RelWithDebInfo
cmake --install . --prefix=../install/x64_Release --config RelWithDebInfo
CD ..
{% if build_doc %}
:: Build for documentation
CD documentation
cmake -A x64 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_DOC=ON ../../..
cmake --build . --config RelWithDebInfo
cmake --build . --target YYCCDocumentation
cmake --install . --prefix=../install/x64_Release --config RelWithDebInfo
CD ..
{% endif %}
:: Copy header files
XCOPY install\x64_Release\include msvc_install\include\ /E /Y
:: Copy binary files
COPY install\Win32_Release\bin\YYCCTestbench.exe msvc_install\bin\Win32\YYCCTestbench.exe /Y
COPY install\x64_Release\bin\YYCCTestbench.exe msvc_install\bin\x64\YYCCTestbench.exe /Y
:: Copy library files
COPY install\Win32_Debug\lib\YYCCommonplace.lib msvc_install\lib\Win32\Debug\YYCCommonplace.lib /Y
COPY install\Win32_Release\lib\YYCCommonplace.lib msvc_install\lib\Win32\Release\YYCCommonplace.lib /Y
COPY install\x64_Debug\lib\YYCCommonplace.lib msvc_install\lib\x64\Debug\YYCCommonplace.lib /Y
COPY install\x64_Release\lib\YYCCommonplace.lib msvc_install\lib\x64\Release\YYCCommonplace.lib /Y
{% if build_doc %}
:: Copy documentation files
XCOPY install\x64_Release\share msvc_install\share\ /E /Y
{% endif %}
:: Leave build directory and report
CD ..\..
ECHO Windows CMake Build Done

View File

@@ -1,18 +0,0 @@
@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
CD build
:: Build with x64 architecture in Release mode
cmake -A x64 ../..
cmake --build . --config Release
cmake --install . --prefix=../install --config Release
:: Back to root directory
CD ..
CD ..

View File

@@ -14,13 +14,11 @@ PRIVATE
yycc/string/reinterpret.cpp
yycc/string/op.cpp
yycc/patch/fopen.cpp
yycc/patch/stream.cpp
yycc/panic.cpp
yycc/env.cpp
yycc/rust/panic.cpp
yycc/rust/env.cpp
yycc/windows/com.cpp
yycc/windows/dialog.cpp
yycc/windows/winfct.cpp
yycc/windows/console.cpp
yycc/encoding/stl.cpp
yycc/encoding/windows.cpp
yycc/encoding/iconv.cpp
@@ -29,20 +27,6 @@ PRIVATE
yycc/carton/termcolor.cpp
yycc/carton/wcwidth.cpp
yycc/carton/tabulate.cpp
yycc/carton/ironpad.cpp
yycc/carton/csconsole.cpp
yycc/carton/clap/option.cpp
yycc/carton/clap/variable.cpp
yycc/carton/clap/summary.cpp
yycc/carton/clap/application.cpp
yycc/carton/clap/manual.cpp
yycc/carton/clap/parser.cpp
yycc/carton/clap/resolver.cpp
yycc/carton/binstore/types.cpp
yycc/carton/binstore/setting.cpp
yycc/carton/binstore/configuration.cpp
yycc/carton/binstore/storage.cpp
yycc/carton/lexer61.cpp
)
target_sources(YYCCommonplace
PUBLIC
@@ -58,35 +42,29 @@ FILES
yycc/macro/compiler_detector.hpp
yycc/macro/ptr_size_detector.hpp
yycc/macro/class_copy_move.hpp
yycc/macro/printf_checker.hpp
yycc/cenum.hpp
yycc/string.hpp
yycc/flag_enum.hpp
yycc/string/reinterpret.hpp
yycc/string/op.hpp
yycc/patch/ptr_pad.hpp
yycc/patch/fopen.hpp
yycc/patch/stream.hpp
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
yycc/num/safe_op.hpp
yycc/num/op.hpp
yycc/primitive.hpp
yycc/option.hpp
yycc/result.hpp
yycc/prelude.hpp
yycc/panic.hpp
yycc/env.hpp
yycc/rust/prelude.hpp
yycc/rust/primitive.hpp
yycc/rust/panic.hpp
yycc/rust/option.hpp
yycc/rust/result.hpp
yycc/rust/env.hpp
yycc/windows/import_guard_head.hpp
yycc/windows/import_guard_tail.hpp
yycc/windows/com.hpp
yycc/windows/dialog.hpp
yycc/windows/winfct.hpp
yycc/windows/console.hpp
yycc/constraint.hpp
yycc/constraint/builder.hpp
yycc/encoding/stl.hpp
yycc/encoding/windows.hpp
yycc/encoding/iconv.hpp
@@ -95,26 +73,6 @@ FILES
yycc/carton/termcolor.hpp
yycc/carton/wcwidth.hpp
yycc/carton/tabulate.hpp
yycc/carton/ironpad.hpp
yycc/carton/csconsole.hpp
yycc/carton/clap.hpp
yycc/carton/clap/types.hpp
yycc/carton/clap/option.hpp
yycc/carton/clap/variable.hpp
yycc/carton/clap/summary.hpp
yycc/carton/clap/application.hpp
yycc/carton/clap/manual.hpp
yycc/carton/clap/validator.hpp
yycc/carton/clap/parser.hpp
yycc/carton/clap/resolver.hpp
yycc/carton/binstore.hpp
yycc/carton/binstore/types.hpp
yycc/carton/binstore/serdes.hpp
yycc/carton/binstore/setting.hpp
yycc/carton/binstore/configuration.hpp
yycc/carton/binstore/storage.hpp
yycc/carton/lexer61.hpp
yycc/carton/fft.hpp
)
# Setup header infomations
target_include_directories(YYCCommonplace
@@ -146,10 +104,9 @@ PUBLIC
$<$<PLATFORM_ID:Darwin>:YYCC_OS_MACOS>
$<$<PLATFORM_ID:iOS>:YYCC_OS_MACOS> # We brutally think iOS as macOS.
# Compiler macro
$<$<CXX_COMPILER_ID:MSVC>:YYCC_CC_MSVC>
$<$<CXX_COMPILER_ID:GNU>:YYCC_CC_GCC>
$<$<CXX_COMPILER_ID:Clang>:YYCC_CC_CLANG>
$<$<CXX_COMPILER_ID:AppleClang>:YYCC_CC_CLANG> # We brutally think AppleClang is Clang.
$<$<CXX_COMPILER_ID:MSVC>:YYCC_CC_MSVC>
# Endian macro
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},LITTLE_ENDIAN>:YYCC_ENDIAN_LITTLE>
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},BIG_ENDIAN>:YYCC_ENDIAN_BIG>
@@ -167,15 +124,6 @@ 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
@@ -187,15 +135,6 @@ PUBLIC
$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>
)
# Fix GCC std::stacktrace link error
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
target_link_libraries(YYCCommonplace PRIVATE stdc++exp)
else ()
target_link_libraries(YYCCommonplace PRIVATE stdc++_libbacktrace)
endif ()
endif ()
# Install binary and headers
install(TARGETS YYCCommonplace
EXPORT YYCCommonplaceTargets

View File

@@ -0,0 +1,348 @@
#include "ArgParser.hpp"
#include "EncodingHelper.hpp"
#include "ConsoleHelper.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <shellapi.h>
#include <processenv.h>
#include "WinImportSuffix.hpp"
#endif
namespace YYCC::ArgParser {
#pragma region Arguments List
ArgumentList ArgumentList::CreateFromStd(int argc, char* argv[]) {
std::vector<yycc_u8string> args;
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
if (argv[i] != nullptr)
args.emplace_back(yycc_u8string(YYCC::EncodingHelper::ToUTF8(argv[i])));
}
return ArgumentList(std::move(args));
}
#if defined(YYCC_OS_WINDOWS)
ArgumentList ArgumentList::CreateFromWin32() {
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw
// prepare list
std::vector<yycc_u8string> args;
// try fetching from Win32 functions
int argc;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argv != NULL) {
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
if (argv[i] != nullptr) {
yycc_u8string u8_argv;
if (YYCC::EncodingHelper::WcharToUTF8(argv[i], u8_argv))
args.emplace_back(std::move(u8_argv));
}
}
}
LocalFree(argv);
// return result
return ArgumentList(std::move(args));
}
#endif
ArgumentList::ArgumentList(std::vector<yycc_u8string>&& arguments) :
m_Arguments(arguments), m_ArgumentsCursor(0u) {}
void ArgumentList::Prev() {
if (m_ArgumentsCursor == 0u)
throw std::runtime_error("attempt to move on the head of iterator.");
--m_ArgumentsCursor;
}
void ArgumentList::Next() {
if (IsEOF()) throw std::runtime_error("attempt to move on the tail of iterator.");
++m_ArgumentsCursor;
}
const yycc_u8string& ArgumentList::Argument() const {
if (IsEOF()) throw std::runtime_error("attempt to get data on the tail of iterator.");
return m_Arguments[m_ArgumentsCursor];
}
bool ArgumentList::IsSwitch(bool* is_long_name, yycc_u8string* long_name, yycc_char8_t* short_name) const {
// check eof first
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
// check long name first, then check short name
if (IsLongNameSwitch(long_name)) {
if (is_long_name != nullptr) *is_long_name = true;
return true;
}
if (IsShortNameSwitch(short_name)) {
if (is_long_name != nullptr) *is_long_name = false;
return true;
}
// not matched
return false;
}
bool ArgumentList::IsLongNameSwitch(yycc_u8string* name_part) const {
// fetch current parameter
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
// find double slash
if (param.find(AbstractArgument::DOUBLE_DASH) != 0u) return false;
// check gotten long name
yycc_u8string_view long_name = yycc_u8string_view(param).substr(2u);
if (!AbstractArgument::IsLegalLongName(long_name)) return false;
// set checked long name if possible and return
if (name_part != nullptr)
*name_part = long_name;
return true;
}
bool ArgumentList::IsShortNameSwitch(yycc_char8_t* name_part) const {
// fetch current parameter
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
// if the length is not exactly equal to 2,
// or it not starts with dash,
// it is impossible a short name
if (param.size() != 2u || param[0] != AbstractArgument::DASH) return false;
// check gotten short name
yycc_char8_t short_name = param[1];
if (!AbstractArgument::IsLegalShortName(short_name)) return false;
// set checked short name if possible and return
if (name_part != nullptr)
*name_part = short_name;
return true;
}
bool ArgumentList::IsValue(yycc_u8string* val) const {
bool is_value = !IsSwitch();
if (is_value && val != nullptr)
*val = m_Arguments[m_ArgumentsCursor];
return is_value;
}
bool ArgumentList::IsEOF() const { return m_ArgumentsCursor >= m_Arguments.size(); }
void ArgumentList::Reset() { m_ArgumentsCursor = 0u; }
#pragma endregion
#pragma region Abstract Argument
const yycc_u8string AbstractArgument::DOUBLE_DASH = YYCC_U8("--");
const yycc_char8_t AbstractArgument::DASH = YYCC_U8_CHAR('-');
const yycc_char8_t AbstractArgument::NO_SHORT_NAME = YYCC_U8_CHAR(0);
const yycc_char8_t AbstractArgument::MIN_SHORT_NAME = YYCC_U8_CHAR('!');
const yycc_char8_t AbstractArgument::MAX_SHORT_NAME = YYCC_U8_CHAR('~');
bool AbstractArgument::IsLegalShortName(yycc_char8_t short_name) {
if (short_name == AbstractArgument::DASH || // dash is not allowed
short_name < AbstractArgument::MIN_SHORT_NAME || short_name > AbstractArgument::MAX_SHORT_NAME) { // non-display ASCII chars are not allowed
return false;
}
// okey
return true;
}
bool AbstractArgument::IsLegalLongName(const yycc_u8string_view& long_name) {
// empty is not allowed
if (long_name.empty()) return false;
// non-display ASCII chars are not allowed
for (const auto& val : long_name) {
if (val < AbstractArgument::MIN_SHORT_NAME || val > AbstractArgument::MAX_SHORT_NAME)
return false;
}
// okey
return true;
}
AbstractArgument::AbstractArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description, const yycc_char8_t* argument_example,
bool is_optional) :
m_LongName(), m_ShortName(AbstractArgument::NO_SHORT_NAME), m_Description(), m_ArgumentExample(),
m_IsOptional(is_optional), m_IsCaptured(false) {
// try to assign long name and check it
if (long_name != nullptr) {
m_LongName = long_name;
if (!AbstractArgument::IsLegalLongName(m_LongName))
throw std::invalid_argument("Given long name is invalid.");
}
// try to assign short name and check it
if (short_name != AbstractArgument::NO_SHORT_NAME) {
m_ShortName = short_name;
if (!AbstractArgument::IsLegalShortName(m_ShortName))
throw std::invalid_argument("Given short name is invalid.");
}
// check short name and long name existence
if (!HasShortName() && !HasLongName())
throw std::invalid_argument("you must specify an one of long name or short name.");
// try to assign other string values
if (description != nullptr) m_Description = description;
if (argument_example != nullptr) m_ArgumentExample = argument_example;
}
AbstractArgument::~AbstractArgument() {}
bool AbstractArgument::HasLongName() const { return !m_LongName.empty(); }
const yycc_u8string& AbstractArgument::GetLongName() const { return m_LongName; }
bool AbstractArgument::HasShortName() const { return m_ShortName != NO_SHORT_NAME; }
yycc_char8_t AbstractArgument::GetShortName() const { return m_ShortName; }
bool AbstractArgument::HasDescription() const { return !m_Description.empty(); }
const yycc_u8string& AbstractArgument::GetDescription() const { return m_Description; }
bool AbstractArgument::HasArgumentExample() const { return !m_ArgumentExample.empty(); }
const yycc_u8string& AbstractArgument::GetArgumentExample() const { return m_ArgumentExample; }
bool AbstractArgument::IsOptional() const { return m_IsOptional; }
bool AbstractArgument::IsCaptured() const { return m_IsCaptured; }
void AbstractArgument::SetCaptured(bool is_captured) { m_IsCaptured = is_captured; }
#pragma endregion
#pragma region Option Context
OptionContext::OptionContext(
const yycc_char8_t* summary, const yycc_char8_t* description,
std::initializer_list<AbstractArgument*> arguments) :
m_Summary(), m_Description() {
// assign summary and description
if (summary != nullptr) m_Summary = summary;
if (description != nullptr) m_Description = description;
// insert argument list and check them
for (auto* arg : arguments) {
// insert into long name map if necessary
if (arg->HasLongName()) {
auto result = m_LongNameMap.try_emplace(arg->GetLongName(), arg);
if (!result.second) throw std::invalid_argument("duplicated long name!");
}
// insert into short name map if necessary
if (arg->HasShortName()) {
auto result = m_ShortNameMap.try_emplace(arg->GetShortName(), arg);
if (!result.second) throw std::invalid_argument("duplicated short name!");
}
// insert into argument list
m_Arguments.emplace_back(arg);
}
}
OptionContext::~OptionContext() {}
bool OptionContext::Parse(ArgumentList& al) {
// reset argument list first
al.Reset();
// prepare variables and start loop
yycc_u8string long_name;
yycc_char8_t short_name;
bool is_long_name;
while (!al.IsEOF()) {
// if we can not find any switches, return with error
if (!al.IsSwitch(&is_long_name, &long_name, &short_name)) return false;
// find corresponding argument by long name or short name.
// if we can not find it, return with error.
AbstractArgument* arg;
if (is_long_name) {
auto finder = m_LongNameMap.find(long_name);
if (finder == m_LongNameMap.end()) return false;
arg = finder->second;
} else {
auto finder = m_ShortNameMap.find(short_name);
if (finder == m_ShortNameMap.end()) return false;
arg = finder->second;
}
// if this argument has been captured, raise error
if (arg->IsCaptured()) return false;
// call user parse function of found argument
if (arg->Parse(al)) {
// success. mark it is captured
arg->SetCaptured(true);
} else {
// failed, return error
return false;
}
// move to next argument
al.Next();
}
// after processing all argument,
// we should check whether all non-optional argument are captured.
for (const auto* arg : m_Arguments) {
if (!arg->IsOptional() && !arg->IsCaptured())
return false;
}
// okey
return true;
}
void OptionContext::Reset() {
for (auto* arg : m_Arguments) {
// clear user data and unset captured
arg->Reset();
arg->SetCaptured(false);
}
}
void OptionContext::Help() const {
// print summary and description if necessary
if (!m_Summary.empty())
YYCC::ConsoleHelper::WriteLine(m_Summary.c_str());
if (!m_Description.empty())
YYCC::ConsoleHelper::WriteLine(m_Description.c_str());
// blank line
YYCC::ConsoleHelper::WriteLine(YYCC_U8(""));
// print argument list
for (const auto* arg : m_Arguments) {
yycc_u8string argstr;
// print indent
argstr += YYCC_U8("\t");
// print optional head
bool is_optional = arg->IsOptional();
if (is_optional) argstr += YYCC_U8("[");
// switch name
bool short_name = arg->HasShortName(), long_name = arg->HasLongName();
if (short_name) {
argstr += YYCC_U8("-");
argstr += arg->GetShortName();
}
if (long_name) {
if (short_name) argstr += YYCC_U8(", ");
argstr += YYCC_U8("--");
argstr += arg->GetLongName();
}
// argument example
if (arg->HasArgumentExample()) {
argstr += YYCC_U8(" ");
argstr += arg->GetArgumentExample();
}
// optional tail
if (is_optional) argstr += YYCC_U8("]");
// argument description
if (arg->HasDescription()) {
// eol and double indent
argstr += YYCC_U8("\n\t\t");
// description
argstr += arg->GetDescription();
}
// write into console
YYCC::ConsoleHelper::WriteLine(argstr.c_str());
}
}
#pragma endregion
}

View File

@@ -0,0 +1,465 @@
#pragma once
#include "YYCCInternal.hpp"
#include "Constraints.hpp"
#include "EncodingHelper.hpp"
#include "ParserHelper.hpp"
#include <cstring>
#include <functional>
#include <vector>
#include <map>
#include <stdexcept>
/**
* @brief Universal argument parser.
* @details
* For how to use this namespace, please see \ref arg_parser.
*/
namespace YYCC::ArgParser {
/**
* @brief The advanced wrapper of the list containing command line arguments.
* @details
* This class is used by OptionContext and argument class internally for convenience.
* It should not be constrcuted directly.
* Programmer should choose proper static creation function to create instance of this class.
*/
class ArgumentList {
public:
/**
* @brief Create argument list from the parameters of standard C main function.
* @param[in] argc The argument count passed to standard C main function.
* @param[in] argv The argument value passed to standard C main function.
* @return Extracted argument list instance.
* @remarks
* First item in command line will be stripped,
* because in most cases it points to executable self
* and should not be seen as a part of arguments.
*/
static ArgumentList CreateFromStd(int argc, char* argv[]);
#if defined(YYCC_OS_WINDOWS)
/**
* @brief Create argument list from Win32 function.
* @details
* @return Extracted argument list instance.
* @remarks
* First item in command line will be stripped,
* because in most cases it points to executable self
* and should not be seen as a part of arguments.
* \par
* Programmer should use this function instead of CreateFromStd(),
* because that function involve encoding issue on Windows, especially command line including non-ASCII chars.
* Only this function guaranteen that return correct argument list on Windows.
*/
static ArgumentList CreateFromWin32();
#endif
private:
/**
* @brief Constructor of ArgumentList used internally.
* @param[in] arguments
* Underlying argument list.
* This argument list should remove first executable name before passing it to there.
*/
ArgumentList(std::vector<yycc_u8string>&& arguments);
public:
YYCC_DEF_CLS_COPY_MOVE(ArgumentList);
public:
/**
* @brief Move to previous argument.
* @exception std::runtime_error Try moving at the head of argument list.
*/
void Prev();
/**
* @brief Move to next argument.
* @exception std::runtime_error Try moving at the tail of argument list.
*/
void Next();
/**
* @brief Get the string of current argument.
* @exception std::runtime_error Try fetching data at the tail of argument list.
* @return The constant reference to the string of current argument.
*/
const yycc_u8string& Argument() const;
/**
* @brief Check whether current argument is a option / switch.
* @param[out] is_long_name
* It will be set true if this argument is long name, otherwise short name.
* nullptr if you don't want to receive this infomation.
* @param[out] long_name
* The container holding matched long name if it is (double dash stripped).
* nullptr if you don't want to receive this infomation.
* @param[out] short_name
* The variable holding matched short name if it is (dash stripped).
* nullptr if you don't want to receive this infomation.
* @exception std::runtime_error Try fetching data at the tail of argument list.
* @return
* True if it is, otherwise false.
* If this function return false, all given parameters are in undefined status.
*/
bool IsSwitch(
bool* is_long_name = nullptr,
yycc_u8string* long_name = nullptr,
yycc_char8_t* short_name = nullptr) const;
/**
* @brief Check whether current argument is a value.
* @param[out] val
* The variable holding value if it is.
* nullptr if you don't want to receive this infomation.
* @exception std::runtime_error Try fetching data at the tail of argument list.
* @return True if it is, otherwise false.
*/
bool IsValue(yycc_u8string* val = nullptr) const;
/**
* @brief Check whether we are at the tail of argument list.
* @details
* Please note EOF is a special state that you can not fetch data from it.
* EOF is the next element of the last element of argument list.
* It more like \c end() in most C++ container.
* @return True if it is, otherwise false.
*/
bool IsEOF() const;
/**
* @brief Reset cursor to the head of argument list for reuse.
*/
void Reset();
private:
/**
* @brief Check whether current argument is long name option / switch.
* @details This function is used by IsSwitch() internally.
* @param[out] name_part
* The container holding matched long name if it is (double dash stripped).
* nullptr if you don't want to receive this infomation.
* @return True if it is, otherwise false.
*/
bool IsLongNameSwitch(yycc_u8string* name_part = nullptr) const;
/**
* @brief Check whether current argument is short name option / switch.
* @details This function is used by IsSwitch() internally.
* @param[out] name_part
* The variable holding matched short name if it is (dash stripped).
* nullptr if you don't want to receive this infomation.
* @return True if it is, otherwise false.
*/
bool IsShortNameSwitch(yycc_char8_t* name_part = nullptr) const;
private:
std::vector<yycc_u8string> m_Arguments;
size_t m_ArgumentsCursor;
};
/**
* @brief The base class of every argument.
* @details Programmer can inherit this class and implement essential functions to create custom argument.
*/
class AbstractArgument {
friend class OptionContext;
// Long name and short name constants and checker.
public:
static const yycc_u8string DOUBLE_DASH; ///< The constant value representing double dash (\c --)
static const yycc_char8_t DASH; ///< The constant value representing dash (\c -)
static const yycc_char8_t NO_SHORT_NAME; ///< The constant value representing that there is not short value.
static const yycc_char8_t MIN_SHORT_NAME; ///< The constant value representing the minimum value of valid ASCII chars in short and long name.
static const yycc_char8_t MAX_SHORT_NAME; ///< The constant value representing the maximum value of valid ASCII chars in short and long name.
/**
* @brief Check whether given short name is valid.
* @details
* An ASCII code of valid short name
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
* It also can not be #DASH.
* @param[in] short_name Short name for checking.
* @return True if it is valid, otherwise false.
*/
static bool IsLegalShortName(yycc_char8_t short_name);
/**
* @brief Check whether given long name is valid.
* @details
* An ASCII code of every item in valid long name
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
* However it can be #DASH. This is different with short name.
* @param[in] long_name Long name for checking.
* @return True if it is valid, otherwise false.
*/
static bool IsLegalLongName(const yycc_u8string_view& long_name);
// Constructor & destructor
public:
/**
* @brief Constructor an argument
* @param[in] long_name The long name of this argument. nullptr if no long name.
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
* @param[in] is_optional
* True if this argument is optional argument.
* Optional argument can be absent in argument list.
* Non-optional argument must be presented in argument list,
* otherwise parser will fail.
* @exception std::invalid_argument Given short name or long name are invalid.
*/
AbstractArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name = AbstractArgument::NO_SHORT_NAME,
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
bool is_optional = false);
virtual ~AbstractArgument();
YYCC_DEL_CLS_COPY_MOVE(AbstractArgument);
// ===== User Implementation =====
protected:
/**
* @brief User implemented custom parse function
* @param[in] al The argument list for parsing.
* @return True if parse is success, otherwise false.
* @remarks
* When enter this function, argument list points to switch self.
* After success parsing, you should point it to the argument this function last accepted.
* For exmaple, for command line "-i 114514",
* when enter this function, this argument list point to "-i",
* and you should set it to "114514" when exiting this function.
*/
virtual bool Parse(ArgumentList& al) = 0;
/**
* @brief User implemented custom reset function
* @remarks
* In this function, user should claer its stored value if is has.
* You don't need clar capture state. That is done by library self.
*/
virtual void Reset() = 0;
// ===== Basic Infos =====
public:
/// @brief Check whether this argument specify long name.
/// @return True if it is, otherwise false.
bool HasLongName() const;
/// @brief Get specified long name.
/// @return Specified long name.
const yycc_u8string& GetLongName() const;
/// @brief Check whether this argument specify short name.
/// @return True if it is, otherwise false.
bool HasShortName() const;
/// @brief Get specified short name.
/// @return Specified short name.
yycc_char8_t GetShortName() const;
/// @brief Check whether this argument specify description.
/// @return True if it is, otherwise false.
bool HasDescription() const;
/// @brief Get specified description.
/// @return Specified description.
const yycc_u8string& GetDescription() const;
/// @brief Check whether this argument specify example.
/// @return True if it is, otherwise false.
bool HasArgumentExample() const;
/// @brief Get specified example.
/// @return Specified example.
const yycc_u8string& GetArgumentExample() const;
/// @brief Check whether this argument is optional.
/// @return True if it is, otherwise false.
bool IsOptional() const;
private:
yycc_u8string m_LongName;
yycc_char8_t m_ShortName;
yycc_u8string m_Description;
yycc_u8string m_ArgumentExample;
bool m_IsOptional;
// ===== Capture State =====
public:
/// @brief Check whether this argument has been captured.
/// @return True if it is, otherwise false.
bool IsCaptured() const;
private:
/**
* @brief Set capture state of this argument.
* @details This function is used internally by OptionContext.
* @param[in] is_captured New states of captured.
*/
void SetCaptured(bool is_captured);
bool m_IsCaptured;
};
/// @brief The core of argument parser, also manage all arguments.
class OptionContext {
public:
/**
* @brief Construct option context.
* @param[in] summary The summary of this application which will be printed in help text.
* @param[in] description The description of this application which will be printed in help text.
* @param[in] arguments The initializer list including pointers to all arguments.
*/
OptionContext(
const yycc_char8_t* summary, const yycc_char8_t* description,
std::initializer_list<AbstractArgument*> arguments);
~OptionContext();
YYCC_DEL_CLS_COPY_MOVE(OptionContext);
public:
/**
* @brief Start a parse.
* @param[in] al The reference to ArgumentList for parsing.
* @return
* True if success, otherwise false.
* If this function return false, you should not visit any arguments it managed.
*/
bool Parse(ArgumentList& al);
/**
* @brief Reset all managed argument to default state thus you can start another parsing.
*/
void Reset();
/**
* @brief Print help text in \c stdout.
*/
void Help() const;
private:
yycc_u8string m_Summary;
yycc_u8string m_Description;
std::vector<AbstractArgument*> m_Arguments;
std::map<yycc_u8string, AbstractArgument*> m_LongNameMap;
std::map<yycc_char8_t, AbstractArgument*> m_ShortNameMap;
};
#pragma region Argument Presets
/**
* @brief Arithmetic (integral, floating point. except bool) type argument
* @tparam _Ty The internal stored type belongs to arithmetic type.
*/
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
class NumberArgument : public AbstractArgument {
public:
/**
* @brief Constructor an arithmetic argument
* @param[in] long_name The long name of this argument. nullptr if no long name.
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
* @param[in] is_optional True if this argument is optional argument.
* @param[in] constraint The constraint applied to this argument.
* @exception std::invalid_argument Given short name or long name are invalid.
*/
NumberArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
bool is_optional = false,
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
virtual ~NumberArgument() {}
YYCC_DEL_CLS_COPY_MOVE(NumberArgument);
public:
/// @brief Get stored data in argument.
_Ty Get() const {
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
return m_Data;
}
protected:
virtual bool Parse(ArgumentList& al) override {
// try get corresponding value
yycc_u8string strval;
al.Next();
if (al.IsEOF() || !al.IsValue(&strval)) {
al.Prev();
return false;
}
// try parsing value
if (!YYCC::ParserHelper::TryParse<_Ty>(strval, m_Data)) return false;
// check constraint
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
// okey
return true;
}
virtual void Reset() override {
std::memset(&m_Data, 0, sizeof(m_Data));
}
protected:
_Ty m_Data;
Constraints::Constraint<_Ty> m_Constraint;
};
/**
* @brief A simple switch type argument which do not store any value.
*/
class SwitchArgument : public AbstractArgument {
public:
/**
* @brief Constructor an switch argument
* @param[in] long_name The long name of this argument. nullptr if no long name.
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
* @exception std::invalid_argument Given short name or long name are invalid.
*/
SwitchArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description = nullptr) :
// bool switch must be optional, because it is false if no given switch.
// bool switch doesn't have argument, so it doesn't have example property.
AbstractArgument(long_name, short_name, description, nullptr, true) {}
virtual ~SwitchArgument() {}
YYCC_DEL_CLS_COPY_MOVE(SwitchArgument);
protected:
virtual bool Parse(ArgumentList& al) override { return true; } // simply return true because no value to store.
virtual void Reset() override {} // nothing need to be reset.
};
/// @brief String type argument
class StringArgument : public AbstractArgument {
public:
/**
* @brief Constructor a string argument
* @param[in] long_name The long name of this argument. nullptr if no long name.
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
* @param[in] is_optional True if this argument is optional argument.
* @param[in] constraint The constraint applied to this argument.
* @exception std::invalid_argument Given short name or long name are invalid.
*/
StringArgument(
const yycc_char8_t* long_name, yycc_char8_t short_name,
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
bool is_optional = false,
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
virtual ~StringArgument() {}
YYCC_DEL_CLS_COPY_MOVE(StringArgument);
public:
/// @brief Get stored data in argument.
const yycc_u8string& Get() const {
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
return m_Data;
}
protected:
virtual bool Parse(ArgumentList& al) override {
// try get corresponding value
al.Next();
if (al.IsEOF() || !al.IsValue(&m_Data)) {
al.Prev();
return false;
}
// check constraint
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
// okey
return true;
}
virtual void Reset() override {
m_Data.clear();
}
protected:
yycc_u8string m_Data;
Constraints::Constraint<yycc_u8string> m_Constraint;
};
#pragma endregion
}

View File

@@ -0,0 +1,186 @@
#include "ConfigManager.hpp"
#include "EncodingHelper.hpp"
#include "IOHelper.hpp"
#include "EnumHelper.hpp"
#include <stdexcept>
namespace YYCC::ConfigManager {
#pragma region Abstract Setting
AbstractSetting::AbstractSetting(const yycc_u8string_view& name) : m_Name(name), m_RawData() {
if (m_Name.empty())
throw std::invalid_argument("the name of setting should not be empty");
}
AbstractSetting::~AbstractSetting() {}
const yycc_u8string& AbstractSetting::GetName() const { return m_Name; }
void AbstractSetting::ResizeData(size_t new_size) { m_RawData.resize(new_size); }
const void* AbstractSetting::GetDataPtr() const { return m_RawData.data(); }
void* AbstractSetting::GetDataPtr() { return m_RawData.data(); }
size_t AbstractSetting::GetDataSize() const { return m_RawData.size(); }
#pragma endregion
#pragma region Core Manager
CoreManager::CoreManager(
const yycc_u8string_view& cfg_file_path,
uint64_t version_identifier,
std::initializer_list<AbstractSetting*> settings) :
m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
// Mark: no need to check cfg file path
// it will be checked at creating file handle
// assign settings
for (auto* setting : settings) {
auto result = m_Settings.try_emplace(setting->GetName(), setting);
if (!result.second) {
// if not inserted because duplicated, raise exception
throw std::invalid_argument("Duplicated setting name");
}
}
}
ConfigLoadResult CoreManager::Load() {
// prepare result variables
ConfigLoadResult ret = ConfigLoadResult::OK;
// reset all settings first
Reset();
// get file handle
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("rb")));
if (fs.get() == nullptr) {
// if we fail to get, it means that we do not have corresponding cfg file.
// all settings should be reset to default value.
ret = ConfigLoadResult::Created;
return ret;
}
// fetch version info
uint64_t version_info;
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
ret = ConfigLoadResult::Created;
return ret;
}
// check version
// if read version is greater than we expected,
// it means that this cfg file is created by the program higer than this.
// we should not read anything from it.
// however, for compaitibility reason, we allow read old cfg data.
if (version_info > m_VersionIdentifier) {
ret = ConfigLoadResult::ForwardNew;
return ret;
} else if (version_info < m_VersionIdentifier) {
EnumHelper::Add(ret, ConfigLoadResult::Migrated);
}
// fetch setting item from file
yycc_u8string name_cache;
while (true) {
// try fetch setting name
// fetch name length
size_t name_length;
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
// we also check whether reach EOF at there.
if (std::feof(fs.get())) break;
else {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
}
// fetch name body
name_cache.resize(name_length);
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get setting data length
size_t data_length;
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get matched setting first
const auto& found = m_Settings.find(name_cache);
if (found != m_Settings.end()) {
// found. read data for it
found->second->ResizeData(data_length);
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// call user defined load function
// if fail to parse, reset to default value
if (!found->second->UserLoad()) {
EnumHelper::Add(ret, ConfigLoadResult::ItemError);
found->second->UserReset();
}
} else {
// fail to find. skip this unknown setting
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
}
}
return ret;
}
bool CoreManager::Save() {
// get file handle
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("wb")));
// if we fail to get, return false.
if (fs == nullptr) return false;
// write config data
uint64_t version_info = m_VersionIdentifier;
if (std::fwrite(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info))
return false;
// iterate all data for writing
for (const auto& pair : m_Settings) {
// do user defined save
// if failed, skip this setting
if (!pair.second->UserSave())
continue;
// write setting name
// write name length
size_t name_length = pair.first.size();
if (std::fwrite(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length))
return false;
// write name body
if (std::fwrite(pair.first.c_str(), 1u, name_length, fs.get()) != name_length)
return false;
// write setting daat
// write data length
size_t data_length = pair.second->GetDataSize();
if (std::fwrite(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length))
return false;
// write data body
if (std::fwrite(pair.second->GetDataPtr(), 1u, data_length, fs.get()) != data_length)
return false;
}
// all settings done, return true
return true;
}
void CoreManager::Reset() {
for (const auto& pair : m_Settings) {
pair.second->UserReset();
}
}
#pragma endregion
}

View File

@@ -0,0 +1,269 @@
#pragma once
#include "YYCCInternal.hpp"
#include "Constraints.hpp"
#include <memory>
#include <vector>
#include <map>
#include <initializer_list>
#include <type_traits>
#include <algorithm>
#include <functional>
#include <stdexcept>
#include <cstring>
/**
* @brief Universal configuration manager
* @details For how to use this namespace, please see \ref config_manager.
*/
namespace YYCC::ConfigManager {
/**
* @brief The load result of loading config.
*/
enum class ConfigLoadResult {
OK = 0, ///< Success load configs.
Created = 1 << 0, ///< Given file is not existing, we create all configs in default values.
ForwardNew = 1 << 1, ///< Detect the config file created by higher version. We create all configs in default values.
Migrated = 1 << 2, ///< Detect the config file created by lower version. We try migrate configs written in it.
BrokenFile = 1 << 3, ///< Given file has bad format. Thus some configs are kept as its default values.
ItemError = 1 << 4 ///< Some config can not be recognized from the data read from file so they are reset to default value.
};
using UnderlyingConfigLoadResult_t = std::underlying_type_t<ConfigLoadResult>;
/// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential functions to create custom setting.
class AbstractSetting {
friend class CoreManager;
public:
/**
* @brief Construct a setting
* @param[in] name The name of this setting.
* @exception std::invalid_argument Name of setting is empty.
*/
AbstractSetting(const yycc_u8string_view& name);
virtual ~AbstractSetting();
YYCC_DEL_CLS_COPY_MOVE(AbstractSetting);
// Name interface
public:
/// @brief Get name of this setting.
/// @details Name was used in storing setting in file.
const yycc_u8string& GetName() const;
private:
yycc_u8string m_Name;
// User Implementations
protected:
/// @brief User implemented custom load function
/// @remarks
/// In this function, programmer should read data from internal buffer
/// and store it to its own another internal variables.
/// @return True if success, otherwise false.
virtual bool UserLoad() = 0;
/// @brief User implemented custom save function
/// @remarks
/// In this function, programmer should write data,
/// which is stored in another variavle by it own, to internal buffer.
/// @return True if success, otherwise false.
virtual bool UserSave() = 0;
/// @brief User implemented custom reset function
/// @remarks In this function, programmer should reset its internal variable to default value.
virtual void UserReset() = 0;
// Buffer related functions
protected:
/// @brief Resize internal buffer to given size.
/// @remarks It is usually used in UserSave.
/// @param[in] new_size The new size of internal buffer.
void ResizeData(size_t new_size);
/// @brief Get data pointer to internal buffer.
/// @remarks It is usually used in UserLoad.
const void* GetDataPtr() const;
/// @brief Get mutable data pointer to internal buffer.
/// @remarks It is usually used in UserSave.
void* GetDataPtr();
/// @brief Get the length of internal buffer.
size_t GetDataSize() const;
private:
std::vector<uint8_t> m_RawData;
};
/// @brief Settings manager and config file reader writer.
class CoreManager {
public:
/**
* @brief Build core manager.
* @param[in] cfg_file_path The path to config file.
* @param[in] version_identifier The identifier of version. Higher is newer. Lower config will try doing migration.
* @param[in] settings An initializer list containing pointers to all managed settings.
*/
CoreManager(
const yycc_u8string_view& cfg_file_path,
uint64_t version_identifier,
std::initializer_list<AbstractSetting*> settings);
~CoreManager() {}
YYCC_DEL_CLS_COPY_MOVE(CoreManager);
// Core functions
public:
/// @brief Load settings from file.
/// @details Before loading, all settings will be reset to default value first.
/// @return What happend when loading config. This function always success.
ConfigLoadResult Load();
/// @brief Save settings to file.
/// @return True if success, otherwise false.
bool Save();
/// @brief Reset all settings to default value.
void Reset();
private:
yycc_u8string m_CfgFilePath;
uint64_t m_VersionIdentifier;
std::map<yycc_u8string, AbstractSetting*> m_Settings;
};
#pragma region Setting Presets
/**
* @brief Arithmetic (integral, floating point, bool) and enum type setting
* @tparam _Ty The internal stored type belongs to arithmetic type.
*/
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> || std::is_enum_v<_Ty>, int> = 0>
class NumberSetting : public AbstractSetting {
public:
/**
* @brief Construct arithmetic type setting.
* @param[in] name The name of this setting.
* @param[in] default_value The default value of this setting.
* @param[in] constraint The constraint applied to this setting.
* @exception std::invalid_argument Name of setting is empty.
*/
NumberSetting(
const yycc_u8string_view& name, _Ty default_value,
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {}
virtual ~NumberSetting() {}
YYCC_DEL_CLS_COPY_MOVE(NumberSetting);
/// @brief Get stored data in setting.
_Ty Get() const { return m_Data; }
/**
* @brief Set data to setting.
* @param[in] new_data The new data.
* @return True if success, otherwise false (given value is invalid)
*/
bool Set(_Ty new_data) {
// validate data
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data))
return false;
// assign data
m_Data = new_data;
return true;
}
protected:
virtual bool UserLoad() override {
// read data
if (sizeof(m_Data) != GetDataSize())
return false;
m_Data = *reinterpret_cast<const _Ty*>(GetDataPtr());
// check data
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
return true;
}
virtual bool UserSave() override {
// write data
ResizeData(sizeof(m_Data));
*reinterpret_cast<_Ty*>(GetDataPtr()) = m_Data;
return true;
}
virtual void UserReset() override {
m_Data = m_DefaultData;
}
_Ty m_Data, m_DefaultData;
Constraints::Constraint<_Ty> m_Constraint;
};
/// @brief String type setting
class StringSetting : public AbstractSetting {
public:
/**
* @brief Construct string setting
* @param[in] name The name of this setting.
* @param[in] default_value The default value of this setting.
* @param[in] constraint The constraint applied to this setting.
* @exception std::invalid_argument Name of setting is empty.
*/
StringSetting(
const yycc_u8string_view& name, const yycc_u8string_view& default_value,
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) {
m_Data = default_value;
m_DefaultData = default_value;
}
virtual ~StringSetting() {}
YYCC_DEL_CLS_COPY_MOVE(StringSetting);
/// @brief Get reference to stored string.
const yycc_u8string& Get() const { return m_Data; }
/**
* @brief Set string data to setting.
* @param[in] new_data The new string data.
* @return True if success, otherwise false (given value is invalid)
*/
bool Set(const yycc_u8string_view& new_data) {
// check data validation
yycc_u8string new_data_cache(new_data);
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data_cache))
return false;
// assign data
m_Data = std::move(new_data_cache);
return true;
}
protected:
virtual bool UserLoad() override {
// read string length
size_t string_length;
if (GetDataSize() < sizeof(string_length))
return false;
string_length = *reinterpret_cast<const size_t*>(GetDataPtr());
// read string body
if (GetDataSize() != sizeof(string_length) + string_length)
return false;
m_Data.assign(
reinterpret_cast<const yycc_char8_t*>(static_cast<const uint8_t*>(GetDataPtr()) + sizeof(string_length)),
string_length
);
// check data
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
return false;
return true;
}
virtual bool UserSave() override {
// allocate result buffer
size_t string_length = m_Data.size();
ResizeData(sizeof(string_length) + string_length);
// get pointer
uint8_t* ptr = static_cast<uint8_t*>(GetDataPtr());
// assign string length
*reinterpret_cast<size_t*>(ptr) = string_length;
// assign string body
std::memcpy(ptr + sizeof(string_length), m_Data.data(), string_length);
return true;
}
virtual void UserReset() override {
m_Data = m_DefaultData;
}
yycc_u8string m_Data, m_DefaultData;
Constraints::Constraint<yycc_u8string> m_Constraint;
};
#pragma endregion
}

View File

@@ -0,0 +1,282 @@
#include "ConsoleHelper.hpp"
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"
#include <iostream>
// Include Windows used headers in Windows.
#if defined(YYCC_OS_WINDOWS)
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <io.h>
#include <fcntl.h>
#include "WinImportSuffix.hpp"
#endif
namespace YYCC::ConsoleHelper {
#pragma region Windows Specific Functions
#if defined(YYCC_OS_WINDOWS)
static bool RawEnableColorfulConsole(FILE* fs) {
if (!_isatty(_fileno(fs))) return false;
HANDLE h_output;
DWORD dw_mode;
h_output = (HANDLE)_get_osfhandle(_fileno(fs));
if (!GetConsoleMode(h_output, &dw_mode)) return false;
if (!SetConsoleMode(h_output, dw_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT)) return false;
return true;
}
/*
Reference:
* https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
* https://stackoverflow.com/questions/69830460/reading-utf-8-input
There is 3 way to make Windows console enable UTF8 mode.
First one is calling SetConsoleCP and SetConsoleOutputCP.
The side effect of this is std::cin and std::cout is broken,
however there is a patch for this issue.
Second one is calling _set_mode with _O_U8TEXT or _O_U16TEXT to enable Unicode mode for Windows console.
This also have side effect which is stronger than first one.
All puts family functions (ASCII-based output functions) will throw assertion exception.
You only can use putws family functions (wide-char-based output functions).
However these functions can not be used without calling _set_mode in Windows design.
There still is another method, using WriteConsoleW directly visiting console.
This function family can output correct string without calling any extra functions!
This method is what we adopted.
*/
template<bool _bIsConsole>
static yycc_u8string WinConsoleRead(HANDLE hStdIn) {
using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>;
// Prepare an internal buffer because the read data may not be fully used.
// For example, we may read x\ny in a single calling but after processing \n, this function will return
// so y will temporarily stored in this internal buffer for next using.
// Thus this function is not thread safe.
static std::basic_string<_TChar> internal_buffer;
// create return value buffer
std::basic_string<_TChar> return_buffer;
// Prepare some variables
DWORD dwReadNumberOfChars;
_TChar szReadChars[64];
size_t eol_pos;
// try fetching EOL
while (true) {
// if internal buffer is empty,
// try fetching it.
if (internal_buffer.empty()) {
// console and non-console use different method to read.
if constexpr (_bIsConsole) {
// console handle, use ReadConsoleW.
// read from console, the read data is wchar based
if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(_TChar), &dwReadNumberOfChars, NULL))
break;
} else {
// anything else, use ReadFile instead.
// the read data is utf8 based
if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL))
break;
}
// send to internal buffer
if (dwReadNumberOfChars == 0) break;
internal_buffer.append(szReadChars, dwReadNumberOfChars);
}
// try finding EOL in internal buffer
if constexpr (std::is_same_v<_TChar, char>) eol_pos = internal_buffer.find_first_of('\n');
else eol_pos = internal_buffer.find_first_of(L'\n');
// check finding result
if (eol_pos == std::wstring::npos) {
// the whole string do not include EOL, fully appended to return value
return_buffer += internal_buffer;
internal_buffer.clear();
// need more data, continue while
} else {
// split result
// push into result and remain some in internal buffer.
return_buffer.append(internal_buffer, 0u, eol_pos);
internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place.
// break while mean success finding
break;
}
}
// post-process for return value
yycc_u8string real_return_buffer;
if constexpr (_bIsConsole) {
// console mode need convert wchar to utf8
YYCC::EncodingHelper::WcharToUTF8(return_buffer, real_return_buffer);
} else {
// non-console just copt the result
real_return_buffer = EncodingHelper::ToUTF8(return_buffer);
}
// every mode need delete \r words
YYCC::StringHelper::Replace(real_return_buffer, YYCC_U8("\r"), YYCC_U8(""));
// return value
return real_return_buffer;
}
static void WinConsoleWrite(const yycc_u8string& strl, bool to_stderr) {
// Prepare some Win32 variables
// fetch stdout handle first
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
DWORD dwConsoleMode;
DWORD dwWrittenNumberOfChars;
// if stdout was redirected, this handle may point to a file handle or anything else,
// WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
// console handle, use WriteConsoleW.
// convert utf8 string to wide char first
std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl));
size_t wstrl_size = wstrl.size();
// write string with size check
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(wstrl_size), &dwWrittenNumberOfChars, NULL);
}
} else {
// anything else, use WriteFile instead.
// WriteFile do not need extra convertion, because it is direct writing.
// check whether string length is overflow
size_t strl_size = strl.size() * sizeof(yycc_u8string::value_type);
// write string with size check
if (strl_size <= std::numeric_limits<DWORD>::max()) {
WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
}
}
}
#endif
#pragma endregion
bool EnableColorfulConsole() {
#if defined(YYCC_OS_WINDOWS)
bool ret = true;
ret &= RawEnableColorfulConsole(stdout);
ret &= RawEnableColorfulConsole(stderr);
return ret;
#else
// just return true and do nothing
return true;
#endif
}
yycc_u8string ReadLine() {
#if defined(YYCC_OS_WINDOWS)
// get stdin mode
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
// use different method to get according to whether stdin is redirected
DWORD dwConsoleMode;
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
return WinConsoleRead<true>(hStdIn);
} else {
return WinConsoleRead<false>(hStdIn);
}
#else
// in linux, directly use C++ function to fetch.
std::string cmd;
if (std::getline(std::cin, cmd).fail()) cmd.clear();
return EncodingHelper::ToUTF8(cmd);
#endif
}
template<bool bNeedFmt, bool bIsErr, bool bHasEOL>
static void RawWrite(const yycc_char8_t* u8_fmt, va_list argptr) {
// Buiild string need to be written first
// If no format string or plain string for writing, return.
if (u8_fmt == nullptr) return;
// Build or simply copy string
yycc_u8string strl;
if constexpr (bNeedFmt) {
// treat as format string
va_list argcpy;
va_copy(argcpy, argptr);
strl = YYCC::StringHelper::VPrintf(u8_fmt, argcpy);
va_end(argcpy);
} else {
// treat as plain string
strl = u8_fmt;
}
// Checkout whether add EOL
if constexpr (bHasEOL) {
strl += YYCC_U8("\n");
}
#if defined(YYCC_OS_WINDOWS)
// call Windows specific writer
WinConsoleWrite(strl, bIsErr);
#else
// in linux, directly use C function to write.
std::fputs(EncodingHelper::ToOrdinary(strl.c_str()), bIsErr ? stderr : stdout);
#endif
}
void Format(const yycc_char8_t* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
RawWrite<true, false, false>(u8_fmt, argptr);
va_end(argptr);
}
void FormatLine(const yycc_char8_t* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
RawWrite<true, false, true>(u8_fmt, argptr);
va_end(argptr);
}
void Write(const yycc_char8_t* u8_strl) {
va_list empty{};
RawWrite<false, false, false>(u8_strl, empty);
}
void WriteLine(const yycc_char8_t* u8_strl) {
va_list empty{};
RawWrite<false, false, true>(u8_strl, empty);
}
void ErrFormat(const yycc_char8_t* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
RawWrite<true, true, false>(u8_fmt, argptr);
va_end(argptr);
}
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
RawWrite<true, true, true>(u8_fmt, argptr);
va_end(argptr);
}
void ErrWrite(const yycc_char8_t* u8_strl) {
va_list empty{};
RawWrite<false, true, false>(u8_strl, empty);
}
void ErrWriteLine(const yycc_char8_t* u8_strl) {
va_list empty{};
RawWrite<false, true, true>(u8_strl, empty);
}
}

View File

@@ -0,0 +1,92 @@
#pragma once
#include "YYCCInternal.hpp"
#include <cstdio>
#include <string>
/**
* @brief The helper providing universal C\# style console function and other console related stuff
* @details
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
*/
namespace YYCC::ConsoleHelper {
/**
* @brief Enable console color support for Windows.
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
* @return True if success, otherwise false.
* @remarks
* This function only works on Windows and do nothing on other platforms such as Linux,
* because we assume all terminals existing on other platform support color feature as default.
*/
bool EnableColorfulConsole();
/**
* @brief Reads the next line of UTF8 characters from the standard input stream.
* @return
* The next line of UTF8 characters from the input stream.
* Empty string if user just press Enter key or function failed.
*/
yycc_u8string ReadLine();
/**
* @brief
* Writes the text representation of the specified object
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void Format(const yycc_char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void FormatLine(const yycc_char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void Write(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void WriteLine(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the text representation of the specified object
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void ErrFormat(const yycc_char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ErrWrite(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ErrWriteLine(const yycc_char8_t* u8_strl);
}

View File

@@ -0,0 +1,378 @@
#include "DialogHelper.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"
namespace YYCC::DialogHelper {
#pragma region FileFilters
bool FileFilters::Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il) {
// assign filter name
if (filter_name == nullptr) return false;
FilterName name(filter_name);
// assign filter patterns
FilterModes modes;
for (const yycc_char8_t* pattern : il) {
if (pattern != nullptr)
modes.emplace_back(yycc_u8string(pattern));
}
// check filter patterns
if (modes.empty()) return false;
// add into pairs and return
m_Filters.emplace_back(std::make_pair(std::move(name), std::move(modes)));
return true;
}
bool FileFilters::Generate(WinFileFilters& win_result) const {
// clear Windows oriented data
win_result.Clear();
// build new Windows oriented string vector first
for (const auto& it : m_Filters) {
// convert name to wchar
WinFileFilters::WinFilterName name;
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first, name))
return false;
// convert pattern and join them
const auto& filter_modes = it.second;
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";")));
WinFileFilters::WinFilterModes modes;
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes))
return false;
// append new pair
win_result.m_WinFilters.emplace_back(std::make_pair(name, modes));
}
// check filter size
// if it overflow the maximum value, return false
size_t count = win_result.m_WinFilters.size();
if (count > std::numeric_limits<UINT>::max())
return false;
// create new win data struct
// and assign string pointer from internal built win string vector.
win_result.m_WinDataStruct.reset(new COMDLG_FILTERSPEC[count]);
for (size_t i = 0u; i < count; ++i) {
win_result.m_WinDataStruct[i].pszName = win_result.m_WinFilters[i].first.c_str();
win_result.m_WinDataStruct[i].pszSpec = win_result.m_WinFilters[i].second.c_str();
}
// everything is okey
return true;
}
#pragma endregion
#pragma region File Dialog
bool FileDialog::Generate(WinFileDialog& win_result) const {
// clear Windows oriented data
win_result.Clear();
// set owner
win_result.m_WinOwner = m_Owner;
// build file filters
if (!m_FileTypes.Generate(win_result.m_WinFileTypes))
return false;
// check default file type index
// check value overflow (comparing with >= because we need plus 1 for file type index later)
if (m_DefaultFileTypeIndex >= std::numeric_limits<UINT>::max())
return false;
// check invalid index (overflow the length or registered file types if there is file type)
if (m_FileTypes.Count() != 0u && m_DefaultFileTypeIndex >= m_FileTypes.Count())
return false;
// set index with additional plus according to Windows specification.
win_result.m_WinDefaultFileTypeIndex = static_cast<UINT>(m_DefaultFileTypeIndex + 1);
// build title and init file name
if (m_HasTitle) {
if (!YYCC::EncodingHelper::UTF8ToWchar(m_Title, win_result.m_WinTitle))
return false;
win_result.m_HasTitle = true;
}
if (m_HasInitFileName) {
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitFileName, win_result.m_WinInitFileName))
return false;
win_result.m_HasInitFileName = true;
}
// fetch init directory
if (m_HasInitDirectory) {
// convert to wpath
std::wstring w_init_directory;
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitDirectory, w_init_directory))
return false;
// fetch IShellItem*
// Ref: https://stackoverflow.com/questions/76306324/how-to-set-default-folder-for-ifileopendialog-interface
IShellItem* init_directory = NULL;
HRESULT hr = SHCreateItemFromParsingName(w_init_directory.c_str(), NULL, IID_PPV_ARGS(&init_directory));
if (FAILED(hr)) return false;
// assign IShellItem*
win_result.m_WinInitDirectory.reset(init_directory);
}
// everything is okey
return true;
}
#pragma endregion
#pragma region Windows Dialog Code
enum class CommonFileDialogType {
OpenFile,
OpenMultipleFiles,
SaveFile,
OpenFolder
};
/**
* @brief Extract display name from given IShellItem*.
* @param item[in] The pointer to IShellItem for extracting.
* @param ret[out] Extracted display name container.
* @return True if success, otherwise false.
* @remarks This is an assist function of CommonFileDialog.
*/
static bool ExtractDisplayName(IShellItem* item, yycc_u8string& ret) {
// fetch display name from IShellItem*
LPWSTR _name;
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
if (FAILED(hr)) return false;
COMHelper::SmartLPWSTR display_name(_name);
// convert result
if (!YYCC::EncodingHelper::WcharToUTF8(display_name.get(), ret))
return false;
// finished
return true;
}
/**
* @brief General file dialog.
* @param params[in] User specified parameter controlling the behavior of this file dialog,
* including title, file types and etc.
* @param ret[out] The path to user selected files or folders.
* For multiple selection, the count of items >= 1. For other scenario, the count of item is 1.
* @return True if success, otherwise false (input parameters is wrong or user click "Cancel" in popup window).
* @remarks This function is the real underlying function of all dialog functions.
*/
template<CommonFileDialogType EDialogType>
static bool CommonFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
// prepare result variable
HRESULT hr;
// check whether COM environment has been initialized
if (!COMHelper::IsInitialized()) return false;
// create file dialog instance
// fetch dialog CLSID first
CLSID dialog_clsid;
switch (EDialogType) {
case CommonFileDialogType::OpenFile:
case CommonFileDialogType::OpenMultipleFiles:
case CommonFileDialogType::OpenFolder:
dialog_clsid = CLSID_FileOpenDialog;
break;
case CommonFileDialogType::SaveFile:
dialog_clsid = CLSID_FileSaveDialog;
break;
default:
return false;
}
// create raw dialog pointer
IFileDialog* _pfd = nullptr;
hr = CoCreateInstance(
dialog_clsid,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&_pfd)
);
if (FAILED(hr)) return false;
// create memory-safe dialog pointer
COMHelper::SmartIFileDialog pfd(_pfd);
// set options for dialog
// before setting, always get the options first in order.
// not to override existing options.
DWORD dwFlags;
hr = pfd->GetOptions(&dwFlags);
if (FAILED(hr)) return false;
// modify options
switch (EDialogType) {
// We want user only can pick file system files: FOS_FORCEFILESYSTEM.
// Open dialog default: FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR
// Save dialog default: FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR
// Pick folder: FOS_PICKFOLDERS
case CommonFileDialogType::OpenFile:
dwFlags |= FOS_FORCEFILESYSTEM;
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
break;
case CommonFileDialogType::OpenMultipleFiles:
dwFlags |= FOS_FORCEFILESYSTEM;
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
dwFlags |= FOS_ALLOWMULTISELECT;
break;
case CommonFileDialogType::SaveFile:
dwFlags |= FOS_FORCEFILESYSTEM;
dwFlags |= FOS_OVERWRITEPROMPT | FOS_NOREADONLYRETURN | FOS_PATHMUSTEXIST | FOS_NOCHANGEDIR;
break;
case CommonFileDialogType::OpenFolder:
dwFlags |= FOS_FORCEFILESYSTEM;
dwFlags |= FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
dwFlags |= FOS_PICKFOLDERS;
break;
default:
return false;
}
// set folder dialog options
hr = pfd->SetOptions(dwFlags);
if (FAILED(hr)) return false;
// build Windows used file dialog parameters
WinFileDialog win_params;
if (!params.Generate(win_params))
return false;
// setup title and init file name
if (win_params.HasTitle()) {
hr = pfd->SetTitle(win_params.GetTitle());
if (FAILED(hr)) return false;
}
if (win_params.HasInitFileName()) {
hr = pfd->SetFileName(win_params.GetInitFileName());
if (FAILED(hr)) return false;
}
// setup init directory
if (win_params.HasInitDirectory()) {
hr = pfd->SetFolder(win_params.GetInitDirectory());
}
// set file types and default file index when we picking file
if constexpr (EDialogType != CommonFileDialogType::OpenFolder) {
// set file types list
const auto& file_filters = win_params.GetFileTypes();
hr = pfd->SetFileTypes(file_filters.GetFilterCount(), file_filters.GetFilterSpecs());
if (FAILED(hr)) return false;
// set default file type index
hr = pfd->SetFileTypeIndex(win_params.GetDefaultFileTypeIndex());
if (FAILED(hr)) return false;
}
// show the dialog
hr = pfd->Show(win_params.HasOwner() ? win_params.GetOwner() : nullptr);
if (FAILED(hr)) return false;
// obtain result when user click "OK" button.
switch (EDialogType) {
case CommonFileDialogType::OpenFile:
case CommonFileDialogType::OpenFolder:
case CommonFileDialogType::SaveFile:
{
// obtain one file entry
IShellItem* _item;
hr = pfd->GetResult(&_item);
if (FAILED(hr)) return false;
COMHelper::SmartIShellItem result_item(_item);
// extract display name
yycc_u8string result_name;
if (!ExtractDisplayName(result_item.get(), result_name))
return false;
// append result
ret.emplace_back(std::move(result_name));
}
break;
case CommonFileDialogType::OpenMultipleFiles:
{
// try casting file dialog to file open dialog
// Ref: https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface
IFileOpenDialog* _pfod = nullptr;
hr = pfd->QueryInterface(IID_PPV_ARGS(&_pfod));
if (FAILED(hr)) return false;
COMHelper::SmartIFileOpenDialog pfod(_pfod);
// obtain multiple file entires
IShellItemArray* _items;
hr = pfod->GetResults(&_items);
if (FAILED(hr)) return false;
COMHelper::SmartIShellItemArray result_items(_items);
// analyze file entries
// get array count first
DWORD result_items_count = 0u;
hr = result_items->GetCount(&result_items_count);
if (FAILED(hr)) return false;
// iterate array
for (DWORD i = 0u; i < result_items_count; ++i) {
// fetch item by index
IShellItem* _item;;
hr = result_items->GetItemAt(i, &_item);
if (FAILED(hr)) return false;
COMHelper::SmartIShellItem result_item(_item);
// extract display name
yycc_u8string result_name;
if (!ExtractDisplayName(result_item.get(), result_name))
return false;
// append result
ret.emplace_back(std::move(result_name));
}
}
break;
default:
return false;
}
// everything is okey
return true;
}
#pragma endregion
#pragma region Wrapper Functions
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret) {
std::vector<yycc_u8string> cache;
bool isok = CommonFileDialog<CommonFileDialogType::OpenFile>(params, cache);
if (isok) ret = cache.front();
return isok;
}
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
return CommonFileDialog<CommonFileDialogType::OpenMultipleFiles>(params, ret);
}
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret) {
std::vector<yycc_u8string> cache;
bool isok = CommonFileDialog<CommonFileDialogType::SaveFile>(params, cache);
if (isok) ret = cache.front();
return isok;
}
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret) {
std::vector<yycc_u8string> cache;
bool isok = CommonFileDialog<CommonFileDialogType::OpenFolder>(params, cache);
if (isok) ret = cache.front();
return isok;
}
#pragma endregion
}
#endif

View File

@@ -0,0 +1,312 @@
#pragma once
#include "YYCCInternal.hpp"
#if defined(YYCC_OS_WINDOWS)
#include "COMHelper.hpp"
#include <string>
#include <vector>
#include <initializer_list>
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include <shlobj_core.h>
#include "WinImportSuffix.hpp"
/**
* @brief The namespace providing Windows universal dialog features.
* @details
* This namespace only available on Windows platform.
* See also \ref dialog_helper.
*/
namespace YYCC::DialogHelper {
/**
* @brief The class representing the file types region in file dialog.
* @details
* This class is served for Windows used.
* Programmer should \b not create this class manually.
*/
class WinFileFilters {
friend class FileFilters;
friend class WinFileDialog;
public:
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
YYCC_DEL_CLS_COPY_MOVE(WinFileFilters);
/// @brief Get the count of available file filters
UINT GetFilterCount() const {
return static_cast<UINT>(m_WinFilters.size());
}
/// @brief Get pointer to Windows used file filters declarations
const COMDLG_FILTERSPEC* GetFilterSpecs() const {
return m_WinDataStruct.get();
}
protected:
using WinFilterModes = std::wstring;
using WinFilterName = std::wstring;
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
std::vector<WinFilterPair> m_WinFilters;
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
/// @brief Clear all current file filters
void Clear() {
m_WinDataStruct.reset();
m_WinFilters.clear();
}
};
/**
* @brief The class representing the file types region in file dialog.
* @details
* This class is served for programmer using.
* But you don't need create it on your own.
* You can simply fetch it by FileDialog::ConfigreFileTypes ,
* because this class is a part of FileDialog.
*/
class FileFilters {
public:
FileFilters() : m_Filters() {}
YYCC_DEL_CLS_COPY_MOVE(FileFilters);
/**
* @brief Add a filter pair in file types list.
* @param[in] filter_name The friendly name of the filter.
* @param[in] il
* A C++ initialize list containing acceptable file filter pattern.
* Every entries must be `const yycc_char8_t*` representing a single filter pattern.
* The list at least should have one valid pattern.
* This function will not validate these filter patterns, so please write them carefully.
* @return True if added success, otherwise false.
* @remarks
* This function allow you register multiple filter patterns for single friendly name.
* For example: <TT>Add(u8"Microsoft Word (*.doc; *.docx)", {u8"*.doc", u8"*.docx"})</TT>
*/
bool Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il);
/**
* @brief Get the count of added filter pairs.
* @return The count of already added filter pairs.
*/
size_t Count() const { return m_Filters.size(); }
/// @brief Clear filter pairs for following re-use.
void Clear() { m_Filters.clear(); }
/**
* @brief Generate Windows dialog system used data struct.
* @param[out] win_result The class receiving the generated filter data struct.
* @return True if generation success, otherwise false.
* @remarks
* Programmer should not call this function,
* this function is used as YYCC internal code.
*/
bool Generate(WinFileFilters& win_result) const;
protected:
using FilterModes = std::vector<yycc_u8string>;
using FilterName = yycc_u8string;
using FilterPair = std::pair<FilterName, FilterModes>;
std::vector<FilterPair> m_Filters;
};
/**
* @brief The class representing the file dialog.
* @details
* This class is served for Windows used.
* Programmer should \b not create this class manually.
*/
class WinFileDialog {
friend class FileDialog;
public:
WinFileDialog() :
m_WinOwner(NULL),
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
m_WinInitDirectory(nullptr) {}
YYCC_DEL_CLS_COPY_MOVE(WinFileDialog);
/// @brief Get whether this dialog has owner.
bool HasOwner() const { return m_WinOwner != NULL; }
/// @brief Get the \c HWND of dialog owner.
HWND GetOwner() const { return m_WinOwner; }
/// @brief Get the struct holding Windows used file filters data.
const WinFileFilters& GetFileTypes() const { return m_WinFileTypes; }
/// @brief Get the index of default selected file filter.
UINT GetDefaultFileTypeIndex() const { return m_WinDefaultFileTypeIndex; }
/// @brief Get whether dialog has custom title.
bool HasTitle() const { return m_HasTitle; }
/// @brief Get custom title of dialog.
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
/// @brief Get whether dialog has custom initial file name.
bool HasInitFileName() const { return m_HasInitFileName; }
/// @brief Get custom initial file name of dialog
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); }
/// @brief Get whether dialog has custom initial directory.
bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
/// @brief Get custom initial directory of dialog.
IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); }
protected:
HWND m_WinOwner;
WinFileFilters m_WinFileTypes;
/**
* @brief The default selected file type in dialog
* @remarks
* This is 1-based index according to Windows specification.
* In other words, when generating this struct from FileDialog to this struct this field should plus 1.
* Because the same field located in FileDialog is 0-based index.
*/
UINT m_WinDefaultFileTypeIndex;
bool m_HasTitle, m_HasInitFileName;
std::wstring m_WinTitle, m_WinInitFileName;
COMHelper::SmartIShellItem m_WinInitDirectory;
/// @brief Clear all data and reset them to default value.
void Clear() {
m_WinOwner = nullptr;
m_WinFileTypes.Clear();
m_WinDefaultFileTypeIndex = 0u;
m_HasTitle = m_HasInitFileName = false;
m_WinTitle.clear();
m_WinInitFileName.clear();
m_WinInitDirectory.reset();
}
};
/**
* @brief The class representing the file dialog.
* @details
* This class is served for programming using to describe every aspectes of the dialog.
* For how to use this struct, see \ref dialog_helper.
*/
class FileDialog {
public:
FileDialog() :
m_Owner(NULL),
m_FileTypes(),
m_DefaultFileTypeIndex(0u),
m_Title(), m_InitFileName(), m_InitDirectory(),
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
YYCC_DEL_CLS_COPY_MOVE(FileDialog);
/**
* @brief Set the owner of dialog.
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
*/
void SetOwner(HWND owner) { m_Owner = owner; }
/**
* @brief Set custom title of dialog
* @param[in] title The string pointer to custom title, or nullptr to remove it.
*/
void SetTitle(const yycc_char8_t* title) {
if (m_HasTitle = title != nullptr)
m_Title = title;
}
/**
* @brief Fetch the struct describing file filters for future configuration.
* @return The reference to the struct describing file filters.
*/
FileFilters& ConfigreFileTypes() {
return m_FileTypes;
}
/**
* @brief Set the index of default selected file filter.
* @param[in] idx
* The index to default one.
* This must be a valid index in file filters.
*/
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
/**
* @brief Set the initial file name of dialog
* @details If set, the file name will always be same one when opening dialog.
* @param[in] init_filename String pointer to initial file name, or nullptr to remove it.
*/
void SetInitFileName(const yycc_char8_t* init_filename) {
if (m_HasInitFileName = init_filename != nullptr)
m_InitFileName = init_filename;
}
/**
* @brief Set the initial directory of dialog
* @details If set, the opended directory will always be the same one when opening dialog
* @param[in] init_dir
* String pointer to initial directory.
* Invalid path or nullptr will remove this feature.
*/
void SetInitDirectory(const yycc_char8_t* init_dir) {
if (m_HasInitDirectory = init_dir != nullptr)
m_InitDirectory = init_dir;
}
/// @brief Clear file dialog parameters for following re-use.
void Clear() {
m_Owner = nullptr;
m_HasTitle = m_HasInitFileName = m_HasInitDirectory = false;
m_Title.clear();
m_InitFileName.clear();
m_InitDirectory.clear();
m_FileTypes.Clear();
m_DefaultFileTypeIndex = 0u;
}
/**
* @brief Generate Windows dialog system used data struct.
* @param[out] win_result The class receiving the generated filter data struct.
* @return True if generation is success, otherwise false.
* @remarks
* Programmer should not call this function.
* This function is used as YYCC internal code.
*/
bool Generate(WinFileDialog& win_result) const;
protected:
HWND m_Owner;
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
yycc_u8string m_Title, m_InitFileName, m_InitDirectory;
FileFilters m_FileTypes;
/**
* @brief The default selected file type in dialog
* @remarks
* The index Windows used is 1-based index.
* But for universal experience, we order this is 0-based index.
* And do convertion when generating Windows used struct.
*/
size_t m_DefaultFileTypeIndex;
};
/**
* @brief Open the dialog which order user select single file to open.
* @param[in] params The configuration of dialog.
* @param[out] ret Full path to user selected file.
* @return False if user calcel the operation or something went wrong, otherwise true.
*/
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret);
/**
* @brief Open the dialog which order user select multiple file to open.
* @param[in] params The configuration of dialog.
* @param[out] ret The list of full path of user selected files.
* @return False if user calcel the operation or something went wrong, otherwise true.
*/
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret);
/**
* @brief Open the dialog which order user select single file to save.
* @param[in] params The configuration of dialog.
* @param[out] ret Full path to user selected file.
* @return False if user calcel the operation or something went wrong, otherwise true.
*/
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret);
/**
* @brief Open the dialog which order user select single directory to open.
* @param[in] params The configuration of dialog.
* @param[out] ret Full path to user selected directory.
* @return False if user calcel the operation or something went wrong, otherwise true.
*/
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret);
}
#endif

Some files were not shown because too many files have changed in this diff Show More