Compare commits
70 Commits
a6668dff04
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 422aa152ff | |||
| 92ce0e29cb | |||
| a0f032c28b | |||
| b51ded2101 | |||
| 09f07d99f7 | |||
| c19561cb54 | |||
| 9ad199073a | |||
| 908d48a7c1 | |||
| aa6dd3031a | |||
| 19df293463 | |||
| 71eb0741f6 | |||
| 09fea7e0a3 | |||
| aecf9bb8cc | |||
| 6449ae1977 | |||
| 1c1e709ed1 | |||
| fe4193efa7 | |||
| 746d20a835 | |||
| 8989e909ad | |||
| 718fe426bf | |||
| 1a4074fd98 | |||
| 74027e7297 | |||
| 044c04aa07 | |||
| e161dafac5 | |||
| 4d9487813b | |||
| 7a34057836 | |||
| 17053f4ebf | |||
| de2b927a14 | |||
| a50233ab6e | |||
| 6dfd957ce9 | |||
| 215a8ce8b8 | |||
| 2b6ac98f27 | |||
| c708e1e672 | |||
| e929ba3776 | |||
| 6dbd031e00 | |||
| 337734d340 | |||
| 45f32297da | |||
| 47bb60f0e4 | |||
| 408ea5ef33 | |||
| cc5e6239ba | |||
| a077604c7d | |||
| 7a2edb92b3 | |||
| 9ce1608be0 | |||
| b8f794a879 | |||
| 622d3e0eb1 | |||
| bac1600558 | |||
| 96e5172d7a | |||
| 5993ae59c0 | |||
| fece224ec5 | |||
| e864b0115e | |||
| 8a604ee813 | |||
| b3ace3d820 | |||
| 75442061e9 | |||
| 194f055039 | |||
| fcd0b3364f | |||
| 8a7387c7ff | |||
| 23c2378ebc | |||
| 9369728759 | |||
| 6c9e23f628 | |||
| f49d974a46 | |||
| 6c2dba74d1 | |||
| 19086f44e2 | |||
| 8cd125a4b9 | |||
| 5ff8f2d8cc | |||
| 772bfbeb15 | |||
| 6b29b7f715 | |||
| 6a97b13f66 | |||
| 79e8af89fe | |||
| d64c6669b4 | |||
| f078dd4399 | |||
| e4387439ee |
4
.github/scripts/README.md
vendored
Normal file
4
.github/scripts/README.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# GitHub Action Scripts
|
||||||
|
|
||||||
|
These script files are only used for GitHub Action.
|
||||||
|
These script files should only be executed in their root directory respectively.
|
||||||
17
.github/scripts/gbenchmark/linux.sh
vendored
Normal file
17
.github/scripts/gbenchmark/linux.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create build and install directory
|
||||||
|
mkdir build install
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
cmake --build .
|
||||||
|
cmake --install . --prefix=../install
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Record install directory
|
||||||
|
cd install
|
||||||
|
export benchmark_ROOT=$(pwd)
|
||||||
|
cd ..
|
||||||
17
.github/scripts/gbenchmark/macos.sh
vendored
Normal file
17
.github/scripts/gbenchmark/macos.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create build and install directory
|
||||||
|
mkdir build install
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
cmake --build .
|
||||||
|
cmake --install . --prefix=../install
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Record install directory
|
||||||
|
cd install
|
||||||
|
export benchmark_ROOT=$(pwd)
|
||||||
|
cd ..
|
||||||
17
.github/scripts/gbenchmark/windows.bat
vendored
Normal file
17
.github/scripts/gbenchmark/windows.bat
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
:: Create build and install directory
|
||||||
|
MKDIR build
|
||||||
|
MKDIR install
|
||||||
|
|
||||||
|
:: Build project
|
||||||
|
CD build
|
||||||
|
cmake -A x64 -DCMAKE_CXX_STANDARD=23 -DBENCHMARK_ENABLE_TESTING=OFF ..
|
||||||
|
cmake --build . --config Release
|
||||||
|
cmake --install . --prefix=../install --config Release
|
||||||
|
CD ..
|
||||||
|
|
||||||
|
:: Record install directory
|
||||||
|
CD install
|
||||||
|
SET benchmark_ROOT=%CD%
|
||||||
|
CD ..
|
||||||
17
.github/scripts/gtest/linux.sh
vendored
Normal file
17
.github/scripts/gtest/linux.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create build and install directory
|
||||||
|
mkdir build install
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
cmake --build .
|
||||||
|
cmake --install . --prefix=../install
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Record install directory
|
||||||
|
cd install
|
||||||
|
export GTest_ROOT=$(pwd)
|
||||||
|
cd ..
|
||||||
17
.github/scripts/gtest/macos.sh
vendored
Normal file
17
.github/scripts/gtest/macos.sh
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create build and install directory
|
||||||
|
mkdir build install
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
cmake --build .
|
||||||
|
cmake --install . --prefix=../install
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Record install directory
|
||||||
|
cd install
|
||||||
|
export GTest_ROOT=$(pwd)
|
||||||
|
cd ..
|
||||||
17
.github/scripts/gtest/windows.bat
vendored
Normal file
17
.github/scripts/gtest/windows.bat
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
:: Create build and install directory
|
||||||
|
MKDIR build
|
||||||
|
MKDIR install
|
||||||
|
|
||||||
|
:: Build project
|
||||||
|
CD build
|
||||||
|
cmake -A x64 -DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON ..
|
||||||
|
cmake --build . --config Release
|
||||||
|
cmake --install . --prefix=../install --config Release
|
||||||
|
CD ..
|
||||||
|
|
||||||
|
:: Record install directory
|
||||||
|
CD install
|
||||||
|
SET GTest_ROOT=%CD%
|
||||||
|
CD ..
|
||||||
19
.github/scripts/linux.sh
vendored
Normal file
19
.github/scripts/linux.sh
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create build directory and enter it
|
||||||
|
mkdir bin
|
||||||
|
cd bin
|
||||||
|
# Create internal build and install directory, then enter it
|
||||||
|
mkdir build
|
||||||
|
mkdir install
|
||||||
|
|
||||||
|
# Build in Release mode
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DYYCC_BUILD_TEST=ON -DGTest_ROOT=$GTest_ROOT -DYYCC_BUILD_BENCHMARK=ON -Dbenchmark_ROOT=$benchmark_ROOT ../..
|
||||||
|
cmake --build .
|
||||||
|
cmake --install . --prefix=../install
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Back to root directory
|
||||||
|
cd ..
|
||||||
19
.github/scripts/macos.sh
vendored
Normal file
19
.github/scripts/macos.sh
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create build directory and enter it
|
||||||
|
mkdir bin
|
||||||
|
cd bin
|
||||||
|
# Create internal build and install directory, then enter it
|
||||||
|
mkdir build
|
||||||
|
mkdir install
|
||||||
|
|
||||||
|
# Build in Release mode
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DYYCC_BUILD_TEST=ON -DGTest_ROOT=$GTest_ROOT -DYYCC_BUILD_BENCHMARK=ON -Dbenchmark_ROOT=$benchmark_ROOT ../..
|
||||||
|
cmake --build .
|
||||||
|
cmake --install . --prefix=../install
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
# Back to root directory
|
||||||
|
cd ..
|
||||||
18
.github/scripts/windows.bat
vendored
Normal file
18
.github/scripts/windows.bat
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
:: Create build directory and enter it
|
||||||
|
MKDIR bin
|
||||||
|
CD bin
|
||||||
|
:: Create internal build and install directory, then enter it
|
||||||
|
MKDIR build
|
||||||
|
MKDIR install
|
||||||
|
|
||||||
|
:: Build with x64 architecture in Release mode
|
||||||
|
CD build
|
||||||
|
cmake -A x64 -DYYCC_BUILD_TEST=ON -DGTest_ROOT=%GTest_ROOT% -DYYCC_BUILD_BENCHMARK=ON -Dbenchmark_ROOT=%benchmark_ROOT% ../..
|
||||||
|
cmake --build . --config Release
|
||||||
|
cmake --install . --prefix=../install --config Release
|
||||||
|
CD ..
|
||||||
|
|
||||||
|
:: Back to root directory
|
||||||
|
CD ..
|
||||||
66
.github/workflows/linux.yml
vendored
Normal file
66
.github/workflows/linux.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
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
|
||||||
61
.github/workflows/macos.yml
vendored
Normal file
61
.github/workflows/macos.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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
35
.github/workflows/nightly.yml.disabled
vendored
@@ -1,35 +0,0 @@
|
|||||||
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
|
|
||||||
78
.github/workflows/windows.yml
vendored
Normal file
78
.github/workflows/windows.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
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
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
out/
|
out/
|
||||||
build/
|
build/
|
||||||
install/
|
install/
|
||||||
|
extern/
|
||||||
|
|
||||||
# Ignore CMake generated stuff
|
# Ignore CMake generated stuff
|
||||||
src/yycc/version.hpp
|
src/yycc/version.hpp
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ set(YYCC_INSTALL_BIN_PATH ${CMAKE_INSTALL_BINDIR} CACHE PATH
|
|||||||
set(YYCC_INSTALL_DOC_PATH ${CMAKE_INSTALL_DOCDIR} CACHE PATH
|
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.")
|
"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.
|
# Include dependency.
|
||||||
# GTest is required if we build test
|
# GTest is required if we build test
|
||||||
if (YYCC_BUILD_TEST)
|
if (YYCC_BUILD_TEST)
|
||||||
@@ -37,6 +40,10 @@ endif ()
|
|||||||
if (YYCC_BUILD_BENCHMARK)
|
if (YYCC_BUILD_BENCHMARK)
|
||||||
find_package(benchmark REQUIRED)
|
find_package(benchmark REQUIRED)
|
||||||
endif ()
|
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
|
# Iconv is required if we are not in Windows or user request it
|
||||||
if (YYCC_ENFORCE_ICONV OR (NOT WIN32))
|
if (YYCC_ENFORCE_ICONV OR (NOT WIN32))
|
||||||
find_package(Iconv REQUIRED)
|
find_package(Iconv REQUIRED)
|
||||||
|
|||||||
70
COMPILE.md
70
COMPILE.md
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
## Choose Version
|
## 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).
|
We suggest that you only use stable version (tagged commit).
|
||||||
The latest commit always present current works.
|
The latest commit always present current works.
|
||||||
It means that it is not stable and work in progress.
|
It means that it is not stable and work in progress.
|
||||||
@@ -24,10 +27,16 @@ So you actually do not need Google Test, Google Benchmark and Doxygen.
|
|||||||
### Compiler
|
### Compiler
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> You may face some issues when building on macOS with Clang. That's not your fault.
|
> You may face some issues when building on macOS with Apple Clang. That's not your fault.
|
||||||
> Clang used libc++ library lacks some essential features used by this project.
|
> Clang and Apple Clang used libc++ library lacks some essential features used by this project.
|
||||||
> A possible solution is that use GCC and libstdc++ on macOS instead of default Clang and libc++.
|
> This is especially not good for Apple Clang because Apple Clang is usually behind Clang a bunch of versions.
|
||||||
> Build issue may be resolved until libc++ finish these features: `std::stacktrace` and `std::views::enumerate`.
|
>
|
||||||
|
> 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
|
||||||
|
|
||||||
@@ -103,10 +112,18 @@ If you are a developer (developer of this project, or use this project as depend
|
|||||||
|
|
||||||
"User Build" is basically how GitHub Action build this project.
|
"User Build" is basically how GitHub Action build this project.
|
||||||
|
|
||||||
Execute `.github/windows_build.bat` on Windows or `.github/linux_build.sh` on POSIX-like OS (Linux and macOS) under **the root directory** of this project. The final built artifact is under `bin/install` directory.
|
Under **the root directory** of this project, execute:
|
||||||
|
|
||||||
|
- `script/windows_build.bat` on Windows
|
||||||
|
- or `script/linux_build.sh` on Linux
|
||||||
|
- or `script/macos_build.sh` on macOS
|
||||||
|
|
||||||
|
The final built artifact is under `bin/install` directory.
|
||||||
|
|
||||||
### Developer Build
|
### Developer Build
|
||||||
|
|
||||||
|
#### Configurable Variables
|
||||||
|
|
||||||
First, there is a list listing all variables you may configure during compiling.
|
First, 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_TEST`: Set it to `ON` to build test. `OFF` in default.
|
||||||
@@ -121,11 +138,48 @@ It may be useful for the developer who firstly use this project in their own pro
|
|||||||
Please note that generated documentation is different in different platforms.
|
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.
|
* `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.
|
The usage of this option has been introduced in previous "Iconv" chapter.
|
||||||
* `GTest_ROOT`: TODO
|
* `GTest_ROOT`: Set to the install path of Google Test
|
||||||
* `benchmark_ROOT`: TODO
|
if you have enable `YYCC_BUILD_TEST` and want to use your personal built Google Test.
|
||||||
* `Iconv_ROOT`: TODO
|
* `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.
|
||||||
* `CMAKE_CXX_STANDARD`: Set C++ standard version of project.
|
* `CMAKE_CXX_STANDARD`: Set C++ standard version of project.
|
||||||
`23` in default and this version can not be lower than C++23.
|
`23` in default and this version can not be lower than C++23.
|
||||||
You usually do not need change this.
|
You usually do not need change this.
|
||||||
* `CMAKE_POSITION_INDEPENDENT_CODE`: Set it to `True` to enable PIC.
|
* `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.
|
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
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2024-2025 yyc12345
|
Copyright (c) 2024-2026 yyc12345
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
0
script/.gitignore → asset/.gitignore
vendored
0
script/.gitignore → asset/.gitignore
vendored
0
script/uv.lock → asset/uv.lock
generated
0
script/uv.lock → asset/uv.lock
generated
@@ -26,3 +26,8 @@ PRIVATE
|
|||||||
YYCCommonplace
|
YYCCommonplace
|
||||||
benchmark::benchmark
|
benchmark::benchmark
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Install binary
|
||||||
|
install(TARGETS YYCCBenchmark
|
||||||
|
RUNTIME DESTINATION ${YYCC_INSTALL_BIN_PATH}
|
||||||
|
)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace yyccbench::carton::fft {
|
|||||||
using TIndex = size_t;
|
using TIndex = size_t;
|
||||||
using TFloat = float;
|
using TFloat = float;
|
||||||
using TComplex = std::complex<TFloat>;
|
using TComplex = std::complex<TFloat>;
|
||||||
template<size_t N>
|
template<TIndex N>
|
||||||
using TFft = FFT::Fft<TIndex, TFloat, N>;
|
using TFft = FFT::Fft<TIndex, TFloat, N>;
|
||||||
|
|
||||||
constexpr TIndex FFT_POINTS = 1024u;
|
constexpr TIndex FFT_POINTS = 1024u;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
|
||||||
@PACKAGE_INIT@
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
# Find Iconv if we have found it.
|
||||||
|
if ("@Iconv_FOUND@")
|
||||||
|
find_package(Iconv REQUIRED)
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Include targets file
|
# Include targets file
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/YYCCommonplaceTargets.cmake")
|
include("${CMAKE_CURRENT_LIST_DIR}/YYCCommonplaceTargets.cmake")
|
||||||
|
|
||||||
check_required_components(YYCCommonplace)
|
check_required_components(YYCCommonplace)
|
||||||
|
|||||||
30
cmake/check_charconv.cmake
Normal file
30
cmake/check_charconv.cmake
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
message(STATUS "Checking charconv implementation...")
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
|
||||||
|
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/chars_format.cpp" TEST_CODE_SNIPPET)
|
||||||
|
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_CHARS_FORMAT)
|
||||||
|
message(STATUS "Support std::chars_format: ${YYCC_CHARCONV_HAS_CHARS_FORMAT}")
|
||||||
|
|
||||||
|
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/from_chars_result.cpp" TEST_CODE_SNIPPET)
|
||||||
|
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_FROM_CHARS_RESULT)
|
||||||
|
message(STATUS "Support std::from_chars_result: ${YYCC_CHARCONV_HAS_FROM_CHARS_RESULT}")
|
||||||
|
|
||||||
|
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/to_chars_result.cpp" TEST_CODE_SNIPPET)
|
||||||
|
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_TO_CHARS_RESULT)
|
||||||
|
message(STATUS "Support std::to_chars_result: ${YYCC_CHARCONV_HAS_TO_CHARS_RESULT}")
|
||||||
|
|
||||||
|
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/from_chars_int.cpp" TEST_CODE_SNIPPET)
|
||||||
|
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_FROM_CHARS_INT)
|
||||||
|
message(STATUS "Support std::from_chars with integral type: ${YYCC_CHARCONV_HAS_FROM_CHARS_INT}")
|
||||||
|
|
||||||
|
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/from_chars_float.cpp" TEST_CODE_SNIPPET)
|
||||||
|
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT)
|
||||||
|
message(STATUS "Suppoer std::from_chars with float point type: ${YYCC_CHARCONV_HAS_FROM_CHARS_FLOAT}")
|
||||||
|
|
||||||
|
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/to_chars_int.cpp" TEST_CODE_SNIPPET)
|
||||||
|
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_TO_CHARS_INT)
|
||||||
|
message(STATUS "Support std::to_chars with integral type: ${YYCC_CHARCONV_HAS_TO_CHARS_INT}")
|
||||||
|
|
||||||
|
file(READ "${CMAKE_CURRENT_LIST_DIR}/check_charconv/to_chars_float.cpp" TEST_CODE_SNIPPET)
|
||||||
|
check_cxx_source_compiles("${TEST_CODE_SNIPPET}" YYCC_CHARCONV_HAS_TO_CHARS_FLOAT)
|
||||||
|
message(STATUS "Support std::to_chars with float point type: ${YYCC_CHARCONV_HAS_TO_CHARS_FLOAT}")
|
||||||
8
cmake/check_charconv/chars_format.cpp
Normal file
8
cmake/check_charconv/chars_format.cpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#include <charconv>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
auto scientific = std::chars_format::scientific;
|
||||||
|
auto fixed = std::chars_format::fixed;
|
||||||
|
auto general = std::chars_format::general;
|
||||||
|
auto hex = std::chars_format::hex;
|
||||||
|
}
|
||||||
16
cmake/check_charconv/from_chars_float.cpp
Normal file
16
cmake/check_charconv/from_chars_float.cpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include <charconv>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
const char probe[] = "0.0";
|
||||||
|
const char* first = probe;
|
||||||
|
const char* last = first + sizeof(probe);
|
||||||
|
|
||||||
|
{
|
||||||
|
float value;
|
||||||
|
auto rv = std::from_chars(first, last, value, std::chars_format::general);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
double value;
|
||||||
|
auto rv = std::from_chars(first, last, value, std::chars_format::general);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
cmake/check_charconv/from_chars_int.cpp
Normal file
41
cmake/check_charconv/from_chars_int.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include <charconv>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
const char probe[] = "0";
|
||||||
|
const char* first = probe;
|
||||||
|
const char* last = first + sizeof(probe);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::int8_t value;
|
||||||
|
auto rv = std::from_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::int16_t value;
|
||||||
|
auto rv = std::from_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::int32_t value;
|
||||||
|
auto rv = std::from_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::int64_t value;
|
||||||
|
auto rv = std::from_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::uint8_t value;
|
||||||
|
auto rv = std::from_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::uint16_t value;
|
||||||
|
auto rv = std::from_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::uint32_t value;
|
||||||
|
auto rv = std::from_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::uint64_t value;
|
||||||
|
auto rv = std::from_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
cmake/check_charconv/from_chars_result.cpp
Normal file
9
cmake/check_charconv/from_chars_result.cpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include <charconv>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
std::from_chars_result result {
|
||||||
|
.ptr = nullptr,
|
||||||
|
.ec = std::errc{},
|
||||||
|
};
|
||||||
|
}
|
||||||
16
cmake/check_charconv/to_chars_float.cpp
Normal file
16
cmake/check_charconv/to_chars_float.cpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include <charconv>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
char buffer[1024];
|
||||||
|
char* first = buffer;
|
||||||
|
char* last = first + sizeof(buffer);
|
||||||
|
|
||||||
|
{
|
||||||
|
float value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, std::chars_format::general, 6);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
double value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, std::chars_format::general, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
cmake/check_charconv/to_chars_int.cpp
Normal file
41
cmake/check_charconv/to_chars_int.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include <charconv>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
char buffer[1024];
|
||||||
|
char* first = buffer;
|
||||||
|
char* last = first + sizeof(buffer);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::int8_t value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::int16_t value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::int32_t value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::int64_t value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::uint8_t value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::uint16_t value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::uint32_t value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::uint64_t value = 0;
|
||||||
|
auto rv = std::to_chars(first, last, value, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
cmake/check_charconv/to_chars_result.cpp
Normal file
9
cmake/check_charconv/to_chars_result.cpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include <charconv>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
std::to_chars_result result {
|
||||||
|
.ptr = nullptr,
|
||||||
|
.ec = std::errc{},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,19 +1,36 @@
|
|||||||
# Configure Doxygen config file
|
# Extract all public macros defined in YYCC
|
||||||
|
# However, you should note that these extratcted macros have generator expressions.
|
||||||
|
get_target_property(YYCC_COMPILE_DEFINITIONS YYCCommonplace COMPILE_DEFINITIONS)
|
||||||
|
if (YYCC_COMPILE_DEFINITIONS STREQUAL "YYCC_COMPILE_DEFINITIONS-NOTFOUND")
|
||||||
|
message(FATAL_ERROR "Cannot extract compile definitions from YYCCommonplace.")
|
||||||
|
endif ()
|
||||||
|
# Convert list to string for expanding in future.
|
||||||
|
list(JOIN YYCC_COMPILE_DEFINITIONS " " YYCC_MACRO_GENERATOR_EXPRESSIONS)
|
||||||
|
|
||||||
|
# We simply configure Doxygen config file first.
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_CURRENT_LIST_DIR}/Doxyfile.in
|
${CMAKE_CURRENT_LIST_DIR}/Doxyfile.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
||||||
@ONLY
|
@ONLY
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add custom target
|
# Then we use "file GENERATE" syntax to generate per-config truely Doxyfile used by Doxygen.
|
||||||
|
# Because there is no "$<>" syntax in Doxyfile, so we can safely use it.
|
||||||
|
# Please note that the generation of "file GENERATE" syntax will be postponed until the build stage.
|
||||||
|
file(GENERATE
|
||||||
|
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/Doxyfile"
|
||||||
|
INPUT "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
|
||||||
|
TARGET YYCCommonplace
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add custom target using per-config Doxyfile
|
||||||
add_custom_target (YYCCDocumentation
|
add_custom_target (YYCCDocumentation
|
||||||
doxygen ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
Doxygen::doxygen "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/Doxyfile"
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
COMMENT "Generating documentation" VERBATIM
|
COMMENT "Generating documentation" VERBATIM
|
||||||
)
|
)
|
||||||
|
|
||||||
# Install built documentation
|
# Install built documentation
|
||||||
install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
|
install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
|
||||||
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
|
|
||||||
DESTINATION ${YYCC_INSTALL_DOC_PATH}
|
DESTINATION ${YYCC_INSTALL_DOC_PATH}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2306,7 +2306,7 @@ PERLMOD_MAKEVAR_PREFIX =
|
|||||||
# C-preprocessor directives found in the sources and include files.
|
# C-preprocessor directives found in the sources and include files.
|
||||||
# The default value is: YES.
|
# The default value is: YES.
|
||||||
|
|
||||||
ENABLE_PREPROCESSING = NO
|
ENABLE_PREPROCESSING = YES
|
||||||
|
|
||||||
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
|
# 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
|
# 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.
|
# recursively expanded use the := operator instead of the = operator.
|
||||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||||
|
|
||||||
PREDEFINED = YYCC_DOXYGEN
|
PREDEFINED = @YYCC_MACRO_GENERATOR_EXPRESSIONS@
|
||||||
|
|
||||||
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
|
# 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
|
# tag can be used to specify a list of macro names that should be expanded. The
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
namespace YYCC::ArgParser {
|
|
||||||
/**
|
|
||||||
|
|
||||||
\page arg_parser Universal Argument Parser
|
|
||||||
|
|
||||||
YYCC::ArgParser provides an universal way to parsing command line arguments.
|
|
||||||
|
|
||||||
Universal argument parser has similar design with universal config manager,
|
|
||||||
it is highly recommand that read \ref config_manager chapter first,
|
|
||||||
because you will have a clear understanding of this namespace after reading universal config manager chapter.
|
|
||||||
|
|
||||||
There is an example about how to use universal argument parser.
|
|
||||||
In following content, we will describe it in detail.
|
|
||||||
|
|
||||||
\code{.cpp}
|
|
||||||
class TestArgParser {
|
|
||||||
public:
|
|
||||||
TestArgParser() :
|
|
||||||
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
|
|
||||||
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
|
|
||||||
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
|
|
||||||
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
|
|
||||||
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
|
|
||||||
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the test of argument parser."), {
|
|
||||||
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
|
|
||||||
&m_BoolArgument, &m_ClampedFloatArgument
|
|
||||||
}) {}
|
|
||||||
~TestArgParser() {}
|
|
||||||
|
|
||||||
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
|
|
||||||
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
|
|
||||||
YYCC::ArgParser::StringArgument m_StringArgument;
|
|
||||||
|
|
||||||
YYCC::ArgParser::SwitchArgument m_BoolArgument;
|
|
||||||
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
|
|
||||||
|
|
||||||
YYCC::ArgParser::OptionContext m_OptionContext;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize argument parser.
|
|
||||||
TestArgParser test;
|
|
||||||
// Get argument list for parsing from standard C main function.
|
|
||||||
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
|
|
||||||
// Start parsing
|
|
||||||
test.Parse(al);
|
|
||||||
// Get captured string argument
|
|
||||||
if (test.m_StringArgument.IsCaptured())
|
|
||||||
auto val = test.m_StringArgument.Get();
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
These code can resolve following command line:
|
|
||||||
|
|
||||||
\code{.sh}
|
|
||||||
exec -i 114514 -f 2.0 --string fuck -b --clamped-float 0.5
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
For convenience, we define following terms used in this article.
|
|
||||||
|
|
||||||
\li Every items in command line: Argument.
|
|
||||||
\li \c -i, \c --clamped-float: \b Switch / \b Option. the argument starts with dash or double dash.
|
|
||||||
\li \c 114514: \b Value. the value of switch.
|
|
||||||
|
|
||||||
\section arg_parser__argument Argument
|
|
||||||
|
|
||||||
Argument is the leaf of argument parser.
|
|
||||||
It has the same position as setting in universal config manager.
|
|
||||||
|
|
||||||
\subsection arg_parser__argument__presets Argument Presets
|
|
||||||
|
|
||||||
Like setting in universal config manager,
|
|
||||||
we also provide various common used argument presets.
|
|
||||||
Current'y we support following argument presets:
|
|
||||||
|
|
||||||
\li NumberArgument: The argument storing arithmetic type (except \c bool) inside. Such as <TT>-i 114514</TT> in example.
|
|
||||||
\li StringArgument: The argument storing string inside. Such as <TT>--string fuck</TT> in example.
|
|
||||||
\li SwitchArgument: The argument storing nothing. It is just a simple switch. Such as <TT>-b</TT> in example.
|
|
||||||
|
|
||||||
When constructing these argument,
|
|
||||||
you need provide one from long name or short name, or both of them.
|
|
||||||
Short name is the argument starting with dash and long name starts with double dash.
|
|
||||||
You don't need add dash or double dash prefix when providing these names.
|
|
||||||
Please note only ASCII characters, which can be displayed on screen, can be used in these names.
|
|
||||||
|
|
||||||
Optionally, you can provide description when constructing,
|
|
||||||
which will tell user how this switch does and more infomation about this switch.
|
|
||||||
And, you can add an example to tell user which value is valid.
|
|
||||||
|
|
||||||
Next, you can specify an argument to be optional.
|
|
||||||
Optional argument can be absent in command line.
|
|
||||||
Oppositely, non-optional argument must be presented in command line,
|
|
||||||
otherwise parser will return false to indicate an error.
|
|
||||||
For checking whether an optional argument is specified,
|
|
||||||
please call AbstractArgument::IsCaptured().
|
|
||||||
|
|
||||||
Last, you can optionally assign a constraint to it,
|
|
||||||
to help argument limit its value.
|
|
||||||
|
|
||||||
However SwitchArgument must be optional argument.
|
|
||||||
Because it is true if user specify it explicit it,
|
|
||||||
and will be false if user do not give this flag.
|
|
||||||
SwitchArgument doesn't have constraint features,
|
|
||||||
because it doesn't store any value inside.
|
|
||||||
Thus no need to limit this.
|
|
||||||
|
|
||||||
\subsection arg_parser__argument__custom Custom Argument
|
|
||||||
|
|
||||||
In most cases, the combination use of argument presets and constraints is enough.
|
|
||||||
However, if you still are urge to create your personal argument,
|
|
||||||
please inherit AbstractArgument and implement essential class functions.
|
|
||||||
For the class functions you need to implement,
|
|
||||||
please refer to our argument presets.
|
|
||||||
|
|
||||||
\section arg_parser__argument_list Argument List
|
|
||||||
|
|
||||||
Argument list is a struct used by parser for parsing.
|
|
||||||
It is a higher wrapper of a simple list containing argument items.
|
|
||||||
We provide 2 ways to get argument list.
|
|
||||||
|
|
||||||
\li ArgumentList::CreateFromStd: Create argument list from standard C main function parameters.
|
|
||||||
\li ArgumentList::CreateFromWin32: Create argument list from Win32 functions in Windows.
|
|
||||||
You should use this function in Windows instead of ArgumentList::CreateFromStd.
|
|
||||||
Because the command line passed in standard C main function has encoding issue in Windows.
|
|
||||||
Use this function you will fetch correct argument list especially command including non-ASCII characters.
|
|
||||||
|
|
||||||
Please note the first argument in given command line will be stripped.
|
|
||||||
Because in most cases it point to the executable self,
|
|
||||||
and should not be seen as the part of argument list.
|
|
||||||
|
|
||||||
\section arg_parser__option_context Option Context
|
|
||||||
|
|
||||||
Please note any unknow argument will let the parser return false.
|
|
||||||
This is different with other argument parsers.
|
|
||||||
In other common argument parsers,
|
|
||||||
they will collect all unknow argument as positional argument,
|
|
||||||
or just simply ignore them.
|
|
||||||
|
|
||||||
OptionContext also will not add \c -h or \c --help switch automatically.
|
|
||||||
This is also differnent with other parsers.
|
|
||||||
You should manually add it.
|
|
||||||
However, OptionContext provide a universal help print function, OptionContext::Help.
|
|
||||||
You can directly call it to output help text if you needed (fail to parse or user order help).
|
|
||||||
|
|
||||||
\section arg_parser__limitation Limitation
|
|
||||||
|
|
||||||
This universal argument parser is a tiny parser.
|
|
||||||
It only just fulfill my personal requirements.
|
|
||||||
So it only accepts limited command line syntax.
|
|
||||||
In following content I will tell you some syntaxes which this parser \b not accept.
|
|
||||||
|
|
||||||
\subsection arg_parser__limitation__flag_combination Flag Combination
|
|
||||||
|
|
||||||
\code{.sh}
|
|
||||||
exec -l -s -h
|
|
||||||
exec -lsh
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
Parser accept first line but not accept the second line.
|
|
||||||
You must write these flags independently.
|
|
||||||
|
|
||||||
\subsection arg_parser__limitation__equal_symbol Equal Symbol
|
|
||||||
|
|
||||||
\code{.sh}
|
|
||||||
exec --value 114514
|
|
||||||
exec --value=114514
|
|
||||||
exec --value:114514
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
Parser only accept first line command.
|
|
||||||
You can not use equal symbol or any other symbol to assign value for specified argument.
|
|
||||||
You must write value after the argument immediately please.
|
|
||||||
|
|
||||||
\subsection arg_parser__limitation__variable_argument Variable Argument
|
|
||||||
|
|
||||||
\code{.sh}
|
|
||||||
exec -DSOME_VARABLE=SOME_VALUE
|
|
||||||
exec -D SOME_VARIABLE=SOME_VALUE
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
Parser only accept second line.
|
|
||||||
However you nned to write a custom argument or constraint to holding this value.
|
|
||||||
|
|
||||||
\subsection arg_parser__limitation__switch_dependency Switch Dependency
|
|
||||||
|
|
||||||
\code{.sh}
|
|
||||||
exec --action-a --action-b
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
For command line written above,
|
|
||||||
if you hope \c --action-a and \c --action-b is exclusive,
|
|
||||||
or \c --action-b only be valid if \c --action-a specified,
|
|
||||||
you should manually implement this.
|
|
||||||
Parser don't have such features to process this switch dependency.
|
|
||||||
|
|
||||||
The thing you need to do is set these switches are \b not optional.
|
|
||||||
And after parser do a success parsing,
|
|
||||||
manually calling AbstractArgument::IsCaptured to fetch whether corresponding switches are captured,
|
|
||||||
then do your personal dependency check.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
208
doc/src/carton/binstore.dox
Normal file
208
doc/src/carton/binstore.dox
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
namespace yycc::carton::binstore {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page binstore Binary Settings Storage (Binstore)
|
||||||
|
|
||||||
|
The binstore module provides a binary settings storage system that allows
|
||||||
|
applications to persistently store and retrieve configuration settings in
|
||||||
|
a binary format. It includes functionality for type-safe serialization and deserialization,
|
||||||
|
setting management with unique tokens for access control, version control with migration strategies,
|
||||||
|
and comprehensive error handling.
|
||||||
|
|
||||||
|
\section binstore__overview Overview
|
||||||
|
|
||||||
|
The binstore module consists of several key components:
|
||||||
|
|
||||||
|
\li types: Basic types and error handling for the module
|
||||||
|
\li serdes: Serialization/deserialization functionality for different data types
|
||||||
|
\li setting: Management of settings with name-based lookup and token-based access
|
||||||
|
\li configuration: Version and settings collection management
|
||||||
|
\li storage: Main storage class for loading/saving settings to/from files or streams
|
||||||
|
|
||||||
|
\section binstore__example Example Usage
|
||||||
|
|
||||||
|
Here is a complete example showing how to use the binstore module:
|
||||||
|
|
||||||
|
\code{.cpp}
|
||||||
|
#include <yycc.hpp>
|
||||||
|
#include <yycc/carton/binstore.hpp>
|
||||||
|
#include <yycc/patch/stream.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
using namespace yycc::carton::binstore;
|
||||||
|
using namespace yycc::patch::stream;
|
||||||
|
|
||||||
|
enum class LogLevel : uint8_t { Debug, Info, Warning, Error };
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Create settings collection
|
||||||
|
auto settings = setting::SettingCollection();
|
||||||
|
auto int_setting_token = settings.add_setting(setting::Setting(u8"max_connections"));
|
||||||
|
auto float_setting_token = settings.add_setting(setting::Setting(u8"timeout"));
|
||||||
|
auto string_setting_token = settings.add_setting(setting::Setting(u8"server_address"));
|
||||||
|
auto bool_setting_token = settings.add_setting(setting::Setting(u8"enable_logging"));
|
||||||
|
auto enum_setting_token = settings.add_setting(setting::Setting(u8"log_level"));
|
||||||
|
|
||||||
|
// Create configuration with version 1
|
||||||
|
auto config = configuration::Configuration(1, std::move(settings));
|
||||||
|
|
||||||
|
// Create storage with the configuration
|
||||||
|
auto storage = storage::Storage(std::move(config));
|
||||||
|
|
||||||
|
// Using appropriate SerDes types for different data types
|
||||||
|
using IntSerDes = serdes::IntegralSerDes<int32_t>;
|
||||||
|
using FloatSerDes = serdes::FloatingPointSerDes<float>;
|
||||||
|
using StringSerDes = serdes::StringSerDes;
|
||||||
|
using BoolSerDes = serdes::BoolSerDes<true>; // true as default value
|
||||||
|
using EnumSerDes = serdes::EnumSerDes<LogLevel, LogLevel::Info>;
|
||||||
|
|
||||||
|
// Set values
|
||||||
|
storage.set_value<IntSerDes>(int_setting_token, 100);
|
||||||
|
storage.set_value<FloatSerDes>(float_setting_token, 2.5f);
|
||||||
|
storage.set_value<StringSerDes>(string_setting_token, u8"localhost");
|
||||||
|
storage.set_value<BoolSerDes>(bool_setting_token, true);
|
||||||
|
storage.set_value<EnumSerDes>(enum_setting_token, LogLevel::Debug);
|
||||||
|
|
||||||
|
// Save to file
|
||||||
|
if (auto result = storage.save_into_file("config.bin"); result.has_value()) {
|
||||||
|
std::cout << "Configuration saved successfully" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Failed to save configuration" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load from file
|
||||||
|
auto new_config = configuration::Configuration(1, setting::SettingCollection());
|
||||||
|
auto new_storage = storage::Storage(std::move(new_config));
|
||||||
|
|
||||||
|
if (auto result = new_storage.load_from_file("config.bin", storage::LoadStrategy::MigrateOld); result.has_value()) {
|
||||||
|
std::cout << "Configuration loaded successfully" << std::endl;
|
||||||
|
|
||||||
|
// Get values
|
||||||
|
int32_t max_conn = new_storage.get_value<IntSerDes>(int_setting_token);
|
||||||
|
float timeout = new_storage.get_value<FloatSerDes>(float_setting_token);
|
||||||
|
std::u8string addr = new_storage.get_value<StringSerDes>(string_setting_token);
|
||||||
|
bool logging = new_storage.get_value<BoolSerDes>(bool_setting_token);
|
||||||
|
LogLevel level = new_storage.get_value<EnumSerDes>(enum_setting_token);
|
||||||
|
|
||||||
|
std::cout << "Max connections: " << max_conn << std::endl;
|
||||||
|
std::cout << "Timeout: " << timeout << std::endl;
|
||||||
|
std::cout << "Server address: " << addr << std::endl;
|
||||||
|
std::cout << "Logging enabled: " << (logging ? "yes" : "no") << std::endl;
|
||||||
|
std::cout << "Log level: " << static_cast<int>(level) << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Failed to load configuration" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section binstore__components Components
|
||||||
|
|
||||||
|
\subsection binstore__settings Settings Management
|
||||||
|
|
||||||
|
Settings are identified by unique names and accessed via tokens. The [SettingCollection](\ref setting::SettingCollection)
|
||||||
|
manages a collection of settings and ensures no duplicates.
|
||||||
|
|
||||||
|
\subsection binstore__configuration Configuration
|
||||||
|
|
||||||
|
The [Configuration](\ref configuration::Configuration) class holds the version identifier and the collection of settings.
|
||||||
|
Version control is crucial for handling configuration migration between application versions.
|
||||||
|
|
||||||
|
\subsection binstore__storage Storage
|
||||||
|
|
||||||
|
The [Storage](\ref storage::Storage) class is the main interface for setting/getting values and loading/saving configurations.
|
||||||
|
It provides methods for both file-based and stream-based operations.
|
||||||
|
|
||||||
|
\subsection binstore__serdes Serialization/Deserialization
|
||||||
|
|
||||||
|
SerDes (Serializer/Deserializer) classes handle type-safe conversion between values and their binary representation.
|
||||||
|
Built-in SerDes types include:
|
||||||
|
|
||||||
|
\li Integral types ([IntegralSerDes](\ref serdes::IntegralSerDes))
|
||||||
|
\li Floating-point types ([FloatingPointSerDes](\ref serdes::FloatingPointSerDes))
|
||||||
|
\li String types ([StringSerDes](\ref serdes::StringSerDes))
|
||||||
|
\li Boolean types ([BoolSerDes](\ref serdes::BoolSerDes))
|
||||||
|
\li Enum types ([EnumSerDes](\ref serdes::EnumSerDes))
|
||||||
|
|
||||||
|
For some of them, you can specify value range and default value via template parameters.
|
||||||
|
|
||||||
|
\section binstore__load_strategies Load Strategies
|
||||||
|
|
||||||
|
The binstore module provides different strategies for handling version mismatches:
|
||||||
|
|
||||||
|
\li [OnlyCurrent](\ref storage::LoadStrategy::OnlyCurrent): Only accept configurations with matching version
|
||||||
|
\li [MigrateOld](\ref storage::LoadStrategy::MigrateOld): Accept matching and older versions, reject newer versions
|
||||||
|
\li [AcceptAll](\ref storage::LoadStrategy::AcceptAll): Accept all versions (not recommended for production)
|
||||||
|
|
||||||
|
\section binstore__custom_serdes Custom SerDes
|
||||||
|
|
||||||
|
Custom SerDes (Serializer/Deserializer) can be created by implementing the \c SerDes concept.
|
||||||
|
A valid SerDes must satisfy the following requirements:
|
||||||
|
|
||||||
|
\li Have a type alias called \c ValueType indicating the corresponding setting type
|
||||||
|
\li Have a member function called \c serialize that accepts a const reference of the setting data and returns \c ByteArray
|
||||||
|
or \c std::nullopt if serialization fails.
|
||||||
|
\li Have a member function called \c deserialize that converts \c ByteArray to the desired type
|
||||||
|
or returns \c std::nullopt if deserialization fails.
|
||||||
|
\li Have a member function called \c reset that returns a default \c ByteArray value.
|
||||||
|
|
||||||
|
Here is an example of a custom SerDes for storing IPv4 addresses:
|
||||||
|
|
||||||
|
\code{.cpp}
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
struct IPv4Address {
|
||||||
|
std::uint8_t octets[4];
|
||||||
|
|
||||||
|
IPv4Address() : octets{0, 0, 0, 0} {}
|
||||||
|
IPv4Address(std::uint8_t a, std::uint8_t b, std::uint8_t c, std::uint8_t d) {
|
||||||
|
octets[0] = a; octets[1] = b; octets[2] = c; octets[3] = d;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IPv4SerDes {
|
||||||
|
using ValueType = IPv4Address;
|
||||||
|
static constexpr size_t VALUE_SIZE = sizeof(IPv4Address); // 4 octets
|
||||||
|
|
||||||
|
std::optional<types::ByteArray> serialize(const ValueType& value) const {
|
||||||
|
types::ByteArray ba;
|
||||||
|
ba.resize_data(VALUE_SIZE);
|
||||||
|
std::memcpy(ba.get_data_ptr(), value.octets, VALUE_SIZE);
|
||||||
|
return ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ValueType> deserialize(const types::ByteArray& ba) const {
|
||||||
|
if (ba.get_data_size() != VALUE_SIZE) return std::nullopt;
|
||||||
|
|
||||||
|
ValueType value;
|
||||||
|
std::memcpy(value.octets, ba.get_data_ptr(), VALUE_SIZE);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
types::ByteArray reset() const {
|
||||||
|
// Reset to local address
|
||||||
|
ValueType default_value(127, 0, 0, 1);
|
||||||
|
return this->serialize(default_value).value();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
To use the custom SerDes:
|
||||||
|
|
||||||
|
\code{.cpp}
|
||||||
|
// Add setting to collection
|
||||||
|
auto ip_setting_token = settings.add_setting(setting::Setting(u8"server_ip"));
|
||||||
|
|
||||||
|
// Use custom SerDes
|
||||||
|
IPv4SerDes ip_serdes;
|
||||||
|
storage.set_value<IPv4SerDes>(ip_setting_token, IPv4Address(192, 168, 1, 1));
|
||||||
|
|
||||||
|
// Retrieve value
|
||||||
|
IPv4Address ip_addr = storage.get_value<IPv4SerDes>(ip_setting_token);
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
187
doc/src/carton/clap.dox
Normal file
187
doc/src/carton/clap.dox
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
namespace yycc::carton::clap {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page clap Command Line Argument Parser (CLAP)
|
||||||
|
|
||||||
|
Command Line Argument Parser (CLAP) module for handling command line arguments and environment variables.
|
||||||
|
This module provides a comprehensive system for defining, parsing, and validating command line
|
||||||
|
arguments and environment variables. It includes components for defining application metadata,
|
||||||
|
command line options, variables, and utilities for parsing and validation.
|
||||||
|
|
||||||
|
\section clap__overview Overview
|
||||||
|
|
||||||
|
The CLAP module consists of several key components:
|
||||||
|
|
||||||
|
\li Types: Error types and result types used throughout the module
|
||||||
|
\li Validator: Type-safe validation for command line argument values
|
||||||
|
\li Option: Command line options with short and long names
|
||||||
|
\li Variable: Environment variables that can be captured
|
||||||
|
\li Summary: Application metadata (name, version, author, description)
|
||||||
|
\li Application: Complete application definition with options and variables
|
||||||
|
\li Manual: Help and version information generation
|
||||||
|
\li Parser: Command line argument parsing functionality
|
||||||
|
\li Resolver: Environment variable resolution functionality
|
||||||
|
|
||||||
|
\section clap__example Example Usage
|
||||||
|
|
||||||
|
Here is a complete example showing how to use the CLAP module:
|
||||||
|
|
||||||
|
\code{.cpp}
|
||||||
|
#include <yycc.hpp>
|
||||||
|
#include <yycc/carton/clap.hpp>
|
||||||
|
#include <yycc/patch/stream.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace yycc::carton::clap;
|
||||||
|
using namespace yycc::patch::stream;
|
||||||
|
|
||||||
|
// Define an application with options and variables
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
// Create application summary
|
||||||
|
auto summary = summary::Summary(u8"MyApp", u8"author", u8"1.0.0", u8"A sample application");
|
||||||
|
|
||||||
|
// Create options collection
|
||||||
|
auto options = option::OptionCollection();
|
||||||
|
auto int_opt = options.add_option(option::Option(u8"i", u8"int", u8"NUM", u8"integral argument"));
|
||||||
|
auto float_opt = options.add_option(option::Option(u8"f", std::nullopt, u8"NUM", u8"floating point argument"));
|
||||||
|
auto string_opt = options.add_option(option::Option(std::nullopt, u8"string", u8"STR", u8"string argument"));
|
||||||
|
auto flag_opt = options.add_option(option::Option(u8"v", std::nullopt, std::nullopt, u8"verbose mode"));
|
||||||
|
|
||||||
|
// Create variables collection
|
||||||
|
auto variables = variable::VariableCollection();
|
||||||
|
auto env_var = variables.add_variable(variable::Variable(u8"ENV_VAR", u8"Environment variable description", true));
|
||||||
|
|
||||||
|
// Create the application and manual
|
||||||
|
auto app = application::Application(std::move(summary), std::move(options), std::move(variables));
|
||||||
|
auto manual = manual::Manual(app);
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
auto result = parser::Parser::from_system(app);
|
||||||
|
if (result.has_value()) {
|
||||||
|
auto parser = std::move(result.value());
|
||||||
|
|
||||||
|
// Get values using validators
|
||||||
|
using IntValidator = validator::IntegralValidator<int>;
|
||||||
|
using FloatValidator = validator::FloatingPointValidator<float>;
|
||||||
|
using StringValidator = validator::StringValidator;
|
||||||
|
|
||||||
|
// Check and get integer option
|
||||||
|
if (auto int_val = parser.get_value_option<IntValidator>(int_opt); int_val.has_value()) {
|
||||||
|
std::cout << "Integer value: " << int_val.value() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and get float option
|
||||||
|
if (auto float_val = parser.get_value_option<FloatValidator>(float_opt); float_val.has_value()) {
|
||||||
|
std::cout << "Float value: " << float_val.value() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and get string option
|
||||||
|
if (auto str_val = parser.get_value_option<StringValidator>(string_opt); str_val.has_value()) {
|
||||||
|
std::cout << "String value: " << str_val.value() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check flag option
|
||||||
|
if (auto flag_val = parser.get_flag_option(flag_opt); flag_val.has_value() && flag_val.value()) {
|
||||||
|
std::cout << "Verbose mode enabled" << std::endl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Print help if parsing failed
|
||||||
|
manual.print_help(std::cout);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
This code handles command lines like:
|
||||||
|
\code{.sh}
|
||||||
|
./myapp -i 123 -f 2.5 --string "hello world" -v
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section clap__components Components
|
||||||
|
|
||||||
|
\subsection clap__application Application Definition
|
||||||
|
|
||||||
|
The [Application](\ref application::Application) class represents a complete command line application with its summary, options, and environment variables.
|
||||||
|
It combines the application metadata, command line options, and environment variables into a single unit.
|
||||||
|
|
||||||
|
\subsection clap__options Options
|
||||||
|
|
||||||
|
[Option](\ref option::Option) is command line arguments that can accept values or act as flags.
|
||||||
|
They can have both short names (single character)
|
||||||
|
and long names (full text). The [OptionCollection](\ref option::OptionCollection) manages a collection of options and ensures no duplicates.
|
||||||
|
|
||||||
|
\subsection clap__variables Variables
|
||||||
|
|
||||||
|
[Variable](\ref variable::Variable) represent environment variables that can be captured and validated. The [VariableCollection](\ref variable::VariableCollection)
|
||||||
|
manages a collection of environment variables and ensures no duplicates.
|
||||||
|
|
||||||
|
\subsection clap__parsing Parsing
|
||||||
|
|
||||||
|
The [Parser](\ref parser::Parser) class handles command line argument parsing. It can be created from user-provided arguments
|
||||||
|
or from system arguments (argc/argv). Values are retrieved using type-safe validators.
|
||||||
|
|
||||||
|
\subsection clap__validation Validation
|
||||||
|
|
||||||
|
Validators ensure type-safe validation of command line argument values.
|
||||||
|
The module provides built-in validators for:
|
||||||
|
|
||||||
|
\li Integral types ([IntegralValidator](\ref validator::IntegralValidator))
|
||||||
|
\li Floating-point types ([FloatingPointValidator](\ref validator::FloatingPointValidator))
|
||||||
|
\li String types ([StringValidator](\ref validator::StringValidator))
|
||||||
|
|
||||||
|
For some of them, you also can specify value range via template arguments.
|
||||||
|
|
||||||
|
\section clap__custom_validators Custom Validator
|
||||||
|
|
||||||
|
Custom validators can be created by implementing the \c Validator concept.
|
||||||
|
A valid validator must satisfy the following requirements:
|
||||||
|
|
||||||
|
\li Have a type alias called \c ReturnType indicating the return value type
|
||||||
|
\li Have a member function called \c validate that receives <TT>const std::u8string_view&</TT> as its only argument
|
||||||
|
and returns validated \c ReturnType or \c std::nullopt if validation fails
|
||||||
|
|
||||||
|
Here is an example of a custom validator that validates email addresses:
|
||||||
|
|
||||||
|
\code{.cpp}
|
||||||
|
#include <yycc/string/reinterpret.hpp>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
struct EmailValidator {
|
||||||
|
using ReturnType = std::u8string;
|
||||||
|
|
||||||
|
std::optional<ReturnType> validate(const std::u8string_view& sv) const {
|
||||||
|
// Simple email validation using regex
|
||||||
|
static const std::regex email_regex(
|
||||||
|
R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
|
||||||
|
|
||||||
|
auto email_str = yycc::string::reinterpret::as_ordinary_view(sv);
|
||||||
|
if (std::regex_match(email_str, email_regex)) {
|
||||||
|
return sv;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
To use the custom validator:
|
||||||
|
|
||||||
|
\code{.cpp}
|
||||||
|
// Add option to application
|
||||||
|
auto email_opt = options.add_option(option::Option(std::nullopt, u8"email", u8"EMAIL", u8"Email address"));
|
||||||
|
|
||||||
|
// Use custom validator
|
||||||
|
if (auto email_val = parser.get_value_option<EmailValidator>(email_opt); email_val.has_value()) {
|
||||||
|
std::cout << yycc::patch::format(u8"Valid email: {}", email_val); << std::endl;
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section clap__limitations Limitations
|
||||||
|
|
||||||
|
Due to the limitations of implementation,
|
||||||
|
CLAP now only allow only zero or one associated value for single option.
|
||||||
|
More than one assocciated value for single option is not supported.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
74
doc/src/carton/csconsole.dox
Normal file
74
doc/src/carton/csconsole.dox
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
namespace yycc::carton::csconsole {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page csconsole Universal IO Function
|
||||||
|
|
||||||
|
This namespace provide universal console IO function which is more like C\# provided,
|
||||||
|
because Windows is lacking in UTF8 console IO.
|
||||||
|
|
||||||
|
\section csconsole__deprecation Deprecation Notes
|
||||||
|
|
||||||
|
This namespace, or this module is deprecated.
|
||||||
|
Its provided functions are too aggressive and can not cover all use scenarios.
|
||||||
|
So it is suggested not to use this namespace.
|
||||||
|
Programmers should handle Windows UTF8 issues on their own.
|
||||||
|
|
||||||
|
\section csconsole__why Why?
|
||||||
|
|
||||||
|
Windows console doesn't support UTF8 very well.
|
||||||
|
The standard input output functions can not work properly with UTF8 on Windows.
|
||||||
|
So we create this namespace and provide various console-related functions
|
||||||
|
to patch Windows console and let it more like the console in other platforms.
|
||||||
|
|
||||||
|
The function provided in this function can be called in any platforms.
|
||||||
|
In Windows, the implementation will use Windows native function,
|
||||||
|
and in other platform, the implementation will redirect request to standard C function like \c std::fputs and etc.
|
||||||
|
So the programmer do not need to be worried about which function should they use,
|
||||||
|
and don't need to use macro to use different IO function in different platforms.
|
||||||
|
It is just enough that fully use the functions provided in this namespace.
|
||||||
|
|
||||||
|
All IO functions this namespace provided are UTF8-based.
|
||||||
|
It also means that input output string should always be UTF8 encoded.
|
||||||
|
|
||||||
|
\section csconsole__input Input Functions
|
||||||
|
|
||||||
|
Please note that EOL will automatically converted into LF on Windows platform, not CRLF.
|
||||||
|
This action actually is removing all CR chars in result string.
|
||||||
|
This behavior affect nothing in most cases but it still is possible break something in some special case.
|
||||||
|
|
||||||
|
Due to implementation, if you decide to use this function,
|
||||||
|
you should give up using any other function to read stdin stream,
|
||||||
|
such as \c std::gets() and \c std::cin.
|
||||||
|
Because this function may read chars which is more than needed.
|
||||||
|
These extra chars will be stored in this function and can be used next calling.
|
||||||
|
But these chars can not be visited by stdin again.
|
||||||
|
This behavior may cause bug.
|
||||||
|
So if you decide using this function, stick on it and do not change.
|
||||||
|
|
||||||
|
Due to implementation, this function do not support hot switch of stdin.
|
||||||
|
It means that stdin can be redirected before first calling of this function,
|
||||||
|
but it should not be redirected during program running.
|
||||||
|
The reason is the same one introduced above.
|
||||||
|
|
||||||
|
\section csconsole__output Output Functions
|
||||||
|
|
||||||
|
In current implementation, EOL will not be converted automatically to CRLF.
|
||||||
|
This is different with other stream read functions provided in this namespace.
|
||||||
|
|
||||||
|
Comparing with other stream read functions provided in this namespace,
|
||||||
|
stream write function support hot switch of stdout and stderr.
|
||||||
|
Because they do not have internal buffer storing something.
|
||||||
|
|
||||||
|
In this namespace, there are various stream write function.
|
||||||
|
There is a list telling you how to choose one from them for using:
|
||||||
|
|
||||||
|
\li Functions with leading "e" (like eformat, ewrite) will write data into stderr,
|
||||||
|
otherwise they will write data into stdout.
|
||||||
|
\li Functions with embedded "format" (format, format_line, eformat, eformat_line) are output functions with format feature like \c std::fprintf(),
|
||||||
|
otherwise the functions with embedded "write" in the name (write, write_line, ewrite, ewrite_line) will only write plain string like \c std::fputs().
|
||||||
|
\li Functions with trailing "line" (format_line, write_line, eformat_line, ewrite_line) will write extra EOL to break current line.
|
||||||
|
This is commonly used, otherwise functions will only write the text provided by arguments,
|
||||||
|
without adding something.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
93
doc/src/carton/fft.dox
Normal file
93
doc/src/carton/fft.dox
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
namespace yycc::carton::fft {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page fft Homemade FFT
|
||||||
|
|
||||||
|
This namespace provides a fast Fourier transform (FFT) implementation for signal processing applications.
|
||||||
|
It includes classes for performing FFT computations on complex and real-valued signals, along with
|
||||||
|
window functions to reduce spectral leakage.
|
||||||
|
|
||||||
|
\section fft__basic_usage Basic Usage
|
||||||
|
|
||||||
|
To use the FFT functionality for general purposes, use the FriendlyFft class:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include "yycc/carton/fft.hpp"
|
||||||
|
using namespace yycc::carton::fft;
|
||||||
|
|
||||||
|
// Create FFT instance for 8-point transform
|
||||||
|
FriendlyFft<size_t, float, 8u> fft;
|
||||||
|
|
||||||
|
// Prepare input data (must be power of 2 in length)
|
||||||
|
float time_scope[8] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
|
||||||
|
float freq_scope[4]; // Output is half the input size
|
||||||
|
|
||||||
|
// Create window function to reduce spectral leakage
|
||||||
|
Window<size_t, float, 8u> window(WindowType::HanningWindow);
|
||||||
|
|
||||||
|
// Perform FFT transformation
|
||||||
|
fft.easy_compute(time_scope, freq_scope, window);
|
||||||
|
|
||||||
|
// freq_scope now contains frequency domain data
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section fft__window_functions Window Functions
|
||||||
|
|
||||||
|
The library provides window functions to reduce spectral leakage:
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Create a Hanning window for 16-point data
|
||||||
|
Window<size_t, float, 16u> hanning_window(WindowType::HanningWindow);
|
||||||
|
|
||||||
|
// Apply window to your data
|
||||||
|
float data[16];
|
||||||
|
// ... initialize data ...
|
||||||
|
hanning_window.apply_window(data);
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section fft__direct_fft Direct FFT Computation
|
||||||
|
|
||||||
|
For more control over the FFT computation, use the core Fft class:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include "yycc/carton/fft.hpp"
|
||||||
|
|
||||||
|
// Create FFT instance for 16-point transform
|
||||||
|
Fft<size_t, double, 16u> fft;
|
||||||
|
|
||||||
|
// Prepare complex input data
|
||||||
|
std::complex<double> data[16];
|
||||||
|
// ... initialize complex data ...
|
||||||
|
|
||||||
|
// Perform FFT transformation
|
||||||
|
fft.compute(data);
|
||||||
|
// data now contains transformed values
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section fft__predefined_types Predefined Types
|
||||||
|
|
||||||
|
The library provides commonly used FFT types for convenience:
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Float precision FFTs
|
||||||
|
Fft4F fft4f; // 4-point float FFT
|
||||||
|
Fft8F fft8f; // 8-point float FFT
|
||||||
|
Fft16F fft16f; // 16-point float FFT
|
||||||
|
Fft256F fft256f; // 256-point float FFT
|
||||||
|
|
||||||
|
// Double precision FFTs
|
||||||
|
Fft4 fft4; // 4-point double FFT
|
||||||
|
Fft8 fft8; // 8-point double FFT
|
||||||
|
Fft16 fft16; // 16-point double FFT
|
||||||
|
Fft256 fft256; // 256-point double FFT
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section fft__requirements Requirements
|
||||||
|
|
||||||
|
- Template parameters must satisfy certain constraints:
|
||||||
|
- \c TIndex: The index type used by FFT which must be an unsigned integral type.
|
||||||
|
- \c TFloat: The float point type used by FFT.
|
||||||
|
- \c VN: The point of FFT which must be a power of 2 and >= 2.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
namespace YYCC::ExceptionHelper {
|
namespace yycc::carton::ironpad {
|
||||||
/**
|
/**
|
||||||
|
|
||||||
\page exception_helper Unhandled Exception Handler
|
\page ironpad Unhandled Exception Handler
|
||||||
|
|
||||||
Most Linux users are familiar with core dump.
|
Most Linux users are familiar with using core dump to find bugs.
|
||||||
However core dump is a tough work on Windows especially most Windows users are naive for getting core dump.
|
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.
|
So it is essential to make an easy-to-visit core dump feature for Windows program.
|
||||||
YYCC provides this feature in YYCC::ExceptionHelper.
|
This is the reason why I create this module, yycc::carton::ironpad.
|
||||||
|
|
||||||
You may know Google also has a similar and universal project called Crashpad used by Google Chrome.
|
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.
|
That's right. But it is too heavy.
|
||||||
I just want to implement a tiny but worked core dump feature on Windows.
|
I just want to implement a tiny but worked core dump feature on Windows.
|
||||||
|
|
||||||
This module is Windows specific.
|
This module is Windows specific.
|
||||||
It will be invisible on other platforms.
|
It still be available on other operating systems but all of its functions are do nothing.
|
||||||
|
|
||||||
\section exception_helper__usage Usage
|
\section ironpad__usage Usage
|
||||||
|
|
||||||
\subsection exception_helper__usage__code Register Code
|
\subsection ironpad__usage__code Register Code
|
||||||
|
|
||||||
In most scenarios, programmer only need call #Register when program started or module loaded.
|
In most scenarios, programmer only need call #startup when program started or module loaded.
|
||||||
And call #Unregister when program exited or module unloaded.
|
And call #shutdown when program exited or module unloaded.
|
||||||
All details are hidden by these 2 feature.
|
All details are hidden by these 2 feature.
|
||||||
Programmer do not need worried about the implementation of unhandled exception handler.
|
Programmer do not need worried about the implementation of unhandled exception handler.
|
||||||
|
|
||||||
Optionally, you can provide a function pointer during calling #Register as a callback.
|
Optionally, you can provide a function pointer during calling #startup as a callback.
|
||||||
The prototype of this function pointer is #ExceptionCallback.
|
The prototype of this function pointer is #ExceptionCallback.
|
||||||
This callback will be called if any unhandled exception happened.
|
This callback will be called if any unhandled exception happened.
|
||||||
It provides 2 pathes to log file and core dump file respectively.
|
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.
|
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,
|
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,
|
only the callback provided in first success registering will be called when unhandled exception happened,
|
||||||
due to \ref exception_helper__notes__singleton design.
|
due to \ref ironpad__notes__singleton design.
|
||||||
|
|
||||||
\subsection exception_helper__usage__location Location
|
\subsection ironpad__usage__location Location
|
||||||
|
|
||||||
When unhandled exception occurs,
|
When unhandled exception occurs,
|
||||||
unhandled exception handler will try to record error log and core dump in following path:
|
unhandled exception handler will try to record error log and core dump in following path:
|
||||||
|
|
||||||
\li Error Log: <TT>\%LOCALAPPDATA\%\\CrashDumps\\<I>program.exe</I>.<I>pid</I>.log</TT>
|
\li Error Log: <TT>\%LOCALAPPDATA\%\\IronPad\\<I>program.exe</I>.<I>pid</I>.log</TT>
|
||||||
\li Core Dump: <TT>\%LOCALAPPDATA\%\\CrashDumps\\<I>program.exe</I>.<I>pid</I>.dmp</TT>
|
\li Core Dump: <TT>\%LOCALAPPDATA\%\\IronPad\\<I>program.exe</I>.<I>pid</I>.dmp</TT>
|
||||||
|
|
||||||
The italic characters <I>program.exe</I> and <I>pid</I> will be replaced by program name and process ID respectively at runtime.
|
The italic characters <I>program.exe</I> and <I>pid</I> will be replaced by program name and process ID respectively at runtime.
|
||||||
Directory <TT>\%LOCALAPPDATA\%\\CrashDumps</TT> also is Windows used crash dump directory.
|
Directory <TT>\%LOCALAPPDATA\%\\IronPad</TT> is the dedicated directory for this module.
|
||||||
So you may see some other core dumps done by Windows in it.
|
So you may see the generated logs and dumps in it.
|
||||||
|
|
||||||
\subsection exception_helper__usage__last_remedy Last Remedy
|
\subsection ironpad__usage__last_remedy Last Remedy
|
||||||
|
|
||||||
If unhandled exception handler occurs error, these stuff may not be generated correctly.
|
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.
|
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.
|
especially the error occurs in back trace function.
|
||||||
There is no guaranteen that unhandled exception handler must generate error log and core dump.
|
There is no guaranteen that unhandled exception handler must generate error log and core dump.
|
||||||
|
|
||||||
\section exception_helper__notes Notes
|
\section ironpad__notes Notes
|
||||||
|
|
||||||
\subsection exception_helper__notes__thread_safe Thread Safe
|
\subsection ironpad__notes__thread_safe Thread Safe
|
||||||
|
|
||||||
All exposed functions in YYCC::ExceptionHelper are thread safe.
|
All exposed functions in this namespace are thread safe.
|
||||||
The implementation uses \c std:mutex to ensure this.
|
The implementation uses \c std::mutex to ensure this.
|
||||||
|
|
||||||
\subsection exception_helper__notes__singleton Singleton Handler
|
\subsection ironpad__notes__singleton Singleton Handler
|
||||||
|
|
||||||
YYCC::ExceptionHelper also have a mechanism that make sure the same unhandled exception handler implementation only appear once in the same process.
|
This namespace also have a mechanism that make sure the same unhandled exception handler implementation only appear once in the same process.
|
||||||
For example, you have an executable program A.exe, and 2 dynamic libraries B.dll and C.dll.
|
For example, you have an executable program A.exe, and 2 dynamic libraries B.dll and C.dll.
|
||||||
A.exe and B.dll use YYCC unhandled exception handler feature but C.dll not.
|
A.exe and B.dll use YYCC unhandled exception handler feature but C.dll not.
|
||||||
A.exe will load B.dll and C.dll at runtime.
|
A.exe will load B.dll and C.dll at runtime.
|
||||||
Although both A.exe and B.dll call #Register,
|
Although both A.exe and B.dll call #startup,
|
||||||
when unhandled exception occurs, there is only one error report output,
|
when unhandled exception occurs, there is only one error report output,
|
||||||
which may be generated by A.exe or B.dll accoridng to their order of loading.
|
which may be generated by A.exe or B.dll accoridng to their order of loading.
|
||||||
|
|
||||||
The core purpose of this is making sure the program will not output too many error report for the same unhandled exception,
|
The core purpose of this is making sure the program will not output too many error report for the same unhandled exception,
|
||||||
no matter how many modules calling #Register are loaded.
|
no matter how many modules calling #startup are loaded.
|
||||||
Only one error report is enough.
|
Only one error report is enough.
|
||||||
|
|
||||||
More precisely, we use \c CreateMutexW to create an unique mutex in Windows global scope,
|
More precisely, we use \c CreateMutexW to create an unique mutex in Windows global scope,
|
||||||
to make sure #Register only run once in the same process.
|
to make sure #startup only run once in the same process.
|
||||||
It is very like the implementation of singleton application.
|
It is very like the implementation of singleton application.
|
||||||
|
|
||||||
\subsection exception_helper__notes__recursive_calling Recursive Calling
|
\subsection ironpad__notes__recursive_calling Recursive Calling
|
||||||
|
|
||||||
The implementation of unhandled exception handler may also will throw exception.
|
The implementation of unhandled exception handler may also will throw exception.
|
||||||
This will cause infinite recursive calling.
|
This will cause infinite recursive calling.
|
||||||
YYCC::ExceptionHelper has internal mechanism to prevent this bad case.
|
This namespace has internal mechanism to prevent this bad case.
|
||||||
If this really happened, the handler will quit silent and will not cause any issue.
|
If this really happened, the handler will quit silent and will not cause any issue.
|
||||||
Programmer don't need to worry about this.
|
Programmer don't need to worry about this.
|
||||||
|
|
||||||
\subsection exception_helper__notes__user_callback The Timing of User Callback
|
\subsection ironpad__notes__user_callback The Timing of User Callback
|
||||||
|
|
||||||
The timing of calling user callback is the tail of unhandled exception handler.
|
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.
|
It means that all log and coredump have been written if possible before calling callback.
|
||||||
102
doc/src/carton/lexer61.dox
Normal file
102
doc/src/carton/lexer61.dox
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
namespace yycc::carton::lexer61 {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page lexer61 Homemade Command Line Lexer
|
||||||
|
|
||||||
|
This namespace provides a lexer for parsing command-line arguments, supporting various quoting mechanisms,
|
||||||
|
escape sequences, and Unicode characters. It follows the standard shell parsing rules for handling
|
||||||
|
arguments containing spaces and special characters.
|
||||||
|
|
||||||
|
\section lexer61__basic_usage Basic Usage
|
||||||
|
|
||||||
|
To parse command line arguments, create a Lexer61 instance and call the Lexer61::lex() method:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include "yycc/carton/lexer61.hpp"
|
||||||
|
using namespace yycc::carton::lexer61;
|
||||||
|
|
||||||
|
Lexer61 lexer;
|
||||||
|
auto result = lexer.lex(u8"program arg1 arg2 arg3");
|
||||||
|
|
||||||
|
if (result.has_value()) {
|
||||||
|
auto args = std::move(result.value());
|
||||||
|
// args contains: [u8"program", u8"arg1", u8"arg2", u8"arg3"]
|
||||||
|
for (const auto& arg : args) {
|
||||||
|
std::wcout << reinterpret_cast<const wchar_t*>(arg.c_str()) << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section lexer61__quoting_support Quoting Support
|
||||||
|
|
||||||
|
The lexer supports both single and double quotes for grouping arguments with spaces:
|
||||||
|
|
||||||
|
\code
|
||||||
|
Lexer61 lexer;
|
||||||
|
|
||||||
|
// Double quotes
|
||||||
|
auto result1 = lexer.lex(u8R"(program "argument with spaces" end)");
|
||||||
|
// Result: [u8"program", u8"argument with spaces", u8"end"]
|
||||||
|
|
||||||
|
// Single quotes
|
||||||
|
auto result2 = lexer.lex(u8"program 'another argument' end");
|
||||||
|
// Result: [u8"program", u8"another argument", u8"end"]
|
||||||
|
|
||||||
|
// Mixed quotes
|
||||||
|
auto result3 = lexer.lex(u8R"(program "double quoted 'single inside'" 'single quoted "double inside"')");
|
||||||
|
// Result: [u8"program", u8"double quoted 'single inside'", u8"single quoted \"double inside\""]
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section lexer61__escape_sequences Escape Sequences
|
||||||
|
|
||||||
|
The lexer supports escape sequences for including special characters:
|
||||||
|
|
||||||
|
\code
|
||||||
|
Lexer61 lexer;
|
||||||
|
auto result = lexer.lex(u8R"(program escaped\ space "quoted with \" quote" 'single with \' quote')");
|
||||||
|
|
||||||
|
// Result: [u8"program", u8"escaped space", u8"quoted with \" quote", u8"single with \' quote"]
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section lexer61__unicode_support Unicode Support
|
||||||
|
|
||||||
|
The lexer fully supports Unicode characters in command line arguments:
|
||||||
|
|
||||||
|
\code
|
||||||
|
Lexer61 lexer;
|
||||||
|
auto result = lexer.lex(u8"程序 中文 参数");
|
||||||
|
// Result: [u8"程序", u8"中文", u8"参数"]
|
||||||
|
|
||||||
|
// With quotes
|
||||||
|
auto result2 = lexer.lex(u8R"(程序 "中文 参数" '另一个"引号"参数')");
|
||||||
|
// Result: [u8"程序", u8"中文 参数", u8"另一个\"引号\"参数"]
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section lexer61__empty_arguments Empty Arguments
|
||||||
|
|
||||||
|
Empty arguments can be represented with empty quotes:
|
||||||
|
|
||||||
|
\code
|
||||||
|
Lexer61 lexer;
|
||||||
|
auto result = lexer.lex(u8R"(program "" '')");
|
||||||
|
|
||||||
|
// Result: [u8"program", u8"", u8""]
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section lexer61__error_handling Error Handling
|
||||||
|
|
||||||
|
The lexer uses \c std::expected for error handling:
|
||||||
|
|
||||||
|
\code
|
||||||
|
Lexer61 lexer;
|
||||||
|
auto result = lexer.lex(u8R"(program "unclosed quote)");
|
||||||
|
|
||||||
|
if (!result.has_value()) {
|
||||||
|
// Handle error - in this case, unclosed quote
|
||||||
|
std::cerr << "Error: unclosed quote" << std::endl;
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
202
doc/src/carton/pycodec.dox
Normal file
202
doc/src/carton/pycodec.dox
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
namespace yycc::carton::pycodec {
|
||||||
|
/**
|
||||||
|
\page pycodec Unified Codec (Python-like Codec)
|
||||||
|
|
||||||
|
\section pycodec__overview Overview
|
||||||
|
|
||||||
|
The unified encoding conversion module provides a consistent interface for character encoding conversion across different platforms.
|
||||||
|
It automatically selects the appropriate backend implementation based on the platform and available features.
|
||||||
|
|
||||||
|
\section pycodec__classes Available Classes
|
||||||
|
|
||||||
|
\subsection pycodec__classes__char Character to/from UTF-8 Conversion
|
||||||
|
|
||||||
|
Convert between named encodings and UTF-8 using a unified interface:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/carton/pycodec.hpp>
|
||||||
|
|
||||||
|
// Example: Converting from a named encoding to UTF-8
|
||||||
|
CharToUtf8 converter("GBK"); // or "ISO-8859-1", "SHIFT-JIS", etc.
|
||||||
|
|
||||||
|
std::string gbk_text = "你好,世界!";
|
||||||
|
auto result = converter.to_utf8(gbk_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting from UTF-8 to a named encoding
|
||||||
|
Utf8ToChar converter("GBK");
|
||||||
|
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界!";
|
||||||
|
auto result = converter.to_char(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::string gbk_text = result.value();
|
||||||
|
// Use gbk_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\subsection pycodec__classes__wchar Wide Character to/from UTF-8 Conversion
|
||||||
|
|
||||||
|
Convert between wide character strings and UTF-8:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/carton/pycodec.hpp>
|
||||||
|
|
||||||
|
// Example: Converting wide character to UTF-8
|
||||||
|
WcharToUtf8 converter;
|
||||||
|
|
||||||
|
std::wstring wide_text = L"Hello, 世界!";
|
||||||
|
auto result = converter.to_utf8(wide_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-8 to wide character
|
||||||
|
Utf8ToWchar converter;
|
||||||
|
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界!";
|
||||||
|
auto result = converter.to_wchar(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::wstring wide_text = result.value();
|
||||||
|
// Use wide_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\subsection pycodec__classes__utf16_utf32 UTF-8 to/from UTF-16/UTF-32 Conversion
|
||||||
|
|
||||||
|
Convert between UTF encodings:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/carton/pycodec.hpp>
|
||||||
|
|
||||||
|
// Example: Converting UTF-8 to UTF-16
|
||||||
|
Utf8ToUtf16 converter;
|
||||||
|
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界!";
|
||||||
|
auto result = converter.to_utf16(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u16string utf16_text = result.value();
|
||||||
|
// Use utf16_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-16 to UTF-8
|
||||||
|
Utf16ToUtf8 converter;
|
||||||
|
|
||||||
|
std::u16string utf16_text = u"Hello, 世界!";
|
||||||
|
auto result = converter.to_utf8(utf16_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-8 to UTF-32
|
||||||
|
Utf8ToUtf32 converter;
|
||||||
|
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界! 🌍";
|
||||||
|
auto result = converter.to_utf32(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u32string utf32_text = result.value();
|
||||||
|
// Use utf32_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-32 to UTF-8
|
||||||
|
Utf32ToUtf8 converter;
|
||||||
|
|
||||||
|
std::u32string utf32_text = U"Hello, 世界! 🌍";
|
||||||
|
auto result = converter.to_utf8(utf32_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section pycodec__utility Utility Functions
|
||||||
|
|
||||||
|
\subsection pycodec__utility__validation Encoding Name Validation
|
||||||
|
|
||||||
|
Check if an encoding name is valid in the current environment:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/carton/pycodec.hpp>
|
||||||
|
|
||||||
|
// Example: Validating an encoding name
|
||||||
|
bool is_valid = is_valid_encoding_name(u8"UTF-8");
|
||||||
|
if (is_valid) {
|
||||||
|
std::cout << "UTF-8 is a valid encoding name\n";
|
||||||
|
} else {
|
||||||
|
std::cout << "UTF-8 is not supported\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test another encoding
|
||||||
|
is_valid = is_valid_encoding_name(u8"GBK");
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section pycodec__error_handling Error Handling
|
||||||
|
|
||||||
|
All functions in this module return a result containing either
|
||||||
|
a ConvError struct represents conversion errors, or the final converted string.
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/carton/pycodec.hpp>
|
||||||
|
|
||||||
|
CharToUtf8 converter("INVALID_ENCODING_NAME");
|
||||||
|
std::string text = "Hello";
|
||||||
|
|
||||||
|
auto result = converter.to_utf8(text);
|
||||||
|
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string converted = result.value();
|
||||||
|
// Process successfully converted string
|
||||||
|
} else {
|
||||||
|
// Handle conversion failure
|
||||||
|
std::cout << "Conversion failed\n";
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section pycodec__backend_specifics Platform-Specific Backends
|
||||||
|
|
||||||
|
For detailed information about the specific platform backends, see:
|
||||||
|
|
||||||
|
\li \ref encoding__windows : Windows-specific implementation using Win32 APIs
|
||||||
|
\li \ref encoding__iconv : Iconv-based implementation for POSIX-like systems
|
||||||
|
|
||||||
|
\section pycodec__notes Notes
|
||||||
|
|
||||||
|
For all supported encoding names and their aliases,
|
||||||
|
please browse code written in <TT>script/pycodec</TT> located in our source code.
|
||||||
|
|
||||||
|
Please also note that not all encoding name has implementation for all platforms.
|
||||||
|
Some uncommon encoding names are not supported on some backend due to the limitations of the corresponding baskend.
|
||||||
|
These also can be found in that directory introduced above.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
107
doc/src/carton/tabulate.dox
Normal file
107
doc/src/carton/tabulate.dox
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
namespace yycc::carton::tabulate {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page tabulate Tabulate Utilities
|
||||||
|
|
||||||
|
This namespace provides utilities for creating formatted tables in console output.
|
||||||
|
It supports Unicode text, automatic column width calculation, customizable headers,
|
||||||
|
and flexible display options.
|
||||||
|
|
||||||
|
\section tabulate__basic_usage Basic Usage
|
||||||
|
|
||||||
|
To create a simple table with headers and data rows:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include "yycc/carton/tabulate.hpp"
|
||||||
|
using namespace yycc::carton::tabulate;
|
||||||
|
|
||||||
|
// Create a table with 3 columns
|
||||||
|
Tabulate table(3);
|
||||||
|
|
||||||
|
// Set the header
|
||||||
|
table.set_header({u8"Name", u8"Age", u8"City"});
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
table.add_row({u8"Alice", u8"30", u8"New York"});
|
||||||
|
table.add_row({u8"Bob", u8"25", u8"Los Angeles"});
|
||||||
|
table.add_row({u8"Charlie", u8"35", u8"Chicago"});
|
||||||
|
|
||||||
|
// Print the table
|
||||||
|
table.print();
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
This will output:
|
||||||
|
\verbatim
|
||||||
|
Name Age City
|
||||||
|
----- --- ---------
|
||||||
|
Alice 30 New York
|
||||||
|
Bob 25 Los Angeles
|
||||||
|
Charlie 35 Chicago
|
||||||
|
\endverbatim
|
||||||
|
|
||||||
|
\section tabulate__customization Customization Options
|
||||||
|
|
||||||
|
You can customize various aspects of the table:
|
||||||
|
|
||||||
|
\code
|
||||||
|
Tabulate table(3);
|
||||||
|
|
||||||
|
// Set a custom separator bar
|
||||||
|
table.set_bar(u8"===");
|
||||||
|
|
||||||
|
// Add a prefix (useful for indentation)
|
||||||
|
table.set_prefix(u8"> ");
|
||||||
|
|
||||||
|
// Control header and bar visibility
|
||||||
|
table.show_header(false); // Hide header
|
||||||
|
table.show_bar(true); // Show separator bar
|
||||||
|
|
||||||
|
// Set header
|
||||||
|
table.set_header({u8"Col1", u8"Col2", u8"Col3"});
|
||||||
|
|
||||||
|
// Add data
|
||||||
|
table.add_row({u8"data1", u8"data2", u8"data3"});
|
||||||
|
|
||||||
|
table.print();
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section tabulate__unicode_support Unicode Support
|
||||||
|
|
||||||
|
The library fully supports Unicode text in tables:
|
||||||
|
|
||||||
|
\code
|
||||||
|
Tabulate table(3);
|
||||||
|
|
||||||
|
// Set Unicode header
|
||||||
|
table.set_header({u8"姓名", u8"年龄", u8"城市"});
|
||||||
|
|
||||||
|
// Add Unicode data
|
||||||
|
table.add_row({u8"张三", u8"30", u8"北京"});
|
||||||
|
table.add_row({u8"李四", u8"25", u8"上海"});
|
||||||
|
|
||||||
|
table.print();
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section tabulate__stream_output Stream Output
|
||||||
|
|
||||||
|
You can print to any output stream, not just stdout:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
Tabulate table(2);
|
||||||
|
table.set_header({u8"Key", u8"Value"});
|
||||||
|
table.add_row({u8"temp", u8"25C"});
|
||||||
|
|
||||||
|
// Print to file
|
||||||
|
std::ofstream file("table.txt");
|
||||||
|
table.print(file);
|
||||||
|
|
||||||
|
// Or print to stringstream
|
||||||
|
std::stringstream ss;
|
||||||
|
table.print(ss);
|
||||||
|
std::string table_str = ss.str();
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
121
doc/src/carton/termcolor.dox
Normal file
121
doc/src/carton/termcolor.dox
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
namespace yycc::carton::termcolor {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page termcolor Terminal Color Utilities
|
||||||
|
|
||||||
|
This namespace provides functions to generate ANSI escape sequence for terminal font color and style.
|
||||||
|
It also provides functions to add color and style for given string with ANSI Escape Sequence.
|
||||||
|
|
||||||
|
Supported color is limited in 16 colors,
|
||||||
|
because these color is implemented by ASCII Escape Code: https://en.wikipedia.org/wiki/ANSI_escape_code .
|
||||||
|
So if your terminal do not support this, such as default Windows terminal, or teletypewriter,
|
||||||
|
you will see some unrecognised characters surrounding with your output.
|
||||||
|
That's ASCII Escape Code.
|
||||||
|
|
||||||
|
This namespace is basically the imitation of the Python package with same name.
|
||||||
|
|
||||||
|
\section termcolor__basic_colors Basic Colors
|
||||||
|
|
||||||
|
To use basic foreground and background colors:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include "yycc/carton/termcolor.hpp"
|
||||||
|
using namespace yycc::carton::termcolor;
|
||||||
|
|
||||||
|
// Print red text
|
||||||
|
std::cout << colored(u8"Error message", Color::Red) << std::endl;
|
||||||
|
|
||||||
|
// Print blue text with yellow background
|
||||||
|
cprint(u8"Info message", Color::Blue, Color::Yellow);
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section termcolor__light_colors Light Colors
|
||||||
|
|
||||||
|
The namespace provides both standard and light versions of colors:
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Standard green
|
||||||
|
std::cout << colored(u8"Success", Color::Green) << std::endl;
|
||||||
|
|
||||||
|
// Light green (brighter variant)
|
||||||
|
std::cout << colored(u8"Notice", Color::LightGreen) << std::endl;
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section termcolor__text_styles Text Styles
|
||||||
|
|
||||||
|
Multiple text styles can be combined using bitwise operations:
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Bold text
|
||||||
|
std::cout << colored(u8"Important", Color::Red, Color::Default, Attribute::Bold) << std::endl;
|
||||||
|
|
||||||
|
// Underlined text
|
||||||
|
std::cout << colored(u8"Underlined", Color::Blue, Color::Default, Attribute::Underline) << std::endl;
|
||||||
|
|
||||||
|
// Combined styles
|
||||||
|
auto combined_style = yycc::cenum::merge(Attribute::Bold, Attribute::Italic);
|
||||||
|
cprint(u8"Bold and italic", Color::Magenta, Color::Default, combined_style);
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section termcolor__convenience_functions Convenience Functions
|
||||||
|
|
||||||
|
Several convenience functions are available for direct printing:
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Print to stdout with color
|
||||||
|
cprint(u8"Hello World", Color::Green);
|
||||||
|
|
||||||
|
// Print to stderr with color and style
|
||||||
|
ceprint(u8"Warning", Color::Yellow, Color::Default, Attribute::Bold);
|
||||||
|
|
||||||
|
// Print with line break
|
||||||
|
cprintln(u8"Success", Color::LightGreen);
|
||||||
|
|
||||||
|
// Print to stderr with line break
|
||||||
|
ceprintln(u8"Critical Error", Color::LightRed, Color::Default, Attribute::Blink);
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section termcolor__color_enums Available Colors
|
||||||
|
|
||||||
|
Available foreground and background colors include:
|
||||||
|
|
||||||
|
\li Color::Black
|
||||||
|
\li Color::Red
|
||||||
|
\li Color::Green
|
||||||
|
\li Color::Yellow
|
||||||
|
\li Color::Blue
|
||||||
|
\li Color::Magenta
|
||||||
|
\li Color::Cyan
|
||||||
|
\li Color::White
|
||||||
|
\li Color::LightBlack
|
||||||
|
\li Color::LightRed
|
||||||
|
\li Color::LightGreen
|
||||||
|
\li Color::LightYellow
|
||||||
|
\li Color::LightBlue
|
||||||
|
\li Color::LightMagenta
|
||||||
|
\li Color::LightCyan
|
||||||
|
\li Color::LightWhite
|
||||||
|
\li Color::Default
|
||||||
|
|
||||||
|
\section termcolor__style_enums Available Styles
|
||||||
|
|
||||||
|
Available text styles include:
|
||||||
|
|
||||||
|
\li Attribute::Bold
|
||||||
|
\li Attribute::Dark
|
||||||
|
\li Attribute::Italic
|
||||||
|
\li Attribute::Underline
|
||||||
|
\li Attribute::Blink
|
||||||
|
\li Attribute::Reverse
|
||||||
|
\li Attribute::Concealed
|
||||||
|
\li Attribute::Default
|
||||||
|
|
||||||
|
\section termcolor__old_macros Where is Old Macros
|
||||||
|
|
||||||
|
If you have used YYCC 1.x version, you may know that these features are also presented but in a bunch of macros style.
|
||||||
|
These macros is removed since YYCC 2.0.
|
||||||
|
Since YYCC 2.0, we suggest you to use these new provided functions instead,
|
||||||
|
because they are more robust and correspond with our new style of coding by \c std::format and etc.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
103
doc/src/carton/wcwidth.dox
Normal file
103
doc/src/carton/wcwidth.dox
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
namespace yycc::carton::wcwidth {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page wcwidth Cross-platform Wcwidth
|
||||||
|
|
||||||
|
This namespace provides cross-platform implementations of character width calculation functions,
|
||||||
|
similar to the Linux-specific \c wcswidth function. It supports Unicode text and ANSI escape sequences,
|
||||||
|
making it suitable for calculating display widths of text in terminals across different platforms.
|
||||||
|
|
||||||
|
\section wcwidth__basic_usage Basic Usage
|
||||||
|
|
||||||
|
To calculate the display width of a string in a terminal:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/carton/wcwidth.hpp>
|
||||||
|
using namespace yycc::carton::wcwidth;
|
||||||
|
|
||||||
|
// Calculate width of ASCII text
|
||||||
|
size_t width1 = wcwidth(U'a'); // Returns 1
|
||||||
|
auto width2 = wcswidth(u8"Hello"); // Returns 5
|
||||||
|
|
||||||
|
// Calculate width of Unicode text
|
||||||
|
auto width3 = wcswidth(u8"你好世界"); // Returns 8 (Chinese chars typically take 2 spaces each)
|
||||||
|
auto width4 = wcswidth(u8"ありがとう"); // Returns 10 (Japanese katakana)
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section wcwidth__ansi_support ANSI Escape Sequence Support
|
||||||
|
|
||||||
|
The library can handle ANSI escape sequences (like color codes) in text
|
||||||
|
which is not supported by Linux \c wcswidth.
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include "yycc/carton/termcolor.hpp"
|
||||||
|
using namespace yycc::carton::termcolor;
|
||||||
|
|
||||||
|
// Calculate width of colored text
|
||||||
|
auto colored_text = colored(u8"Hello World", Color::Red);
|
||||||
|
auto width = wcswidth(colored_text);
|
||||||
|
// Returns the width of "Hello World" (ignoring the ANSI escape sequences)
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section wcwidth__error_handling Error Handling
|
||||||
|
|
||||||
|
The functions use \c std::expected for error handling:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include "yycc/carton/wcwidth.hpp"
|
||||||
|
|
||||||
|
// Safe way to handle potential errors
|
||||||
|
auto result = wcswidth(u8"\033?"); // Invalid ANSI sequence
|
||||||
|
|
||||||
|
if (result.has_value()) {
|
||||||
|
size_t width = result.value();
|
||||||
|
std::cout << "Width: " << width << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Invalid string" << std::endl;
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section wcwidth__character_types Supported Character Types
|
||||||
|
|
||||||
|
The library provides two main functions:
|
||||||
|
|
||||||
|
- wcwidth(): Calculate width of a single character.
|
||||||
|
- wcswidth(): Calculate width of a string.
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Using wcwidth for single characters
|
||||||
|
size_t char_width = wcwidth(U'A'); // Returns 1
|
||||||
|
size_t emoji_width = wcwidth(U'😀'); // Returns width of emoji
|
||||||
|
|
||||||
|
// Using wcswidth for strings
|
||||||
|
auto str_width1 = wcswidth(u8"Hello"); // Returns 5
|
||||||
|
auto str_width2 = wcswidth(U"Unicode String"); // Returns string length in display width
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section wcwidth__platform_differences Platform Considerations
|
||||||
|
|
||||||
|
This library addresses platform differences in wide character handling:
|
||||||
|
|
||||||
|
\li On Linux, \c whar_t is 4 bytes and can represent any Unicode character
|
||||||
|
\li On Windows, \c whar_t is 2 bytes and may require surrogate pairs for some characters
|
||||||
|
|
||||||
|
So this library uses \c char32_t internally to ensure consistent behavior across platforms,
|
||||||
|
and expose functions with \c char32_t and \c char8_t string container respectively for user.
|
||||||
|
|
||||||
|
\section wcwidth__unicode_support Unicode and East Asian Widths
|
||||||
|
|
||||||
|
The library properly handles East Asian character widths:
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Chinese characters typically occupy 2 terminal spaces
|
||||||
|
auto chinese_width = wcswidth(u8"中文"); // Returns 4 (2 chars × 2 spaces each)
|
||||||
|
|
||||||
|
// Japanese characters
|
||||||
|
auto kana_width = wcswidth(u8"ありがとう"); // Returns 10 (5 kana × 2 spaces each)
|
||||||
|
|
||||||
|
// Mixed text
|
||||||
|
auto mixed_width = wcswidth(u8"Hello 世界"); // Returns 8 (5 ASCII + 1 space + 2 Chinese chars × 2 spaces)
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -1,35 +1,37 @@
|
|||||||
namespace YYCC::EnumHelper {
|
namespace yycc::cenum {
|
||||||
/**
|
/**
|
||||||
|
|
||||||
\page enum_helper Scoped Enum Helper
|
\page cenum Scoped Enum Helper
|
||||||
|
|
||||||
\section enum_helper__intro Intro
|
\section cenum__intro Intro
|
||||||
|
|
||||||
C++ introduce a new enum called scoped enum.
|
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,
|
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.
|
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.
|
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.
|
Programmer must implement them for scoped enum one by one but it is a hardship and inconvenient.
|
||||||
It is a hardship and inconvenient.
|
This is the reason why I invent this class.
|
||||||
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.
|
||||||
|
|
||||||
\section enum_helper__Usage Usage
|
\section cenum__Usage Usage
|
||||||
|
|
||||||
In this namespace, we provide all bitwise functions related to scoped enum type which may be used.
|
In this namespace, we provide all bitwise functions related to scoped enum type which may be used.
|
||||||
See YYCC::EnumHelper for more detail (It is more clear to read function annotation than I introduce in there repeatedly).
|
See yycc::cenum for more detail (It is more clear to read function annotation than I introduce in there repeatedly).
|
||||||
|
|
||||||
\section enum_helper__why Why not Operator Overload
|
\section cenum__why Why not Operator Overload Way
|
||||||
|
|
||||||
I have try it (and you even can see the relic of it in source code).
|
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.
|
But it need a extra statement written in following to include it, otherwise compiler can not see it.
|
||||||
|
|
||||||
\code
|
\code
|
||||||
using namespace YYCC::EnumHelper;
|
using namespace yycc::cenum;
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
Another reason why I do not use this method is that
|
The last and most important 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.
|
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.
|
So I gave up this solution.
|
||||||
|
It is much better that order user explicitly specify when to use them.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
namespace YYCC::ConfigManager {
|
|
||||||
/**
|
|
||||||
|
|
||||||
\page config_manager Universal Config Manager
|
|
||||||
|
|
||||||
YYCC::ConfigManager give programmer an universal way to manage its program settings.
|
|
||||||
There is an example about how to use universal config manager.
|
|
||||||
In following content, we will describe it in detail.
|
|
||||||
|
|
||||||
\code
|
|
||||||
class TestConfigManager {
|
|
||||||
public:
|
|
||||||
enum class TestEnum : int8_t {
|
|
||||||
Test1, Test2, Test3
|
|
||||||
};
|
|
||||||
|
|
||||||
TestConfigManager() :
|
|
||||||
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
|
|
||||||
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
|
|
||||||
m_FloatSetting(YYCC_U8("float-setting"), 0.0f, YYCC::Constraints::GetNumberRangeConstraint<float>(-1.0f, 1.0f)),
|
|
||||||
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
|
|
||||||
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
|
|
||||||
&m_IntSetting, &m_StringSetting, &m_FloatSetting, &m_EnumSetting
|
|
||||||
})
|
|
||||||
{}
|
|
||||||
~TestConfigManager() {}
|
|
||||||
|
|
||||||
YYCC::ConfigManager::NumberSetting<int32_t> m_IntSetting;
|
|
||||||
YYCC::ConfigManager::StringSetting m_StringSetting;
|
|
||||||
YYCC::ConfigManager::NumberSetting<float> m_FloatSetting;
|
|
||||||
YYCC::ConfigManager::NumberSetting<TestEnum> m_EnumSetting;
|
|
||||||
|
|
||||||
YYCC::ConfigManager::CoreManager m_CoreManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize config manager
|
|
||||||
TestConfigManager test;
|
|
||||||
// Load settings.
|
|
||||||
test.m_CoreManager.Load()
|
|
||||||
// Get string setting value.
|
|
||||||
auto val = test.m_StringSetting.Get();
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
\section config_manager__setting Setting
|
|
||||||
|
|
||||||
Setting can be seen as the leaf of the config tree.
|
|
||||||
Each setting describe a single configuration entry.
|
|
||||||
|
|
||||||
\subsection config_manager__setting__presets Setting Presets
|
|
||||||
|
|
||||||
We currently provide 2 setting preset classes which you can directly use.
|
|
||||||
|
|
||||||
\li NumberSetting: The setting storing a number inside.
|
|
||||||
It is a template class. Support all arithmetic and enum types (integral, floating point, bool, enum).
|
|
||||||
\li StringSetting: The setting storing a string inside.
|
|
||||||
|
|
||||||
When constructing these settings,
|
|
||||||
you need to provide its unique name which will be used when saving to file or reading from file.
|
|
||||||
Also you need to provide a default value for it.
|
|
||||||
It will be used when fail to read file or initializing itself.
|
|
||||||
|
|
||||||
Optionally, you also can provide a constraint to setting.
|
|
||||||
Constraint is the struct instructing library to limit value in specified range.
|
|
||||||
It usually is used for making sure the setting stored value is valid.
|
|
||||||
See \ref constraints chapters to know how we provide constraints.
|
|
||||||
|
|
||||||
\subsection config_manager__setting__custom Custom Setting
|
|
||||||
|
|
||||||
In most cases, the combination use of setting presets and constraints is enough.
|
|
||||||
However, if you still are urge to create your personal setting,
|
|
||||||
please inherit AbstractSetting and implement essential class functions.
|
|
||||||
For the class functions you need to implement,
|
|
||||||
please refer to our setting presets, NumberSetting and StringSetting.
|
|
||||||
|
|
||||||
\section config_manager__core_manager Core Manager
|
|
||||||
|
|
||||||
CoreManager manage a collection of settings.
|
|
||||||
And have responsibility to reading and writing config file.
|
|
||||||
|
|
||||||
We highly suggest that you create a personal config manager class like example does.
|
|
||||||
Then put essential settings and core manager inside it.
|
|
||||||
Please note you must place core manager after all settings.
|
|
||||||
Because the order of C++ initializing its class member is the order you declared them.
|
|
||||||
The constructor of core manager need the pointer to all it managed settings,
|
|
||||||
so it must be initialized after initializing all settings.
|
|
||||||
|
|
||||||
When initializing core manager, you need assign config file path first.
|
|
||||||
Then you need specify a version number.
|
|
||||||
Version number is important.
|
|
||||||
It will be used when reading config file and only can be increased if needed (version can not downgrade).
|
|
||||||
The last argument is an initializer list which contain the \b pointer to all settings this manager managed.
|
|
||||||
|
|
||||||
When executing YYCC::ConfigManager::CoreManager::Load to load configs, it will perform following steps one by one:
|
|
||||||
|
|
||||||
<UL>
|
|
||||||
<LI>
|
|
||||||
Open given config file.
|
|
||||||
<UL>
|
|
||||||
<LI>
|
|
||||||
If given file is not existing, loading function will simply return and all configs will be reset to its default value.
|
|
||||||
</LI>
|
|
||||||
<LI>
|
|
||||||
Success to open file, go to next step.
|
|
||||||
</LI>
|
|
||||||
</UL>
|
|
||||||
</LI>
|
|
||||||
|
|
||||||
<LI>
|
|
||||||
Fetch version number from file.
|
|
||||||
<UL>
|
|
||||||
<LI>
|
|
||||||
If fail to read version number from file, loading function will simply return and all configs will be reset to its default value.
|
|
||||||
</LI>
|
|
||||||
<LI>
|
|
||||||
If the version of config file is higher than your specified version number when constructing this class,
|
|
||||||
core manager will assume you are trying to read a config file created by a higher version program,
|
|
||||||
and will reject reading and use default value for all settings.
|
|
||||||
</LI>
|
|
||||||
<LI>
|
|
||||||
If the version of config file is lower than your specified version number,
|
|
||||||
core manager will try to read config file and do proper migration (set default value for configs which do not existing) if possible.
|
|
||||||
</LI>
|
|
||||||
<LI>
|
|
||||||
If the version of config file is equal than your specified version number,
|
|
||||||
core manager will read config file normally.
|
|
||||||
</LI>
|
|
||||||
</UL>
|
|
||||||
</LI>
|
|
||||||
|
|
||||||
<LI>
|
|
||||||
Read config file body.
|
|
||||||
<UL>
|
|
||||||
<LI>
|
|
||||||
If any IO error occurs when reading, loading function will simply return.
|
|
||||||
All read config will keep their read value and all configs which has not been read will keep their default value.
|
|
||||||
</LI>
|
|
||||||
<LI>
|
|
||||||
If some config can not parse binary data to its type,
|
|
||||||
this config will be skipped and core manager will process next config.
|
|
||||||
This config will keep its default value.
|
|
||||||
</LI>
|
|
||||||
</UL>
|
|
||||||
</LI>
|
|
||||||
</UL>
|
|
||||||
|
|
||||||
All of these scenarios can be found by the return value of loading function.
|
|
||||||
The return type of loading function, ConfigLoadResult is a flag enum.
|
|
||||||
You can find whether loading process happend specified issue by using bitwise operation on it.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
namespace YYCC::ConsoleHelper {
|
|
||||||
/**
|
|
||||||
|
|
||||||
\page console_helper Console Helper
|
|
||||||
|
|
||||||
This helper provide console related stuff.
|
|
||||||
This helper includes 2 parts.
|
|
||||||
First part is console color.
|
|
||||||
It was constituted by a bunch of macros.
|
|
||||||
The second part is universal console IO function because Windows is lacking in UTF8 console IO.
|
|
||||||
All of these parts will be introduced in following content.
|
|
||||||
|
|
||||||
\section console_helper__color Console Color
|
|
||||||
|
|
||||||
YYCC::ConsoleHelper provide a bunch of macros which can allow you output colorful text in terminal.
|
|
||||||
Supported color is limited in 16 colors,
|
|
||||||
because these color is implemented by ASCII Escape Code: https://en.wikipedia.org/wiki/ANSI_escape_code .
|
|
||||||
So if your terminal do not support this, such as default Windows terminal, or teletypewriter,
|
|
||||||
you will see some unrecognised characters surrounding with your output.
|
|
||||||
That's ASCII Escape Code.
|
|
||||||
|
|
||||||
\subsection console_helper__color__enable_win_color Enable Color in Windows Console
|
|
||||||
|
|
||||||
As we introduced in above,
|
|
||||||
you may know Windows console does not support ASCII Escape Code color in default.
|
|
||||||
However #EnableColorfulConsole can fix this issue.
|
|
||||||
|
|
||||||
#EnableColorfulConsole will forcely enable ASCII Escape Code support in Windows console if possible.
|
|
||||||
Thus you can write colorful text in Windows console freely.
|
|
||||||
We suggest you to call this function at the beginning of program.
|
|
||||||
|
|
||||||
Considering most Linux console supports ASCII Escape Code very well,
|
|
||||||
this function does nothing in non-Windows platform.
|
|
||||||
So it is not essential that brack this function calling with Windows-only \c \#if.
|
|
||||||
|
|
||||||
\subsection console_helper__color__common Common Usage
|
|
||||||
|
|
||||||
For common scenarios, you can use macro like this:
|
|
||||||
|
|
||||||
\code
|
|
||||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("Light Red Text")));
|
|
||||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("I am " YYCC_COLOR_LIGHT_RED("Light Red")));
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
In first line, it will make <TT>"Light Red Text"</TT> to be shown in light red color.
|
|
||||||
And for second line, it will make <TT>"Light Red"</TT> to be shown in light red color,
|
|
||||||
but <TT>"I am "</TT> will keep default console font color.
|
|
||||||
|
|
||||||
You also may notice this macro is used with YYCC_U8 macro.
|
|
||||||
Because #WriteLine only accept UTF8 argument.
|
|
||||||
So please note if you use console color macro with YYCC_U8,
|
|
||||||
please make YYCC_U8 always is located the outside.
|
|
||||||
Otherwise, YYCC_U8 will fail to make the whole become UTF8 stirng as we introduced in \ref library_encoding.
|
|
||||||
Because console color macro is implemented by string literal concatenation internally.
|
|
||||||
|
|
||||||
YYCC_COLOR_LIGHT_RED is a member in YYCC_COLOR macro family.
|
|
||||||
YYCC_COLOR macro family has 16 members for 16 different colors:
|
|
||||||
|
|
||||||
\li YYCC_COLOR_BLACK
|
|
||||||
\li YYCC_COLOR_RED
|
|
||||||
\li YYCC_COLOR_GREEN
|
|
||||||
\li YYCC_COLOR_YELLOW
|
|
||||||
\li YYCC_COLOR_BLUE
|
|
||||||
\li YYCC_COLOR_MAGENTA
|
|
||||||
\li YYCC_COLOR_CYAN
|
|
||||||
\li YYCC_COLOR_WHITE
|
|
||||||
\li YYCC_COLOR_LIGHT_BLACK
|
|
||||||
\li YYCC_COLOR_LIGHT_RED
|
|
||||||
\li YYCC_COLOR_LIGHT_GREEN
|
|
||||||
\li YYCC_COLOR_LIGHT_YELLOW
|
|
||||||
\li YYCC_COLOR_LIGHT_BLUE
|
|
||||||
\li YYCC_COLOR_LIGHT_MAGENTA
|
|
||||||
\li YYCC_COLOR_LIGHT_CYAN
|
|
||||||
\li YYCC_COLOR_LIGHT_WHITE
|
|
||||||
|
|
||||||
\subsection console_helper__color__embedded Embedded Usgae
|
|
||||||
|
|
||||||
In some cases, you want change console at some time point and reset it in another time point.
|
|
||||||
You can use color macros like following example:
|
|
||||||
|
|
||||||
\code
|
|
||||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLORHDR_LIGHT_BLUE));
|
|
||||||
|
|
||||||
// Write as much as you liked
|
|
||||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("some string"));
|
|
||||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("another string"));
|
|
||||||
|
|
||||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLORTAIL));
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
At first line, we output YYCC_COLORHDR_LIGHT_BLUE which is in YYCC_COLORHDR macro family.
|
|
||||||
It is colorful text ASCII Escape Code head.
|
|
||||||
It will make all following output become light blue color,
|
|
||||||
until the last line we output YYCC_COLORTAIL to reset console color to original color.
|
|
||||||
|
|
||||||
Same as YYCC_COLOR macro family,
|
|
||||||
YYCC_COLORHDR macro family also has 16 members for 16 different colors:
|
|
||||||
|
|
||||||
\li YYCC_COLORHDR_BLACK
|
|
||||||
\li YYCC_COLORHDR_RED
|
|
||||||
\li YYCC_COLORHDR_GREEN
|
|
||||||
\li YYCC_COLORHDR_YELLOW
|
|
||||||
\li YYCC_COLORHDR_BLUE
|
|
||||||
\li YYCC_COLORHDR_MAGENTA
|
|
||||||
\li YYCC_COLORHDR_CYAN
|
|
||||||
\li YYCC_COLORHDR_WHITE
|
|
||||||
\li YYCC_COLORHDR_LIGHT_BLACK
|
|
||||||
\li YYCC_COLORHDR_LIGHT_RED
|
|
||||||
\li YYCC_COLORHDR_LIGHT_GREEN
|
|
||||||
\li YYCC_COLORHDR_LIGHT_YELLOW
|
|
||||||
\li YYCC_COLORHDR_LIGHT_BLUE
|
|
||||||
\li YYCC_COLORHDR_LIGHT_MAGENTA
|
|
||||||
\li YYCC_COLORHDR_LIGHT_CYAN
|
|
||||||
\li YYCC_COLORHDR_LIGHT_WHITE
|
|
||||||
|
|
||||||
However YYCC_COLORTAIL is YYCC_COLORTAIL.
|
|
||||||
There is no other variant for different colors.
|
|
||||||
Because all tail of colorful ASCII Escape Code is same.
|
|
||||||
|
|
||||||
\section console_helper__universal_io Universal IO Function
|
|
||||||
|
|
||||||
\subsection console_helper__universal_io__why Why?
|
|
||||||
|
|
||||||
Windows console doesn't support UTF8 very well.
|
|
||||||
The standard input output functions can not work properly with UTF8 on Windows.
|
|
||||||
So we create this namespace and provide various console-related functions
|
|
||||||
to patch Windows console and let it more like the console in other platforms.
|
|
||||||
|
|
||||||
The function provided in this function can be called in any platforms.
|
|
||||||
In Windows, the implementation will use Windows native function,
|
|
||||||
and in other platform, the implementation will redirect request to standard C function like \c std::fputs and etc.
|
|
||||||
So the programmer do not need to be worried about which function should they use,
|
|
||||||
and don't need to use macro to use different IO function in different platforms.
|
|
||||||
It is just enough that fully use the functions provided in this namespace.
|
|
||||||
|
|
||||||
All IO functions this namespace provided are UTF8-based.
|
|
||||||
It also means that input output string should always be UTF8 encoded.
|
|
||||||
|
|
||||||
\subsection console_helper__universal_io__input Input Functions
|
|
||||||
|
|
||||||
Please note that EOL will automatically converted into LF on Windows platform, not CRLF.
|
|
||||||
This action actually is removing all CR chars in result string.
|
|
||||||
This behavior affect nothing in most cases but it still is possible break something in some special case.
|
|
||||||
|
|
||||||
Due to implementation, if you decide to use this function,
|
|
||||||
you should give up using any other function to read stdin stream,
|
|
||||||
such as \c std::gets() and \c std::cin.
|
|
||||||
Because this function may read chars which is more than needed.
|
|
||||||
These extra chars will be stored in this function and can be used next calling.
|
|
||||||
But these chars can not be visited by stdin again.
|
|
||||||
This behavior may cause bug.
|
|
||||||
So if you decide using this function, stick on it and do not change.
|
|
||||||
|
|
||||||
Due to implementation, this function do not support hot switch of stdin.
|
|
||||||
It means that stdin can be redirected before first calling of this function,
|
|
||||||
but it should not be redirected during program running.
|
|
||||||
The reason is the same one introduced above.
|
|
||||||
|
|
||||||
\subsection console_helper__universal_io__output Output Functions
|
|
||||||
|
|
||||||
In current implementation, EOL will not be converted automatically to CRLF.
|
|
||||||
This is different with other stream read functions provided in this namespace.
|
|
||||||
|
|
||||||
Comparing with other stream read functions provided in this namespace,
|
|
||||||
stream write function support hot switch of stdout and stderr.
|
|
||||||
Because they do not have internal buffer storing something.
|
|
||||||
|
|
||||||
In this namespace, there are various stream write function.
|
|
||||||
There is a list telling you how to choose one from them for using:
|
|
||||||
|
|
||||||
\li Functions with leading "Err" will write data into stderr,
|
|
||||||
otherwise they will write data into stdout.
|
|
||||||
\li Functions with embedded "Format" are output functions with format feature
|
|
||||||
like \c std::fprintf(), otherwise the functions with embedded "Write" will
|
|
||||||
only write plain string like \c std::fputs().
|
|
||||||
\li Functions with trailing "Line" will write extra EOL to break current line.
|
|
||||||
This is commonly used, otherwise functions will only write the text provided by arguments,
|
|
||||||
without adding something.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
166
doc/src/encoding/iconv.dox
Normal file
166
doc/src/encoding/iconv.dox
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
namespace yycc::encoding::iconv {
|
||||||
|
/**
|
||||||
|
\page encoding__iconv Iconv-based Codec
|
||||||
|
|
||||||
|
\section encoding__iconv__overview Overview
|
||||||
|
|
||||||
|
The Iconv-based encoding conversion module provides encoding conversion functionality using the iconv library.
|
||||||
|
This module is available when you are in POSIX system, or enable iconv support manually when configuring the library.
|
||||||
|
|
||||||
|
\section encoding__iconv__classes Available Classes
|
||||||
|
|
||||||
|
\subsection encoding__iconv__classes__char Char to/from UTF-8 Conversion
|
||||||
|
|
||||||
|
Convert between character encodings and UTF-8:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/iconv.hpp>
|
||||||
|
|
||||||
|
// Example: Creating a converter from Latin-1 to UTF-8
|
||||||
|
CharToUtf8 converter("ISO-8859-1");
|
||||||
|
|
||||||
|
std::string latin1_text = "Café résumé naïve";
|
||||||
|
auto result = converter.to_utf8(latin1_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Creating a converter from UTF-8 to Latin-1
|
||||||
|
Utf8ToChar converter("ISO-8859-1");
|
||||||
|
|
||||||
|
std::u8string utf8_text = u8"Café résumé naïve";
|
||||||
|
auto result = converter.to_char(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::string latin1_text = result.value();
|
||||||
|
// Use latin1_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\subsection encoding__iconv__classes__wchar WChar to/from UTF-8 Conversion
|
||||||
|
|
||||||
|
Convert between wide character and UTF-8:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/iconv.hpp>
|
||||||
|
|
||||||
|
// Example: Converting wide character to UTF-8
|
||||||
|
WcharToUtf8 converter;
|
||||||
|
|
||||||
|
std::wstring wide_text = L"Hello, 世界!";
|
||||||
|
auto result = converter.to_utf8(wide_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-8 to wide character
|
||||||
|
Utf8ToWchar converter;
|
||||||
|
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界!";
|
||||||
|
auto result = converter.to_wchar(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::wstring wide_text = result.value();
|
||||||
|
// Use wide_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\subsection encoding__iconv__classes__utf16_utf32 UTF-8 to/from UTF-16/UTF-32 Conversion
|
||||||
|
|
||||||
|
Convert between UTF encodings:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/iconv.hpp>
|
||||||
|
|
||||||
|
// Example: Converting UTF-8 to UTF-16
|
||||||
|
Utf8ToUtf16 converter;
|
||||||
|
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界!";
|
||||||
|
auto result = converter.to_utf16(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u16string utf16_text = result.value();
|
||||||
|
// Use utf16_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-16 to UTF-8
|
||||||
|
Utf16ToUtf8 converter;
|
||||||
|
|
||||||
|
std::u16string utf16_text = u"Hello, 世界!";
|
||||||
|
auto result = converter.to_utf8(utf16_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-8 to UTF-32
|
||||||
|
Utf8ToUtf32 converter;
|
||||||
|
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界! 🌍";
|
||||||
|
auto result = converter.to_utf32(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u32string utf32_text = result.value();
|
||||||
|
// Use utf32_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-32 to UTF-8
|
||||||
|
Utf32ToUtf8 converter;
|
||||||
|
|
||||||
|
std::u32string utf32_text = U"Hello, 世界! 🌍";
|
||||||
|
auto result = converter.to_utf8(utf32_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section encoding__iconv__error_handling Error Handling
|
||||||
|
|
||||||
|
All functions in this module return a result containing either
|
||||||
|
a ConvError struct represents conversion errors, or the final converted string.
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/iconv.hpp>
|
||||||
|
|
||||||
|
CharToUtf8 converter("INVALID_ENCODING");
|
||||||
|
// Note: Constructor errors might be detected during conversion
|
||||||
|
|
||||||
|
std::string text = "Hello";
|
||||||
|
auto result = converter.to_utf8(text);
|
||||||
|
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string converted = result.value();
|
||||||
|
// Process successfully converted string
|
||||||
|
} else {
|
||||||
|
// Handle conversion failure
|
||||||
|
std::cout << "Conversion failed\n";
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
98
doc/src/encoding/stl.dox
Normal file
98
doc/src/encoding/stl.dox
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
namespace yycc::encoding::stl {
|
||||||
|
/**
|
||||||
|
\page encoding__stl STL-based Codec
|
||||||
|
|
||||||
|
\section encoding__stl__overview Overview
|
||||||
|
|
||||||
|
The STL-based encoding conversion module provides cross-platform encoding conversion functionality using the standard library's codecvt facets.
|
||||||
|
This module is designed to handle conversions between UTF-8, UTF-16, and UTF-32 encodings using the standard C++ locale facilities.
|
||||||
|
|
||||||
|
\section encoding__stl__attentions Attentions
|
||||||
|
|
||||||
|
The underlying implementation of this module is deprecated by C++ STL and may be removed in future versions of C++.
|
||||||
|
So please use this module carefully or considering use our \ref pycodec module instead.
|
||||||
|
|
||||||
|
\section encoding__stl__functions Available Functions
|
||||||
|
|
||||||
|
\subsection encoding__stl__functions__utf16 UTF-8 to/from UTF-16 Conversion
|
||||||
|
|
||||||
|
Convert between UTF-8 and UTF-16 encodings using standard library facilities:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/stl.hpp>
|
||||||
|
|
||||||
|
// Example: Converting UTF-8 to UTF-16
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界!";
|
||||||
|
auto result = to_utf16(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u16string utf16_text = result.value();
|
||||||
|
// Use utf16_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-16 to UTF-8
|
||||||
|
std::u16string utf16_text = u"Hello, 世界!";
|
||||||
|
auto result = to_utf8(utf16_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\subsection encoding__stl__functions__utf32 UTF-8 to/from UTF-32 Conversion
|
||||||
|
|
||||||
|
Convert between UTF-8 and UTF-32 encodings:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/stl.hpp>
|
||||||
|
|
||||||
|
// Example: Converting UTF-8 to UTF-32
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界! 🌍";
|
||||||
|
auto result = to_utf32(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u32string utf32_text = result.value();
|
||||||
|
// Use utf32_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-32 to UTF-8
|
||||||
|
std::u32string utf32_text = U"Hello, 世界! 🌍";
|
||||||
|
auto result = to_utf8(utf32_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section encoding__stl__error_handling Error Handling
|
||||||
|
|
||||||
|
All functions in this module return a result containing either
|
||||||
|
a ConvError struct represents conversion errors, or the final converted string.
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/stl.hpp>
|
||||||
|
|
||||||
|
std::u8string invalid_utf8 = "\xFF\xFE"; // Invalid UTF-8 sequence
|
||||||
|
auto result = to_utf16(invalid_utf8);
|
||||||
|
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u16string converted = result.value();
|
||||||
|
// Process successfully converted string
|
||||||
|
} else {
|
||||||
|
// Handle conversion failure
|
||||||
|
std::cout << "Conversion failed\n";
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
191
doc/src/encoding/windows.dox
Normal file
191
doc/src/encoding/windows.dox
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
namespace yycc::encoding::windows {
|
||||||
|
/**
|
||||||
|
\page encoding__windows Win32-based Codec
|
||||||
|
|
||||||
|
\section encoding__windows__overview Overview
|
||||||
|
|
||||||
|
The Windows-specific encoding conversion module provides encoding conversion functionality
|
||||||
|
using Windows API functions such as `WideCharToMultiByte` and `MultiByteToWideChar`.
|
||||||
|
This module is available only on Windows platforms and offers efficient conversion
|
||||||
|
between various character encodings including wide character, multi-byte, and UTF-8.
|
||||||
|
|
||||||
|
\section encoding__windows__functions Available Functions
|
||||||
|
|
||||||
|
\subsection encoding__windows__functions__wchar Wide Character to/from Multi-byte Conversion
|
||||||
|
|
||||||
|
Convert between wide character strings and multi-byte strings using Windows code pages:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/windows.hpp>
|
||||||
|
|
||||||
|
// Example: Converting wide character string to multi-byte with specific code page
|
||||||
|
std::wstring wide_text = L"Hello, 世界!";
|
||||||
|
auto result = to_char(wide_text, CP_UTF8); // Using UTF-8 code page
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::string multi_byte_text = result.value();
|
||||||
|
// Use multi_byte_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting multi-byte string to wide character with specific code page
|
||||||
|
std::string multi_byte_text = "Hello, 世界!";
|
||||||
|
auto result = to_wchar(multi_byte_text, CP_UTF8);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::wstring wide_text = result.value();
|
||||||
|
// Use wide_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\subsection encoding__windows__functions__mbcs Multi-byte to/from Multi-byte Conversion
|
||||||
|
|
||||||
|
Convert between different multi-byte encodings by using wide character as an intermediate:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/windows.hpp>
|
||||||
|
|
||||||
|
// Example: Converting between two different code pages
|
||||||
|
std::string source_text = "Hello, world!";
|
||||||
|
auto result = to_char(source_text, CP_ACP, CP_UTF8); // ANSI to UTF-8
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::string utf8_text = result.value();
|
||||||
|
// Use converted UTF-8 text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\subsection encoding__windows__functions__utf8 UTF-8 Specific Conversions
|
||||||
|
|
||||||
|
Specialized functions for UTF-8 conversion without requiring explicit code page specification:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/windows.hpp>
|
||||||
|
|
||||||
|
// Example: Converting wide character to UTF-8
|
||||||
|
std::wstring wide_text = L"Hello, 世界!";
|
||||||
|
auto result = to_utf8(wide_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-8 to wide character
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界!";
|
||||||
|
auto result = to_wchar(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::wstring wide_text = result.value();
|
||||||
|
// Use wide_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting multi-byte to UTF-8
|
||||||
|
std::string multi_byte_text = "Hello, world!";
|
||||||
|
auto result = to_utf8(multi_byte_text, CP_ACP);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-8 to multi-byte
|
||||||
|
std::u8string utf8_text = u8"Hello, world!";
|
||||||
|
auto result = to_char(utf8_text, CP_ACP);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::string multi_byte_text = result.value();
|
||||||
|
// Use multi_byte_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\subsection encoding__windows__functions__utf16_utf32 UTF-8 to/from UTF-16/UTF-32 Conversion
|
||||||
|
|
||||||
|
Available on Windows with Microsoft STL for conversion between UTF encodings:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/windows.hpp>
|
||||||
|
|
||||||
|
// Example: Converting UTF-8 to UTF-16
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界!";
|
||||||
|
auto result = to_utf16(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u16string utf16_text = result.value();
|
||||||
|
// Use utf16_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-16 to UTF-8
|
||||||
|
std::u16string utf16_text = u"Hello, 世界!";
|
||||||
|
auto result = to_utf8(utf16_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-8 to UTF-32
|
||||||
|
std::u8string utf8_text = u8"Hello, 世界! 🌍";
|
||||||
|
auto result = to_utf32(utf8_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u32string utf32_text = result.value();
|
||||||
|
// Use utf32_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\code
|
||||||
|
// Example: Converting UTF-32 to UTF-8
|
||||||
|
std::u32string utf32_text = U"Hello, 世界! 🌍";
|
||||||
|
auto result = to_utf8(utf32_text);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::u8string utf8_text = result.value();
|
||||||
|
// Use utf8_text...
|
||||||
|
} else {
|
||||||
|
// Handle conversion error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section encoding__windows__error_handling Error Handling
|
||||||
|
|
||||||
|
All functions in this module return a result containing either
|
||||||
|
a ConvError struct represents conversion errors, or the final converted string.
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/encoding/windows.hpp>
|
||||||
|
|
||||||
|
std::wstring invalid_text = /* some problematic string */;
|
||||||
|
auto result = to_char(invalid_text, CP_UTF8);
|
||||||
|
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::string converted = result.value();
|
||||||
|
// Process successfully converted string
|
||||||
|
} else {
|
||||||
|
// Handle conversion failure
|
||||||
|
std::cout << "Conversion failed\n";
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
namespace YYCC::EncodingHelper {
|
|
||||||
/**
|
|
||||||
|
|
||||||
\page encoding_helper Encoding Helper
|
|
||||||
|
|
||||||
YYCC::EncodingHelper namespace include all encoding related functions:
|
|
||||||
|
|
||||||
\li The convertion between ordinary string and UTF8 string which has been introduced in chapter \ref library_encoding.
|
|
||||||
\li Windows specific convertion between \c WCHAR, UTF8 string and string encoded by other encoding.
|
|
||||||
\li The convertion among UTF8, UTF16 and UTF32.
|
|
||||||
|
|
||||||
\section encoding_helper__ordinary_utf8_conv Ordinary & UTF8 Convertion
|
|
||||||
|
|
||||||
These convertion functions have been introduced in previous page.
|
|
||||||
See \ref library_encoding for more infomation.
|
|
||||||
|
|
||||||
YYCC supports following convertions:
|
|
||||||
|
|
||||||
\li #ToUTF8: Convert ordinary string to UTF8 string.
|
|
||||||
\li #ToUTF8View: Same as ToUTF8, but return string view instead.
|
|
||||||
\li #ToOrdinary: Convert UTF8 string to ordinary string.
|
|
||||||
\li #ToOrdinaryView: Same as ToOrdinary, but return string view instead.
|
|
||||||
|
|
||||||
\section encoding_helper__win_conv Windows Specific Convertion
|
|
||||||
|
|
||||||
During Windows programming, the convertion between Microsoft specified \c wchar_t and \c char is an essential operation.
|
|
||||||
Because Windows has 2 different function system, the functions ended with A and the functions ended with W.
|
|
||||||
(Microsoft specified \c wchar_t is \c 2 bytes long. It's different with Linux defined common 4 bytes long).
|
|
||||||
Thus YYCC provides these convertion functions in Windows to help programmer have better programming experience.
|
|
||||||
|
|
||||||
These functions are Windows specific, so they will be invisible in other platforms.
|
|
||||||
Please use them carefully (make sure that you are using them only in Windows environment).
|
|
||||||
|
|
||||||
YYCC supports following convertions:
|
|
||||||
|
|
||||||
\li #WcharToChar: Convert \c wchar_t string to code page specified string.
|
|
||||||
\li #CharToWchar: The reversed convertion of WcharToChar.
|
|
||||||
\li #CharToChar: Convert string between 2 different code pages. It's a shortcut of calling CharToWchar and WcharToChar successively.
|
|
||||||
\li #WcharToUTF8: Convert \c wchar_t string to UTF8 string.
|
|
||||||
\li #UTF8ToWchar: The reversed convertion of WcharToUTF8.
|
|
||||||
\li #CharToUTF8: Convert code page specified string to UTF8 string.
|
|
||||||
\li #UTF8ToChar: The reversed convertion of CharToUTF8.
|
|
||||||
|
|
||||||
Code Page is a Windows concept.
|
|
||||||
If you don't understand it, please view corresponding Microsoft documentation.
|
|
||||||
|
|
||||||
\section encoding_helper__utf_conv UTF8 UTF16 UTF32 Convertion
|
|
||||||
|
|
||||||
The convertion between UTF8, UTF16 and UTF32 is not common but essential.
|
|
||||||
These convertions can be achieved by standard library functions and classes.
|
|
||||||
(they are actually done by standard library functions in our implementation)
|
|
||||||
But we provided functions are easy to use and have clear interface.
|
|
||||||
|
|
||||||
These functions are different with the functions introduced above.
|
|
||||||
They can be used in any platform, not confined in Windows platforms.
|
|
||||||
|
|
||||||
YYCC supports following convertions:
|
|
||||||
|
|
||||||
\li #UTF8ToUTF16: Convert UTF8 string to UTF16 string.
|
|
||||||
\li #UTF16ToUTF8: The reversed convertion of UTF8ToUTF16.
|
|
||||||
\li #UTF8ToUTF32: Convert UTF8 string to UTF32 string.
|
|
||||||
\li #UTF32ToUTF8: The reversed convertion of UTF8ToUTF32.
|
|
||||||
|
|
||||||
\section encoding_helper__overloads Function Overloads
|
|
||||||
|
|
||||||
Every encoding convertion functions (except the convertion between UTF8 and ordinary string) have 4 different overloads for different scenarios.
|
|
||||||
Take #WcharToChar for example.
|
|
||||||
There are following 4 overloads:
|
|
||||||
|
|
||||||
\code
|
|
||||||
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page);
|
|
||||||
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page);
|
|
||||||
std::string WcharToChar(const std::wstring_view& src, UINT code_page);
|
|
||||||
std::string WcharToChar(const wchar_t* src, UINT code_page);
|
|
||||||
\endcode
|
|
||||||
|
|
||||||
\subsection encoding_helper__overloads_destination Destination String
|
|
||||||
|
|
||||||
According to the return value, these 4 overload can be divided into 2 types.
|
|
||||||
The first type returns bool. The second type returns \c std::string instance.
|
|
||||||
|
|
||||||
For the first type, it always return bool to indicate whether the convertion is success.
|
|
||||||
Due to this, the function must require an argument for holding the result string.
|
|
||||||
So you can see the functions belonging to this type always require a reference to \c std::string in argument.
|
|
||||||
|
|
||||||
Oppositely, the second directly returns result by return value.
|
|
||||||
It doesn't care the success of convertion and will return empty string if convertion failed.
|
|
||||||
Programmer can more naturally use it because the retuen value itself is the result.
|
|
||||||
There is no need to declare a variable before calling convertion function for holding result.
|
|
||||||
|
|
||||||
All in all, the first type overload should be used in strict scope.
|
|
||||||
The success of convertion will massively affect the behavior of your following code.
|
|
||||||
For example, the convertion code is delivered to some system function and it should not be empty and etc.
|
|
||||||
The second type overload usually is used in lossen scenarios.
|
|
||||||
For exmaple, this overload usually is used in console output because it usually doesn't matter.
|
|
||||||
There is no risk even if the convertion failed (just output a blank string).
|
|
||||||
|
|
||||||
For the first type, please note that there is \b NO guarantee that the argument holding return value is not changed.
|
|
||||||
Even the convertion is failed, the argument holding return value may still be changed by function itself.
|
|
||||||
|
|
||||||
In this case, the type of result is \c std::string because this is function required.
|
|
||||||
In other functions, such as #WcharToUTF8, the type of result can be \c yycc_u8string or etc.
|
|
||||||
So please note the type of result is decided by convertion function itself, not only \c std::string.
|
|
||||||
|
|
||||||
\subsection encoding_helper__overloads__source Source String
|
|
||||||
|
|
||||||
According to the way providing source string,
|
|
||||||
these 4 overload also can be divided into 2 types.
|
|
||||||
The first type take a reference to constant \c std::wstring_view.
|
|
||||||
The second type take a pointer to constant \c wchar_t.
|
|
||||||
|
|
||||||
For first type, it will take the whole string for convertion, including \b embedded NUL terminal.
|
|
||||||
Please note we use string view as argument.
|
|
||||||
It is compatible with corresponding raw string pointer and string container.
|
|
||||||
So it is safe to directly pass \c std::wstring for this function.
|
|
||||||
|
|
||||||
For second type, it will assume that you passed argument is a NUL terminated string and send it for convertion.
|
|
||||||
|
|
||||||
The result is clear.
|
|
||||||
If you want to process string with \b embedded NUL terminal, please choose first type overload.
|
|
||||||
Otherwise the second type overload is enough.
|
|
||||||
|
|
||||||
Same as destination string, the type of source is also decided by the convertion function itself.
|
|
||||||
For exmaple, the type of source in #UTF8ToWchar is \c yycc_u8string_view and \c yycc_char8_t,
|
|
||||||
not \c std::wstring and \c wchar_t.
|
|
||||||
|
|
||||||
\subsection encoding_helper__overloads__extra Extra Argument
|
|
||||||
|
|
||||||
There is an extra argument called \c code_page for #WcharToChar.
|
|
||||||
It indicates the code page of destination string,
|
|
||||||
because this function will convert \c wchar_t string to the string with specified code page encoding.
|
|
||||||
|
|
||||||
Some convertion functions have extra argument like this,
|
|
||||||
because they need more infomations to decide what they need to do.
|
|
||||||
Some convertion functions don't have extra argument.
|
|
||||||
For exmaple, the convertion between \c wchar_t string and UTF8 string.
|
|
||||||
Because both source string and destination string are concrete.
|
|
||||||
There is no need to provide any more infomations.
|
|
||||||
|
|
||||||
\subsection encoding_helper__overloads__conclusion Conclusion
|
|
||||||
|
|
||||||
Mixing 2 types of source string and 2 types of destination string,
|
|
||||||
we have 4 different overload as we illustrated before.
|
|
||||||
Programmer can use them freely according to your requirements.
|
|
||||||
And don't forget to provide extra argument if function required.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
85
doc/src/env.dox
Normal file
85
doc/src/env.dox
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
namespace yycc::env {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page env Environment Operations
|
||||||
|
|
||||||
|
This namespace provide various environment operations inspired by Rust standard library.
|
||||||
|
|
||||||
|
\section env__var Environment Variable Operations
|
||||||
|
|
||||||
|
These functions allow manipulation of environment variables with proper error handling using \c std::expected.
|
||||||
|
|
||||||
|
\li get_var(): Get the value of a given environment variable name
|
||||||
|
\li set_var(): Set the value of a given environment variable name
|
||||||
|
\li del_var(): Delete environment variable with given name
|
||||||
|
\li get_vars(): Get all environment variables as a list of name-value pairs
|
||||||
|
|
||||||
|
There is an example usage of these functions below:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/env.hpp>
|
||||||
|
|
||||||
|
auto result = yycc::env::get_var(u8"PATH");
|
||||||
|
if (result.has_value()) {
|
||||||
|
auto value = result.value();
|
||||||
|
// Use the value...
|
||||||
|
} else {
|
||||||
|
// Handle the error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section env__path Path Operations
|
||||||
|
|
||||||
|
These functions provide access to various important paths in the system environment.
|
||||||
|
|
||||||
|
\li current_dir(): Returns the current working directory
|
||||||
|
\li current_exe(): Returns the path of the current running executable
|
||||||
|
\li home_dir(): Returns the path of the current user's home directory if known
|
||||||
|
\li temp_dir(): Returns the path of a temporary directory
|
||||||
|
|
||||||
|
There is an example usage of these functions below:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/env.hpp>
|
||||||
|
#include <yycc/patch/stream.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace yycc::patch::stream;
|
||||||
|
|
||||||
|
auto cwd_result = yycc::env::current_dir();
|
||||||
|
if (cwd_result.has_value()) {
|
||||||
|
std::cout << "Current directory: " << cwd_result.value() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto exe_result = yycc::env::current_exe();
|
||||||
|
if (exe_result.has_value()) {
|
||||||
|
std::cout << "Executable path: " << exe_result.value() << std::endl;
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section env__arg Command Line Argument Operations
|
||||||
|
|
||||||
|
These functions provide access to command-line arguments passed to the program.
|
||||||
|
|
||||||
|
\li get_args(): Returns the arguments that this program was started with
|
||||||
|
|
||||||
|
There is an example usage of these functions below:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/env.hpp>
|
||||||
|
#include <yycc/patch/stream.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace yycc::patch::stream;
|
||||||
|
|
||||||
|
auto args_result = yycc::env::get_args();
|
||||||
|
if (args_result.has_value()) {
|
||||||
|
auto args = args_result.value();
|
||||||
|
for (auto& arg : args) {
|
||||||
|
std::cout << "Arg: " << arg << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<TD><CENTER>
|
<TD><CENTER>
|
||||||
<B>YYCCommonplace Programming Manual</B>
|
<B>YYCCommonplace Programming Manual</B>
|
||||||
|
|
||||||
Copyright 2024 by yyc12345.
|
Copyright 2024-2026 by yyc12345.
|
||||||
</CENTER></TD>
|
</CENTER></TD>
|
||||||
</TR>
|
</TR>
|
||||||
</TABLE>
|
</TABLE>
|
||||||
@@ -25,58 +25,80 @@
|
|||||||
<TR>
|
<TR>
|
||||||
<TD ALIGN="LEFT" VALIGN="TOP">
|
<TD ALIGN="LEFT" VALIGN="TOP">
|
||||||
|
|
||||||
<B>General Features</B>
|
<B>Overviews</B>
|
||||||
|
|
||||||
\li \subpage intro
|
\li \subpage intro
|
||||||
|
|
||||||
\li \subpage premise_and_principle
|
\li \subpage premise_and_principle
|
||||||
|
|
||||||
<!--
|
<B>STL Enhancements</B>
|
||||||
\li \subpage library_macros
|
|
||||||
|
|
||||||
\li \subpage library_encoding
|
\li \subpage macro
|
||||||
|
|
||||||
\li \subpage encoding_helper
|
\li \subpage cenum
|
||||||
|
|
||||||
\li \subpage string_helper
|
\li \subpage string__reinterpret
|
||||||
|
|
||||||
\li \subpage parser_helper
|
\li \subpage string__op
|
||||||
|
|
||||||
\li \subpage console_helper
|
\li \subpage num__parser
|
||||||
|
|
||||||
\li \subpage io_helper
|
\li \subpage num__op
|
||||||
|
|
||||||
\li \subpage std_patch
|
\li \subpage num__safe_cast
|
||||||
|
|
||||||
\li \subpage enum_helper
|
\li \subpage num__safe_op
|
||||||
-->
|
|
||||||
|
|
||||||
<B>Advanced Features</B>
|
\li \subpage patch
|
||||||
|
|
||||||
<!--
|
\li \subpage env
|
||||||
\li \subpage constraints
|
|
||||||
|
|
||||||
\li \subpage config_manager
|
\li \subpage rust
|
||||||
|
|
||||||
\li \subpage arg_parser
|
<B>Text Encoding</B>
|
||||||
-->
|
|
||||||
|
\li \subpage encoding__stl
|
||||||
|
|
||||||
|
\li \subpage encoding__windows
|
||||||
|
|
||||||
|
\li \subpage encoding__iconv
|
||||||
|
|
||||||
|
\li \subpage pycodec
|
||||||
|
|
||||||
</TD>
|
</TD>
|
||||||
<TD ALIGN="LEFT" VALIGN="TOP">
|
<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>
|
<B>Windows Specific Features</B>
|
||||||
|
|
||||||
<!--
|
\li \subpage windows__import_guard
|
||||||
\li \subpage win_import
|
|
||||||
|
|
||||||
\li \subpage com_helper
|
\li \subpage windows__com
|
||||||
|
|
||||||
\li \subpage dialog_helper
|
\li \subpage windows__dialog
|
||||||
|
|
||||||
\li \subpage win_fct_helper
|
\li \subpage windows__winfct
|
||||||
|
|
||||||
\li \subpage exception_helper
|
\li \subpage windows__console
|
||||||
-->
|
|
||||||
|
|
||||||
</TD>
|
</TD>
|
||||||
</TR>
|
</TR>
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
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>
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
323
doc/src/macro.dox
Normal file
323
doc/src/macro.dox
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
30
doc/src/num/op.dox
Normal file
30
doc/src/num/op.dox
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace yycc::num::op {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page num__op Numeric Operations
|
||||||
|
|
||||||
|
Namespace yycc::num::op provides functions for robust numeric operations inspired by Rust's approach to primitive type operations.
|
||||||
|
|
||||||
|
Currently, this namespace only supports unsigned integer ceiling division, though more operations may be added in the future based on demand.
|
||||||
|
|
||||||
|
\section num__op__div_ceil Ceiling Division
|
||||||
|
|
||||||
|
The \c div_ceil function performs division between two unsigned integers and rounds up the result.
|
||||||
|
It uses a safe algorithm that avoids potential overflow issues that could occur with the traditional formula <TT>(lhs + rhs - 1) / rhs</TT>.
|
||||||
|
|
||||||
|
The function computes: <TT>(lhs % rhs == 0) ? (lhs / rhs) : (lhs / rhs) + 1u</TT>
|
||||||
|
The function prevents division by zero by checking the divisor before performing the operation and throwing a std::logic_error if the divisor is zero.
|
||||||
|
|
||||||
|
Here are some examples showing how to use this function:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/num/op.hpp>
|
||||||
|
|
||||||
|
// Ceiling division examples
|
||||||
|
uint32_t result1 = op::div_ceil(uint32_t(10), uint32_t(3)); // Results in 4
|
||||||
|
uint32_t result2 = op::div_ceil(uint32_t(9), uint32_t(3)); // Results in 3
|
||||||
|
uint32_t result3 = op::div_ceil(uint32_t(1), uint32_t(10)); // Results in 1
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
70
doc/src/num/parser.dox
Normal file
70
doc/src/num/parser.dox
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
63
doc/src/num/safe_cast.dox
Normal file
63
doc/src/num/safe_cast.dox
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
namespace yycc::num::safe_cast {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page num__safe_cast Numeric Safe Casting
|
||||||
|
|
||||||
|
Namespace yycc::num::safe_cast provides functions which safely cast numeric value from one type to another.
|
||||||
|
|
||||||
|
\section num__safe_cast__overview Overview
|
||||||
|
|
||||||
|
When writing C++ code, casting between types with different ranges is very important
|
||||||
|
but greatly easy to make mistakes which finally cause fatal errors.
|
||||||
|
Inspired by Rust's approach to type conversion,
|
||||||
|
this namespace provides safe casting functions that handle potential overflow and underflow issues.
|
||||||
|
|
||||||
|
\section num__safe_cast__functions Functions
|
||||||
|
|
||||||
|
The namespace provides two main functions:
|
||||||
|
\li \c to() - Direct conversion for cases where the destination type can definitely hold the source value
|
||||||
|
which means definitely safe conversions (widening conversions).
|
||||||
|
\li \c try_to() - Attempt conversion and return a Result type that includes error information if the conversion fails
|
||||||
|
which means potentially risky conversions (narrowing conversions).
|
||||||
|
|
||||||
|
The \c try_to function returns a \c std::expected.
|
||||||
|
If the conversion succeeds, the result contains the converted value.
|
||||||
|
If it fails, it contains a error info.
|
||||||
|
|
||||||
|
\section num__safe_cast__examples Examples
|
||||||
|
|
||||||
|
Here are some examples showing how to use the safe casting functions:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/num/safe_cast.hpp>
|
||||||
|
|
||||||
|
// Safe conversion using 'to' function
|
||||||
|
uint32_t val1 = safe_cast::to<uint32_t>(static_cast<int16_t>(123));
|
||||||
|
|
||||||
|
// Potentially risky conversion using 'try_to' function
|
||||||
|
auto result = safe_cast::try_to<int16_t>(static_cast<int32_t>(12345));
|
||||||
|
if (result.has_value()) {
|
||||||
|
auto converted = result.value();
|
||||||
|
// Use converted value
|
||||||
|
} else {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section num__safe_cast__notes Notes
|
||||||
|
|
||||||
|
The safety of conversions is determined at compile time using the \c CAN_SAFE_TO meta-programming concept.
|
||||||
|
However, for variable-length data types (like \c size_t ), the safety determination may vary across platforms,
|
||||||
|
which could affect code portability. For this reason, it would be better to use \c try_to for these types
|
||||||
|
for better robust application on different platforms.
|
||||||
|
|
||||||
|
\section num__safe_cast__limitations Limitations
|
||||||
|
|
||||||
|
This namespace supports safe casting between integral types only.
|
||||||
|
Currently unsupported conversions include:
|
||||||
|
|
||||||
|
\li Floating-point to floating-point conversions
|
||||||
|
\li Floating-point to integer conversions
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
86
doc/src/num/safe_op.dox
Normal file
86
doc/src/num/safe_op.dox
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
namespace yycc::num::safe_op {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page num__safe_op Numeric Safe Arithmetic Operations
|
||||||
|
|
||||||
|
Namespace yycc::num::safe_op provides Rust-like safe arithmetic operations
|
||||||
|
for handling overflow, underflow, and other undefined behaviors in C++.
|
||||||
|
|
||||||
|
\section num__safe_op__overview Overview
|
||||||
|
|
||||||
|
Inspired by Rust's rich set of arithmetic operators,
|
||||||
|
this namespace provides safe arithmetic operations that handle potential overflow, underflow, and other undefined behaviors that commonly occur in C++.
|
||||||
|
It offers multiple strategies for handling arithmetic operations including wrapping, checked, overflowing, saturating, and strict operations.
|
||||||
|
|
||||||
|
\section num__safe_op__operation_types Operation Types
|
||||||
|
|
||||||
|
The namespace provides several families of arithmetic operations:
|
||||||
|
|
||||||
|
\li \c wrapping_* operations: Perform arithmetic with wrapping on overflow/underflow (similar to unsigned integer behavior)
|
||||||
|
\li \c checked_* operations: Return std::optional containing the result, or std::nullopt if overflow/underflow occurs
|
||||||
|
\li \c overflowing_* operations: Return a pair with the result and a boolean indicating whether overflow occurred
|
||||||
|
\li \c saturating_* operations: Clamp the result to the min/max value when overflow/underflow occurs
|
||||||
|
\li \c strict_* operations: Throw exceptions when overflow/underflow occurs
|
||||||
|
\li \c ordinary operations (add, sub, mul, div): Alias to wrapping operations for safe default behavior
|
||||||
|
|
||||||
|
\section num__safe_op__arithmetic_functions Arithmetic Functions
|
||||||
|
|
||||||
|
For each operation type, the namespace provides functions for the four basic arithmetic operations:
|
||||||
|
\li \c _add : Addition
|
||||||
|
\li \c _sub : Subtraction
|
||||||
|
\li \c _mul : Multiplication
|
||||||
|
\li \c _div : Division
|
||||||
|
|
||||||
|
For example, for wrapping operations: \c wrapping_add, \c wrapping_sub, \c wrapping_mul, \c wrapping_div.
|
||||||
|
|
||||||
|
\section num__safe_op__examples Examples
|
||||||
|
|
||||||
|
Here are some examples showing how to use the safe arithmetic functions:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/num/safe_op.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// Wrapping addition - wraps around on overflow
|
||||||
|
uint8_t result1 = safe_op::wrapping_add(uint8_t(200), uint8_t(100)); // Results in 44
|
||||||
|
|
||||||
|
// Checked multiplication - returns std::optional
|
||||||
|
auto result2 = safe_op::checked_mul(int32_t(1000000), int32_t(1000000));
|
||||||
|
if (!result2.has_value()) {
|
||||||
|
std::cout << "Multiplication overflowed!" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Result: " << result2.value() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overflowing subtraction - returns pair of result and overflow flag
|
||||||
|
auto [result3, overflowed] = safe_op::overflowing_sub(int32_t(-10), int32_t(INT32_MIN));
|
||||||
|
if (overflowed) {
|
||||||
|
std::cout << "Subtraction overflowed!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saturating multiplication - clamps to min/max on overflow
|
||||||
|
int32_t result4 = safe_op::saturating_mul(int32_t(1000000), int32_t(1000000)); // Clamps to INT32_MAX
|
||||||
|
|
||||||
|
// Ordinary operations - safe defaults without undefined behavior
|
||||||
|
int32_t result5 = safe_op::add(int32_t(10), int32_t(20)); // 30
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section num__safe_op__undefined_behaviors Handling of Undefined Behaviors
|
||||||
|
|
||||||
|
This namespace handles several undefined behaviors in C++ arithmetic:
|
||||||
|
\li Signed integer overflow and underflow (e.g. INT_MAX + 1)
|
||||||
|
\li Division by zero
|
||||||
|
\li Performing INT_MIN / -1 division (which would result in a value that doesn't fit in the type)
|
||||||
|
|
||||||
|
For division operations, special care is taken to handle these undefined behaviors appropriately depending on the operation type.
|
||||||
|
|
||||||
|
\section num__safe_op__platform_support Platform Support
|
||||||
|
|
||||||
|
The implementation uses hardware-specific overflow detection functions:
|
||||||
|
\li GCC/Clang: Uses built-in functions like __builtin_add_overflow
|
||||||
|
\li Windows: Uses Windows API functions from \c intsafe.h
|
||||||
|
|
||||||
|
This ensures optimal performance across different platforms.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
75
doc/src/patch.dox
Normal file
75
doc/src/patch.dox
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -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.
|
This is inspired from Rust, and also the compromise with STL.
|
||||||
|
|
||||||
Most functions this library provided has Rust-Result-like return value.
|
Most functions this library provided has Rust-Result-like return value.
|
||||||
It means that programmer can handle error correctly.
|
It means that programmer can handle error gracefully.
|
||||||
However, this library is based on STL, another library that may throw C++ exception to indicate error.
|
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.
|
We can not control this behavior of STL, so I forcely apply this rule.
|
||||||
|
|
||||||
@@ -19,18 +19,22 @@ 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.
|
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.
|
However, for other operating system, it do not have too much care.
|
||||||
We brutally make a premise that other operating systems are UNIX-liked and use UTF8 as its encoding.
|
We brutally make a premise that other operating systems are POSIX-compatible and use UTF8 as its encoding.
|
||||||
|
|
||||||
\section premise_and_principle__string_encoding String Encoding
|
\section premise_and_principle__string_encoding String Encoding
|
||||||
|
|
||||||
Before using this library, you should know the encoding strategy of this library first.
|
Before using this library, you should know the encoding strategy of this library first.
|
||||||
In short words, this library use UTF8 encoding everywhere except some special cases list following (not all).
|
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).
|
||||||
|
|
||||||
\li Traditional format function in yycc::string::op.
|
\li Traditional format function in yycc::string::op.
|
||||||
Traditional format function provide some overloads for ordinary string formatting.
|
Traditional format function provide some overloads for ordinary string formatting.
|
||||||
That's because this feature is so common to use in some cases.
|
That's because this feature is so common to use in some cases.
|
||||||
\li The message of Rust panic in yycc::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.
|
\li The message of standard library exception.
|
||||||
For the compatibility with C++ standard library exception,
|
For the compatibility with C++ standard library exception,
|
||||||
we only can use ordinary string as the message of exception.
|
we only can use ordinary string as the message of exception.
|
||||||
|
|||||||
133
doc/src/rust.dox
Normal file
133
doc/src/rust.dox
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
namespace yycc {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page rust Rust Facilities in C++
|
||||||
|
|
||||||
|
This collection of following headers brings Rust-style programming facilities to C++.
|
||||||
|
|
||||||
|
\section rust__primitive Primitive Types
|
||||||
|
|
||||||
|
The yycc::primitive namespace provides primitive types similar to Rust's approach.
|
||||||
|
Especially resolve the problem that the names of C++ primitive types are so long.
|
||||||
|
|
||||||
|
There is an example of using primitive types:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/primitive.hpp>
|
||||||
|
using namespace yycc::primitive;
|
||||||
|
|
||||||
|
i32 value = 42;
|
||||||
|
u64 big_number = 1000000ULL;
|
||||||
|
f64 precision = 3.14159265359;
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section rust__option Option Type
|
||||||
|
|
||||||
|
The yycc::option namespace reproduces Rust's Option type and its members Some and None in C++.
|
||||||
|
Considering C++ has provide \c std::optional, this namespace provided contents are just an alias to it.
|
||||||
|
|
||||||
|
\li yycc::option::Option - Template alias for std::optional
|
||||||
|
\li yycc::option::Some - Function to create an Option with a value
|
||||||
|
\li yycc::option::None - Function to create an empty Option
|
||||||
|
|
||||||
|
There is an example of using \c Option type:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/option.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace yycc::option;
|
||||||
|
|
||||||
|
Option<int> maybe_value = Some<Option<int>>(42);
|
||||||
|
if (maybe_value.has_value()) {
|
||||||
|
std::cout << "Value: " << maybe_value.value() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto empty_value = None<Option<int>>();
|
||||||
|
if (!empty_value.has_value()) {
|
||||||
|
std::cout << "No value present" << std::endl;
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section rust__result Result Type
|
||||||
|
|
||||||
|
The yycc::result namespace reproduces Rust's Result type and its members Ok and Err in C++.
|
||||||
|
Considering C++ has provide \c std::expected, this namespace provided contents are just an alias to it.
|
||||||
|
|
||||||
|
\li yycc::result::Result - Template alias for std::expected
|
||||||
|
\li yycc::result::Ok - Function to create a Result with a success value
|
||||||
|
\li yycc::result::Err - Function to create a Result with an error value
|
||||||
|
|
||||||
|
There is an example of using \c Result type:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/result.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace yycc::result;
|
||||||
|
|
||||||
|
Result<int, int> divide(int a, int b) {
|
||||||
|
if (b == 0) {
|
||||||
|
return Err<Result<int, int>>(-1); // Error code
|
||||||
|
}
|
||||||
|
return Ok<Result<int, int>>(a / b);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = divide(10, 2);
|
||||||
|
if (result.has_value()) {
|
||||||
|
std::cout << "Result: " << result.value() << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Error occurred: " << result.error() << std::endl;
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section rust__panic Panic Mechanism
|
||||||
|
|
||||||
|
The yycc::panic namespace provides Rust-style panic functionality for immediate program termination on unrecoverable errors.
|
||||||
|
This imitates Rust's panic! macro behavior, allowing the program to immediately exit with error information and stack traces.
|
||||||
|
|
||||||
|
\li RS_PANIC: Macro equivalent to Rust's panic! macro.
|
||||||
|
This macro will help you append all filename, line and function info to the real panic trigger function.
|
||||||
|
\li yycc::panic::panic: The actual function called by the macro.
|
||||||
|
User usually does not need to call this function directly.
|
||||||
|
|
||||||
|
There is an example of using this panic mechanism:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/panic.hpp>
|
||||||
|
|
||||||
|
void critical_function(int err_code) {
|
||||||
|
// Some condition that indicates an unrecoverable error
|
||||||
|
if (err_code != 100) {
|
||||||
|
RS_PANIC("Unrecoverable error in critical function with code {}", err_code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
\section rust__prelude Prelude
|
||||||
|
|
||||||
|
The yycc::prelude namespace provides a Rust-like prelude for C++. In Rust, types are automatically imported into all files by default.
|
||||||
|
This default-imported set of types is called the "prelude".
|
||||||
|
This namespace provides a similar concept for C++.
|
||||||
|
|
||||||
|
This namespace will extract following content into \b global scope:
|
||||||
|
|
||||||
|
\li All primitive types defined in yycc::primitive.
|
||||||
|
\li Vec: \c std::vector template alias.
|
||||||
|
\li All functionality from yycc::option and yycc::result namespaces.
|
||||||
|
\li Panic mechanism from yycc::panic.
|
||||||
|
|
||||||
|
There is an example of using this header:
|
||||||
|
|
||||||
|
\code
|
||||||
|
#include <yycc/prelude.hpp>
|
||||||
|
|
||||||
|
// Now all Rust-style facilities are available without prefixes:
|
||||||
|
i32 x = 42; // From primitive
|
||||||
|
Vec<i32> numbers = {1, 2, 3}; // Vector of primitive types
|
||||||
|
auto result = Ok<Result<i32, i32>>(x); // Result type
|
||||||
|
RS_PANIC("Something went wrong"); // Panic macro
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
176
doc/src/string/op.dox
Normal file
176
doc/src/string/op.dox
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
118
doc/src/string/reinterpret.dox
Normal file
118
doc/src/string/reinterpret.dox
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
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.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
namespace YYCC::WinFctHelper {
|
|
||||||
/**
|
|
||||||
|
|
||||||
\page win_fct_helper Windows Function Helper
|
|
||||||
|
|
||||||
This helper give a more convenient way to call Windows functions.
|
|
||||||
|
|
||||||
This namespace is Windows specific.
|
|
||||||
It will be entirely invisible in other platforms.
|
|
||||||
|
|
||||||
Currently this namespace has following functions:
|
|
||||||
|
|
||||||
\li #GetCurrentModule: Get the handle to current module.
|
|
||||||
\li #GetTempDirectory: Get temporary directory in Windows.
|
|
||||||
\li #GetModuleFileName: Get the path to module in file system by given handle.
|
|
||||||
\li #GetLocalAppData: Get the path inside \%LOCALAPPDATA\%
|
|
||||||
\li #IsValidCodePage: Check whether given code page number is valid.
|
|
||||||
\li #CopyFile: The UTF8 version of Win32 \c CopyFile.
|
|
||||||
\li #MoveFile: The UTF8 version of Win32 \c MoveFile.
|
|
||||||
\li #DeleteFile: The UTF8 version of Win32 \c DeleteFile.
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
31
doc/src/windows/com.dox
Normal file
31
doc/src/windows/com.dox
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
23
doc/src/windows/console.dox
Normal file
23
doc/src/windows/console.dox
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace yycc::windows::console {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page windows__console Windows Console Helper
|
||||||
|
|
||||||
|
Namespace yycc::windows::console is designed to resolve some issue of Windows console
|
||||||
|
which is not corresponding to POSIX system console.
|
||||||
|
This namespace also is only available on Windows platform.
|
||||||
|
|
||||||
|
Currently this namespace only has one function: colorful_console(),
|
||||||
|
which enable colorful console output support for \c stdout and \c stderr in Windows.
|
||||||
|
As we introduced, you may know Windows console does not support ASCII Escape Code color in default.
|
||||||
|
This function can fix this issue.
|
||||||
|
This function will forcely enable ASCII Escape Code support in Windows console if possible.
|
||||||
|
Thus you can write colorful text in Windows console freely.
|
||||||
|
We suggest you to call this function at the beginning of program.
|
||||||
|
|
||||||
|
Considering most Linux console supports ASCII Escape Code very well,
|
||||||
|
this function isn't presented in non-Windows platform.
|
||||||
|
So it is essential that brack this function calling with Windows-only \c \#if.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace YYCC::DialogHelper {
|
namespace yycc::windows::dialog {
|
||||||
/**
|
/**
|
||||||
|
|
||||||
\page dialog_helper Dialog Helper
|
\page windows__dialog Dialog Helper
|
||||||
|
|
||||||
Picking files and folders is an important and essential operation under Windows.
|
Picking files and folders is an important and essential operation under Windows.
|
||||||
However the functions picking files and folders are so complex.
|
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.
|
This helper is Windows specific.
|
||||||
It will be totally invisible if you are in other platforms.
|
It will be totally invisible if you are in other platforms.
|
||||||
|
|
||||||
\section dialog_helper__file_dialog Configure File Dialog
|
\section windows__dialog__file_dialog Configure File Dialog
|
||||||
|
|
||||||
The first thing is that we should initialize FileDialog,
|
The first thing is that we should initialize FileDialog,
|
||||||
and configure it according to your requirements.
|
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.
|
It also one of the arguments in final dialog function.
|
||||||
|
|
||||||
\code
|
\code
|
||||||
YYCC::DialogHelper::FileDialog params;
|
FileDialog params;
|
||||||
params.SetOwner(owner_getter());
|
params.set_owner(owner_getter());
|
||||||
params.SetTitle(YYCC_U8("My File Picker"));
|
params.set_title(u8"My File Picker");
|
||||||
params.SetInitFileName(YYCC_U8("test.txt"));
|
params.set_init_file_name(u8"test.txt");
|
||||||
params.SetInitDirectory(initial_directory_getter());
|
params.set_init_directory(initial_directory_getter());
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
\subsection dialog_helper__file_dialog__owner Owner
|
\subsection windows__dialog__file_dialog__owner Owner
|
||||||
|
|
||||||
FileDialog::SetOwner will set owner of this dialog.
|
FileDialog::set_owner() will set owner of this dialog.
|
||||||
It accepts a Microsoft defined \c HWND as argument which should be familiar with Windows programmer.
|
It accepts a Microsoft defined \c HWND as argument which should be familiar with Windows programmer.
|
||||||
If you pass \c NULL to it or skip calling this function, it indicate that there is no owner of this dialog.
|
If you pass \c NULL to it or skip calling this function, it indicate that there is no owner of this dialog.
|
||||||
<I>
|
<I>
|
||||||
@@ -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.
|
But it would be better to have an owner if possible.
|
||||||
</I>
|
</I>
|
||||||
|
|
||||||
\subsection dialog_helper__file_dialog__title Title
|
\subsection windows__dialog__file_dialog__title Title
|
||||||
|
|
||||||
FileDialog::SetTitle will set dialog title of this dialog.
|
FileDialog::set_title() will set dialog title of this dialog.
|
||||||
If you pass \c nullptr or skip calling it,
|
If you pass \c nullptr or skip calling it,
|
||||||
the title of dialog will be filled by system and the function type you calling.
|
the title of dialog will be filled by system and the function type you calling.
|
||||||
For example, the title will be "Open..." if you call open file function,
|
For example, the title will be "Open..." if you call open file function,
|
||||||
@@ -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.
|
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.
|
So I suggest you do not set title except you really want to modify title.
|
||||||
|
|
||||||
\subsection dialog_helper__file_dialog__init_file_name Initial File Name
|
\subsection windows__dialog__file_dialog__init_file_name Initial File Name
|
||||||
|
|
||||||
FileDialog::SetInitFileName will set the initial file name presented in dialog file name input box.
|
FileDialog::set_init_file_name() will set the initial file name presented in dialog file name input box.
|
||||||
If you pass \c nullptr or skip calling it, the text in dialog file name input box will be empty.
|
If you pass \c nullptr or skip calling it, the text in dialog file name input box will be empty.
|
||||||
|
|
||||||
User can modify the name presented in input box later.
|
User can modify the name presented in input box later.
|
||||||
@@ -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.
|
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.
|
However, if you specify this field, the dialog will always presented your specified value in every calling.
|
||||||
|
|
||||||
\subsection dialog_helper__file_dialog__init_directory Initial Directory
|
\subsection windows__dialog__file_dialog__init_directory Initial Directory
|
||||||
|
|
||||||
FileDialog::SetInitDirectory will set the initial directory (startup directory) when opening dialog.
|
FileDialog::set_init_directory() will set the initial directory (startup directory) when opening dialog.
|
||||||
|
|
||||||
In following cases, initial directory will fall back to system behavior:
|
In following cases, initial directory will fall back to system behavior:
|
||||||
|
|
||||||
@@ -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 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.
|
The directory we meeting in the first launch is system defined.
|
||||||
|
|
||||||
\section dialog_helper__file_filters Configure File Filters
|
\section windows__dialog__file_filters Configure File Filters
|
||||||
|
|
||||||
File filters is a drop down list represented in file dialog which allow user filter files by their extensions.
|
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.
|
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:
|
FileFilters takes responsibility for this feature:
|
||||||
|
|
||||||
\code
|
\code
|
||||||
auto& filters = params.ConfigreFileTypes();
|
auto& filters = params.configure_file_types();
|
||||||
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
|
filters.add_filter(u8"Microsoft Word (*.docx; *.doc)", { u8"*.docx", u8"*.doc" });
|
||||||
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
|
filters.add_filter(u8"Microsoft Excel (*.xlsx; *.xls)", { u8"*.xlsx", u8"*.xls" });
|
||||||
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
|
filters.add_filter(u8"Microsoft PowerPoint (*.pptx; *.ppt)", { u8"*.pptx", u8"*.ppt" });
|
||||||
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
|
filters.add_filter(u8"Text File (*.txt)", { u8"*.txt" });
|
||||||
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
|
filters.add_filter(u8"All Files (*.*)", { u8"*.*" });
|
||||||
params.SetDefaultFileTypeIndex(0u);
|
params.set_default_file_type_index(0u);
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
\subsection dialog_helper__file_filters__setup File Filters
|
\subsection windows__dialog__file_filters__setup File Filters
|
||||||
|
|
||||||
We don't need to initialize FileFilters by ourselves.
|
We don't need to initialize FileFilters by ourselves.
|
||||||
Oppositely, we fetch it from FileDialog instance by calling FileDialog::ConfigreFileTypes.
|
Oppositely, we fetch it from FileDialog instance by calling FileDialog::configure_file_types().
|
||||||
After fetching, we can call FileFilters::Add to add a filter pair for file filters.
|
After fetching, we can call FileFilters::add_filter() to add a filter pair for file filters.
|
||||||
|
|
||||||
The first argument is the display text which user will see in file filter drop down box.
|
The first argument is the display text which user will see in file filter drop down box.
|
||||||
|
|
||||||
@@ -107,55 +107,51 @@ It is okey to use multiple wildcard string in list.
|
|||||||
This is suit for those file types involving multiple file extensions, such as the old and new file types of Microsoft Office as we illustracted.
|
This is suit for those file types involving multiple file extensions, such as the old and new file types of Microsoft Office as we illustracted.
|
||||||
Empty list not allowed
|
Empty list not allowed
|
||||||
|
|
||||||
FileFilters::Add also will return a bool to indicate the success of this adding.
|
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.
|
||||||
|
|
||||||
It should at least has one file filter in file dialog.
|
It should at least has one file filter in file dialog.
|
||||||
I don't know the consequence if you don't provide any file filter.
|
I don't know the consequence if you don't provide any file filter.
|
||||||
|
|
||||||
\subsection dialog_helper__file_filters__default_filter Default File Type
|
\subsection windows__dialog__file_filters__default_filter Default File Type
|
||||||
|
|
||||||
FileDialog::SetDefaultFileTypeIndex will set the default selected file filter of this dialog.
|
FileDialog::set_default_file_type_index() will set the default selected file filter of this dialog.
|
||||||
It accepts an index pointing to the file filter which you want to show in default for this file dialog.
|
It accepts an index pointing to the file filter which you want to show in default for this file dialog.
|
||||||
The index of file filters is the order where you call FileFilters::Add above.
|
The index of file filters is the order where you call FileFilters::add_filter() above.
|
||||||
If you pass \c NULL to it or skip calling this function, the first one will be default.
|
If you pass \c NULL to it or skip calling this function, the first one will be default.
|
||||||
|
|
||||||
\section dialog_helper__result Create Dialog and Get Result
|
\section windows__dialog__result Create Dialog and Get Result
|
||||||
|
|
||||||
Finally, we can call file dialog functions by we initialized FileDialog
|
Finally, we can call file dialog functions by we initialized FileDialog
|
||||||
|
|
||||||
\code
|
\code
|
||||||
YYCC::yycc_u8string single_selection;
|
auto result1 = open_file(params);
|
||||||
std::vector<YYCC::yycc_u8string> multiple_selection;
|
auto result2 = open_files(params);
|
||||||
|
auto result3 = save_file(params);
|
||||||
YYCC::DialogHelper::OpenFileDialog(params, single_selection);
|
auto result4 = open_folder(params);
|
||||||
YYCC::DialogHelper::OpenMultipleFileDialog(params, multiple_selection);
|
|
||||||
YYCC::DialogHelper::SaveFileDialog(params, single_selection);
|
|
||||||
YYCC::DialogHelper::OpenFolderDialog(params, single_selection);
|
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
There are 4 file dialogs you can choose:
|
There are 4 file dialogs you can choose:
|
||||||
|
|
||||||
\li #OpenFileDialog: Open single file
|
\li open_file(): Open single file
|
||||||
\li #OpenMultipleFileDialog: Open multiple files
|
\li open_files(): Open multiple files
|
||||||
\li #SaveFileDialog: Save single file
|
\li save_file(): Save single file
|
||||||
\li #OpenFolderDialog: Open single directory
|
\li open_folder(): Open single directory
|
||||||
|
|
||||||
\subsection dialog_helper__result__arguments Arguments
|
\subsection windows__dialog__result__arguments Arguments
|
||||||
|
|
||||||
Among these 4 functions, the first argument always is the reference to FileDialog.
|
Among these 4 functions, the only argument is the reference to FileDialog.
|
||||||
Function will use it to decide what should be shown in this file dialog.
|
Function will use it to decide what would be shown in this file dialog.
|
||||||
|
|
||||||
The second argument always is the reference to the container receiving the result.
|
\subsection windows__dialog__result__return_value Return Value
|
||||||
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>.
|
|
||||||
|
|
||||||
\subsection dialog_helper__result__return_value Return Value
|
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.
|
||||||
|
|
||||||
Please note among these 4 functions will return a bool as its return value to indicate the success of function.
|
\section windows__dialog__notes Notes
|
||||||
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.
|
You may notice there are various classes which we never introduce.
|
||||||
Because they are intermediate classes and should not be used by programmer.
|
Because they are intermediate classes and should not be used by programmer.
|
||||||
@@ -1,25 +1,27 @@
|
|||||||
namespace YYCC {
|
namespace yycc::windows {
|
||||||
/**
|
/**
|
||||||
|
|
||||||
\page win_import Windows Import Guard
|
\page windows__import_guard Windows Import Guard
|
||||||
|
|
||||||
Windows is shitty for the programmer who is familiar with UNIX programming.
|
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.
|
Due to legacy reason, Windows defines various things which are not compatible with UNIX or standard C++ programming.
|
||||||
|
|
||||||
\section win_import__usage Usage
|
\section windows__import_guard__usage Usage
|
||||||
|
|
||||||
YYCC has a way to solve the issue introduced above.
|
YYCC has a way to solve the issue introduced above.
|
||||||
|
|
||||||
\code
|
\code
|
||||||
|
#include <yycc/macro/os_detector.hpp>
|
||||||
|
|
||||||
#if defined(YYCC_OS_WINDOWS)
|
#if defined(YYCC_OS_WINDOWS)
|
||||||
#include <WinImportPrefix.hpp>
|
#include <yycc/windows/import_guard_head.hpp>
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include "other_header_depend_on_windows.h"
|
#include "other_header_depend_on_windows.h"
|
||||||
#include <WinImportSuffix.hpp>
|
#include <yycc/windows/import_guard_tail.hpp>
|
||||||
#endif
|
#endif
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
The including of WinImportPrefix.hpp and WinImportSuffix.hpp is a pair.
|
The including of import_guard_head.hpp and import_guard_tail.hpp is a pair.
|
||||||
They just like a guard bracket the include operation of Windows related headers,
|
They just like a guard bracket the include operation of Windows related headers,
|
||||||
to keep all Windows shitty contents will not be leaked outside.
|
to keep all Windows shitty contents will not be leaked outside.
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ This guard can solve following issues:
|
|||||||
Programmer can not use \c std::max and \c std::min normally.
|
Programmer can not use \c std::max and \c std::min normally.
|
||||||
<UL>
|
<UL>
|
||||||
<LI>Windows defines \c MAX and \c MIN as macros for personal use. This is why this happened.</LI>
|
<LI>Windows defines \c MAX and \c MIN as macros for personal use. This is why this happened.</LI>
|
||||||
<LI>Guard defines some special macros to tell Windows do not create these 2 macros.</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>
|
||||||
</UL>
|
</UL>
|
||||||
</LI>
|
</LI>
|
||||||
<LI>
|
<LI>
|
||||||
@@ -44,12 +46,12 @@ This guard can solve following issues:
|
|||||||
Compiler throw annoy warnings and errors when using specific standard library functions.
|
Compiler throw annoy warnings and errors when using specific standard library functions.
|
||||||
<UL>
|
<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>MSVC will throw warnings and errors when you are using Microsoft so-called \e depracted or \e unsafe standard library functions.</LI>
|
||||||
<LI>YYCCInternal.hpp, which has been included by this pair, defines some macros to purge these warnings and errors out.</LI>
|
<LI>This is also done by CMake public build macros.</LI>
|
||||||
</UL>
|
</UL>
|
||||||
</LI>
|
</LI>
|
||||||
</UL>
|
</UL>
|
||||||
|
|
||||||
\section win_import__notes Notes
|
\section windows__import_guard__notes Notes
|
||||||
|
|
||||||
If you have other header files which are strongly depend on Windows header,
|
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.
|
you should put them into this bracket at the same time like example did.
|
||||||
15
doc/src/windows/winfct.dox
Normal file
15
doc/src/windows/winfct.dox
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace yycc::windows::winfct {
|
||||||
|
/**
|
||||||
|
|
||||||
|
\page windows__winfct Windows Function Helper
|
||||||
|
|
||||||
|
Namespace yycc::windows::winfct gives a more convenient way to call Windows functions.
|
||||||
|
If you want to know how to use these functions, please read the documentation of each function.
|
||||||
|
The return value of most functions is a specific result type.
|
||||||
|
If any error occurs, the result type will be an error, otherwise it will be the true result.
|
||||||
|
|
||||||
|
This namespace is Windows specific.
|
||||||
|
It will be entirely invisible in other platforms.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
# Create build directory and enter it
|
# Create build directory and enter it
|
||||||
mkdir bin
|
mkdir bin
|
||||||
19
script/macos_build.sh
Normal file
19
script/macos_build.sh
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create build directory and enter it
|
||||||
|
mkdir bin
|
||||||
|
cd bin
|
||||||
|
# Create internal build and install directory, then enter it
|
||||||
|
mkdir build
|
||||||
|
mkdir install
|
||||||
|
cd build
|
||||||
|
|
||||||
|
# Build in Release mode
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release ../..
|
||||||
|
cmake --build .
|
||||||
|
cmake --install . --prefix=../install
|
||||||
|
|
||||||
|
# Back to root directory
|
||||||
|
cd ..
|
||||||
|
cd ..
|
||||||
@@ -11,7 +11,7 @@ CD build
|
|||||||
:: Build with x64 architecture in Release mode
|
:: Build with x64 architecture in Release mode
|
||||||
cmake -A x64 ../..
|
cmake -A x64 ../..
|
||||||
cmake --build . --config Release
|
cmake --build . --config Release
|
||||||
cmake --install . --prefix=../install --config Relese
|
cmake --install . --prefix=../install --config Release
|
||||||
|
|
||||||
:: Back to root directory
|
:: Back to root directory
|
||||||
CD ..
|
CD ..
|
||||||
@@ -39,6 +39,10 @@ PRIVATE
|
|||||||
yycc/carton/clap/parser.cpp
|
yycc/carton/clap/parser.cpp
|
||||||
yycc/carton/clap/resolver.cpp
|
yycc/carton/clap/resolver.cpp
|
||||||
yycc/carton/binstore/types.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
|
target_sources(YYCCommonplace
|
||||||
PUBLIC
|
PUBLIC
|
||||||
@@ -55,7 +59,7 @@ FILES
|
|||||||
yycc/macro/ptr_size_detector.hpp
|
yycc/macro/ptr_size_detector.hpp
|
||||||
yycc/macro/class_copy_move.hpp
|
yycc/macro/class_copy_move.hpp
|
||||||
yycc/macro/printf_checker.hpp
|
yycc/macro/printf_checker.hpp
|
||||||
yycc/flag_enum.hpp
|
yycc/cenum.hpp
|
||||||
yycc/string.hpp
|
yycc/string.hpp
|
||||||
yycc/string/reinterpret.hpp
|
yycc/string/reinterpret.hpp
|
||||||
yycc/string/op.hpp
|
yycc/string/op.hpp
|
||||||
@@ -63,6 +67,9 @@ FILES
|
|||||||
yycc/patch/fopen.hpp
|
yycc/patch/fopen.hpp
|
||||||
yycc/patch/stream.hpp
|
yycc/patch/stream.hpp
|
||||||
yycc/patch/format.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/parse.hpp
|
||||||
yycc/num/stringify.hpp
|
yycc/num/stringify.hpp
|
||||||
yycc/num/safe_cast.hpp
|
yycc/num/safe_cast.hpp
|
||||||
@@ -80,8 +87,6 @@ FILES
|
|||||||
yycc/windows/dialog.hpp
|
yycc/windows/dialog.hpp
|
||||||
yycc/windows/winfct.hpp
|
yycc/windows/winfct.hpp
|
||||||
yycc/windows/console.hpp
|
yycc/windows/console.hpp
|
||||||
yycc/constraint.hpp
|
|
||||||
yycc/constraint/builder.hpp
|
|
||||||
yycc/encoding/stl.hpp
|
yycc/encoding/stl.hpp
|
||||||
yycc/encoding/windows.hpp
|
yycc/encoding/windows.hpp
|
||||||
yycc/encoding/iconv.hpp
|
yycc/encoding/iconv.hpp
|
||||||
@@ -102,8 +107,13 @@ FILES
|
|||||||
yycc/carton/clap/validator.hpp
|
yycc/carton/clap/validator.hpp
|
||||||
yycc/carton/clap/parser.hpp
|
yycc/carton/clap/parser.hpp
|
||||||
yycc/carton/clap/resolver.hpp
|
yycc/carton/clap/resolver.hpp
|
||||||
|
yycc/carton/binstore.hpp
|
||||||
yycc/carton/binstore/types.hpp
|
yycc/carton/binstore/types.hpp
|
||||||
yycc/carton/binstore/serializer.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
|
yycc/carton/fft.hpp
|
||||||
)
|
)
|
||||||
# Setup header infomations
|
# Setup header infomations
|
||||||
@@ -136,9 +146,10 @@ PUBLIC
|
|||||||
$<$<PLATFORM_ID:Darwin>:YYCC_OS_MACOS>
|
$<$<PLATFORM_ID:Darwin>:YYCC_OS_MACOS>
|
||||||
$<$<PLATFORM_ID:iOS>:YYCC_OS_MACOS> # We brutally think iOS as macOS.
|
$<$<PLATFORM_ID:iOS>:YYCC_OS_MACOS> # We brutally think iOS as macOS.
|
||||||
# Compiler macro
|
# Compiler macro
|
||||||
|
$<$<CXX_COMPILER_ID:MSVC>:YYCC_CC_MSVC>
|
||||||
$<$<CXX_COMPILER_ID:GNU>:YYCC_CC_GCC>
|
$<$<CXX_COMPILER_ID:GNU>:YYCC_CC_GCC>
|
||||||
$<$<CXX_COMPILER_ID:Clang>:YYCC_CC_CLANG>
|
$<$<CXX_COMPILER_ID:Clang>:YYCC_CC_CLANG>
|
||||||
$<$<CXX_COMPILER_ID:MSVC>:YYCC_CC_MSVC>
|
$<$<CXX_COMPILER_ID:AppleClang>:YYCC_CC_CLANG> # We brutally think AppleClang is Clang.
|
||||||
# Endian macro
|
# Endian macro
|
||||||
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},LITTLE_ENDIAN>:YYCC_ENDIAN_LITTLE>
|
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},LITTLE_ENDIAN>:YYCC_ENDIAN_LITTLE>
|
||||||
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},BIG_ENDIAN>:YYCC_ENDIAN_BIG>
|
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},BIG_ENDIAN>:YYCC_ENDIAN_BIG>
|
||||||
@@ -156,6 +167,15 @@ PUBLIC
|
|||||||
# Fix Windows header file shit
|
# Fix Windows header file shit
|
||||||
$<$<BOOL:${WIN32}>:WIN32_LEAN_AND_MEAN>
|
$<$<BOOL:${WIN32}>:WIN32_LEAN_AND_MEAN>
|
||||||
$<$<BOOL:${WIN32}>:NOMINMAX>
|
$<$<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
|
target_compile_options(YYCCommonplace
|
||||||
PUBLIC
|
PUBLIC
|
||||||
|
|||||||
@@ -1,348 +0,0 @@
|
|||||||
#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
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,465 +0,0 @@
|
|||||||
#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
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
#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
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
#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
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
29
src/yycc/carton/binstore.hpp
Normal file
29
src/yycc/carton/binstore.hpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "binstore/types.hpp"
|
||||||
|
#include "binstore/serdes.hpp"
|
||||||
|
#include "binstore/setting.hpp"
|
||||||
|
#include "binstore/configuration.hpp"
|
||||||
|
#include "binstore/storage.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The main namespace for the binstore module.
|
||||||
|
* @details
|
||||||
|
* 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:
|
||||||
|
*
|
||||||
|
* \li Type-safe serialization and deserialization of various data types
|
||||||
|
* (integers, floating-point numbers, booleans, strings, enums)
|
||||||
|
* \li Setting management with unique tokens for access control
|
||||||
|
* \li Version control for configuration data with migration strategies
|
||||||
|
* \li File and stream-based storage operations
|
||||||
|
* \li Comprehensive error handling for robust operation
|
||||||
|
*
|
||||||
|
* The main components are:
|
||||||
|
* \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
|
||||||
|
*/
|
||||||
|
namespace yycc::carton::binstore {}
|
||||||
21
src/yycc/carton/binstore/configuration.cpp
Normal file
21
src/yycc/carton/binstore/configuration.cpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include "configuration.hpp"
|
||||||
|
|
||||||
|
#define BINSTORE ::yycc::carton::binstore
|
||||||
|
#define TYPES ::yycc::carton::binstore::types
|
||||||
|
|
||||||
|
namespace yycc::carton::binstore::configuration {
|
||||||
|
|
||||||
|
Configuration::Configuration(TYPES::VersionIdentifier version, BINSTORE::setting::SettingCollection&& settings) :
|
||||||
|
version(version), settings(std::move(settings)) {}
|
||||||
|
|
||||||
|
Configuration::~Configuration() {}
|
||||||
|
|
||||||
|
TYPES::VersionIdentifier Configuration::get_version() const {
|
||||||
|
return this->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BINSTORE::setting::SettingCollection& Configuration::get_settings() const {
|
||||||
|
return this->settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace yycc::carton::binstore::configuration
|
||||||
43
src/yycc/carton/binstore/configuration.hpp
Normal file
43
src/yycc/carton/binstore/configuration.hpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../../macro/class_copy_move.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include "setting.hpp"
|
||||||
|
|
||||||
|
#define NS_YYCC_BINSTORE ::yycc::carton::binstore
|
||||||
|
#define NS_YYCC_BINSTORE_TYPES ::yycc::carton::binstore::types
|
||||||
|
|
||||||
|
namespace yycc::carton::binstore::configuration {
|
||||||
|
|
||||||
|
/// @brief Configuration class that holds version and settings for the binstore module.
|
||||||
|
class Configuration {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Configuration object.
|
||||||
|
* @param[in] version The version identifier for this configuration.
|
||||||
|
* @param[in] settings The settings collection to associate with this configuration.
|
||||||
|
*/
|
||||||
|
Configuration(NS_YYCC_BINSTORE_TYPES::VersionIdentifier version, NS_YYCC_BINSTORE::setting::SettingCollection&& settings);
|
||||||
|
~Configuration();
|
||||||
|
YYCC_DEFAULT_COPY_MOVE(Configuration)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Get the version identifier of this configuration.
|
||||||
|
* @return The version identifier.
|
||||||
|
*/
|
||||||
|
NS_YYCC_BINSTORE_TYPES::VersionIdentifier get_version() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the settings collection associated with this configuration.
|
||||||
|
* @return A const reference to the settings collection.
|
||||||
|
*/
|
||||||
|
const NS_YYCC_BINSTORE::setting::SettingCollection& get_settings() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
NS_YYCC_BINSTORE_TYPES::VersionIdentifier version; ///< The version of current configuration struct.
|
||||||
|
NS_YYCC_BINSTORE::setting::SettingCollection settings; ///< All registered settings.
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace yycc::carton::binstore::configuration
|
||||||
|
|
||||||
|
#undef NS_YYCC_BINSTORE_TYPES
|
||||||
|
#undef NS_YYCC_BINSTORE
|
||||||
@@ -5,10 +5,14 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#define NS_YYCC_BINSTORE_TYPES ::yycc::carton::binstore::types
|
#define NS_YYCC_BINSTORE_TYPES ::yycc::carton::binstore::types
|
||||||
|
|
||||||
namespace yycc::carton::binstore::serializer {
|
namespace yycc::carton::binstore::serdes {
|
||||||
|
|
||||||
// TODO: Support numeric list and string list SerDes.
|
// TODO: Support numeric list and string list SerDes.
|
||||||
|
|
||||||
@@ -43,10 +47,11 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
using SerDesValueType = T::ValueType;
|
using SerDesValueType = T::ValueType;
|
||||||
|
|
||||||
template<std::integral T,
|
template<std::integral T,
|
||||||
|
auto TDefault = static_cast<T>(0),
|
||||||
auto TMin = std::numeric_limits<T>::min(),
|
auto TMin = std::numeric_limits<T>::min(),
|
||||||
auto TMax = std::numeric_limits<T>::max(),
|
auto TMax = std::numeric_limits<T>::max()>
|
||||||
auto TDefault = static_cast<T>(0)>
|
|
||||||
struct IntegralSerDes {
|
struct IntegralSerDes {
|
||||||
|
IntegralSerDes() = default;
|
||||||
YYCC_DEFAULT_COPY_MOVE(IntegralSerDes)
|
YYCC_DEFAULT_COPY_MOVE(IntegralSerDes)
|
||||||
|
|
||||||
static_assert(TMin <= TMax);
|
static_assert(TMin <= TMax);
|
||||||
@@ -59,15 +64,15 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
if (value > TMax || value < TMin) return std::nullopt;
|
if (value > TMax || value < TMin) return std::nullopt;
|
||||||
|
|
||||||
NS_YYCC_BINSTORE_TYPES::ByteArray ba;
|
NS_YYCC_BINSTORE_TYPES::ByteArray ba;
|
||||||
ba.ResizeData(VALUE_SIZE);
|
ba.resize_data(VALUE_SIZE);
|
||||||
std::memcpy(ba.GetDataPtr(), &value, VALUE_SIZE);
|
std::memcpy(ba.get_data_ptr(), &value, VALUE_SIZE);
|
||||||
return ba;
|
return ba;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
||||||
if (ba.GetDataSize() != VALUE_SIZE) return std::nullopt;
|
if (ba.get_data_size() != VALUE_SIZE) return std::nullopt;
|
||||||
ValueType value;
|
ValueType value;
|
||||||
std::memcpy(&value, ba.GetDataPtr(), VALUE_SIZE);
|
std::memcpy(&value, ba.get_data_ptr(), VALUE_SIZE);
|
||||||
|
|
||||||
if (value > TMax || value < TMin) return std::nullopt;
|
if (value > TMax || value < TMin) return std::nullopt;
|
||||||
return value;
|
return value;
|
||||||
@@ -77,14 +82,20 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<std::floating_point T,
|
template<std::floating_point T,
|
||||||
|
auto TDefault = static_cast<T>(0),
|
||||||
auto TMin = std::numeric_limits<T>::lowest(),
|
auto TMin = std::numeric_limits<T>::lowest(),
|
||||||
auto TMax = std::numeric_limits<T>::max(),
|
auto TMax = std::numeric_limits<T>::max()>
|
||||||
auto TDefault = static_cast<T>(0)>
|
|
||||||
struct FloatingPointSerDes {
|
struct FloatingPointSerDes {
|
||||||
|
FloatingPointSerDes() {
|
||||||
|
// TODO: Remove this and make it "= default" when 3 common STL make std::isfinite become constexpr.
|
||||||
|
if (!std::isfinite(TMin)) throw std::logic_error("invalid float minimum value.");
|
||||||
|
if (!std::isfinite(TMax)) throw std::logic_error("invalid float maximum value.");
|
||||||
|
}
|
||||||
YYCC_DEFAULT_COPY_MOVE(FloatingPointSerDes)
|
YYCC_DEFAULT_COPY_MOVE(FloatingPointSerDes)
|
||||||
|
|
||||||
static_assert(std::isfinite(TMin));
|
// TODO: Use static_assert once 3 common STL make this become constexpr.
|
||||||
static_assert(std::isfinite(TMax));
|
//static_assert(std::isfinite(TMin));
|
||||||
|
//static_assert(std::isfinite(TMax));
|
||||||
static_assert(TMin <= TMax);
|
static_assert(TMin <= TMax);
|
||||||
static_assert(TDefault >= TMin && TDefault <= TMax);
|
static_assert(TDefault >= TMin && TDefault <= TMax);
|
||||||
|
|
||||||
@@ -95,16 +106,16 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
if (value > TMax || value < TMin) return std::nullopt;
|
if (value > TMax || value < TMin) return std::nullopt;
|
||||||
|
|
||||||
NS_YYCC_BINSTORE_TYPES::ByteArray ba;
|
NS_YYCC_BINSTORE_TYPES::ByteArray ba;
|
||||||
ba.ResizeData(VALUE_SIZE);
|
ba.resize_data(VALUE_SIZE);
|
||||||
std::memcpy(ba.GetDataPtr(), &value, VALUE_SIZE);
|
std::memcpy(ba.get_data_ptr(), &value, VALUE_SIZE);
|
||||||
return ba;
|
return ba;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
||||||
if (ba.GetDataSize() != VALUE_SIZE) return std::nullopt;
|
if (ba.get_data_size() != VALUE_SIZE) return std::nullopt;
|
||||||
|
|
||||||
ValueType value;
|
ValueType value;
|
||||||
std::memcpy(&value, ba.GetDataPtr(), VALUE_SIZE);
|
std::memcpy(&value, ba.get_data_ptr(), VALUE_SIZE);
|
||||||
if (value > TMax || value < TMin) return std::nullopt;
|
if (value > TMax || value < TMin) return std::nullopt;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@@ -112,8 +123,32 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
NS_YYCC_BINSTORE_TYPES::ByteArray reset() const { return this->serialize(TDefault).value(); }
|
NS_YYCC_BINSTORE_TYPES::ByteArray reset() const { return this->serialize(TDefault).value(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T, T TDefault>
|
||||||
|
requires std::is_enum_v<T>
|
||||||
|
struct EnumSerDes {
|
||||||
|
EnumSerDes() = default;
|
||||||
|
YYCC_DEFAULT_COPY_MOVE(EnumSerDes)
|
||||||
|
|
||||||
|
using UnderlyingType = std::underlying_type_t<T>;
|
||||||
|
using ValueType = T;
|
||||||
|
|
||||||
|
std::optional<NS_YYCC_BINSTORE_TYPES::ByteArray> serialize(const ValueType& value) const {
|
||||||
|
return inner.serialize(static_cast<UnderlyingType>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
||||||
|
return inner.deserialize(ba).transform([](auto v) { return static_cast<ValueType>(v); });
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_YYCC_BINSTORE_TYPES::ByteArray reset() const { return inner.reset(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
IntegralSerDes<UnderlyingType, static_cast<UnderlyingType>(TDefault)> inner;
|
||||||
|
};
|
||||||
|
|
||||||
template<bool TDefault = false>
|
template<bool TDefault = false>
|
||||||
struct BoolSerDes {
|
struct BoolSerDes {
|
||||||
|
BoolSerDes() = default;
|
||||||
YYCC_DEFAULT_COPY_MOVE(BoolSerDes)
|
YYCC_DEFAULT_COPY_MOVE(BoolSerDes)
|
||||||
|
|
||||||
using ValueType = bool;
|
using ValueType = bool;
|
||||||
@@ -121,16 +156,16 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
|
|
||||||
std::optional<NS_YYCC_BINSTORE_TYPES::ByteArray> serialize(const ValueType& value) const {
|
std::optional<NS_YYCC_BINSTORE_TYPES::ByteArray> serialize(const ValueType& value) const {
|
||||||
NS_YYCC_BINSTORE_TYPES::ByteArray ba;
|
NS_YYCC_BINSTORE_TYPES::ByteArray ba;
|
||||||
ba.ResizeData(VALUE_SIZE);
|
ba.resize_data(VALUE_SIZE);
|
||||||
std::memcpy(ba.GetDataPtr(), &value, VALUE_SIZE);
|
std::memcpy(ba.get_data_ptr(), &value, VALUE_SIZE);
|
||||||
return ba;
|
return ba;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
||||||
if (ba.GetDataSize() != VALUE_SIZE) return std::nullopt;
|
if (ba.get_data_size() != VALUE_SIZE) return std::nullopt;
|
||||||
|
|
||||||
ValueType value;
|
ValueType value;
|
||||||
std::memcpy(&value, ba.GetDataPtr(), VALUE_SIZE);
|
std::memcpy(&value, ba.get_data_ptr(), VALUE_SIZE);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +173,7 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct StringSerDes {
|
struct StringSerDes {
|
||||||
|
StringSerDes() = default;
|
||||||
YYCC_DEFAULT_COPY_MOVE(StringSerDes)
|
YYCC_DEFAULT_COPY_MOVE(StringSerDes)
|
||||||
|
|
||||||
using ValueType = std::u8string;
|
using ValueType = std::u8string;
|
||||||
@@ -150,12 +186,12 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
|
|
||||||
// Prepare byte array and allocate size.
|
// Prepare byte array and allocate size.
|
||||||
NS_YYCC_BINSTORE_TYPES::ByteArray ba;
|
NS_YYCC_BINSTORE_TYPES::ByteArray ba;
|
||||||
ba.ResizeData(HEADER_SIZE + length);
|
ba.resize_data(HEADER_SIZE + length);
|
||||||
|
|
||||||
// Copy length first
|
// Copy length first
|
||||||
std::memcpy(ba.GetDataPtr(), &length, HEADER_SIZE);
|
std::memcpy(ba.get_data_ptr(), &length, HEADER_SIZE);
|
||||||
// Copy string data
|
// Copy string data
|
||||||
std::memcpy(ba.GetDataPtr(HEADER_SIZE), value.data(), length);
|
std::memcpy(ba.get_data_ptr(HEADER_SIZE), value.data(), length);
|
||||||
|
|
||||||
// Okey
|
// Okey
|
||||||
return ba;
|
return ba;
|
||||||
@@ -163,20 +199,20 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
|
|
||||||
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
std::optional<ValueType> deserialize(const NS_YYCC_BINSTORE_TYPES::ByteArray& ba) const {
|
||||||
// Get byte array size
|
// Get byte array size
|
||||||
size_t ba_size = ba.GetDataSize();
|
size_t ba_size = ba.get_data_size();
|
||||||
// Check whether it has header
|
// Check whether it has header
|
||||||
if (ba_size < HEADER_SIZE) return std::nullopt;
|
if (ba_size < HEADER_SIZE) return std::nullopt;
|
||||||
|
|
||||||
// Get header
|
// Get header
|
||||||
HeaderType length;
|
HeaderType length;
|
||||||
std::memcpy(&length, ba.GetDataPtr(), HEADER_SIZE);
|
std::memcpy(&length, ba.get_data_ptr(), HEADER_SIZE);
|
||||||
|
|
||||||
// Check whether full size is header + length.
|
// Check whether full size is header + length.
|
||||||
if (ba_size != HEADER_SIZE + length) return std::nullopt;
|
if (ba_size != HEADER_SIZE + length) return std::nullopt;
|
||||||
// Prepare result
|
// Prepare result
|
||||||
std::u8string value(length, u8'\0');
|
std::u8string value(length, u8'\0');
|
||||||
// Read into result
|
// Read into result
|
||||||
std::memcpy(value.data(), ba.GetDataPtr(HEADER_SIZE), length);
|
std::memcpy(value.data(), ba.get_data_ptr(HEADER_SIZE), length);
|
||||||
|
|
||||||
// Okey
|
// Okey
|
||||||
return value;
|
return value;
|
||||||
@@ -184,4 +220,7 @@ namespace yycc::carton::binstore::serializer {
|
|||||||
|
|
||||||
NS_YYCC_BINSTORE_TYPES::ByteArray reset() const { return this->serialize(u8"").value(); }
|
NS_YYCC_BINSTORE_TYPES::ByteArray reset() const { return this->serialize(u8"").value(); }
|
||||||
};
|
};
|
||||||
} // namespace yycc::carton::binstore::serializer
|
|
||||||
|
} // namespace yycc::carton::binstore::serdes
|
||||||
|
|
||||||
|
#undef NS_YYCC_BINSTORE_TYPES
|
||||||
89
src/yycc/carton/binstore/setting.cpp
Normal file
89
src/yycc/carton/binstore/setting.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include "setting.hpp"
|
||||||
|
#include "../../patch/format.hpp"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#define TYPES ::yycc::carton::binstore::types
|
||||||
|
#define FORMAT ::yycc::patch::format
|
||||||
|
|
||||||
|
namespace yycc::carton::binstore::setting {
|
||||||
|
|
||||||
|
#pragma region Setting Class
|
||||||
|
|
||||||
|
Setting::Setting(const std::u8string_view& name) : name(name) {
|
||||||
|
if (name.empty()) {
|
||||||
|
throw std::logic_error("the name of setting should not be empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Setting::~Setting() {}
|
||||||
|
|
||||||
|
std::u8string_view Setting::get_name() const {
|
||||||
|
return this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region RegisteredSetting Class
|
||||||
|
|
||||||
|
RegisteredSetting::RegisteredSetting(TYPES::Token token, Setting&& setting) : token(token), setting(std::move(setting)) {}
|
||||||
|
|
||||||
|
RegisteredSetting::~RegisteredSetting() {}
|
||||||
|
|
||||||
|
TYPES::Token RegisteredSetting::get_token() const {
|
||||||
|
return this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Setting& RegisteredSetting::get_setting() const {
|
||||||
|
return this->setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region SettingCollection Class
|
||||||
|
|
||||||
|
SettingCollection::SettingCollection() : names(), settings() {}
|
||||||
|
|
||||||
|
SettingCollection::~SettingCollection() {}
|
||||||
|
|
||||||
|
TYPES::Token SettingCollection::add_setting(Setting&& setting) {
|
||||||
|
auto token = this->settings.size();
|
||||||
|
|
||||||
|
std::u8string name(setting.get_name());
|
||||||
|
auto [_, ok] = this->names.try_emplace(name, token);
|
||||||
|
if (!ok) {
|
||||||
|
throw std::logic_error(FORMAT::format("duplicated setting name {}", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->settings.emplace_back(RegisteredSetting(token, std::move(setting)));
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<TYPES::Token> SettingCollection::find_name(const std::u8string_view& name) const {
|
||||||
|
auto finder = this->names.find(std::u8string(name));
|
||||||
|
if (finder == this->names.end()) return std::nullopt;
|
||||||
|
else return finder->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SettingCollection::has_setting(TYPES::Token token) const {
|
||||||
|
return token < this->settings.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Setting& SettingCollection::get_setting(TYPES::Token token) const {
|
||||||
|
return this->settings.at(token).get_setting();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<RegisteredSetting>& SettingCollection::all_settings() const {
|
||||||
|
return this->settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SettingCollection::length() const {
|
||||||
|
return this->settings.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SettingCollection::empty() const {
|
||||||
|
return this->settings.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
} // namespace yycc::carton::binstore::setting
|
||||||
128
src/yycc/carton/binstore/setting.hpp
Normal file
128
src/yycc/carton/binstore/setting.hpp
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* @file setting.hpp
|
||||||
|
* @brief Setting management for the binstore module.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../../macro/class_copy_move.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#define NS_YYCC_BINSTORE_TYPES ::yycc::carton::binstore::types
|
||||||
|
|
||||||
|
namespace yycc::carton::binstore::setting {
|
||||||
|
|
||||||
|
/// @brief Represents a single setting with a name.
|
||||||
|
class Setting {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Setting object.
|
||||||
|
* @param[in] name The name of the setting.
|
||||||
|
*/
|
||||||
|
Setting(const std::u8string_view& name);
|
||||||
|
~Setting();
|
||||||
|
YYCC_DEFAULT_COPY_MOVE(Setting)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Get the name of the setting.
|
||||||
|
* @return A u8string_view of the setting name.
|
||||||
|
*/
|
||||||
|
std::u8string_view get_name() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::u8string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Represents a registered setting with both a token and setting information.
|
||||||
|
class RegisteredSetting {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @brief Construct a new Registered Setting object.
|
||||||
|
* @param[in] token The unique token for the setting.
|
||||||
|
* @param[in] setting The setting information.
|
||||||
|
*/
|
||||||
|
RegisteredSetting(NS_YYCC_BINSTORE_TYPES::Token token, Setting&& setting);
|
||||||
|
~RegisteredSetting();
|
||||||
|
YYCC_DEFAULT_COPY_MOVE(RegisteredSetting)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Get the token associated with this registered setting.
|
||||||
|
* @return The token.
|
||||||
|
*/
|
||||||
|
NS_YYCC_BINSTORE_TYPES::Token get_token() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the setting instance associated with this registered setting.
|
||||||
|
* @return A const reference to the setting.
|
||||||
|
*/
|
||||||
|
const Setting& get_setting() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
NS_YYCC_BINSTORE_TYPES::Token token; ///< The unique token for the setting.
|
||||||
|
Setting setting; ///< The setting instance.
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Collection of settings with name-based lookup and token-based access.
|
||||||
|
class SettingCollection {
|
||||||
|
public:
|
||||||
|
/// @brief Construct a new Setting Collection object.
|
||||||
|
SettingCollection();
|
||||||
|
~SettingCollection();
|
||||||
|
YYCC_DEFAULT_COPY_MOVE(SettingCollection)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Add a setting to the collection.
|
||||||
|
* @param[in] setting The setting to add.
|
||||||
|
* @return The token assigned to the setting.
|
||||||
|
*/
|
||||||
|
NS_YYCC_BINSTORE_TYPES::Token add_setting(Setting&& setting);
|
||||||
|
/**
|
||||||
|
* @brief Find a setting by its name.
|
||||||
|
* @param[in] name The name of the setting to find.
|
||||||
|
* @return An optional token if the setting was found, std::nullopt otherwise.
|
||||||
|
*/
|
||||||
|
std::optional<NS_YYCC_BINSTORE_TYPES::Token> find_name(const std::u8string_view& name) const;
|
||||||
|
/**
|
||||||
|
* @brief Check if a setting with the given token exists.
|
||||||
|
* @param[in] token The token to check.
|
||||||
|
* @return True if the setting exists, false otherwise.
|
||||||
|
*/
|
||||||
|
bool has_setting(NS_YYCC_BINSTORE_TYPES::Token token) const;
|
||||||
|
/**
|
||||||
|
* @brief Get a setting by its token.
|
||||||
|
* @param[in] token The token of the setting to get.
|
||||||
|
* @return A const reference to the setting.
|
||||||
|
* @exception May throw if the token is invalid.
|
||||||
|
*/
|
||||||
|
const Setting& get_setting(NS_YYCC_BINSTORE_TYPES::Token token) const;
|
||||||
|
/**
|
||||||
|
* @brief Get all registered settings.
|
||||||
|
* @return A const reference to the vector of registered settings.
|
||||||
|
*/
|
||||||
|
const std::vector<RegisteredSetting>& all_settings() const;
|
||||||
|
/**
|
||||||
|
* @brief Get the number of settings in the collection.
|
||||||
|
* @return The number of settings.
|
||||||
|
*/
|
||||||
|
size_t length() const;
|
||||||
|
/**
|
||||||
|
* @brief Check if the collection is empty.
|
||||||
|
* @return True if empty, false otherwise.
|
||||||
|
*/
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::u8string, NS_YYCC_BINSTORE_TYPES::Token> names;
|
||||||
|
std::vector<RegisteredSetting> settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace yycc::carton::binstore::setting
|
||||||
|
|
||||||
|
#undef NS_YYCC_BINSTORE_TYPES
|
||||||
245
src/yycc/carton/binstore/storage.cpp
Normal file
245
src/yycc/carton/binstore/storage.cpp
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
#include "storage.hpp"
|
||||||
|
#include "../../num/safe_cast.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <concepts>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#define TYPES ::yycc::carton::binstore::types
|
||||||
|
#define CFG ::yycc::carton::binstore::configuration
|
||||||
|
#define SERDES ::yycc::carton::binstore::serdes
|
||||||
|
#define SAFECAST ::yycc::num::safe_cast
|
||||||
|
|
||||||
|
namespace yycc::carton::binstore::storage {
|
||||||
|
|
||||||
|
#pragma region Read and Write Helper Functions
|
||||||
|
|
||||||
|
static bool is_eof(std::istream& s) {
|
||||||
|
// Peek next value and check bit flag.
|
||||||
|
s.peek();
|
||||||
|
return s.eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_buffer(std::istream& s, void* buffer, size_t length) {
|
||||||
|
// Cast length
|
||||||
|
auto rv_length = SAFECAST::try_to<std::streamsize>(length);
|
||||||
|
if (!rv_length.has_value()) return false;
|
||||||
|
auto cast_length = rv_length.value();
|
||||||
|
|
||||||
|
// Read data.
|
||||||
|
s.read(static_cast<std::istream::char_type*>(buffer), cast_length);
|
||||||
|
// Check read count.
|
||||||
|
if (s.gcount() != cast_length) return false;
|
||||||
|
// Return IO status.
|
||||||
|
return s.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
requires std::is_pod_v<T>
|
||||||
|
static bool read_pod(std::istream& s, T& val) {
|
||||||
|
return read_buffer(s, &val, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_byte_array(std::istream& s, TYPES::ByteArray& ba) {
|
||||||
|
size_t length = 0;
|
||||||
|
if (!read_pod(s, length)) return false;
|
||||||
|
|
||||||
|
// Resize byte array.
|
||||||
|
// There is an exception may be thrown that resized length is too large.
|
||||||
|
// We need capture it and return error.
|
||||||
|
try {
|
||||||
|
ba.resize_data(length);
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Read data into byte array.
|
||||||
|
read_buffer(s, ba.get_data_ptr(), length);
|
||||||
|
// Okey
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_u8string(std::istream& s, std::u8string& strl) {
|
||||||
|
size_t length = 0;
|
||||||
|
if (!read_pod(s, length)) return false;
|
||||||
|
|
||||||
|
// Same reason for try-catch like ByteArray.
|
||||||
|
try {
|
||||||
|
strl.resize(length);
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Read data into byte array.
|
||||||
|
read_buffer(s, strl.data(), length);
|
||||||
|
// Okey
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool write_buffer(std::ostream& s, const void* buffer, size_t length) {
|
||||||
|
// Cast length
|
||||||
|
auto rv_length = SAFECAST::try_to<std::streamsize>(length);
|
||||||
|
if (!rv_length.has_value()) return false;
|
||||||
|
auto cast_length = rv_length.value();
|
||||||
|
|
||||||
|
// Write data.
|
||||||
|
s.write(static_cast<const std::istream::char_type*>(buffer), cast_length);
|
||||||
|
// There is no function to tell how many data was written,
|
||||||
|
// so directly return.
|
||||||
|
return s.good();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
requires std::is_pod_v<T>
|
||||||
|
static bool write_pod(std::ostream& s, const T& val) {
|
||||||
|
return write_buffer(s, &val, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool write_byte_array(std::ostream& s, const TYPES::ByteArray& ba) {
|
||||||
|
// Write length header.
|
||||||
|
auto length = ba.get_data_size();
|
||||||
|
if (!write_pod(s, length)) return false;
|
||||||
|
// Write body
|
||||||
|
return write_buffer(s, ba.get_data_ptr(), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool write_u8string(std::ostream& s, const std::u8string_view& sv) {
|
||||||
|
// Write length header.
|
||||||
|
auto length = sv.length();
|
||||||
|
if (!write_pod(s, length)) return false;
|
||||||
|
// Write body
|
||||||
|
return write_buffer(s, sv.data(), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
#pragma region Storage Class
|
||||||
|
|
||||||
|
Storage::Storage(CFG::Configuration&& cfg) : cfg(std::move(cfg)), raws() {}
|
||||||
|
|
||||||
|
Storage::~Storage() {}
|
||||||
|
|
||||||
|
TYPES::BinstoreResult<void> Storage::load_from_file(const std::filesystem::path& fpath, LoadStrategy strategy) {
|
||||||
|
std::ifstream fs(fpath, std::ios::binary);
|
||||||
|
if (fs.is_open()) {
|
||||||
|
auto rv = this->load(fs, strategy);
|
||||||
|
fs.close();
|
||||||
|
return rv;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(TYPES::BinstoreError::Io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPES::BinstoreResult<void> Storage::load(std::istream& s, LoadStrategy strategy) {
|
||||||
|
// Before loading, we need clear all stored raw data first.
|
||||||
|
this->clear();
|
||||||
|
|
||||||
|
// Read identifier
|
||||||
|
TYPES::VersionIdentifier version;
|
||||||
|
if (!read_pod(s, version)) return std::unexpected(TYPES::BinstoreError::Io);
|
||||||
|
|
||||||
|
// Check identifier with strategy
|
||||||
|
{
|
||||||
|
bool ok_for_read = false;
|
||||||
|
auto expected_version = this->cfg.get_version();
|
||||||
|
switch (strategy) {
|
||||||
|
case LoadStrategy::OnlyCurrent:
|
||||||
|
ok_for_read = (version == expected_version);
|
||||||
|
break;
|
||||||
|
case LoadStrategy::MigrateOld:
|
||||||
|
ok_for_read = (version <= expected_version);
|
||||||
|
break;
|
||||||
|
case LoadStrategy::AcceptAll:
|
||||||
|
ok_for_read = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!ok_for_read) return std::unexpected(TYPES::BinstoreError::BadVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read settings one by one.
|
||||||
|
const auto& settings = this->cfg.get_settings();
|
||||||
|
while (!is_eof(s)) {
|
||||||
|
// Read setting name
|
||||||
|
std::u8string setting_name;
|
||||||
|
if (!read_u8string(s, setting_name)) return std::unexpected(TYPES::BinstoreError::Io);
|
||||||
|
|
||||||
|
// Read setting body
|
||||||
|
TYPES::ByteArray ba;
|
||||||
|
if (!read_byte_array(s, ba)) return std::unexpected(TYPES::BinstoreError::Io);
|
||||||
|
|
||||||
|
// Check whether there is such setting and its token.
|
||||||
|
auto token_finder = settings.find_name(setting_name);
|
||||||
|
// If no such name, skip this setting.
|
||||||
|
if (!token_finder.has_value()) continue;
|
||||||
|
auto token = token_finder.value();
|
||||||
|
|
||||||
|
// If there is duplicated entry, report error
|
||||||
|
if (this->raws.contains(token)) return std::unexpected(TYPES::BinstoreError::DuplicatedAssign);
|
||||||
|
// Otherwise insert it
|
||||||
|
this->raws.emplace(token, std::move(ba));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okey
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPES::BinstoreResult<void> Storage::save_into_file(const std::filesystem::path& fpath) {
|
||||||
|
std::ofstream fs(fpath, std::ios::binary);
|
||||||
|
if (fs.is_open()) {
|
||||||
|
auto rv = this->save(fs);
|
||||||
|
fs.close();
|
||||||
|
return rv;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(TYPES::BinstoreError::Io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPES::BinstoreResult<void> Storage::save(std::ostream& s) {
|
||||||
|
// Write version identifier
|
||||||
|
auto version = this->cfg.get_version();
|
||||||
|
if (!write_pod(s, version)) return std::unexpected(TYPES::BinstoreError::Io);
|
||||||
|
|
||||||
|
// Write settings one by one
|
||||||
|
const auto& settings = this->cfg.get_settings();
|
||||||
|
for (const auto& [setting_token, setting_value] : this->raws) {
|
||||||
|
// Fetch setting name first
|
||||||
|
auto setting_name = settings.get_setting(setting_token).get_name();
|
||||||
|
|
||||||
|
// Write name
|
||||||
|
if (!write_u8string(s, setting_name)) return std::unexpected(TYPES::BinstoreError::Io);
|
||||||
|
// Write setting body
|
||||||
|
if (!write_byte_array(s, setting_value)) return std::unexpected(TYPES::BinstoreError::Io);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okey
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Storage::clear() {
|
||||||
|
this->raws.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Storage::has_setting(TYPES::Token token) const {
|
||||||
|
return this->cfg.get_settings().has_setting(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Storage::is_setting_stored(TYPES::Token token) const {
|
||||||
|
if (!this->has_setting(token)) throw std::logic_error("given setting token is invalid");
|
||||||
|
return this->raws.contains(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TYPES::ByteArray& Storage::get_raw_value(TYPES::Token token) const {
|
||||||
|
if (!this->has_setting(token)) throw std::logic_error("given setting token is invalid");
|
||||||
|
auto finder = this->raws.find(token);
|
||||||
|
if (finder != this->raws.end()) {
|
||||||
|
return finder->second;
|
||||||
|
} else {
|
||||||
|
throw std::logic_error("given setting has not been stored yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Storage::set_raw_value(TYPES::Token token, TYPES::ByteArray&& ba) {
|
||||||
|
if (!this->has_setting(token)) throw std::logic_error("given setting token is invalid");
|
||||||
|
this->raws.insert_or_assign(token, std::move(ba));
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
} // namespace yycc::carton::binstore::storage
|
||||||
230
src/yycc/carton/binstore/storage.hpp
Normal file
230
src/yycc/carton/binstore/storage.hpp
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
/**
|
||||||
|
* @file storage.hpp
|
||||||
|
* @brief Storage management for the binstore module.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../../macro/class_copy_move.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
#include "configuration.hpp"
|
||||||
|
#include "serdes.hpp"
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <istream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#define NS_YYCC_BINSTORE_TYPES ::yycc::carton::binstore::types
|
||||||
|
#define NS_YYCC_BINSTORE_CFG ::yycc::carton::binstore::configuration
|
||||||
|
#define NS_YYCC_BINSTORE_SERDES ::yycc::carton::binstore::serdes
|
||||||
|
|
||||||
|
namespace yycc::carton::binstore::storage {
|
||||||
|
|
||||||
|
/// @brief The strategy when loading from storage.
|
||||||
|
enum class LoadStrategy {
|
||||||
|
/**
|
||||||
|
* @brief Only accept matched version.
|
||||||
|
* @details
|
||||||
|
* Any loading of other versions will explicitly cause error return.
|
||||||
|
* This is convenient for developer who want control migration by themselves.
|
||||||
|
* They can specify this strategy and try to load data with different version configurations
|
||||||
|
* from older to newer one by one.
|
||||||
|
*/
|
||||||
|
OnlyCurrent,
|
||||||
|
/**
|
||||||
|
* @brief Try to migrate old version.
|
||||||
|
* @details
|
||||||
|
* Accept mateched and any older versions.
|
||||||
|
* Any newer versions will explicitly cause error return.
|
||||||
|
* This strategy is good for developer who are lazy to treat this manually.
|
||||||
|
*/
|
||||||
|
MigrateOld,
|
||||||
|
/**
|
||||||
|
* @brief Accept all version.
|
||||||
|
* @details
|
||||||
|
* This strategy is not suggested.
|
||||||
|
* This strategy only suit for quick demo.
|
||||||
|
*/
|
||||||
|
AcceptAll,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Main storage class for managing binary settings storage.
|
||||||
|
class Storage {
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief All stored values of setting in raw format.
|
||||||
|
* @details Key is the token to already registered settings.
|
||||||
|
* Valus is its stored value in raw data format.
|
||||||
|
*/
|
||||||
|
std::map<NS_YYCC_BINSTORE_TYPES::Token, NS_YYCC_BINSTORE_TYPES::ByteArray> raws;
|
||||||
|
NS_YYCC_BINSTORE_CFG::Configuration cfg; ///< The configuration associated with this storage.
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Storage object.
|
||||||
|
* @param[in] cfg The configuration to associate with this storage.
|
||||||
|
*/
|
||||||
|
Storage(NS_YYCC_BINSTORE_CFG::Configuration&& cfg);
|
||||||
|
~Storage();
|
||||||
|
YYCC_DEFAULT_COPY_MOVE(Storage)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Load settings from a file.
|
||||||
|
* @param[in] fpath Path to the file to load from.
|
||||||
|
* @param[in] strategy The load strategy to use. Defaults to MigrateOld.
|
||||||
|
* @return A BinstoreResult indicating success or failure.
|
||||||
|
*/
|
||||||
|
NS_YYCC_BINSTORE_TYPES::BinstoreResult<void> load_from_file(const std::filesystem::path& fpath,
|
||||||
|
LoadStrategy strategy = LoadStrategy::MigrateOld);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load settings from an input stream.
|
||||||
|
* @param[in] s The input stream to load from.
|
||||||
|
* @param[in] strategy The load strategy to use. Defaults to MigrateOld.
|
||||||
|
* @return A BinstoreResult indicating success or failure.
|
||||||
|
*/
|
||||||
|
NS_YYCC_BINSTORE_TYPES::BinstoreResult<void> load(std::istream& s, LoadStrategy strategy = LoadStrategy::MigrateOld);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save settings to a file.
|
||||||
|
* @param[in] fpath Path to the file to save to.
|
||||||
|
* @return A BinstoreResult indicating success or failure.
|
||||||
|
*/
|
||||||
|
NS_YYCC_BINSTORE_TYPES::BinstoreResult<void> save_into_file(const std::filesystem::path& fpath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save settings to an output stream.
|
||||||
|
* @param[in] s The output stream to save to.
|
||||||
|
* @return A BinstoreResult indicating success or failure.
|
||||||
|
*/
|
||||||
|
NS_YYCC_BINSTORE_TYPES::BinstoreResult<void> save(std::ostream& s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear all raw data saved in internal cache.
|
||||||
|
* @details This will cause every setting was set in default value when user fetching them.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Check whether given token is a valid token to registered settings.
|
||||||
|
* @param[in] token Token for checking.
|
||||||
|
* @return True if it is a registered setting, otherwise false.
|
||||||
|
*/
|
||||||
|
bool has_setting(NS_YYCC_BINSTORE_TYPES::Token token) const;
|
||||||
|
/**
|
||||||
|
* @brief Check whether given token is stored in raw value dictionary.
|
||||||
|
* @param[in] token Token for checking.
|
||||||
|
* @return True if it is stored, otherwise false.
|
||||||
|
* @exception std::logic_error Given token is not point to a registered setting.
|
||||||
|
*/
|
||||||
|
bool is_setting_stored(NS_YYCC_BINSTORE_TYPES::Token token) const;
|
||||||
|
/**
|
||||||
|
* @brief Get raw value of given setting.
|
||||||
|
* @param[in] token Token for fetching.
|
||||||
|
* @return The const reference to given setting's raw data.
|
||||||
|
* @exception std::logic_error Given token is not point to a registered setting.
|
||||||
|
* @exception std::logic_error Given token pointed to setting is not stored yet.
|
||||||
|
*/
|
||||||
|
const NS_YYCC_BINSTORE_TYPES::ByteArray& get_raw_value(NS_YYCC_BINSTORE_TYPES::Token token) const;
|
||||||
|
/**
|
||||||
|
* @brief Set raw value for given setting.
|
||||||
|
* @param[in] token Token for setting.
|
||||||
|
* @param[in] ba The right-value raw value for setting.
|
||||||
|
* @exception std::logic_error Given token is not point to a registered setting.
|
||||||
|
*/
|
||||||
|
void set_raw_value(NS_YYCC_BINSTORE_TYPES::Token token, NS_YYCC_BINSTORE_TYPES::ByteArray&& ba);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Reset given setting into default value.
|
||||||
|
* @tparam T The SerDes applied to this setting.
|
||||||
|
* @param[in] token Token to setting for resetting.
|
||||||
|
* @param[in] serdes Optional SerDes passed if it can not be constructed from default ctor.
|
||||||
|
*/
|
||||||
|
template<NS_YYCC_BINSTORE_SERDES::SerDes T>
|
||||||
|
void reset_value(NS_YYCC_BINSTORE_TYPES::Token token, const T& serdes = T{}) {
|
||||||
|
// Check whether has this setting.
|
||||||
|
if (!has_setting(token)) throw std::logic_error("given setting token is invalid");
|
||||||
|
// Reset it.
|
||||||
|
auto ba = serdes.reset();
|
||||||
|
this->set_raw_value(token, std::move(ba));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Get given setting's value.
|
||||||
|
* @tparam T The SerDes applied to this setting.
|
||||||
|
* @param[in] token Token to setting for fetching value.
|
||||||
|
* @param[in] serdes Optional SerDes passed if it can not be constructed from default ctor.
|
||||||
|
* @return Fetched value. If there is no such setting before or fail to deserialize underlying data,
|
||||||
|
* Default value will be forcely set before the return of this function.
|
||||||
|
*/
|
||||||
|
template<NS_YYCC_BINSTORE_SERDES::SerDes T>
|
||||||
|
NS_YYCC_BINSTORE_SERDES::SerDesValueType<T> get_value(NS_YYCC_BINSTORE_TYPES::Token token, const T& serdes = T{}) {
|
||||||
|
// Check whether has this setting.
|
||||||
|
if (!has_setting(token)) throw std::logic_error("given setting token is invalid");
|
||||||
|
|
||||||
|
// If we have stored raw value, we fetch it first.
|
||||||
|
if (this->is_setting_stored(token)) {
|
||||||
|
// Get raw value.
|
||||||
|
const auto& ba = this->get_raw_value(token);
|
||||||
|
// Try to deserialize it.
|
||||||
|
auto value = serdes.deserialize(ba);
|
||||||
|
// If the result is okey, return it.
|
||||||
|
if (value.has_value()) {
|
||||||
|
return value.value();
|
||||||
|
}
|
||||||
|
// Otherwise we need reset it into default value.
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we do not have this setting, or we need reset it into default value
|
||||||
|
// due to failed deserialization, we need execute following code.
|
||||||
|
// Reset its value first
|
||||||
|
this->reset_value(token, serdes);
|
||||||
|
// The re-fetch its raw value and deserialize it.
|
||||||
|
const auto& ba = this->get_raw_value(token);
|
||||||
|
auto value = serdes.deserialize(ba);
|
||||||
|
// Default value must can be deserialized.
|
||||||
|
// If not, throw exception.
|
||||||
|
if (value.has_value()) {
|
||||||
|
return value.value();
|
||||||
|
} else {
|
||||||
|
throw std::logic_error("default value must can be deserialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Set given setting's value.
|
||||||
|
* @tparam T The SerDes applied to this setting.
|
||||||
|
* @param[in] token Token to setting for setting value.
|
||||||
|
* @param[in] value The value to set.
|
||||||
|
* @param[in] serdes Optional SerDes passed if it can not be constructed from default ctor.
|
||||||
|
* @return True if the setting was set to your given value,
|
||||||
|
* otherwise false, the value was set as default value because given value can not be serialized.
|
||||||
|
*/
|
||||||
|
template<NS_YYCC_BINSTORE_SERDES::SerDes T>
|
||||||
|
bool set_value(NS_YYCC_BINSTORE_TYPES::Token token,
|
||||||
|
const NS_YYCC_BINSTORE_SERDES::SerDesValueType<T>& value,
|
||||||
|
const T& serdes = T{}) {
|
||||||
|
// Check whether has this setting.
|
||||||
|
if (!has_setting(token)) throw std::logic_error("given setting token is invalid");
|
||||||
|
|
||||||
|
// We try to serialize given value first.
|
||||||
|
auto rv_ser = serdes.serialize(value);
|
||||||
|
// If we can serialize it, we directly use it,'
|
||||||
|
// otherwise we need fetch it from default value.
|
||||||
|
auto success_ser = rv_ser.has_value();
|
||||||
|
auto ba = success_ser ? std::move(rv_ser.value()) : serdes.reset();
|
||||||
|
// Assign it to setting's raw value.
|
||||||
|
this->set_raw_value(token, std::move(ba));
|
||||||
|
|
||||||
|
// Return result
|
||||||
|
return success_ser;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace yycc::carton::binstore::storage
|
||||||
|
|
||||||
|
#undef NS_YYCC_BINSTORE_SERDES
|
||||||
|
#undef NS_YYCC_BINSTORE_CFG
|
||||||
|
#undef NS_YYCC_BINSTORE_TYPES
|
||||||
@@ -8,19 +8,19 @@ namespace yycc::carton::binstore::types {
|
|||||||
|
|
||||||
ByteArray::~ByteArray() {}
|
ByteArray::~ByteArray() {}
|
||||||
|
|
||||||
size_t ByteArray::GetDataSize() const {
|
size_t ByteArray::get_data_size() const {
|
||||||
return this->datas.size();
|
return this->datas.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ByteArray::ResizeData(size_t new_size) {
|
void ByteArray::resize_data(size_t new_size) {
|
||||||
this->datas.resize(new_size);
|
this->datas.resize(new_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
const void* ByteArray::GetDataPtr(size_t offset) const {
|
const void* ByteArray::get_data_ptr(size_t offset) const {
|
||||||
return this->datas.data() + offset;
|
return this->datas.data() + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ByteArray::GetDataPtr(size_t offset) {
|
void* ByteArray::get_data_ptr(size_t offset) {
|
||||||
return this->datas.data() + offset;
|
return this->datas.data() + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user