Compare commits
128 Commits
v1.0.0
...
7234b31ee0
Author | SHA1 | Date | |
---|---|---|---|
7234b31ee0 | |||
15aade052f | |||
050bed400d | |||
244e39c4d1 | |||
d52630ac5c | |||
a76f10722d | |||
8a72e6a655 | |||
dfc0c127c5 | |||
2f11ba6023 | |||
00c8f09907 | |||
734cd01da8 | |||
bdeaea294f | |||
ff8c7d04cc | |||
0ab470367c | |||
c4d441f5fa | |||
f8a696b4e8 | |||
f65eff6edf | |||
8fcfa180b4 | |||
e23a1346eb | |||
2576523dbb | |||
9ce52e8d4b | |||
7785773196 | |||
cfbc3c68e0 | |||
8dbe32cb8e | |||
664763afbb | |||
a34bab07c1 | |||
51d288ac4b | |||
20a9ef4166 | |||
17540072d3 | |||
fcac886f07 | |||
27baf2a080 | |||
b9f81c16a0 | |||
54134b342e | |||
ce2b411b0b | |||
5372af79f8 | |||
b79df0c65e | |||
4f0b3d19d1 | |||
f014e54604 | |||
821a592f02 | |||
6043609709 | |||
53e8a77f47 | |||
6d44c7605b | |||
c2f6e29c36 | |||
c102964703 | |||
3605151caf | |||
fa52d7416f | |||
e42a3b6e58 | |||
cec6091996 | |||
6e884d865d | |||
58ec960e9c | |||
732a560a65 | |||
3030a67ca3 | |||
e166dc41ac | |||
a6382d6a22 | |||
adc99274f4 | |||
3abd0969c0 | |||
28ff7008a8 | |||
ab8d74efe6 | |||
df3b602110 | |||
bec36b4b3c | |||
0b7e58c8e8 | |||
831fa130bc | |||
7adac00035 | |||
0cd9582757 | |||
2206825223 | |||
21f7e7f786 | |||
50dd086b53 | |||
c91df3a74f | |||
3858b4f3ec | |||
f3a88e951c | |||
59c185a424 | |||
dc98486fff | |||
72a48b703f | |||
33cb284eb7 | |||
e6c24b8b61 | |||
6da990876e | |||
0ac6b477f9 | |||
1cfbcb3b18 | |||
598aae69ae | |||
656495f22e | |||
e167479de3 | |||
19023cb949 | |||
650fcd12ec | |||
e8a0299fbc | |||
d1c1743dc9 | |||
35318505e4 | |||
f997990af6 | |||
7f373ed354 | |||
31a7cb5675 | |||
87fa30fe82 | |||
ecb06504bc | |||
805ffe70d6 | |||
052fa7f4d1 | |||
9e5bd370c4 | |||
94386c93aa | |||
b13bb445e4 | |||
81fe72c425 | |||
b912be082c | |||
9f47d0fe24 | |||
cc689ce8bb | |||
1ccea1290e | |||
ed549592dd | |||
1c2007928d | |||
a6c543c1b5 | |||
942e4ff8eb | |||
9a18233723 | |||
11b2185bb4 | |||
d27a66e770 | |||
4f1e2447d0 | |||
3075ec583d | |||
2e28dd4c48 | |||
a1699f13db | |||
65b81f5cfa | |||
1c5a85bbb2 | |||
1f04e23092 | |||
06e75924f1 | |||
e374575852 | |||
e2a582e7d2 | |||
588946583c | |||
e1823d4b8e | |||
23b4da95ce | |||
e5b6e8c6c3 | |||
ccb729c718 | |||
44dbbb1c99 | |||
91ba0c22d6 | |||
73ef8af56c | |||
61ad1ff3ce | |||
c15b57d055 |
317
.clang-format
Normal file
317
.clang-format
Normal file
@ -0,0 +1,317 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/clang-format.json
|
||||
---
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseArrows: false
|
||||
AlignCaseColons: false
|
||||
AlignConsecutiveTableGenBreakingDAGArgColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveTableGenCondOperatorColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveTableGenDefinitionColons:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: DontAlign
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowBreakBeforeNoexceptSpecifier: Never
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseExpressionOnASingleLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakAdjacentStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakAfterReturnType: None
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakFunctionDefinitionParameters: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: false
|
||||
BreakTemplateDeclarations: Yes
|
||||
ColumnLimit: 140
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- forever
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^<Q.*'
|
||||
Priority: 200
|
||||
SortPriority: 200
|
||||
CaseSensitive: true
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: true
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: false
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLines:
|
||||
AtEndOfFile: false
|
||||
AtStartOfBlock: false
|
||||
AtStartOfFile: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MainIncludeChar: Quote
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 150
|
||||
PenaltyBreakBeforeFirstCallParameter: 300
|
||||
PenaltyBreakComment: 500
|
||||
PenaltyBreakFirstLessLess: 400
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakScopeResolution: 500
|
||||
PenaltyBreakString: 600
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 50
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 300
|
||||
PointerAlignment: Right
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: false
|
||||
RemoveBracesLLVM: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SkipMacroDefinitionBody: false
|
||||
SortIncludes: Never
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: Lexicographic
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterPlacementOperator: true
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
ExceptDoubleParentheses: false
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
- emit
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
- Q_CLASSINFO
|
||||
- Q_ENUM
|
||||
- Q_ENUM_NS
|
||||
- Q_FLAG
|
||||
- Q_FLAG_NS
|
||||
- Q_GADGET
|
||||
- Q_GADGET_EXPORT
|
||||
- Q_INTERFACES
|
||||
- Q_LOGGING_CATEGORY
|
||||
- Q_MOC_INCLUDE
|
||||
- Q_NAMESPACE
|
||||
- Q_NAMESPACE_EXPORT
|
||||
- Q_OBJECT
|
||||
- Q_PROPERTY
|
||||
- Q_REVISION
|
||||
- Q_DISABLE_COPY
|
||||
- Q_DISABLE_COPY_MOVE
|
||||
- Q_SET_OBJECT_NAME
|
||||
- QT_BEGIN_NAMESPACE
|
||||
- QT_END_NAMESPACE
|
||||
- QML_ADDED_IN_MINOR_VERSION
|
||||
- QML_ANONYMOUS
|
||||
- QML_ATTACHED
|
||||
- QML_DECLARE_TYPE
|
||||
- QML_DECLARE_TYPEINFO
|
||||
- QML_ELEMENT
|
||||
- QML_EXTENDED
|
||||
- QML_EXTENDED_NAMESPACE
|
||||
- QML_EXTRA_VERSION
|
||||
- QML_FOREIGN
|
||||
- QML_FOREIGN_NAMESPACE
|
||||
- QML_IMPLEMENTS_INTERFACES
|
||||
- QML_INTERFACE
|
||||
- QML_NAMED_ELEMENT
|
||||
- QML_REMOVED_IN_MINOR_VERSION
|
||||
- QML_SINGLETON
|
||||
- QML_UNAVAILABLE
|
||||
- QML_UNCREATABLE
|
||||
- QML_VALUE_TYPE
|
||||
- YYCC_DELETE_COPY
|
||||
- YYCC_DELETE_MOVE
|
||||
- YYCC_DELETE_COPY_MOVE
|
||||
- YYCC_DEFAULT_COPY
|
||||
- YYCC_DEFAULT_MOVE
|
||||
- YYCC_DEFAULT_COPY_MOVE
|
||||
- YYCC_DECL_COPY
|
||||
- YYCC_DECL_MOVE
|
||||
- YYCC_DECL_COPY_MOVE
|
||||
TableGenBreakInsideDAGArg: DontBreak
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
...
|
||||
|
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@ -0,0 +1,3 @@
|
||||
[*.{cpp,hpp}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -1,2 +1,3 @@
|
||||
Doxyfile.in eol=lf
|
||||
*.bat eol=crlf
|
||||
*.bat eol=crlf
|
||||
*.sh eol=lf
|
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,11 +1,17 @@
|
||||
# -------------------- Output --------------------
|
||||
## ===== Personal =====
|
||||
# Ignore build resources
|
||||
out/
|
||||
build/
|
||||
install/
|
||||
|
||||
# Ignore CMake generated stuff
|
||||
src/yycc/version.hpp
|
||||
CMakeSettings.json
|
||||
|
||||
# -------------------- VSCode --------------------
|
||||
## ===== VSCode =====
|
||||
.vscode/
|
||||
|
||||
# -------------------- CMake --------------------
|
||||
## ===== CMake =====
|
||||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
@ -18,7 +24,7 @@ compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
|
||||
# -------------------- Visual Studio --------------------
|
||||
## ===== Visual Studio =====
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
|
@ -1,26 +1,43 @@
|
||||
cmake_minimum_required(VERSION 3.23)
|
||||
project(YYCC
|
||||
VERSION 1.0.0
|
||||
VERSION 2.0.0
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
# Setup C++ standard
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# Provide options
|
||||
option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF)
|
||||
option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF)
|
||||
option(YYCC_ENFORCE_ICONV "Enforce iconv support for this library (e.g. in MSYS2 environment)." OFF)
|
||||
|
||||
# Detect MSVC IDE environment.
|
||||
# If we in it, we should add configuration and build type in install path.
|
||||
if (CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
# Do Visual Studio specific
|
||||
set(YYCC_INSTALL_PATH_LIB lib/${CMAKE_VS_PLATFORM_NAME}/$<CONFIG>)
|
||||
set(YYCC_INSTALL_PATH_BIN bin/${CMAKE_VS_PLATFORM_NAME})
|
||||
else()
|
||||
# Other stuff
|
||||
set(YYCC_INSTALL_PATH_LIB lib)
|
||||
set(YYCC_INSTALL_PATH_BIN bin)
|
||||
endif()
|
||||
# Setup install path from CMake provided install path for convenient use.
|
||||
include(GNUInstallDirs)
|
||||
set(YYCC_INSTALL_INCLUDE_PATH ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH
|
||||
"Public header install path relative to CMAKE_INSTALL_PREFIX unless set to an absolute path.")
|
||||
set(YYCC_INSTALL_LIB_PATH ${CMAKE_INSTALL_LIBDIR} CACHE PATH
|
||||
"Library install path relative to CMAKE_INSTALL_PREFIX unless set to an absolute path.")
|
||||
set(YYCC_INSTALL_BIN_PATH ${CMAKE_INSTALL_BINDIR} CACHE PATH
|
||||
"Binary install path relative to CMAKE_INSTALL_PREFIX unless set to an absolute 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.")
|
||||
|
||||
# Import 2 build targets
|
||||
# Include dependency.
|
||||
# GTest is required if we build testbench
|
||||
if (YYCC_BUILD_TESTBENCH)
|
||||
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
find_package(GTest REQUIRED)
|
||||
endif ()
|
||||
# Iconv is required if we are not in Windows or user request it
|
||||
if (YYCC_ENFORCE_ICONV OR (NOT WIN32))
|
||||
find_package(Iconv REQUIRED)
|
||||
endif ()
|
||||
|
||||
# Import 3 build targets
|
||||
add_subdirectory(src)
|
||||
if (YYCC_BUILD_TESTBENCH)
|
||||
add_subdirectory(testbench)
|
||||
@ -29,12 +46,12 @@ if (YYCC_BUILD_DOC)
|
||||
add_subdirectory(doc)
|
||||
endif ()
|
||||
|
||||
# Install project package infos
|
||||
# Package target
|
||||
# Install target and package
|
||||
# Install target
|
||||
install(EXPORT YYCCommonplaceTargets
|
||||
FILE YYCCommonplaceTargets.cmake
|
||||
NAMESPACE YYCC::
|
||||
DESTINATION lib/cmake/YYCCommonplace
|
||||
DESTINATION ${YYCC_INSTALL_LIB_PATH}/cmake/YYCCommonplace
|
||||
)
|
||||
# Package configuration file
|
||||
include(CMakePackageConfigHelpers)
|
||||
@ -46,14 +63,14 @@ write_basic_package_version_file(
|
||||
configure_package_config_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/cmake/YYCCommonplaceConfig.cmake.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfig.cmake"
|
||||
INSTALL_DESTINATION lib/cmake/YYCCommonplace
|
||||
INSTALL_DESTINATION ${YYCC_INSTALL_LIB_PATH}/cmake/YYCCommonplace
|
||||
)
|
||||
# Copy to install destination
|
||||
# Copy package files to install destination
|
||||
install(
|
||||
FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfig.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfigVersion.cmake"
|
||||
DESTINATION
|
||||
lib/cmake/YYCCommonplace
|
||||
${YYCC_INSTALL_LIB_PATH}/cmake/YYCCommonplace
|
||||
)
|
||||
|
||||
|
106
COMPILE.md
Normal file
106
COMPILE.md
Normal file
@ -0,0 +1,106 @@
|
||||
# Compile Manual
|
||||
|
||||
## Choose Version
|
||||
|
||||
We suggest that you only use stable version (tagged commit).
|
||||
The latest commit always present current works.
|
||||
It means that it is not stable and work in progress.
|
||||
|
||||
## Requirements
|
||||
|
||||
* CMake 3.23 at least.
|
||||
* The common compiler supporting C++ 23 (GCC / Clang / MSVC).
|
||||
* Iconv (Optional on Windows. Required on other systems).
|
||||
* [GoogleTest](https://github.com/google/googletest) (Required if you build testbench).
|
||||
* Doxygen (Required if you build documentation).
|
||||
* Python and Astral UV (Required if you use "User Build" method)
|
||||
|
||||
> [!WARNING]
|
||||
> You may face some issues when building on macOS with Clang. That's not your fault.
|
||||
> Clang used libc++ library lacks some essential features used by this project.
|
||||
> You may try other solutions for compiling this project on macOS or with Clang.
|
||||
|
||||
## Preparing
|
||||
|
||||
### GoogleTest
|
||||
|
||||
GoogleTest is required if you need to build testbench.
|
||||
If you don't need this please skip this chapter.
|
||||
|
||||
We use GoogleTest v1.17.0.
|
||||
It would be okey use other versions but I have not test on them.
|
||||
|
||||
> [!WARNING]
|
||||
> When building this project, you may face link error with GoogleTest, especially on Linux.
|
||||
> This issue is caused by that the binary provided by your package manager is built in C++17 and its ABI is incompatible with C++23.
|
||||
> See this [GitHub Issue](https://github.com/google/googletest/issues/4591) for more infomation.
|
||||
> The solution is that download GoogleTest source code and build it in C++23 on your own.
|
||||
> Following content tell you how to do this.
|
||||
|
||||
There are the steps instructing you how to compile GoogleTest manually.
|
||||
|
||||
1. Download GoogleTest source code with given version in GitHub Release page.
|
||||
1. Extract it into a directory.
|
||||
1. Enter this directory and create 2 subdirectory `build` and `install` for CMake build and install respectively.
|
||||
1. Enter `build` directory and configure CMake with extra `-DCMAKE_CXX_STANDARD=23 -Dgtest_force_shared_crt=ON` parameters.
|
||||
1. Use CMake to build GoogleTest
|
||||
1. Use CMake to install GoogleTest into previous we created `install` directory.
|
||||
|
||||
### Iconv
|
||||
|
||||
Iconv is optional on Windows and disabled in default.
|
||||
However, if you are building project on MSYS2 or MINGW platform in Windows, we suggest you enable Iconv feature for more functions.
|
||||
Once you enable this feature, you must prepare installed Iconv which is no problem for MSYS2 environment via package manager.
|
||||
You also can enable this feature under MSVC but you must make sure that you can compile Iconv under MSVC.
|
||||
For how to enable this feature forcely, see following chapters for more infomations.
|
||||
|
||||
On other platforms, Iconv is enabled automatically and can not be disabled.
|
||||
Because there is no other encoding convertion libraries that we can use (Windows has a builtin set of encoding convertion Win32 functions).
|
||||
|
||||
### Doxygen
|
||||
|
||||
Doxygen is required only if you need to build documentation.
|
||||
If you don't need this please skip this chapter.
|
||||
|
||||
We use Doxygen 1.9.7.
|
||||
It would be okey use other versions but I have not test on them.
|
||||
|
||||
YYCCommonplace use Doxygen as its documentation system.
|
||||
So before compiling, you must make sure `doxygen` are presented in your environment.
|
||||
|
||||
## Build and Install
|
||||
|
||||
There are 2 different ways to build this project.
|
||||
If you are the user of this project (just want this project to make something work), please choose "User Build".
|
||||
If you are a developer (developer of this project, or use this project as dependency to develop your project), please choose "Developer Build".
|
||||
|
||||
### User Build
|
||||
|
||||
"User Build" is basically how GitHub Action build this project.
|
||||
|
||||
We use Python 3.11 and UV 0.7.17 to manage our build script generator.
|
||||
It would be okey use other versions but I have not test on them.
|
||||
|
||||
TODO...
|
||||
|
||||
### Developer Build
|
||||
|
||||
TODO...
|
||||
|
||||
There is a list listing all variables you may configure during compiling.
|
||||
|
||||
* `YYCC_BUILD_TESTBENCH`: Set it to `ON` to build testbench. `OFF` in default.
|
||||
It is useful for the developer of this project.
|
||||
It also suit for the user who has runtime issues on their platforms to check whether this project works as expected.
|
||||
* `YYCC_BUILD_DOC`: Set it to `ON` to build documentation. `OFF` in default.
|
||||
It may be useful for the developer who firstly use this project in their own projects.
|
||||
Please note that generated documentation is different in different platforms.
|
||||
* `YYCC_ENFORCE_ICONV`: Set it to `ON` to enable Iconv feature forcely. `OFF` in default.
|
||||
The usage of this option has been introduced in previous "Iconv" chapter.
|
||||
* `GTest_ROOT`: TODO
|
||||
* `Iconv_ROOT`: TODO
|
||||
* `CMAKE_CXX_STANDARD`: Set C++ standard version of project.
|
||||
`23` in default and this version can not be lower than C++23.
|
||||
You usually do not need change this.
|
||||
* `CMAKE_POSITION_INDEPENDENT_CODE`: Set it to `True` to enable PIC.
|
||||
This is essential for those project which use this project and produce dynamicing library as final artifact.
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024-2024 yyc12345
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
11
README.md
11
README.md
@ -1,9 +1,12 @@
|
||||
# YYC Commonplace
|
||||
|
||||
During the development of a few projects, I gradually understand how Windows make the compromise with the code written by its old developers, and what is developer wanted in contemporary C++ standard library under Windows environment. So I create this static library for all of my C++ project, After this, I do not need to write these duplicated code in each project. I can use a clear and easy way to manage these codes. I can easily fix issues found in project using this library by updating a single project, rather than fixing these duplicated code in each project one by one because all of them share the same implementations.
|
||||
YYC Commonplace, or YYCCommonplace (abbr. YYCC) is a static library specifically resolving my requirements in C++ and Windows scope.
|
||||
|
||||
This project mainly is served for my personal use. But I would be honored if you would like to use this in your project. Almost of my projects, except some critical projects (they will copy this project implementations into their own project scope to eliminate non-common library dependency), will gradually adapt to this project and drop their own individual implementations.
|
||||
## Usage
|
||||
|
||||
This project includes Visual Studio project file and CMake support at the same time. So that at least I can use one of them freely.
|
||||
For more usage about this library, please read documentation after building this project with documentation.
|
||||
I also highly recommend that you read documentation first before writing with this library.
|
||||
|
||||
**WIP. Do not use it now.**
|
||||
## Build
|
||||
|
||||
See [Compile Manual](./COMPILE.md).
|
||||
|
@ -1,2 +1,7 @@
|
||||
# Add the targets file
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/YYCCommonplaceTargets.cmake")
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
# Include targets file
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/YYCCommonplaceTargets.cmake")
|
||||
|
||||
check_required_components(YYCCommonplace)
|
@ -4,3 +4,16 @@ configure_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Add custom target
|
||||
add_custom_target (YYCCDocumentation
|
||||
doxygen ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Generating documentation" VERBATIM
|
||||
)
|
||||
|
||||
# Install built documentation
|
||||
install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
|
||||
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
|
||||
DESTINATION ${YYCC_INSTALL_DOC_PATH}
|
||||
)
|
||||
|
@ -2356,7 +2356,7 @@ INCLUDE_FILE_PATTERNS =
|
||||
# recursively expanded use the := operator instead of the = operator.
|
||||
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
|
||||
|
||||
PREDEFINED =
|
||||
PREDEFINED = YYCC_DOXYGEN
|
||||
|
||||
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
|
||||
# tag can be used to specify a list of macro names that should be expanded. The
|
||||
|
200
doc/src/arg_parser.dox
Normal file
200
doc/src/arg_parser.dox
Normal file
@ -0,0 +1,200 @@
|
||||
namespace YYCC::ArgParser {
|
||||
/**
|
||||
|
||||
\page arg_parser Universal Argument Parser
|
||||
|
||||
YYCC::ArgParser provides an universal way to parsing command line arguments.
|
||||
|
||||
Universal argument parser has similar design with universal config manager,
|
||||
it is highly recommand that read \ref config_manager chapter first,
|
||||
because you will have a clear understanding of this namespace after reading universal config manager chapter.
|
||||
|
||||
There is an example about how to use universal argument parser.
|
||||
In following content, we will describe it in detail.
|
||||
|
||||
\code{.cpp}
|
||||
class TestArgParser {
|
||||
public:
|
||||
TestArgParser() :
|
||||
m_IntArgument(YYCC_U8("int"), YYCC_U8_CHAR('i'), YYCC_U8("integral argument"), YYCC_U8("114514")),
|
||||
m_FloatArgument(nullptr, YYCC_U8_CHAR('f'), nullptr, nullptr, true),
|
||||
m_StringArgument(YYCC_U8("string"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true),
|
||||
m_BoolArgument(nullptr, YYCC_U8_CHAR('b'), nullptr),
|
||||
m_ClampedFloatArgument(YYCC_U8("clamped-float"), YYCC::ArgParser::AbstractArgument::NO_SHORT_NAME, nullptr, nullptr, true, YYCC::Constraints::GetMinMaxRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_OptionContext(YYCC_U8("TestArgParser"), YYCC_U8("This is the testbench of argument parser."), {
|
||||
&m_IntArgument, &m_FloatArgument, &m_StringArgument,
|
||||
&m_BoolArgument, &m_ClampedFloatArgument
|
||||
}) {}
|
||||
~TestArgParser() {}
|
||||
|
||||
YYCC::ArgParser::NumberArgument<int32_t> m_IntArgument;
|
||||
YYCC::ArgParser::NumberArgument<float> m_FloatArgument;
|
||||
YYCC::ArgParser::StringArgument m_StringArgument;
|
||||
|
||||
YYCC::ArgParser::SwitchArgument m_BoolArgument;
|
||||
YYCC::ArgParser::NumberArgument<float> m_ClampedFloatArgument;
|
||||
|
||||
YYCC::ArgParser::OptionContext m_OptionContext;
|
||||
};
|
||||
|
||||
// Initialize argument parser.
|
||||
TestArgParser test;
|
||||
// Get argument list for parsing from standard C main function.
|
||||
auto al = YYCC::ArgParser::ArgumentList::CreateFromStd(argc, argv);
|
||||
// Start parsing
|
||||
test.Parse(al);
|
||||
// Get captured string argument
|
||||
if (test.m_StringArgument.IsCaptured())
|
||||
auto val = test.m_StringArgument.Get();
|
||||
\endcode
|
||||
|
||||
These code can resolve following command line:
|
||||
|
||||
\code{.sh}
|
||||
exec -i 114514 -f 2.0 --string fuck -b --clamped-float 0.5
|
||||
\endcode
|
||||
|
||||
For convenience, we define following terms used in this article.
|
||||
|
||||
\li Every items in command line: Argument.
|
||||
\li \c -i, \c --clamped-float: \b Switch / \b Option. the argument starts with dash or double dash.
|
||||
\li \c 114514: \b Value. the value of switch.
|
||||
|
||||
\section arg_parser__argument Argument
|
||||
|
||||
Argument is the leaf of argument parser.
|
||||
It has the same position as setting in universal config manager.
|
||||
|
||||
\subsection arg_parser__argument__presets Argument Presets
|
||||
|
||||
Like setting in universal config manager,
|
||||
we also provide various common used argument presets.
|
||||
Current'y we support following argument presets:
|
||||
|
||||
\li NumberArgument: The argument storing arithmetic type (except \c bool) inside. Such as <TT>-i 114514</TT> in example.
|
||||
\li StringArgument: The argument storing string inside. Such as <TT>--string fuck</TT> in example.
|
||||
\li SwitchArgument: The argument storing nothing. It is just a simple switch. Such as <TT>-b</TT> in example.
|
||||
|
||||
When constructing these argument,
|
||||
you need provide one from long name or short name, or both of them.
|
||||
Short name is the argument starting with dash and long name starts with double dash.
|
||||
You don't need add dash or double dash prefix when providing these names.
|
||||
Please note only ASCII characters, which can be displayed on screen, can be used in these names.
|
||||
|
||||
Optionally, you can provide description when constructing,
|
||||
which will tell user how this switch does and more infomation about this switch.
|
||||
And, you can add an example to tell user which value is valid.
|
||||
|
||||
Next, you can specify an argument to be optional.
|
||||
Optional argument can be absent in command line.
|
||||
Oppositely, non-optional argument must be presented in command line,
|
||||
otherwise parser will return false to indicate an error.
|
||||
For checking whether an optional argument is specified,
|
||||
please call AbstractArgument::IsCaptured().
|
||||
|
||||
Last, you can optionally assign a constraint to it,
|
||||
to help argument limit its value.
|
||||
|
||||
However SwitchArgument must be optional argument.
|
||||
Because it is true if user specify it explicit it,
|
||||
and will be false if user do not give this flag.
|
||||
SwitchArgument doesn't have constraint features,
|
||||
because it doesn't store any value inside.
|
||||
Thus no need to limit this.
|
||||
|
||||
\subsection arg_parser__argument__custom Custom Argument
|
||||
|
||||
In most cases, the combination use of argument presets and constraints is enough.
|
||||
However, if you still are urge to create your personal argument,
|
||||
please inherit AbstractArgument and implement essential class functions.
|
||||
For the class functions you need to implement,
|
||||
please refer to our argument presets.
|
||||
|
||||
\section arg_parser__argument_list Argument List
|
||||
|
||||
Argument list is a struct used by parser for parsing.
|
||||
It is a higher wrapper of a simple list containing argument items.
|
||||
We provide 2 ways to get argument list.
|
||||
|
||||
\li ArgumentList::CreateFromStd: Create argument list from standard C main function parameters.
|
||||
\li ArgumentList::CreateFromWin32: Create argument list from Win32 functions in Windows.
|
||||
You should use this function in Windows instead of ArgumentList::CreateFromStd.
|
||||
Because the command line passed in standard C main function has encoding issue in Windows.
|
||||
Use this function you will fetch correct argument list especially command including non-ASCII characters.
|
||||
|
||||
Please note the first argument in given command line will be stripped.
|
||||
Because in most cases it point to the executable self,
|
||||
and should not be seen as the part of argument list.
|
||||
|
||||
\section arg_parser__option_context Option Context
|
||||
|
||||
Please note any unknow argument will let the parser return false.
|
||||
This is different with other argument parsers.
|
||||
In other common argument parsers,
|
||||
they will collect all unknow argument as positional argument,
|
||||
or just simply ignore them.
|
||||
|
||||
OptionContext also will not add \c -h or \c --help switch automatically.
|
||||
This is also differnent with other parsers.
|
||||
You should manually add it.
|
||||
However, OptionContext provide a universal help print function, OptionContext::Help.
|
||||
You can directly call it to output help text if you needed (fail to parse or user order help).
|
||||
|
||||
\section arg_parser__limitation Limitation
|
||||
|
||||
This universal argument parser is a tiny parser.
|
||||
It only just fulfill my personal requirements.
|
||||
So it only accepts limited command line syntax.
|
||||
In following content I will tell you some syntaxes which this parser \b not accept.
|
||||
|
||||
\subsection arg_parser__limitation__flag_combination Flag Combination
|
||||
|
||||
\code{.sh}
|
||||
exec -l -s -h
|
||||
exec -lsh
|
||||
\endcode
|
||||
|
||||
Parser accept first line but not accept the second line.
|
||||
You must write these flags independently.
|
||||
|
||||
\subsection arg_parser__limitation__equal_symbol Equal Symbol
|
||||
|
||||
\code{.sh}
|
||||
exec --value 114514
|
||||
exec --value=114514
|
||||
exec --value:114514
|
||||
\endcode
|
||||
|
||||
Parser only accept first line command.
|
||||
You can not use equal symbol or any other symbol to assign value for specified argument.
|
||||
You must write value after the argument immediately please.
|
||||
|
||||
\subsection arg_parser__limitation__variable_argument Variable Argument
|
||||
|
||||
\code{.sh}
|
||||
exec -DSOME_VARABLE=SOME_VALUE
|
||||
exec -D SOME_VARIABLE=SOME_VALUE
|
||||
\endcode
|
||||
|
||||
Parser only accept second line.
|
||||
However you nned to write a custom argument or constraint to holding this value.
|
||||
|
||||
\subsection arg_parser__limitation__switch_dependency Switch Dependency
|
||||
|
||||
\code{.sh}
|
||||
exec --action-a --action-b
|
||||
\endcode
|
||||
|
||||
For command line written above,
|
||||
if you hope \c --action-a and \c --action-b is exclusive,
|
||||
or \c --action-b only be valid if \c --action-a specified,
|
||||
you should manually implement this.
|
||||
Parser don't have such features to process this switch dependency.
|
||||
|
||||
The thing you need to do is set these switches are \b not optional.
|
||||
And after parser do a success parsing,
|
||||
manually calling AbstractArgument::IsCaptured to fetch whether corresponding switches are captured,
|
||||
then do your personal dependency check.
|
||||
|
||||
*/
|
||||
}
|
35
doc/src/com_helper.dox
Normal file
35
doc/src/com_helper.dox
Normal file
@ -0,0 +1,35 @@
|
||||
namespace YYCC::COMHelper {
|
||||
/**
|
||||
|
||||
\page com_helper COM Helper
|
||||
|
||||
This namespace is Windows specific.
|
||||
It will be invisible on other platforms.
|
||||
|
||||
This namespace is used by internal functions as intended.
|
||||
They should not be used outside of this library.
|
||||
But if you compel to use them, it is also okey.
|
||||
|
||||
\section com_helper__memory_safe_ptr Memory Safe Pointer Types
|
||||
|
||||
This namespace also provided various memory-safe types for interacting with COM functions.
|
||||
Although Microsoft also has similar smart pointer called \c CComPtr.
|
||||
But this library is eager to hide all Microsoft-related functions calling.
|
||||
Using \c CComPtr is not corresponding with the philosophy of this library.
|
||||
So these standard library based smart pointer types were created.
|
||||
|
||||
\section com_helper__com_guard COM Guard
|
||||
|
||||
This namespace contain a COM Guard which make sure COM was initialized in current module when loading current module.
|
||||
It is essential because all calling to COM functions should be under the premise that COM has been initialized.
|
||||
This guard also will uninitialize COM when unloading this module.
|
||||
|
||||
There is only an exposed function called #IsInitialized for user calling.
|
||||
This function will check whether COM environment is initialized.
|
||||
If you want YYCC automatically initialize COM environment for you,
|
||||
you must call this function in your program at least one time.
|
||||
Otherwise COM Guard code may be unavailable,
|
||||
because compiler may think they are not essential code and drop them.
|
||||
|
||||
*/
|
||||
}
|
151
doc/src/config_manager.dox
Normal file
151
doc/src/config_manager.dox
Normal file
@ -0,0 +1,151 @@
|
||||
namespace YYCC::ConfigManager {
|
||||
/**
|
||||
|
||||
\page config_manager Universal Config Manager
|
||||
|
||||
YYCC::ConfigManager give programmer an universal way to manage its program settings.
|
||||
There is an example about how to use universal config manager.
|
||||
In following content, we will describe it in detail.
|
||||
|
||||
\code
|
||||
class TestConfigManager {
|
||||
public:
|
||||
enum class TestEnum : int8_t {
|
||||
Test1, Test2, Test3
|
||||
};
|
||||
|
||||
TestConfigManager() :
|
||||
m_IntSetting(YYCC_U8("int-setting"), INT32_C(0)),
|
||||
m_StringSetting(YYCC_U8("string-setting"), YYCC_U8("")),
|
||||
m_FloatSetting(YYCC_U8("float-setting"), 0.0f, YYCC::Constraints::GetNumberRangeConstraint<float>(-1.0f, 1.0f)),
|
||||
m_EnumSetting(YYCC_U8("enum-setting"), TestEnum::Test1),
|
||||
m_CoreManager(YYCC_U8("test.cfg"), UINT64_C(0), {
|
||||
&m_IntSetting, &m_StringSetting, &m_FloatSetting, &m_EnumSetting
|
||||
})
|
||||
{}
|
||||
~TestConfigManager() {}
|
||||
|
||||
YYCC::ConfigManager::NumberSetting<int32_t> m_IntSetting;
|
||||
YYCC::ConfigManager::StringSetting m_StringSetting;
|
||||
YYCC::ConfigManager::NumberSetting<float> m_FloatSetting;
|
||||
YYCC::ConfigManager::NumberSetting<TestEnum> m_EnumSetting;
|
||||
|
||||
YYCC::ConfigManager::CoreManager m_CoreManager;
|
||||
};
|
||||
|
||||
// Initialize config manager
|
||||
TestConfigManager test;
|
||||
// Load settings.
|
||||
test.m_CoreManager.Load()
|
||||
// Get string setting value.
|
||||
auto val = test.m_StringSetting.Get();
|
||||
\endcode
|
||||
|
||||
\section config_manager__setting Setting
|
||||
|
||||
Setting can be seen as the leaf of the config tree.
|
||||
Each setting describe a single configuration entry.
|
||||
|
||||
\subsection config_manager__setting__presets Setting Presets
|
||||
|
||||
We currently provide 2 setting preset classes which you can directly use.
|
||||
|
||||
\li NumberSetting: The setting storing a number inside.
|
||||
It is a template class. Support all arithmetic and enum types (integral, floating point, bool, enum).
|
||||
\li StringSetting: The setting storing a string inside.
|
||||
|
||||
When constructing these settings,
|
||||
you need to provide its unique name which will be used when saving to file or reading from file.
|
||||
Also you need to provide a default value for it.
|
||||
It will be used when fail to read file or initializing itself.
|
||||
|
||||
Optionally, you also can provide a constraint to setting.
|
||||
Constraint is the struct instructing library to limit value in specified range.
|
||||
It usually is used for making sure the setting stored value is valid.
|
||||
See \ref constraints chapters to know how we provide constraints.
|
||||
|
||||
\subsection config_manager__setting__custom Custom Setting
|
||||
|
||||
In most cases, the combination use of setting presets and constraints is enough.
|
||||
However, if you still are urge to create your personal setting,
|
||||
please inherit AbstractSetting and implement essential class functions.
|
||||
For the class functions you need to implement,
|
||||
please refer to our setting presets, NumberSetting and StringSetting.
|
||||
|
||||
\section config_manager__core_manager Core Manager
|
||||
|
||||
CoreManager manage a collection of settings.
|
||||
And have responsibility to reading and writing config file.
|
||||
|
||||
We highly suggest that you create a personal config manager class like example does.
|
||||
Then put essential settings and core manager inside it.
|
||||
Please note you must place core manager after all settings.
|
||||
Because the order of C++ initializing its class member is the order you declared them.
|
||||
The constructor of core manager need the pointer to all it managed settings,
|
||||
so it must be initialized after initializing all settings.
|
||||
|
||||
When initializing core manager, you need assign config file path first.
|
||||
Then you need specify a version number.
|
||||
Version number is important.
|
||||
It will be used when reading config file and only can be increased if needed (version can not downgrade).
|
||||
The last argument is an initializer list which contain the \b pointer to all settings this manager managed.
|
||||
|
||||
When executing YYCC::ConfigManager::CoreManager::Load to load configs, it will perform following steps one by one:
|
||||
|
||||
<UL>
|
||||
<LI>
|
||||
Open given config file.
|
||||
<UL>
|
||||
<LI>
|
||||
If given file is not existing, loading function will simply return and all configs will be reset to its default value.
|
||||
</LI>
|
||||
<LI>
|
||||
Success to open file, go to next step.
|
||||
</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
|
||||
<LI>
|
||||
Fetch version number from file.
|
||||
<UL>
|
||||
<LI>
|
||||
If fail to read version number from file, loading function will simply return and all configs will be reset to its default value.
|
||||
</LI>
|
||||
<LI>
|
||||
If the version of config file is higher than your specified version number when constructing this class,
|
||||
core manager will assume you are trying to read a config file created by a higher version program,
|
||||
and will reject reading and use default value for all settings.
|
||||
</LI>
|
||||
<LI>
|
||||
If the version of config file is lower than your specified version number,
|
||||
core manager will try to read config file and do proper migration (set default value for configs which do not existing) if possible.
|
||||
</LI>
|
||||
<LI>
|
||||
If the version of config file is equal than your specified version number,
|
||||
core manager will read config file normally.
|
||||
</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
|
||||
<LI>
|
||||
Read config file body.
|
||||
<UL>
|
||||
<LI>
|
||||
If any IO error occurs when reading, loading function will simply return.
|
||||
All read config will keep their read value and all configs which has not been read will keep their default value.
|
||||
</LI>
|
||||
<LI>
|
||||
If some config can not parse binary data to its type,
|
||||
this config will be skipped and core manager will process next config.
|
||||
This config will keep its default value.
|
||||
</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
All of these scenarios can be found by the return value of loading function.
|
||||
The return type of loading function, ConfigLoadResult is a flag enum.
|
||||
You can find whether loading process happend specified issue by using bitwise operation on it.
|
||||
|
||||
*/
|
||||
}
|
181
doc/src/console_helper.dox
Normal file
181
doc/src/console_helper.dox
Normal file
@ -0,0 +1,181 @@
|
||||
namespace YYCC::ConsoleHelper {
|
||||
/**
|
||||
|
||||
\page console_helper Console Helper
|
||||
|
||||
This helper provide console related stuff.
|
||||
This helper includes 2 parts.
|
||||
First part is console color.
|
||||
It was constituted by a bunch of macros.
|
||||
The second part is universal console IO function because Windows is lacking in UTF8 console IO.
|
||||
All of these parts will be introduced in following content.
|
||||
|
||||
\section console_helper__color Console Color
|
||||
|
||||
YYCC::ConsoleHelper provide a bunch of macros which can allow you output colorful text in terminal.
|
||||
Supported color is limited in 16 colors,
|
||||
because these color is implemented by ASCII Escape Code: https://en.wikipedia.org/wiki/ANSI_escape_code .
|
||||
So if your terminal do not support this, such as default Windows terminal, or teletypewriter,
|
||||
you will see some unrecognised characters surrounding with your output.
|
||||
That's ASCII Escape Code.
|
||||
|
||||
\subsection console_helper__color__enable_win_color Enable Color in Windows Console
|
||||
|
||||
As we introduced in above,
|
||||
you may know Windows console does not support ASCII Escape Code color in default.
|
||||
However #EnableColorfulConsole can fix this issue.
|
||||
|
||||
#EnableColorfulConsole will forcely enable ASCII Escape Code support in Windows console if possible.
|
||||
Thus you can write colorful text in Windows console freely.
|
||||
We suggest you to call this function at the beginning of program.
|
||||
|
||||
Considering most Linux console supports ASCII Escape Code very well,
|
||||
this function does nothing in non-Windows platform.
|
||||
So it is not essential that brack this function calling with Windows-only \c \#if.
|
||||
|
||||
\subsection console_helper__color__common Common Usage
|
||||
|
||||
For common scenarios, you can use macro like this:
|
||||
|
||||
\code
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLOR_LIGHT_RED("Light Red Text")));
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("I am " YYCC_COLOR_LIGHT_RED("Light Red")));
|
||||
\endcode
|
||||
|
||||
In first line, it will make <TT>"Light Red Text"</TT> to be shown in light red color.
|
||||
And for second line, it will make <TT>"Light Red"</TT> to be shown in light red color,
|
||||
but <TT>"I am "</TT> will keep default console font color.
|
||||
|
||||
You also may notice this macro is used with YYCC_U8 macro.
|
||||
Because #WriteLine only accept UTF8 argument.
|
||||
So please note if you use console color macro with YYCC_U8,
|
||||
please make YYCC_U8 always is located the outside.
|
||||
Otherwise, YYCC_U8 will fail to make the whole become UTF8 stirng as we introduced in \ref library_encoding.
|
||||
Because console color macro is implemented by string literal concatenation internally.
|
||||
|
||||
YYCC_COLOR_LIGHT_RED is a member in YYCC_COLOR macro family.
|
||||
YYCC_COLOR macro family has 16 members for 16 different colors:
|
||||
|
||||
\li YYCC_COLOR_BLACK
|
||||
\li YYCC_COLOR_RED
|
||||
\li YYCC_COLOR_GREEN
|
||||
\li YYCC_COLOR_YELLOW
|
||||
\li YYCC_COLOR_BLUE
|
||||
\li YYCC_COLOR_MAGENTA
|
||||
\li YYCC_COLOR_CYAN
|
||||
\li YYCC_COLOR_WHITE
|
||||
\li YYCC_COLOR_LIGHT_BLACK
|
||||
\li YYCC_COLOR_LIGHT_RED
|
||||
\li YYCC_COLOR_LIGHT_GREEN
|
||||
\li YYCC_COLOR_LIGHT_YELLOW
|
||||
\li YYCC_COLOR_LIGHT_BLUE
|
||||
\li YYCC_COLOR_LIGHT_MAGENTA
|
||||
\li YYCC_COLOR_LIGHT_CYAN
|
||||
\li YYCC_COLOR_LIGHT_WHITE
|
||||
|
||||
\subsection console_helper__color__embedded Embedded Usgae
|
||||
|
||||
In some cases, you want change console at some time point and reset it in another time point.
|
||||
You can use color macros like following example:
|
||||
|
||||
\code
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLORHDR_LIGHT_BLUE));
|
||||
|
||||
// Write as much as you liked
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("some string"));
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8("another string"));
|
||||
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(YYCC_COLORTAIL));
|
||||
\endcode
|
||||
|
||||
At first line, we output YYCC_COLORHDR_LIGHT_BLUE which is in YYCC_COLORHDR macro family.
|
||||
It is colorful text ASCII Escape Code head.
|
||||
It will make all following output become light blue color,
|
||||
until the last line we output YYCC_COLORTAIL to reset console color to original color.
|
||||
|
||||
Same as YYCC_COLOR macro family,
|
||||
YYCC_COLORHDR macro family also has 16 members for 16 different colors:
|
||||
|
||||
\li YYCC_COLORHDR_BLACK
|
||||
\li YYCC_COLORHDR_RED
|
||||
\li YYCC_COLORHDR_GREEN
|
||||
\li YYCC_COLORHDR_YELLOW
|
||||
\li YYCC_COLORHDR_BLUE
|
||||
\li YYCC_COLORHDR_MAGENTA
|
||||
\li YYCC_COLORHDR_CYAN
|
||||
\li YYCC_COLORHDR_WHITE
|
||||
\li YYCC_COLORHDR_LIGHT_BLACK
|
||||
\li YYCC_COLORHDR_LIGHT_RED
|
||||
\li YYCC_COLORHDR_LIGHT_GREEN
|
||||
\li YYCC_COLORHDR_LIGHT_YELLOW
|
||||
\li YYCC_COLORHDR_LIGHT_BLUE
|
||||
\li YYCC_COLORHDR_LIGHT_MAGENTA
|
||||
\li YYCC_COLORHDR_LIGHT_CYAN
|
||||
\li YYCC_COLORHDR_LIGHT_WHITE
|
||||
|
||||
However YYCC_COLORTAIL is YYCC_COLORTAIL.
|
||||
There is no other variant for different colors.
|
||||
Because all tail of colorful ASCII Escape Code is same.
|
||||
|
||||
\section console_helper__universal_io Universal IO Function
|
||||
|
||||
\subsection console_helper__universal_io__why Why?
|
||||
|
||||
Windows console doesn't support UTF8 very well.
|
||||
The standard input output functions can not work properly with UTF8 on Windows.
|
||||
So we create this namespace and provide various console-related functions
|
||||
to patch Windows console and let it more like the console in other platforms.
|
||||
|
||||
The function provided in this function can be called in any platforms.
|
||||
In Windows, the implementation will use Windows native function,
|
||||
and in other platform, the implementation will redirect request to standard C function like \c std::fputs and etc.
|
||||
So the programmer do not need to be worried about which function should they use,
|
||||
and don't need to use macro to use different IO function in different platforms.
|
||||
It is just enough that fully use the functions provided in this namespace.
|
||||
|
||||
All IO functions this namespace provided are UTF8-based.
|
||||
It also means that input output string should always be UTF8 encoded.
|
||||
|
||||
\subsection console_helper__universal_io__input Input Functions
|
||||
|
||||
Please note that EOL will automatically converted into LF on Windows platform, not CRLF.
|
||||
This action actually is removing all CR chars in result string.
|
||||
This behavior affect nothing in most cases but it still is possible break something in some special case.
|
||||
|
||||
Due to implementation, if you decide to use this function,
|
||||
you should give up using any other function to read stdin stream,
|
||||
such as \c std::gets() and \c std::cin.
|
||||
Because this function may read chars which is more than needed.
|
||||
These extra chars will be stored in this function and can be used next calling.
|
||||
But these chars can not be visited by stdin again.
|
||||
This behavior may cause bug.
|
||||
So if you decide using this function, stick on it and do not change.
|
||||
|
||||
Due to implementation, this function do not support hot switch of stdin.
|
||||
It means that stdin can be redirected before first calling of this function,
|
||||
but it should not be redirected during program running.
|
||||
The reason is the same one introduced above.
|
||||
|
||||
\subsection console_helper__universal_io__output Output Functions
|
||||
|
||||
In current implementation, EOL will not be converted automatically to CRLF.
|
||||
This is different with other stream read functions provided in this namespace.
|
||||
|
||||
Comparing with other stream read functions provided in this namespace,
|
||||
stream write function support hot switch of stdout and stderr.
|
||||
Because they do not have internal buffer storing something.
|
||||
|
||||
In this namespace, there are various stream write function.
|
||||
There is a list telling you how to choose one from them for using:
|
||||
|
||||
\li Functions with leading "Err" will write data into stderr,
|
||||
otherwise they will write data into stdout.
|
||||
\li Functions with embedded "Format" are output functions with format feature
|
||||
like \c std::fprintf(), otherwise the functions with embedded "Write" will
|
||||
only write plain string like \c std::fputs().
|
||||
\li Functions with trailing "Line" will write extra EOL to break current line.
|
||||
This is commonly used, otherwise functions will only write the text provided by arguments,
|
||||
without adding something.
|
||||
|
||||
*/
|
||||
}
|
49
doc/src/constraints.dox
Normal file
49
doc/src/constraints.dox
Normal file
@ -0,0 +1,49 @@
|
||||
namespace YYCC::Constraints {
|
||||
/**
|
||||
|
||||
\page constraints Constraints
|
||||
|
||||
YYCC::Constraints namespace provide Constraint struct declaration
|
||||
and various common constraint generator function.
|
||||
|
||||
This namespace is specifically used by YYCC::ConfigManager and YYCC::ArgParser namespaces.
|
||||
See \ref config_manager chapter and \ref arg_parser chapter for how to utlize this namespace.
|
||||
|
||||
\section constraints__prototype Prototype
|
||||
|
||||
Constraint instruct library how check whether given value is in range,
|
||||
and how to clamp it if it is invalid.
|
||||
For example, you can use constraint to limit a number in given minimum maximum value,
|
||||
or limit a string in specific format by using regex and etc.
|
||||
|
||||
Constraint is a template struct.
|
||||
The argument of template is the underlying data type which need to be checked.
|
||||
The struct with different template argument is not compatible.
|
||||
|
||||
Currently, this struct only contain 1 function pointer,
|
||||
which is used for detecting whether given value is in range / valid.
|
||||
|
||||
\subsection constraints__presets Constraint Presets
|
||||
|
||||
YYCC::Constraints provides some constraint presets which are commonly used.
|
||||
All functions inside this namespace will return a Constraint instance,
|
||||
and you can directly use it.
|
||||
|
||||
There is a list of all provided functions:
|
||||
|
||||
\li GetMinMaxRangeConstraint(): Limit the number value in given minimum maximum value range (inclusive).
|
||||
\li GetEnumEnumerationConstraint(): Limit the enum value by given all possible value set.
|
||||
\li GetStringEnumerationConstraint(): Limit the string by given all possible value set.
|
||||
|
||||
\subsection config_manager__constraint__custom Custom Constraint
|
||||
|
||||
For creating your personal constraint,
|
||||
you need to create Constraint instance manually.
|
||||
You can browse all existing constraint preset functions code for know how to write it.
|
||||
|
||||
The things you need to do is simple.
|
||||
First, you need decide the template argument of Constraint.
|
||||
Second, you need assign class member of Constraint by C++ lambda syntax.
|
||||
|
||||
*/
|
||||
}
|
169
doc/src/dialog_helper.dox
Normal file
169
doc/src/dialog_helper.dox
Normal file
@ -0,0 +1,169 @@
|
||||
namespace YYCC::DialogHelper {
|
||||
/**
|
||||
|
||||
\page dialog_helper Dialog Helper
|
||||
|
||||
Picking files and folders is an important and essential operation under Windows.
|
||||
However the functions picking files and folders are so complex.
|
||||
This helper provides universal dialog picker by simple classes and functions.
|
||||
In following contents we will tell you how to call them.
|
||||
|
||||
This helper is Windows specific.
|
||||
It will be totally invisible if you are in other platforms.
|
||||
|
||||
\section dialog_helper__file_dialog Configure File Dialog
|
||||
|
||||
The first thing is that we should initialize FileDialog,
|
||||
and configure it according to your requirements.
|
||||
|
||||
This class is the data struct representing all aspects of file dialog.
|
||||
It also one of the arguments in final dialog function.
|
||||
|
||||
\code
|
||||
YYCC::DialogHelper::FileDialog params;
|
||||
params.SetOwner(owner_getter());
|
||||
params.SetTitle(YYCC_U8("My File Picker"));
|
||||
params.SetInitFileName(YYCC_U8("test.txt"));
|
||||
params.SetInitDirectory(initial_directory_getter());
|
||||
\endcode
|
||||
|
||||
\subsection dialog_helper__file_dialog__owner Owner
|
||||
|
||||
FileDialog::SetOwner will set owner of this dialog.
|
||||
It accepts a Microsoft defined \c HWND as argument which should be familiar with Windows programmer.
|
||||
If you pass \c NULL to it or skip calling this function, it indicate that there is no owner of this dialog.
|
||||
<I>
|
||||
I don't what will happen if there is no owner for it.
|
||||
But it would be better to have an owner if possible.
|
||||
</I>
|
||||
|
||||
\subsection dialog_helper__file_dialog__title Title
|
||||
|
||||
FileDialog::SetTitle will set dialog title of this dialog.
|
||||
If you pass \c nullptr or skip calling it,
|
||||
the title of dialog will be filled by system and the function type you calling.
|
||||
For example, the title will be "Open..." if you call open file function,
|
||||
and will be "Save As..." if you call save file function.
|
||||
At the same time, the language of this title filled by system is system UI dependent.
|
||||
It means that you do not need to do any extra I18N work for it.
|
||||
So I suggest you do not set title except you really want to modify title.
|
||||
|
||||
\subsection dialog_helper__file_dialog__init_file_name Initial File Name
|
||||
|
||||
FileDialog::SetInitFileName will set the initial file name presented in dialog file name input box.
|
||||
If you pass \c nullptr or skip calling it, the text in dialog file name input box will be empty.
|
||||
|
||||
User can modify the name presented in input box later.
|
||||
But if you assign this value, the dialog will lose the ability that remember the previous name user input in previous 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.
|
||||
|
||||
\subsection dialog_helper__file_dialog__init_directory Initial Directory
|
||||
|
||||
FileDialog::SetInitDirectory will set the initial directory (startup directory) when opening dialog.
|
||||
|
||||
In following cases, initial directory will fall back to system behavior:
|
||||
|
||||
\li Pass \c nullptr to this function.
|
||||
\li Skip calling this function.
|
||||
\li Given directory path is invalid.
|
||||
|
||||
The system default behavior of initial directory is similar with initial file name.
|
||||
The dialog will try remembering the last directory you just entering, and will back into it in the next calling.
|
||||
The directory we meeting in the first launch is system defined.
|
||||
|
||||
\section dialog_helper__file_filters Configure File Filters
|
||||
|
||||
File filters is a drop down list represented in file dialog which allow user filter files by their extensions.
|
||||
It is beneficial to let user get the file which they want in a directory including massive different files.
|
||||
|
||||
<B>For file dialog picking a directory,</B> you can skip this step.
|
||||
Because the file dialog picking directory does not have file filter drop down box.
|
||||
Directory can not be filtered.
|
||||
|
||||
FileFilters takes responsibility for this feature:
|
||||
|
||||
\code
|
||||
auto& filters = params.ConfigreFileTypes();
|
||||
filters.Add(YYCC_U8("Microsoft Word (*.docx; *.doc)"), { YYCC_U8("*.docx"), YYCC_U8("*.doc") });
|
||||
filters.Add(YYCC_U8("Microsoft Excel (*.xlsx; *.xls)"), { YYCC_U8("*.xlsx"), YYCC_U8("*.xls") });
|
||||
filters.Add(YYCC_U8("Microsoft PowerPoint (*.pptx; *.ppt)"), { YYCC_U8("*.pptx"), YYCC_U8("*.ppt") });
|
||||
filters.Add(YYCC_U8("Text File (*.txt)"), { YYCC_U8("*.txt") });
|
||||
filters.Add(YYCC_U8("All Files (*.*)"), { YYCC_U8("*.*") });
|
||||
params.SetDefaultFileTypeIndex(0u);
|
||||
\endcode
|
||||
|
||||
\subsection dialog_helper__file_filters__setup File Filters
|
||||
|
||||
We don't need to initialize FileFilters by ourselves.
|
||||
Oppositely, we fetch it from FileDialog instance by calling FileDialog::ConfigreFileTypes.
|
||||
After fetching, we can call FileFilters::Add to add a filter pair for file filters.
|
||||
|
||||
The first argument is the display text which user will see in file filter drop down box.
|
||||
|
||||
The second argument is a \c std::initializer_list.
|
||||
Every items are Windows used wildcard string instructing which file should be shown in file dialog.
|
||||
It is okey to use multiple wildcard string in list.
|
||||
This is suit for those file types involving multiple file extensions, such as the old and new file types of Microsoft Office as we illustracted.
|
||||
Empty list not allowed
|
||||
|
||||
FileFilters::Add also will return a bool to indicate the success of this adding.
|
||||
|
||||
It should at least has one file filter in file dialog.
|
||||
I don't know the consequence if you don't provide any file filter.
|
||||
|
||||
\subsection dialog_helper__file_filters__default_filter Default File Type
|
||||
|
||||
FileDialog::SetDefaultFileTypeIndex will set the default selected file filter of this dialog.
|
||||
It accepts an index pointing to the file filter which you want to show in default for this file dialog.
|
||||
The index of file filters is the order where you call FileFilters::Add above.
|
||||
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
|
||||
|
||||
Finally, we can call file dialog functions by we initialized FileDialog
|
||||
|
||||
\code
|
||||
YYCC::yycc_u8string single_selection;
|
||||
std::vector<YYCC::yycc_u8string> multiple_selection;
|
||||
|
||||
YYCC::DialogHelper::OpenFileDialog(params, single_selection);
|
||||
YYCC::DialogHelper::OpenMultipleFileDialog(params, multiple_selection);
|
||||
YYCC::DialogHelper::SaveFileDialog(params, single_selection);
|
||||
YYCC::DialogHelper::OpenFolderDialog(params, single_selection);
|
||||
\endcode
|
||||
|
||||
There are 4 file dialogs you can choose:
|
||||
|
||||
\li #OpenFileDialog: Open single file
|
||||
\li #OpenMultipleFileDialog: Open multiple files
|
||||
\li #SaveFileDialog: Save single file
|
||||
\li #OpenFolderDialog: Open single directory
|
||||
|
||||
\subsection dialog_helper__result__arguments Arguments
|
||||
|
||||
Among these 4 functions, the first argument always is the reference to FileDialog.
|
||||
Function will use it to decide what should be shown in this file dialog.
|
||||
|
||||
The second argument always is the reference to the container receiving the result.
|
||||
For single selection, the return type is \c yycc_u8string.
|
||||
For multiple selection, the return type is a list of strings: \c std::vector<yycc_u8string>.
|
||||
|
||||
\subsection dialog_helper__result__return_value Return Value
|
||||
|
||||
Please note among these 4 functions will return a bool as its return value to indicate the success of function.
|
||||
If they return false, it means that the execution of functions are failed or user click Cancel button.
|
||||
In this case, there is no guaranteen to the content of second argument (the real return value).
|
||||
|
||||
\section dialog_helper__notes Notes
|
||||
|
||||
You may notice there are various classes which we never introduce.
|
||||
Because they are intermediate classes and should not be used by programmer.
|
||||
For example:
|
||||
|
||||
\li WinFileDialog: The converted FileDialog passed to Windows.
|
||||
\li WinFileFilters: Same as WinFileDialog. It will be passed to Windows functions.
|
||||
\li etc...
|
||||
|
||||
*/
|
||||
}
|
148
doc/src/encoding_helper.dox
Normal file
148
doc/src/encoding_helper.dox
Normal file
@ -0,0 +1,148 @@
|
||||
namespace YYCC::EncodingHelper {
|
||||
/**
|
||||
|
||||
\page encoding_helper Encoding Helper
|
||||
|
||||
YYCC::EncodingHelper namespace include all encoding related functions:
|
||||
|
||||
\li The convertion between ordinary string and UTF8 string which has been introduced in chapter \ref library_encoding.
|
||||
\li Windows specific convertion between \c WCHAR, UTF8 string and string encoded by other encoding.
|
||||
\li The convertion among UTF8, UTF16 and UTF32.
|
||||
|
||||
\section encoding_helper__ordinary_utf8_conv Ordinary & UTF8 Convertion
|
||||
|
||||
These convertion functions have been introduced in previous page.
|
||||
See \ref library_encoding for more infomation.
|
||||
|
||||
YYCC supports following convertions:
|
||||
|
||||
\li #ToUTF8: Convert ordinary string to UTF8 string.
|
||||
\li #ToUTF8View: Same as ToUTF8, but return string view instead.
|
||||
\li #ToOrdinary: Convert UTF8 string to ordinary string.
|
||||
\li #ToOrdinaryView: Same as ToOrdinary, but return string view instead.
|
||||
|
||||
\section encoding_helper__win_conv Windows Specific Convertion
|
||||
|
||||
During Windows programming, the convertion between Microsoft specified \c wchar_t and \c char is an essential operation.
|
||||
Because Windows has 2 different function system, the functions ended with A and the functions ended with W.
|
||||
(Microsoft specified \c wchar_t is \c 2 bytes long. It's different with Linux defined common 4 bytes long).
|
||||
Thus YYCC provides these convertion functions in Windows to help programmer have better programming experience.
|
||||
|
||||
These functions are Windows specific, so they will be invisible in other platforms.
|
||||
Please use them carefully (make sure that you are using them only in Windows environment).
|
||||
|
||||
YYCC supports following convertions:
|
||||
|
||||
\li #WcharToChar: Convert \c wchar_t string to code page specified string.
|
||||
\li #CharToWchar: The reversed convertion of WcharToChar.
|
||||
\li #CharToChar: Convert string between 2 different code pages. It's a shortcut of calling CharToWchar and WcharToChar successively.
|
||||
\li #WcharToUTF8: Convert \c wchar_t string to UTF8 string.
|
||||
\li #UTF8ToWchar: The reversed convertion of WcharToUTF8.
|
||||
\li #CharToUTF8: Convert code page specified string to UTF8 string.
|
||||
\li #UTF8ToChar: The reversed convertion of CharToUTF8.
|
||||
|
||||
Code Page is a Windows concept.
|
||||
If you don't understand it, please view corresponding Microsoft documentation.
|
||||
|
||||
\section encoding_helper__utf_conv UTF8 UTF16 UTF32 Convertion
|
||||
|
||||
The convertion between UTF8, UTF16 and UTF32 is not common but essential.
|
||||
These convertions can be achieved by standard library functions and classes.
|
||||
(they are actually done by standard library functions in our implementation)
|
||||
But we provided functions are easy to use and have clear interface.
|
||||
|
||||
These functions are different with the functions introduced above.
|
||||
They can be used in any platform, not confined in Windows platforms.
|
||||
|
||||
YYCC supports following convertions:
|
||||
|
||||
\li #UTF8ToUTF16: Convert UTF8 string to UTF16 string.
|
||||
\li #UTF16ToUTF8: The reversed convertion of UTF8ToUTF16.
|
||||
\li #UTF8ToUTF32: Convert UTF8 string to UTF32 string.
|
||||
\li #UTF32ToUTF8: The reversed convertion of UTF8ToUTF32.
|
||||
|
||||
\section encoding_helper__overloads Function Overloads
|
||||
|
||||
Every encoding convertion functions (except the convertion between UTF8 and ordinary string) have 4 different overloads for different scenarios.
|
||||
Take #WcharToChar for example.
|
||||
There are following 4 overloads:
|
||||
|
||||
\code
|
||||
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page);
|
||||
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page);
|
||||
std::string WcharToChar(const std::wstring_view& src, UINT code_page);
|
||||
std::string WcharToChar(const wchar_t* src, UINT code_page);
|
||||
\endcode
|
||||
|
||||
\subsection encoding_helper__overloads_destination Destination String
|
||||
|
||||
According to the return value, these 4 overload can be divided into 2 types.
|
||||
The first type returns bool. The second type returns \c std::string instance.
|
||||
|
||||
For the first type, it always return bool to indicate whether the convertion is success.
|
||||
Due to this, the function must require an argument for holding the result string.
|
||||
So you can see the functions belonging to this type always require a reference to \c std::string in argument.
|
||||
|
||||
Oppositely, the second directly returns result by return value.
|
||||
It doesn't care the success of convertion and will return empty string if convertion failed.
|
||||
Programmer can more naturally use it because the retuen value itself is the result.
|
||||
There is no need to declare a variable before calling convertion function for holding result.
|
||||
|
||||
All in all, the first type overload should be used in strict scope.
|
||||
The success of convertion will massively affect the behavior of your following code.
|
||||
For example, the convertion code is delivered to some system function and it should not be empty and etc.
|
||||
The second type overload usually is used in lossen scenarios.
|
||||
For exmaple, this overload usually is used in console output because it usually doesn't matter.
|
||||
There is no risk even if the convertion failed (just output a blank string).
|
||||
|
||||
For the first type, please note that there is \b NO guarantee that the argument holding return value is not changed.
|
||||
Even the convertion is failed, the argument holding return value may still be changed by function itself.
|
||||
|
||||
In this case, the type of result is \c std::string because this is function required.
|
||||
In other functions, such as #WcharToUTF8, the type of result can be \c yycc_u8string or etc.
|
||||
So please note the type of result is decided by convertion function itself, not only \c std::string.
|
||||
|
||||
\subsection encoding_helper__overloads__source Source String
|
||||
|
||||
According to the way providing source string,
|
||||
these 4 overload also can be divided into 2 types.
|
||||
The first type take a reference to constant \c std::wstring_view.
|
||||
The second type take a pointer to constant \c wchar_t.
|
||||
|
||||
For first type, it will take the whole string for convertion, including \b embedded NUL terminal.
|
||||
Please note we use string view as argument.
|
||||
It is compatible with corresponding raw string pointer and string container.
|
||||
So it is safe to directly pass \c std::wstring for this function.
|
||||
|
||||
For second type, it will assume that you passed argument is a NUL terminated string and send it for convertion.
|
||||
|
||||
The result is clear.
|
||||
If you want to process string with \b embedded NUL terminal, please choose first type overload.
|
||||
Otherwise the second type overload is enough.
|
||||
|
||||
Same as destination string, the type of source is also decided by the convertion function itself.
|
||||
For exmaple, the type of source in #UTF8ToWchar is \c yycc_u8string_view and \c yycc_char8_t,
|
||||
not \c std::wstring and \c wchar_t.
|
||||
|
||||
\subsection encoding_helper__overloads__extra Extra Argument
|
||||
|
||||
There is an extra argument called \c code_page for #WcharToChar.
|
||||
It indicates the code page of destination string,
|
||||
because this function will convert \c wchar_t string to the string with specified code page encoding.
|
||||
|
||||
Some convertion functions have extra argument like this,
|
||||
because they need more infomations to decide what they need to do.
|
||||
Some convertion functions don't have extra argument.
|
||||
For exmaple, the convertion between \c wchar_t string and UTF8 string.
|
||||
Because both source string and destination string are concrete.
|
||||
There is no need to provide any more infomations.
|
||||
|
||||
\subsection encoding_helper__overloads__conclusion Conclusion
|
||||
|
||||
Mixing 2 types of source string and 2 types of destination string,
|
||||
we have 4 different overload as we illustrated before.
|
||||
Programmer can use them freely according to your requirements.
|
||||
And don't forget to provide extra argument if function required.
|
||||
|
||||
*/
|
||||
}
|
35
doc/src/enum_helper.dox
Normal file
35
doc/src/enum_helper.dox
Normal file
@ -0,0 +1,35 @@
|
||||
namespace YYCC::EnumHelper {
|
||||
/**
|
||||
|
||||
\page enum_helper Scoped Enum Helper
|
||||
|
||||
\section enum_helper__intro Intro
|
||||
|
||||
C++ introduce a new enum called scoped enum.
|
||||
It is better than legacy C enum because it will not leak name into namespace where it locate,
|
||||
and also can specify an underlying type to it to make sure it is stored as specified size.
|
||||
However, the shortcoming of it is that it lack bitwise operator comparing with legacy C enum.
|
||||
Programmer must implement them for scoped enum one by one.
|
||||
It is a hardship and inconvenient.
|
||||
This is the reason why I invent this class
|
||||
|
||||
\section enum_helper__Usage Usage
|
||||
|
||||
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).
|
||||
|
||||
\section enum_helper__why Why not Operator Overload
|
||||
|
||||
I have try it (and you even can see the relic of it in source code).
|
||||
But it need a extra statement written in following to include it, otherwise compiler can not see it.
|
||||
|
||||
\code
|
||||
using namespace YYCC::EnumHelper;
|
||||
\endcode
|
||||
|
||||
Another reason why I do not use this method is that
|
||||
this overload strategy may be applied to some type which should not be applied by accient, such as non-scoped enum type.
|
||||
So I gave up this solution.
|
||||
|
||||
*/
|
||||
}
|
110
doc/src/exception_helper.dox
Normal file
110
doc/src/exception_helper.dox
Normal file
@ -0,0 +1,110 @@
|
||||
namespace YYCC::ExceptionHelper {
|
||||
/**
|
||||
|
||||
\page exception_helper Unhandled Exception Handler
|
||||
|
||||
Most Linux users are familiar with core dump.
|
||||
However core dump is a tough work on Windows especially most Windows users are naive for getting core dump.
|
||||
So it is essential to make an easy-to-visit core dump Feature for Windows program.
|
||||
YYCC provides this feature in YYCC::ExceptionHelper.
|
||||
|
||||
You may know Google also has a similar and universal project called Crashpad used by Google Chrome.
|
||||
That's right. But it is too heavy.
|
||||
I just want to implement a tiny but worked core dump feature on Windows.
|
||||
|
||||
This module is Windows specific.
|
||||
It will be invisible on other platforms.
|
||||
|
||||
\section exception_helper__usage Usage
|
||||
|
||||
\subsection exception_helper__usage__code Register Code
|
||||
|
||||
In most scenarios, programmer only need call #Register when program started or module loaded.
|
||||
And call #Unregister when program exited or module unloaded.
|
||||
All details are hidden by these 2 feature.
|
||||
Programmer do not need worried about the implementation of unhandled exception handler.
|
||||
|
||||
Optionally, you can provide a function pointer during calling #Register as a callback.
|
||||
The prototype of this function pointer is #ExceptionCallback.
|
||||
This callback will be called if any unhandled exception happened.
|
||||
It provides 2 pathes to log file and core dump file respectively.
|
||||
So that you can use an explicit way, e.g. \c MessageBox, to tell user exception happened and where are the log files,
|
||||
especially in GUI application because the default output stream, \c stderr, is invisible in GUI application.
|
||||
|
||||
However, please note the pathes provided by callback may be empty.
|
||||
In this case, it means that handler fail to create corresponding log files.
|
||||
Also, if you trying to register unhandled exception handler on the same process in different module with different callback,
|
||||
only the callback provided in first success registering will be called when unhandled exception happened,
|
||||
due to \ref exception_helper__notes__singleton design.
|
||||
|
||||
\subsection exception_helper__usage__location Location
|
||||
|
||||
When unhandled exception occurs,
|
||||
unhandled exception handler will try to record error log and core dump in following path:
|
||||
|
||||
\li Error Log: <TT>\%LOCALAPPDATA\%\\CrashDumps\\<I>program.exe</I>.<I>pid</I>.log</TT>
|
||||
\li Core Dump: <TT>\%LOCALAPPDATA\%\\CrashDumps\\<I>program.exe</I>.<I>pid</I>.dmp</TT>
|
||||
|
||||
The italic characters <I>program.exe</I> and <I>pid</I> will be replaced by program name and process ID respectively at runtime.
|
||||
Directory <TT>\%LOCALAPPDATA\%\\CrashDumps</TT> also is Windows used crash dump directory.
|
||||
So you may see some other core dumps done by Windows in it.
|
||||
|
||||
\subsection exception_helper__usage__last_remedy Last Remedy
|
||||
|
||||
If unhandled exception handler occurs error, these stuff may not be generated correctly.
|
||||
The end user may not find them and send them to you.
|
||||
There is a last remedy for this scenario.
|
||||
|
||||
Unhandled exception handler will still output error log in \c stderr no matter whether error log or core dump is created.
|
||||
So end user always can fetch error log from console.
|
||||
You only need to instruct end user open command prompt, launch application, reproduce error and get the output error log in console.
|
||||
In this case, you can not get core dump. But you can get error log.
|
||||
It is not good for debugging but it is better than nothing.
|
||||
|
||||
Also please note the last remedy may still have a little bit possibility to occurs error and output nothing,
|
||||
especially the error occurs in back trace function.
|
||||
There is no guaranteen that unhandled exception handler must generate error log and core dump.
|
||||
|
||||
\section exception_helper__notes Notes
|
||||
|
||||
\subsection exception_helper__notes__thread_safe Thread Safe
|
||||
|
||||
All exposed functions in YYCC::ExceptionHelper are thread safe.
|
||||
The implementation uses \c std:mutex to ensure this.
|
||||
|
||||
\subsection exception_helper__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.
|
||||
For example, you have an executable program A.exe, and 2 dynamic libraries B.dll and C.dll.
|
||||
A.exe and B.dll use YYCC unhandled exception handler feature but C.dll not.
|
||||
A.exe will load B.dll and C.dll at runtime.
|
||||
Although both A.exe and B.dll call #Register,
|
||||
when unhandled exception occurs, there is only one error report output,
|
||||
which may be generated by A.exe or B.dll accoridng to their order of loading.
|
||||
|
||||
The core purpose of this is making sure the program will not output too many error report for the same unhandled exception,
|
||||
no matter how many modules calling #Register are loaded.
|
||||
Only one error report is enough.
|
||||
|
||||
More precisely, we use \c CreateMutexW to create an unique mutex in Windows global scope,
|
||||
to make sure #Register only run once in the same process.
|
||||
It is very like the implementation of singleton application.
|
||||
|
||||
\subsection exception_helper__notes__recursive_calling Recursive Calling
|
||||
|
||||
The implementation of unhandled exception handler may also will throw exception.
|
||||
This will cause infinite recursive calling.
|
||||
YYCC::ExceptionHelper has internal mechanism to prevent this bad case.
|
||||
If this really happened, the handler will quit silent and will not cause any issue.
|
||||
Programmer don't need to worry about this.
|
||||
|
||||
\subsection exception_helper__notes__user_callback The Timing of User Callback
|
||||
|
||||
The timing of calling user callback is the tail of unhandled exception handler.
|
||||
It means that all log and coredump have been written if possible before calling callback.
|
||||
Because user callback may still raise exception.
|
||||
We want all essential log files has been written before calling it,
|
||||
so that at least we can visit them on disk or console.
|
||||
|
||||
*/
|
||||
}
|
@ -1,10 +1,85 @@
|
||||
/**
|
||||
|
||||
\mainpage YYCCommonplace Library Manual
|
||||
\mainpage YYCCommonplace Programming Manual
|
||||
|
||||
This manual is organized into the following chapters and appendices:
|
||||
<TABLE CELLPADDING="8" CELLSPACING="0" SUMMARY="TITLE BAR" WIDTH="100%" BORDER="0">
|
||||
<TR>
|
||||
<TD><CENTER>
|
||||
\image html yycc_icon.png
|
||||
</CENTER></TD>
|
||||
<TD><CENTER>
|
||||
<B>YYCCommonplace Programming Manual</B>
|
||||
|
||||
\subpage intro
|
||||
Copyright 2024 by yyc12345.
|
||||
</CENTER></TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
<TABLE CELLPADDING="8" CELLSPACING="0" SUMMARY="TITLE BAR" WIDTH="100%" BORDER="0">
|
||||
<TR>
|
||||
<TD>
|
||||
This software and manual are provided under the terms of the MIT License.
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
<TABLE CELLPADDING="8" CELLSPACING="0" SUMMARY="Table of Contents" WIDTH="100%" BORDER="0">
|
||||
<TR>
|
||||
<TD ALIGN="LEFT" VALIGN="TOP">
|
||||
|
||||
<B>General Features</B>
|
||||
|
||||
\li \subpage intro
|
||||
|
||||
\li \subpage premise_and_principle
|
||||
|
||||
<!--
|
||||
\li \subpage library_macros
|
||||
|
||||
\li \subpage library_encoding
|
||||
|
||||
\li \subpage encoding_helper
|
||||
|
||||
\li \subpage string_helper
|
||||
|
||||
\li \subpage parser_helper
|
||||
|
||||
\li \subpage console_helper
|
||||
|
||||
\li \subpage io_helper
|
||||
|
||||
\li \subpage std_patch
|
||||
|
||||
\li \subpage enum_helper
|
||||
-->
|
||||
|
||||
<B>Advanced Features</B>
|
||||
|
||||
<!--
|
||||
\li \subpage constraints
|
||||
|
||||
\li \subpage config_manager
|
||||
|
||||
\li \subpage arg_parser
|
||||
-->
|
||||
|
||||
</TD>
|
||||
<TD ALIGN="LEFT" VALIGN="TOP">
|
||||
|
||||
<B>Windows Specific Features</B>
|
||||
|
||||
<!--
|
||||
\li \subpage win_import
|
||||
|
||||
\li \subpage com_helper
|
||||
|
||||
\li \subpage dialog_helper
|
||||
|
||||
\li \subpage win_fct_helper
|
||||
|
||||
\li \subpage exception_helper
|
||||
-->
|
||||
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
*/
|
||||
|
@ -2,10 +2,202 @@
|
||||
|
||||
\page intro Introduction to YYCCommonplace
|
||||
|
||||
work in progress
|
||||
YYCCommonplace, or YYC Commonplace (abbr. YYCC),
|
||||
is a static library providing various useful C++ functions
|
||||
when programming with standard library or Windows environment.
|
||||
|
||||
\section work in progress
|
||||
At the beginning, during the development of a few projects,
|
||||
I gradually understand how Windows make the compromise with the code written by its old developers,
|
||||
and what is developer wanted in contemporary C++ standard library under Windows environment.
|
||||
So I create this static library for all of my C++ project.
|
||||
After this, I do not need to write these duplicated code in each project.
|
||||
I can use a clear and easy way to manage these codes.
|
||||
I can easily fix issues found in project using this library by updating a single project,
|
||||
rather than fixing these duplicated code in each project one by one
|
||||
because all of them share the same implementations.
|
||||
This is the origin of the 1.x version of YYCC.
|
||||
|
||||
work in progress
|
||||
After a few years ago, I start to write in Rust and more complicated codes.
|
||||
I was allured by Rust and hope all these feature Rust holded can be adapted into C++,
|
||||
so I start to refactor this library in modern way.
|
||||
However, the compatibility with low C++ standard version is now become the shortcoming of this library.
|
||||
I was forced to considering the compatibility with C++ 17 and it cause a huge work.
|
||||
So, after think twice, I decide to drop the support of C++ 17, which the higest C++ standard I can used for Virtools project.
|
||||
And increase the standard to C++ 23 directly to have better experience.
|
||||
That's the origin of the 2.x version of YYCC.
|
||||
|
||||
This project mainly is served for my personal use.
|
||||
But I would be honored if you would like to use this in your project.
|
||||
Almost of my projects will gradually adapt to this project and drop their own individual implementations.
|
||||
|
||||
\section intro__why Why YYCCommonplace
|
||||
|
||||
\subsection intro__why__windows Windows Issues
|
||||
|
||||
I frequently program on Windows environment because the software I programming for, Virtools, is Windows-only software.
|
||||
During programming, I found Windows is super lack in UTF8 supports.
|
||||
Programmer loves UTF8, because it can handle all charcaters over the world in one encoding and is still compatible with C-Style string.
|
||||
However, Windows use a weird way to achieve internationalization, 2 different function trailing, A and W for legacy code and modern code respectively.
|
||||
The worst things is that the char type W trailing function used, \c WCHAR, is defined as 2 bytes long, not 4 bytes long as Linux does (\c wchar_t).
|
||||
It mean that one emoji charcater will be torn into 2 \c WCHAR on Windows because emoji code unit is higher than the maximum value of \c WCHAR.
|
||||
|
||||
Also, there are various issues which should not be presented.
|
||||
For example, Microsoft invents various \e safe standard library functions to prevent possible overflow issues raised by \c std::fgets and etc.
|
||||
also, MSVC may throw weird error when you using some specific standard library functions.
|
||||
You need to define some weird macros to disable this shitty behavior.
|
||||
|
||||
There are various non-standard issue you may faced on Windows programming.
|
||||
All in all, programming on Windows is a tough work.
|
||||
This is one of the reasons why I create this library.
|
||||
I create much wrappers for these weird Windows functions.
|
||||
Thus I can have a similar Linux C++ programming experience on Windows.
|
||||
|
||||
\subsection intro__why__std Standard Library Issues
|
||||
|
||||
The eccentric decision of standard commission also is the reason why I create this library.
|
||||
|
||||
\li C++ standard commission prefer to provide one function with very fundamental classes and functions
|
||||
and programmer need to write too much code to achieve a simple work.
|
||||
\li C++ standard commission seems doesn't want to bring any features the programmer urgent needed.
|
||||
\li C++ standard commission loves delete programmer loved convenient functions and classes.
|
||||
\li etc...
|
||||
|
||||
There is not a proper way to \e format a string in C++ until C++ 20 (\c std::format).
|
||||
String related functions, such as split, lower, higher, replace, now still not be included in standard library.
|
||||
Programmer loved, easy to used encoding convertion functions and classes are deprecate now and will be removed in future.
|
||||
|
||||
That's why I create this library.
|
||||
I bring these functions in this library.
|
||||
Not industrial level, but easy to use and have enough performance in my project.
|
||||
|
||||
\subsection intro__why__boost Boost Issues
|
||||
|
||||
Bosst is a powerful C++ library. But the shortcoming is overt. It's tooooo big.
|
||||
This drawback will be more obvious considering the bad dependency mechanism of C++.
|
||||
Although the most of Boost sub-library is header-only, but some library still need to link with Boost.
|
||||
It order you download the whole Boost library and extract it in your hard disk.
|
||||
Cost more than half hour of your life, 5+ GB disk space and the life time of your disk.
|
||||
|
||||
The functions belonging to Boost is industrial level.
|
||||
But what I want is not industrial level functions.
|
||||
I only need a function which can barely finish my work. That's enough.
|
||||
I don't need extreme performance. I just want my code works.
|
||||
|
||||
So I create this library, bring some Boost functions with ordinary but not bad implementation.
|
||||
|
||||
<!--
|
||||
\section intro__usage Library Usage
|
||||
|
||||
Before using this library, I suggest you read this manual fully to have a full overview of this library.
|
||||
Otherwise you may make mistake during using this library.
|
||||
I suggest you read this manual from top to bottom in the left tree panel, one by one.
|
||||
|
||||
YYCC library self provides some build scripts for convenient use which are located in \c script directory.
|
||||
Please note all of these script should be executed in the root of YYCC project, not the script directory
|
||||
(i.e. work directory is the root directory of YYCC).
|
||||
All scripts will try to do a simple check about this if you accidently execute them in a wrong place.
|
||||
|
||||
If you are not willing to use our build script, or our build script went wrong,
|
||||
you can create your personal build script by viewing our build script.
|
||||
|
||||
\subsection intro__usage__linux Linux
|
||||
|
||||
Building YYCC on Linux is easy to do by executing <TT>script/linux_build.sh</TT>.
|
||||
After script done, you will find installation result in directory <TT>bin/install</TT>.
|
||||
Then other CMake project can utilize it (non-CMake project also can utilize this).
|
||||
|
||||
\subsection intro__usage__win Windows
|
||||
|
||||
For building on Windows, there are 2 distribution types which YYCC can create.
|
||||
First is CMake distribution, this distribution is served for other CMake project using.
|
||||
Another one is MSVC distribution, this distribution is served for other MSVC project using.
|
||||
These have different directory layout which is specifically designed for corresponding build tools.
|
||||
See following section for more details.
|
||||
|
||||
\subsection intro__usage__win__execute Execute Build Script
|
||||
|
||||
For creating distribution on Windows, please execute script <TT>python3 script/gen_win_build.py</TT> first.
|
||||
Then execute <TT>script/win_build.bat</TT> to generate final result.
|
||||
|
||||
\c script/gen_win_build.py is the generator of \c script/win_build.bat.
|
||||
It will accept various arguments and generate a proper real build script for you.
|
||||
Currently \c script/gen_win_build.py supports following arguments:
|
||||
|
||||
\li \c -c, \c --cpp \c [cpp_version]: Specify the version of C++ standard for building.
|
||||
Due to the different defination of UTF8 char type,
|
||||
C++ 20 program can not use this library built by C++ 17 environment.
|
||||
So this switch give you a chance to decide the version of C++ standard used when building.
|
||||
The lowest and defult version of C++ standard is 17.
|
||||
\li \c -d, \c --build-doc: Specify this if you want to build documentation.
|
||||
End user usually needs documentation,
|
||||
however if you are the developer of this library, you may need this switch.
|
||||
Because documentation take too much disk space and cost a bunch of time for building and copying.
|
||||
In default, generator will produce script which do not build documentation automatically.
|
||||
\li \c -p, \c --pic: Enable Position Independent Code flag on non-Windows platfotm.
|
||||
This flag is crucial to linking this library to another dynamic library.
|
||||
If you do not specify this flag, the linking process will fail.
|
||||
|
||||
After script done, you will find CMake distribution in directory <TT>bin/<I>cpp_ver</I>/install</TT>.
|
||||
and you will also find your MSVC distribution in directory <TT>bin/<I>cpp_ver</I>/msvc_install</TT>.
|
||||
\e cpp_ver in path will be replaced by the C++ version you specified.
|
||||
|
||||
\subsubsection intro__usage__win__cmake CMake Distribution
|
||||
|
||||
CMake distribution has following directory structure.
|
||||
|
||||
\verbatim
|
||||
YYCC
|
||||
├─Win32_Debug: Win32 Debug package
|
||||
│ ├─include: Headers
|
||||
│ └─lib: Library for linking and CMake package file
|
||||
├─Win32_Release: Win32 Release package
|
||||
│ ├─bin: Executable testbench
|
||||
│ ├─include: Headers
|
||||
│ └─lib: Library for linking and CMake package file
|
||||
├─x64_Debug: x64 Debug package
|
||||
│ ├─include: Headers
|
||||
│ └─lib: Library for linking and CMake package file
|
||||
└─x64_Release: x64 Release package
|
||||
├─bin: Executable testbench
|
||||
├─include: Headers
|
||||
├─lib: Library for linking and CMake package file
|
||||
└─share: Documentation
|
||||
\endverbatim
|
||||
|
||||
Every different architecture and build type have a single and full directory.
|
||||
CMake project can use one of by adding their build type in \c find_package path.
|
||||
So that CMake will automatically utilize correct package when switching build type.
|
||||
|
||||
\subsubsection intro__usage__win__msvc MSVC Distribution
|
||||
|
||||
MSVC distribution has following directory structure.
|
||||
|
||||
\verbatim
|
||||
YYCC
|
||||
├─bin
|
||||
│ ├─Win32: Win32 Release testbench
|
||||
│ └─x64: x64 Release testbench
|
||||
├─include: Headers
|
||||
├─lib
|
||||
│ ├─Win32
|
||||
│ │ ├─Debug: Win32 Debug library for linking
|
||||
│ │ └─Release: Win32 Release library for linking
|
||||
│ └─x64
|
||||
│ ├─Debug: x64 Debug library for linking
|
||||
│ └─Release: x64 Release library for linking
|
||||
└─share: Documentation
|
||||
\endverbatim
|
||||
|
||||
The different between MSVC distribution and CMake distribution is
|
||||
that MSVC distribution places all static library under one director \c lib.
|
||||
Thus in MSVC project user can simply spcify the install path of YYCC,
|
||||
and use MSVC macros in path to choose correct static library for linking
|
||||
|
||||
\section intro__debug Debug Tips
|
||||
|
||||
YYCC CMake build script contains a special option called \c YYCC_DEBUG_UE_FILTER.
|
||||
If you set it to true, it will add a public macro \c YYCC_DEBUG_UE_FILTER to YYCC project.
|
||||
This macro will enable special code path for the convenience of debugging \ref exception_helper related features.
|
||||
So in common use, user should not enable this option.
|
||||
-->
|
||||
*/
|
||||
|
50
doc/src/io_helper.dox
Normal file
50
doc/src/io_helper.dox
Normal file
@ -0,0 +1,50 @@
|
||||
namespace YYCC::IOHelper {
|
||||
/**
|
||||
|
||||
\page io_helper IO Helper
|
||||
|
||||
Actually, YYCC::IOHelper includes functions which can not be placed in other place.
|
||||
|
||||
\section io_helper__ptr_pri_padding Pointer Print Padding
|
||||
|
||||
When printing pointer on screen, programmer usually left-pad zero to make it looks good.
|
||||
However, the count of zero for padding is different in x86 and x64 architecture (8 for x86 and 16 for x64).
|
||||
Macro \c PRI_XPTR_LEFT_PADDING will help you to resolve this issue.
|
||||
|
||||
Macro \c PRI_XPTR_LEFT_PADDING will be defined to following value according to the target system architecture.
|
||||
|
||||
\li \c "08": On x86 system.
|
||||
\li \c "016": On x64 system.
|
||||
|
||||
There is an example for how to use it:
|
||||
|
||||
\code
|
||||
void* raw_ptr = blabla();
|
||||
std::printf(stdout, "Raw Pointer 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR, raw_ptr);
|
||||
\endcode
|
||||
|
||||
Note \c PRIXPTR is defined by standard library for formatting pointer as hexadecimal style.
|
||||
|
||||
\section io_helper__smart_file Smart FILE Pointer
|
||||
|
||||
#SmartStdFile use \c std::unique_ptr with custom deleter to implement smart \c FILE*.
|
||||
It is useful in the cases that you want to automatically free opened file when leaving corresponding scope.
|
||||
|
||||
\section io_helper__utf8_fopen UTF8 fopen
|
||||
|
||||
In Windows, standard \c std::fopen can not handle UTF8 file name in common environment.
|
||||
So we create this function to give programmer an universal \c fopen in UTF8 style.
|
||||
|
||||
In Windows platform, this function will try to convert its argument to \c wchar_t
|
||||
and calling Microsoft specific \c _wfopen function to open file.
|
||||
If encoding convertion or \c _wfopen failed, this function will return \c nullptr like \c std::fopen does.
|
||||
In other platforms, it will simply redirect calling to \c std::fopen.
|
||||
|
||||
There is a simple example:
|
||||
|
||||
\code
|
||||
FILE* fs = YYCC::IOHelper::FOpen(YYCC_U8("/path/to/file"), YYCC_U8("rb"));
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
228
doc/src/library_encoding.dox
Normal file
228
doc/src/library_encoding.dox
Normal file
@ -0,0 +1,228 @@
|
||||
namespace YYCC {
|
||||
/**
|
||||
|
||||
\page library_encoding Library Encoding
|
||||
|
||||
Before using this library, you should know the encoding strategy of this library first.
|
||||
In short words, this library use UTF8 encoding everywhere except some special cases,
|
||||
for example, function explicitly order the encoding of input parameters.
|
||||
|
||||
In following content of this article, you will know the details about how we use UTF8 in this library.
|
||||
|
||||
\section library_encoding__utf8_type UTF8 Type
|
||||
|
||||
YYCC uses custom UTF8 char type, string container and string view all over the library, from parameters to return value.
|
||||
Following content will introduce how we define them.
|
||||
|
||||
\subsection library_encoding__utf8_type__char_type Char Type
|
||||
|
||||
YYCC library has its own UTF8 char type, \c yycc_char8_t.
|
||||
This is how we define it:
|
||||
|
||||
\code
|
||||
#if defined(__cpp_char8_t)
|
||||
using yycc_char8_t = char8_t;
|
||||
#else
|
||||
using yycc_char8_t = unsigned char;
|
||||
#endif
|
||||
\endcode
|
||||
|
||||
If your environment (higher or equal to C++ 20) supports \c char8_t provided by standard library, \c yycc_char8_t is just an alias to \c char8_t,
|
||||
otherwise (lower than C++ 20, e.g. C++ 17), \c yycc_char8_t will be defined as \c unsigned \c char like C++ 20 does (this can be seen as a polyfill).
|
||||
|
||||
This means that if you already have used \c char8_t provided by standard library,
|
||||
you do not need to do any extra modification before using this library.
|
||||
Because all types are compatible.
|
||||
|
||||
\subsection library_encoding__utf8_type__container_type String Container and View
|
||||
|
||||
We define string container and string view like this:
|
||||
|
||||
\code
|
||||
using yycc_u8string = std::basic_string<yycc_char8_t>;
|
||||
using yycc_u8string_view = std::basic_string_view<yycc_char8_t>;
|
||||
\endcode
|
||||
|
||||
The real code written in library may be slightly different with this but they have same meanings.
|
||||
|
||||
In \c char8_t environment, they are just the alias to \c std::u8string and \c std::u8string_view respectively.
|
||||
So if you have already used them, no need to any modification for your code before using this library.
|
||||
|
||||
\subsection library_encoding__utf8_type__why Why?
|
||||
|
||||
You may curious why I create a new UTF8 char type, rather than using standard library UTF8 char type directly. There are 2 reasons.
|
||||
|
||||
First, It was too late that I notice I can use standard library UTF8 char type.
|
||||
My UTF8 char type has been used in library everywhere and its tough to fully replace them into standard library UTF8 char type.
|
||||
|
||||
Second, UTF8 related content of standard library is \e volatile.
|
||||
I notice standard library change UTF8 related functions frequently and its API are not stable.
|
||||
For example, standard library brings \c std::codecvt_utf8 in C++ 11, deprecate it in C++ 17 and even remove it in C++ 26.
|
||||
That's unacceptable! So I create my own UTF8 type to avoid the scenario that standard library remove \c char8_t in future.
|
||||
|
||||
\section library_encoding__concept Concepts
|
||||
|
||||
In following content, you may be face with 2 words: ordinary string and UTF8 string.
|
||||
|
||||
UTF8 string, as its name, is the string encoded with UTF8.
|
||||
The char type of it must is \c yycc_char8_t.
|
||||
(equivalent to \c char8_t after C++ 20.)
|
||||
|
||||
Ordinary string means the plain, native string.
|
||||
The result of C++ string literal without any prefix \c "foo bar" is a rdinary string.
|
||||
The char type of it is \c char.
|
||||
Its encoding depends on compiler and environment.
|
||||
(UTF8 in Linux, or system code page in Windows if UTF8 switch was not enabled in MSVC.)
|
||||
|
||||
For more infomation, please browse CppReference:
|
||||
https://en.cppreference.com/w/cpp/language/string_literal
|
||||
|
||||
\section library_encoding__utf8_literal UTF8 Literal
|
||||
|
||||
String literal is a C++ concept.
|
||||
If you are not familar with it, please browse related article first, such as CppReference.
|
||||
|
||||
\subsection library_encoding__utf8_literal__single Single Literal
|
||||
|
||||
In short words, YYCC allow you declare an UTF8 literal like this:
|
||||
|
||||
\code
|
||||
YYCC_U8("This is UTF8 literal.")
|
||||
\endcode
|
||||
|
||||
YYCC_U8 is macro.
|
||||
You don't need add extra \c u8 prefix in string given to the macro.
|
||||
This macro will do this automatically.
|
||||
|
||||
In detail, this macro do a \c reinterpret_cast to change the type of given argument to \c const \c yycc_char8_t* forcely.
|
||||
This ensure that declared UTF8 literal is compatible with YYCC UTF8 types.
|
||||
|
||||
\subsection library_encoding__utf8_literal__char Single Char
|
||||
|
||||
Same as UTF8 literal, YYCC allow you cast normal \c char into \c yycc_char8_t as following code:
|
||||
|
||||
\code
|
||||
YYCC_U8_CHAR('A')
|
||||
\endcode
|
||||
|
||||
YYCC_U8_CHAR is a macro.
|
||||
It just simply use \c static_cast to cast given value to \c yycc_char8_t.
|
||||
It doesn't mean that you can cast non-ASCII characters,
|
||||
because the space these characters occupied usually more than the maximum value of \c char.
|
||||
For example, following code is \b invalid:
|
||||
|
||||
\code
|
||||
YYCC_U8_CHAR('文') // INVALID!
|
||||
\endcode
|
||||
|
||||
\subsection library_encoding__utf8_literal__concatenation Literal Concatenation
|
||||
|
||||
YYCC_U8 macro also works for string literal concatenation:
|
||||
|
||||
\code
|
||||
YYCC_U8("Error code: " PRIu32 ". Please contact me.");
|
||||
\endcode
|
||||
|
||||
According to C++ standard for string literal concatenation,
|
||||
<I>"If one of the strings has an encoding prefix and the other does not, the one that does not will be considered to have the same encoding prefix as the other."</I>
|
||||
At the same time, YYCC_U8 macro will automatically add \c u8 prefix for the first component of this string literal concatenation.
|
||||
So the whole string will be UTF8 literal.
|
||||
It also order you should \b not add any prefix for other components of this string literal concatenation.
|
||||
|
||||
\subsection library_encoding__utf8_literal__why Why?
|
||||
|
||||
You may know that C++ standard allows programmer declare an UTF8 literal explicitly by writing code like this:
|
||||
|
||||
\code
|
||||
u8"foo bar"
|
||||
\endcode
|
||||
|
||||
This is okey. But it may incompatible with YYCC UTF8 char type.
|
||||
According to C++ standard, this UTF8 literal syntax will only return \c const \c char8_t* if your C++ standard higher or equal to C++ 20,
|
||||
otherwise it will return \c const \c char*.
|
||||
This behavior cause that you can not assign this UTF8 literal to \c yycc_u8string if you are in the environment which do not support \c char8_t,
|
||||
because their types are different.
|
||||
Thereas you can not use the functions provided by this library because they are all use YYCC defined UTF8 char type.
|
||||
|
||||
\section library_encoding__utf8_pointer UTF8 String Pointer
|
||||
|
||||
String pointer means the raw pointer pointing to a string, such as \c const \c char*, \c char*, \c char32_t* and etc.
|
||||
|
||||
Many legacy code assume \c char* is encoded with UTF8 (the exception is Windows). But \c char* is incompatible with \c yycc_char8_t.
|
||||
YYCC provides YYCC::EncodingHelper::ToUTF8 to resolve this issue. There is an exmaple:
|
||||
|
||||
\code
|
||||
const char* absolutely_is_utf8 = "I confirm this is encoded with UTF8.";
|
||||
const yycc_char8_t* converted = YYCC::EncodingHelper::ToUTF8(absolutely_is_utf8);
|
||||
|
||||
char* mutable_utf8 = const_cast<char*>(absolutely_is_utf8); // This is not safe. Just for example.
|
||||
yycc_char8_t* mutable_converted = YYCC::EncodingHelper::ToUTF8(mutable_utf8);
|
||||
\endcode
|
||||
|
||||
YYCC::EncodingHelper::ToUTF8 has 2 overloads which can handle constant and mutable stirng pointer convertion respectively.
|
||||
|
||||
YYCC also has ability that convert YYCC UTF8 char type to ordinary char type by YYCC::EncodingHelper::ToOrdinary.
|
||||
Here is an exmaple:
|
||||
|
||||
\code
|
||||
const yycc_char8_t* yycc_utf8 = YYCC_U8("I am UTF8 string.");
|
||||
const char* converted = YYCC::EncodingHelper::ToOrdinary(yycc_utf8);
|
||||
|
||||
yycc_char8_t* mutable_yycc_utf8 = const_cast<char*>(yycc_utf8); // Not safe. Also just for example.
|
||||
char* mutable_converted = YYCC::EncodingHelper::ToOrdinary(mutable_yycc_utf8);
|
||||
\endcode
|
||||
|
||||
Same as YYCC::EncodingHelper::ToUTF8, YYCC::EncodingHelper::ToOrdinary also has 2 overloads to handle constant and mutable string pointer.
|
||||
|
||||
\section library_encoding__utf8_container UTF8 String Container
|
||||
|
||||
String container usually means the standard library string container, such as \c std::string, \c std::wstring, \c std::u32string and etc.
|
||||
|
||||
In many personal project, programmer may use \c std::string everywhere because \c std::u8string may not be presented when writing peoject.
|
||||
How to do convertion between ordinary string container and YYCC UTF8 string container?
|
||||
It is definitely illegal that directly do force convertion. Because they may have different class layout.
|
||||
Calm down and I will tell you how to do correct convertion.
|
||||
YYCC provides YYCC::EncodingHelper::ToUTF8 to convert ordinary string container to YYCC UTF8 string container.
|
||||
There is an exmaple:
|
||||
|
||||
\code
|
||||
std::string ordinary_string("I am UTF8");
|
||||
yycc_u8string yycc_string = YYCC::EncodingHelper::ToUTF8(ordinary_string);
|
||||
auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string);
|
||||
\endcode
|
||||
|
||||
Actually, YYCC::EncodingHelper::ToUTF8 accepts a reference to \c std::string_view as argument.
|
||||
However, there is a implicit convertion from \c std::string to \c std::string_view,
|
||||
so you can directly pass a \c std::string instance to it.
|
||||
|
||||
String view will reduce unnecessary memory copy.
|
||||
If you just want to pass ordinary string container to function, and this function accepts \c yycc_u8string_view as its argument,
|
||||
you can use alternative YYCC::EncodingHelper::ToUTF8View.
|
||||
|
||||
\code
|
||||
std::string ordinary_string("I am UTF8");
|
||||
yycc_u8string_view yycc_string = YYCC::EncodingHelper::ToUTF8View(ordinary_string);
|
||||
auto result = YYCC::EncodingHelper::UTF8ToUTF32(yycc_string);
|
||||
\endcode
|
||||
|
||||
Comparing with previous one, this example use less memory.
|
||||
The reduced memory is the content of \c yycc_string because string view is a view, not the copy of original string.
|
||||
|
||||
Same as UTF8 string pointer, we also have YYCC::EncodingHelper::ToOrdinary and YYCC::EncodingHelper::ToOrdinaryView do correspondant reverse convertion.
|
||||
Try to do your own research and figure out how to use them.
|
||||
It's pretty easy.
|
||||
|
||||
\section library_encoding__windows Warnings to Windows Programmer
|
||||
|
||||
Due to the legacy of MSVC, the encoding of \c char* may not be UTF8 in most cases.
|
||||
If you run the convertion code introduced in this article with the string which is not encoded with UTF8, it may cause undefined behavior.
|
||||
|
||||
To enable UTF8 mode of MSVC, please deliver \c /utf-8 switch to MSVC.
|
||||
Thus you can use the functions introduced in this article safely.
|
||||
Otherwise, you must guarteen that the argument you provided to these functions is encoded by UTF8 manually.
|
||||
|
||||
Linux user do not need care this.
|
||||
Because almost Linux distro use UTF8 in default.
|
||||
|
||||
*/
|
||||
}
|
122
doc/src/library_macros.dox
Normal file
122
doc/src/library_macros.dox
Normal file
@ -0,0 +1,122 @@
|
||||
namespace YYCC {
|
||||
/**
|
||||
|
||||
\page library_macros Library Macros
|
||||
|
||||
In this page we will introduce the macros defined by this library
|
||||
which can not be grouped in other topic.
|
||||
|
||||
\section library_macros__batch_class_copy_move Library Version and Version Comparison
|
||||
|
||||
Version is a important things in modern software development, especially for a library.
|
||||
In YYCC, we use Semantic Versioning as our version standard.
|
||||
For more infomations about it, please see: https://semver.org/
|
||||
|
||||
First, YYCC has its own version and it can be visited by
|
||||
\c YYCC_VER_MAJOR, \c YYCC_VER_MINOR, and \c YYCC_VER_PATCH.
|
||||
Each part of Semantic Versioning is provided individually.
|
||||
|
||||
YYCC also provide a bunch of macros to compare 2 versions.
|
||||
It also provides a way to check YYCC version in program using YYCC,
|
||||
because some of them rely on a specific version of YYCC.
|
||||
There is a list of these comparison macros.
|
||||
|
||||
\li YYCC_VERCMP_E
|
||||
\li YYCC_VERCMP_NE
|
||||
\li YYCC_VERCMP_G
|
||||
\li YYCC_VERCMP_GE
|
||||
\li YYCC_VERCMP_NL
|
||||
\li YYCC_VERCMP_L
|
||||
\li YYCC_VERCMP_LE
|
||||
\li YYCC_VERCMP_NG
|
||||
|
||||
You may notice all of these macros are starts with \c YYCC_VERCMP_,
|
||||
and their tails are inspired from x86 ASM comparison jump code.
|
||||
For example, \c E means "equal" and \c NE means "not equal",
|
||||
\c G means "greater", \c GE means "greater or equal", and \c NG means "not gretaer".
|
||||
|
||||
All of these macros take 6 arguments,
|
||||
for the first 3 arguments, we call them "left version".
|
||||
From left to right they are the major part, minor part and patch part of semantic version.
|
||||
And for the last 3 arguments, we call them "right version".
|
||||
From left to right they are the major part, minor part and patch part of semantic version.
|
||||
There is a example about checking whether YYCC library version is exactly what we wanted version.
|
||||
|
||||
\code
|
||||
#if YYCC_VERCMP_NE(YYCC_VER_MAJOR, YYCC_VER_MINOR, YYCC_VER_PATCH, 1, 3 ,0)
|
||||
#error "Not Matched YYCC Version"
|
||||
#endif
|
||||
\endcode
|
||||
|
||||
\section library_macros__platform_checker Platform Checker
|
||||
|
||||
In many cross platform applications,
|
||||
programmer usually write code adapted to different platforms in one source file
|
||||
and enable them respectively by macros representing the target platform.
|
||||
As a cross platform library,
|
||||
YYCC also has this feature and you can utilize it if you don't have other ways to so the same things.
|
||||
|
||||
\subsection library_macros__platform_checker__values Values
|
||||
|
||||
YYCC always define a macro called \c YYCC_OS to indicate the system of target platform.
|
||||
In implementation, it will check following list from top to bottom to set matched value for it.
|
||||
|
||||
\li \c YYCC_OS_WINDOWS: Windows environment. It is done by checking whether environment define \c _WIN32 macro.
|
||||
\li \c YYCC_OS_LINUX: In current implementation, this means target platform is \b NOT Windows.
|
||||
|
||||
\subsection library_macros__platform_checker__usage Usage
|
||||
|
||||
Now you know any possible value of \c YYCC_OS.
|
||||
The next step is how to use it to enable specified code in specific target platform.
|
||||
We take Windows platform for example.
|
||||
Assume \c blabla() function is Windows specific.
|
||||
We have following example code:
|
||||
|
||||
\code
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
blabla();
|
||||
#endif
|
||||
\endcode
|
||||
|
||||
It's enough and simple that use \c \#if to bracket the Windows specified code.
|
||||
|
||||
\section library_macros__batch_class_copy_move Batch Class Copy / Move Functions
|
||||
|
||||
YYCC provides 6 macros to batchly remove class copy constructor and move constructor,
|
||||
or set default class copy constructor and move constructor.
|
||||
|
||||
<UL>
|
||||
<LI>
|
||||
\c YYCC_DEL_CLS_COPY: Declare following 2 statements which delete copy constrcutor and copy assign operator.
|
||||
<UL>
|
||||
<LI><TT>CLSNAME(const CLSNAME&) = delete;</TT></LI>
|
||||
<LI><TT>CLSNAME& operator=(const CLSNAME&) = delete;</TT></LI>
|
||||
</UL>
|
||||
</LI>
|
||||
<LI>
|
||||
\c YYCC_DEL_CLS_MOVE: Declare following 2 statements which delete move constrcutor and move assign operator.
|
||||
<UL>
|
||||
<LI><TT>CLSNAME(CLSNAME&&) = delete;</TT></LI>
|
||||
<LI><TT>CLSNAME& operator=(CLSNAME&&) = delete;</TT></LI>
|
||||
</UL>
|
||||
</LI>
|
||||
<LI>\c YYCC_DEL_CLS_COPY_MOVE: The combination of \c YYCC_DEL_CLS_COPY and \c YYCC_DEL_CLS_MOVE.</LI>
|
||||
<LI>
|
||||
\c YYCC_DEF_CLS_COPY: Declare following 2 statements which set default copy constrcutor and copy assign operator.
|
||||
<UL>
|
||||
<LI><TT>CLSNAME(const CLSNAME&) = default;</TT></LI>
|
||||
<LI><TT>CLSNAME& operator=(const CLSNAME&) = default;</TT></LI>
|
||||
</UL>
|
||||
</LI>
|
||||
<LI>
|
||||
\c YYCC_DEF_CLS_MOVE: Declare following 2 statements which set default move constrcutor and move assign operator.
|
||||
<UL>
|
||||
<LI><TT>CLSNAME(CLSNAME&&) = default;</TT></LI>
|
||||
<LI><TT>CLSNAME& operator=(CLSNAME&&) = default;</TT></LI>
|
||||
</UL>
|
||||
</LI>
|
||||
<LI>\c YYCC_DEF_CLS_COPY_MOVE: The combination of \c YYCC_DEF_CLS_COPY and \c YYCC_DEF_CLS_MOVE.</LI>
|
||||
</UL>
|
||||
|
||||
*/
|
||||
}
|
88
doc/src/parser_helper.dox
Normal file
88
doc/src/parser_helper.dox
Normal file
@ -0,0 +1,88 @@
|
||||
namespace YYCC::ParserHelper {
|
||||
/**
|
||||
|
||||
\page parser_helper Parser Helper
|
||||
|
||||
This helper is served for the convertion between number and string.
|
||||
|
||||
\section parser_helper_supported_types Supported Types
|
||||
|
||||
Functions located in this helper support the convertion between string and following types:
|
||||
|
||||
\li Integral types (except \c bool): \c int, \c uint32_t, \c char and etc.
|
||||
\li Floating point types: \c float, \c double and etc.
|
||||
\li \c bool
|
||||
|
||||
Please note in C++, \c bool is integral type but we list it individually because parser will treat it specially.
|
||||
For \c bool type, parser will try doing convertion between it and \c "true" \c "false" string.
|
||||
(\b case-insensitive. It means that \c true can be converted from \c "true", \c "True" or \c "TRUE".)
|
||||
|
||||
\section parser_helper__try_parse Try Parse
|
||||
|
||||
#TryParse will try to parse string into caller specified type.
|
||||
All of them accept an UTF8 string view at first argument,
|
||||
require that you provide a container receiving converted result in the second argument,
|
||||
and return a bool value to indicate whether the convertion is successful.
|
||||
There are some examples:
|
||||
|
||||
\code
|
||||
uint32_t val;
|
||||
YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("123"), val);
|
||||
YYCC::ParserHelper::TryParse<uint32_t>(YYCC_U8("7fff"), val, 16);
|
||||
\endcode
|
||||
|
||||
For floating point type, this function allows caller to specify extra argument providing the format of given number string (\c std::chars_format).
|
||||
For integral type, this function allows caller to specify extra argument providing the base of given number string.
|
||||
|
||||
\section parser_helper__parse Parse
|
||||
|
||||
#Parse is similar to #TryParse.
|
||||
But it will not return bool value to indicate success and doesn't have the argument receiving result.
|
||||
It only accepts an UTF8 string view as the only one argument, and return result directly.
|
||||
If the convertion failed, the return value is \b undefined (but usually is the default value of given type).
|
||||
There is an example:
|
||||
|
||||
\code
|
||||
uint32_t val = YYCC::ParserHelper::Parse<uint32_t>(YYCC_U8("123"));
|
||||
\endcode
|
||||
|
||||
For integral and floating point value,
|
||||
it has same extra argument with #TryParse to provide more number infomation.
|
||||
|
||||
Using this function is dangerous if the validation of your input is important.
|
||||
In this case, please use #TryParse instead.
|
||||
|
||||
\section parser_helper__to_string To String
|
||||
|
||||
#ToString basically is the reversed operation of #Parse.
|
||||
It gets the string representation of given type.
|
||||
The only argument of these functions is the type which need to be converted to its string representation.
|
||||
And they will return yycc_u8string as result.
|
||||
There is an example:
|
||||
|
||||
\code
|
||||
auto result = YYCC::ParserHelper::ToString<uint32_t>(UINT32_C(114));
|
||||
\endcode
|
||||
|
||||
For floating point type, this function allows caller to specify extra arguments
|
||||
which provides the format (\c std::chars_format) and precision when getting string representation.
|
||||
For integral type, this function allows caller to specify extra argument
|
||||
providing the base of number when getting string representation.
|
||||
|
||||
\section parser_helper__notes Notes
|
||||
|
||||
All functions within this helper are implementated by standard library functions.
|
||||
These functions just make a good wrapper for complex standard library functions.
|
||||
And give you a experience like C\# parser functions.
|
||||
|
||||
Basically, all functions located in this helper have possibility to throw exception.
|
||||
But this possibility are more close to the possibility that \c new statement throw \c std::bad_alloc.
|
||||
So in most cases you can assume these functions will not throw any exception.
|
||||
|
||||
All functions are template functions.
|
||||
The argument of template is the type these functions need to be processed.
|
||||
Although C++ have \e smart template type deduction,
|
||||
it would be better to specify template argument manually to explicitly specify your desired type.
|
||||
|
||||
*/
|
||||
}
|
47
doc/src/premise_and_principle.dox
Normal file
47
doc/src/premise_and_principle.dox
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
|
||||
\page premise_and_principle Premise and Principle
|
||||
|
||||
When programming with this library, there is some premise and principle you should noticed.
|
||||
|
||||
\section premise_and_principle__exception_is_error Exception is Error
|
||||
|
||||
The most crucial spot of this library is <B>"Exception is Error"</B>.
|
||||
When some functions throw exception, it should cause program paniked, rather than recover from it.
|
||||
This is inspired from Rust, and also the compromise with STL.
|
||||
|
||||
Most functions this library provided has Rust-Result-like return value.
|
||||
It means that programmer can handle error correctly.
|
||||
However, this library is based on STL, another library that may throw C++ exception to indicate error.
|
||||
We can not control this behavior of STL, so I forcely apply this rule.
|
||||
|
||||
\section premise_and_principle__os_encoding OS Encoding
|
||||
|
||||
This library has special treat with Windows to make it works on Windows.
|
||||
However, for other operating system, it do not have too much care.
|
||||
We brutally make a premise that other operating systems are UNIX-liked and use UTF8 as its encoding.
|
||||
|
||||
\section premise_and_principle__string_encoding String Encoding
|
||||
|
||||
Before using this library, you should know the encoding strategy of this library first.
|
||||
In short words, this library use UTF8 encoding everywhere except some special cases list following (not all).
|
||||
|
||||
\li Traditional format function in yycc::string::op.
|
||||
Traditional format function provide some overloads for ordinary string formatting.
|
||||
That's because this feature is so common to use in some cases.
|
||||
\li The message of Rust panic in yycc::rust::panic.
|
||||
Due to the limitation of \c std::format, we only can use ordinary string as its message content.
|
||||
\li The message of standard library exception.
|
||||
For the compatibility with C++ standard library exception,
|
||||
we only can use ordinary string as the message of exception.
|
||||
|
||||
\section premise_and_principle__cmake All in CMake
|
||||
|
||||
Since YYCC 2.0 version, we do not provide MSVC install layout.
|
||||
Any projects use this project should use CMake or CMake-compatible software as its build system.
|
||||
|
||||
The reason why we make this decision is that some essential contents are written in CMake files.
|
||||
For example, some environment detection macros and Windows environment patches.
|
||||
If you do not use CMake, these contents will not be presented in project and cause bad behavior when using this project.
|
||||
|
||||
*/
|
112
doc/src/std_patch.dox
Normal file
112
doc/src/std_patch.dox
Normal file
@ -0,0 +1,112 @@
|
||||
namespace YYCC::StdPatch {
|
||||
/**
|
||||
|
||||
\page std_patch Standard Library Patch
|
||||
|
||||
\section std_patch__starts_with_ends_with Starts With & Ends With
|
||||
|
||||
\c std::basic_string::starts_with and \c std::basic_string::ends_with (also available in \c std::basic_string_view)
|
||||
are functions introduced in C++ 20 and unavailable in C++ 17.
|
||||
YYCC::StdPatch provides a patch for these function in C++ 17 environment.
|
||||
Please note these implementations are following implementation instruction presented by CppReference website.
|
||||
And it should have the same performance with vanilla functions because Microsoft STL use the same way to implement.
|
||||
These implementations will not fallback to vanilla function even they are available.
|
||||
Because their performance are good.
|
||||
|
||||
To use these functions, you just need to call them like corresponding vanilla functions.
|
||||
Our implementations provide all necessary overloads.
|
||||
The only thing you need to do is provide the string self as the first argument,
|
||||
because our implementations can not be inserted as a class member of string.
|
||||
There is an example:
|
||||
|
||||
\code
|
||||
YYCC::StdPatch::StartsWith(YYCC_U8("aabbcc"), YYCC_U8("aa"));
|
||||
YYCC::StdPatch::EndsWith(YYCC_U8("aabbcc"), YYCC_U8("cc"));
|
||||
\endcode
|
||||
|
||||
\section std_patch__contains Contains
|
||||
|
||||
\c Contains function in standard library ordered and unordered successive container are also introduced in C++ 20.
|
||||
YYCC::StdPatch provides a patch for this function in C++ 17 environment.
|
||||
|
||||
Please note this implementation will fallback to vanilla function if it is available.
|
||||
Because our implementation is a remedy (there is no way to use public class member to have the same performance of vanilla function).
|
||||
There is an example about how to use it:
|
||||
|
||||
\code
|
||||
std::set<int> test { 1, 5 };
|
||||
YYCC::StdPatch::Contains(test, static_cast<int>(5));
|
||||
\endcode
|
||||
|
||||
\section std_patch__fs_path std::filesystem::path Patch
|
||||
|
||||
As you know, the underlying char type of \c std::filesystem::path is \c wchar_t on Windows,
|
||||
and in other platforms, it is simple \c char.
|
||||
Due to this, if you try to create a \c std::filesystem::path instance by calling constructor with an UTF8 char sequence on Windows,
|
||||
the library implementation will assume your input is based on current Windows code page, not UTF8.
|
||||
And the final path stored in \c std::filesystem::path is not what you expcected.
|
||||
|
||||
This patch gives you a way to create \c std::filesystem::path
|
||||
and extract path string stored in \c std::filesystem::path with UTF8 encoding.
|
||||
This patch namespace always use UTF8 as its argument.
|
||||
You should use the functions provided by this namespace on any platforms
|
||||
instead of vanilla \c std::filesystem::path functions.
|
||||
However, if your C++ standard is higher than C++ 20,
|
||||
you can directly use UTF8 string pointer and string container in \c std::filesystem::path,
|
||||
because standard library has supported them.
|
||||
This patch only just want to provide an uniform programming experience.
|
||||
|
||||
This patch is served for Windows but also works on other plaftoms.
|
||||
If you are in Windows, this patch will perform extra operations to achieve goals,
|
||||
and in other platforms, they just redirect request to corresponding vanilla C++ functions.
|
||||
|
||||
\subsection std_patch__fs_path__from_utf8_path Create Path from UTF8 String
|
||||
|
||||
#ToStdPath provides this feature.
|
||||
It accepts an string pointer to UTF8 string and try to create \c std::filesystem::path from it.
|
||||
Function will throw exception if encoding convertion or constructor self failed.
|
||||
There are some example:
|
||||
|
||||
\code
|
||||
auto foobar_path = YYCC::StdPatch::ToStdPath(YYCC_U8("/foo/bar"));
|
||||
auto slashed_path = foobar_path / YYCC::StdPatch::ToStdPath(YYCC_U8("test"));
|
||||
auto replaced_ext = foobar_path.replace_extension(YYCC::StdPatch::ToStdPath(YYCC_U8(".txt")));
|
||||
\endcode
|
||||
|
||||
For first line in example, it is obvious that you can create a \c std::filesystem::path from this function.
|
||||
However, for the second and third line in example, what we want to tell you is
|
||||
that you should always use this function in other \c std::filesystem::path functions requiring path string.
|
||||
|
||||
\c std::filesystem::path is a very \e conservative class.
|
||||
Most of its functions only accept \c std::filesystem::path self as argument.
|
||||
For example, \c std::filesystem::path::replace_extension do not accept string as argument.
|
||||
It accepts a reference to \c std::filesystem::path as argument.
|
||||
(it still is possible that pass string pointer or string container to it because they can be converted to \c std::filesystem::path implicitly.)
|
||||
It's great. This is what we expected!
|
||||
We now can safely deliver the result generated by our function to these functions,
|
||||
and don't need to worry about the encoding of we provided string.
|
||||
Because all strings have been converted to \c std::filesystem::path by our function before passing them.
|
||||
|
||||
So, the second line will produce \c "/foo/bar/test"
|
||||
and the third line will produce \c "/foo/bar.txt" in any platforms.
|
||||
|
||||
You may notice std::filesystem::u8path.
|
||||
However it is depracted since C++ 20,
|
||||
because \c std::filesystem::path directly supports UTF8 by \c char8_t since C++ 20.
|
||||
Because C++ standard is volatile, we create this function to have an uniform programming experience.
|
||||
|
||||
\subsection std_patch__fs_path__to_utf8_path Extract UTF8 Path String from Path
|
||||
|
||||
#ToUTF8Path provides this feature.
|
||||
It basically is the reversed operation of #ToStdPath.
|
||||
It is usually used when you have done all path work in \c std::filesystem::path
|
||||
and want to get the result.
|
||||
There is an example:
|
||||
|
||||
\code
|
||||
auto foobar_path = YYCC::StdPatch::ToStdPath(YYCC_U8("/foo/bar"));
|
||||
auto result = YYCC::StdPatch::ToUTF8Path(foobar_path / YYCC::StdPatch::ToStdPath(YYCC_U8("test")));
|
||||
\endcode
|
||||
|
||||
*/
|
||||
}
|
149
doc/src/string_helper.dox
Normal file
149
doc/src/string_helper.dox
Normal file
@ -0,0 +1,149 @@
|
||||
namespace YYCC::StringHelper {
|
||||
/**
|
||||
|
||||
\page string_helper String Helper
|
||||
|
||||
\section string_helper__printf Printf VPrintf
|
||||
|
||||
YYCC::StringHelper provides 4 functions for formatting string.
|
||||
These functions are mainly provided to programmer who can not use C++ 20 \c std::format feature.
|
||||
|
||||
\code
|
||||
bool Printf(yycc_u8string&, const yycc_char8_t*, ...);
|
||||
bool VPrintf(yycc_u8string&, const yycc_char8_t*, va_list argptr);
|
||||
yycc_u8string Printf(const yycc_char8_t*, ...);
|
||||
yycc_u8string VPrintf(const yycc_char8_t*, va_list argptr);
|
||||
\endcode
|
||||
|
||||
#Printf and #VPrintf is similar to \c std::sprintf and \c std::vsprintf.
|
||||
#Printf accepts UTF8 format string and variadic arguments specifying data to print.
|
||||
This is commonly used by programmer.
|
||||
However, #VPrintf also do the same work but its second argument is \c va_list,
|
||||
the representation of variadic arguments.
|
||||
It is mostly used by other function which has variadic arguments.
|
||||
|
||||
The only difference between these function and standard library functions is
|
||||
that you don't need to worry about whether the space of given buffer is enough,
|
||||
because these functions help you to calculate this internally.
|
||||
|
||||
There is the same design like we introduced in \ref encoding_helper.
|
||||
There are 2 overloads for #Printf and #VPrintf respectively.
|
||||
First overload return bool value and require a string container as argument for storing result.
|
||||
The second overload return result string directly.
|
||||
As you expected, first overload will return false if fail to format string (this is barely happened).
|
||||
and second overload will return empty string when formatter failed.
|
||||
|
||||
\section string_helper__replace Replace
|
||||
|
||||
YYCC::StringHelper provide 2 functions for programmer do string replacement:
|
||||
|
||||
\code
|
||||
void Replace(yycc_u8string&, const yycc_u8string_view&, const yycc_u8string_view&);
|
||||
yycc_u8string Replace(const yycc_u8string_view&, const yycc_u8string_view&, const yycc_u8string_view&);
|
||||
\endcode
|
||||
|
||||
The first overload will do replacement in given string container directly.
|
||||
The second overload will produce a copy of original string and do replacement on the copied string.
|
||||
|
||||
#Replace has special treatments for following scenarios:
|
||||
|
||||
\li If given string is empty, the return value will be empty.
|
||||
\li If the character sequence to be replaced is empty string, no replacement will happen.
|
||||
\li If the character sequence will be replaced into string is or empty, it will simply delete found character sequence from given string.
|
||||
|
||||
\section string_helper__join Join
|
||||
|
||||
YYCC::StringHelper provide an universal way for joining string and various specialized join functions.
|
||||
|
||||
\subsection string_helper__join__universal Universal Join Function
|
||||
|
||||
Because C++ list types are various.
|
||||
There is no unique and convenient way to create an universal join function.
|
||||
So we create #JoinDataProvider to describe join context.
|
||||
|
||||
Before using universal join function,
|
||||
you should setup #JoinDataProvider first, the context of join function.
|
||||
It actually is an \c std::function object which can be easily fetched by C++ lambda syntax.
|
||||
This function pointer accept a reference to \c yycc_u8string_view,
|
||||
programmer should set it to the string to be joined when at each calling.
|
||||
And this function pointer return a bool value to indicate the end of join.
|
||||
You can simply return \c false to terminate join process.
|
||||
The argument you assigned to argument will not be taken into join process when you return false.
|
||||
|
||||
Then, you can pass the created #JoinDataProvider object to #Join function.
|
||||
And specify delimiter at the same time.
|
||||
Then you can get the final joined string.
|
||||
There is an example:
|
||||
|
||||
\code
|
||||
std::vector<yycc_u8string> data {
|
||||
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
|
||||
};
|
||||
auto iter = data.cbegin();
|
||||
auto stop = data.cend();
|
||||
auto joined_string = YYCC::StringHelper::Join(
|
||||
[&iter, &stop](yycc_u8string_view& view) -> bool {
|
||||
if (iter == stop) return false;
|
||||
view = *iter;
|
||||
++iter;
|
||||
return true;
|
||||
},
|
||||
delimiter
|
||||
);
|
||||
\endcode
|
||||
|
||||
\subsection string_helper__join__specialized Specialized Join Function
|
||||
|
||||
Despite universal join function,
|
||||
YYCC::StringHelper also provide a specialized join functions for standard library container.
|
||||
For example, the code written above can be written in following code by using this specialized overload.
|
||||
The first two argument is just the begin and end iterator.
|
||||
However, you must make sure that we can dereference it and then implicitly convert it to yycc_u8string_view.
|
||||
Otherwise this overload will throw template error.
|
||||
|
||||
\code
|
||||
std::vector<yycc_u8string> data {
|
||||
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
|
||||
};
|
||||
auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), delimiter);
|
||||
\endcode
|
||||
|
||||
\section string_helper__lower_upper Lower Upper
|
||||
|
||||
String helper provides Python-like string lower and upper function.
|
||||
Both lower and upper function have 2 overloads:
|
||||
|
||||
\code
|
||||
yycc_u8string Lower(const yycc_u8string_view&);
|
||||
void Lower(yycc_u8string&);
|
||||
\endcode
|
||||
|
||||
First overload accepts a string view as argument and return a \b copy whose content are all the lower case of original string.
|
||||
Second overload accepts a mutable string container as argument and will make all characters stored in it become their lower case.
|
||||
You can choose on of them for your flavor and requirements.
|
||||
Upper also has similar 2 overloads.
|
||||
|
||||
\section string_helper__split Split
|
||||
|
||||
String helper provides Python-like string split function.
|
||||
It has 2 types for you:
|
||||
|
||||
\code
|
||||
std::vector<yycc_u8string> Split(const yycc_u8string_view&, const yycc_u8string_view&);
|
||||
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view&, const yycc_u8string_view&);
|
||||
\endcode
|
||||
|
||||
All these overloads take a string view as the first argument representing the string need to be split.
|
||||
The second argument is a string view representing the delimiter for splitting.
|
||||
The only difference between these 2 split function are overt according to their names.
|
||||
The first split function will return a list of copied string as its split result.
|
||||
The second split function will return a list of string view as its split result,
|
||||
and it will keep valid as long as the life time of your given string view argument.
|
||||
It also means that the last overload will cost less memory if you don't need the copy of original string.
|
||||
|
||||
If the source string (the string need to be split) is empty, or the delimiter is empty,
|
||||
the result will only has 1 item and this item is source string itself.
|
||||
There is no way that these methods return an empty list, except the code is buggy.
|
||||
|
||||
*/
|
||||
}
|
23
doc/src/win_fct_helper.dox
Normal file
23
doc/src/win_fct_helper.dox
Normal file
@ -0,0 +1,23 @@
|
||||
namespace YYCC::WinFctHelper {
|
||||
/**
|
||||
|
||||
\page win_fct_helper Windows Function Helper
|
||||
|
||||
This helper give a more convenient way to call Windows functions.
|
||||
|
||||
This namespace is Windows specific.
|
||||
It will be entirely invisible in other platforms.
|
||||
|
||||
Currently this namespace has following functions:
|
||||
|
||||
\li #GetCurrentModule: Get the handle to current module.
|
||||
\li #GetTempDirectory: Get temporary directory in Windows.
|
||||
\li #GetModuleFileName: Get the path to module in file system by given handle.
|
||||
\li #GetLocalAppData: Get the path inside \%LOCALAPPDATA\%
|
||||
\li #IsValidCodePage: Check whether given code page number is valid.
|
||||
\li #CopyFile: The UTF8 version of Win32 \c CopyFile.
|
||||
\li #MoveFile: The UTF8 version of Win32 \c MoveFile.
|
||||
\li #DeleteFile: The UTF8 version of Win32 \c DeleteFile.
|
||||
|
||||
*/
|
||||
}
|
71
doc/src/win_import.dox
Normal file
71
doc/src/win_import.dox
Normal file
@ -0,0 +1,71 @@
|
||||
namespace YYCC {
|
||||
/**
|
||||
|
||||
\page win_import Windows Import Guard
|
||||
|
||||
Windows is shitty for the programmer who is familiar with UNIX programming.
|
||||
Due to legacy reason, Windows defines various things which are not compatible with UNIX or standard C++ programming.
|
||||
|
||||
\section win_import__usage Usage
|
||||
|
||||
YYCC has a way to solve the issue introduced above.
|
||||
|
||||
\code
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include <WinImportPrefix.hpp>
|
||||
#include <Windows.h>
|
||||
#include "other_header_depend_on_windows.h"
|
||||
#include <WinImportSuffix.hpp>
|
||||
#endif
|
||||
\endcode
|
||||
|
||||
The including of WinImportPrefix.hpp and WinImportSuffix.hpp is a pair.
|
||||
They just like a guard bracket the include operation of Windows related headers,
|
||||
to keep all Windows shitty contents will not be leaked outside.
|
||||
|
||||
This guard can solve following issues:
|
||||
|
||||
<UL>
|
||||
<LI>
|
||||
Programmer can not use \c std::max and \c std::min normally.
|
||||
<UL>
|
||||
<LI>Windows defines \c MAX and \c MIN as macros for personal use. This is why this happened.</LI>
|
||||
<LI>Guard defines some special macros to tell Windows do not create these 2 macros.</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
<LI>
|
||||
Programmer will not be affected by the automatical rename of \c GetObject, \c GetClassName and etc.
|
||||
<UL>
|
||||
<LI>These are all macros for Windows personal use to automatically redirect calling to A function and W function by compiling environment.</LI>
|
||||
<LI>Guard \c \#undef these annoy macros.</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
<LI>
|
||||
Compiler throw annoy warnings and errors when using specific standard library functions.
|
||||
<UL>
|
||||
<LI>MSVC will throw warnings and errors when you are using Microsoft so-called \e depracted or \e unsafe standard library functions.</LI>
|
||||
<LI>YYCCInternal.hpp, which has been included by this pair, defines some macros to purge these warnings and errors out.</LI>
|
||||
</UL>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
\section win_import__notes Notes
|
||||
|
||||
If you have other header files which are strongly depend on Windows header,
|
||||
you should put them into this bracket at the same time like example did.
|
||||
Because this guard operate some Windows macros as we introduced above.
|
||||
The headers depending on Windows may throw error if you put them outside of this pair.
|
||||
|
||||
Please note WinImportPrefix.hpp and WinImportSuffix.hpp can be included multiple times.
|
||||
Because they do not have the proprocessor command like <I>\#pragma once</I> or etc to make sure they only can be included once.
|
||||
That's by design. Because we actually may use this pair multiple times.
|
||||
The only thing you should pledge is that you must make sure they are presented by pair.
|
||||
|
||||
This guard is Windows specific.
|
||||
It does nothing if you accidently use it in other platforms such as Linux,
|
||||
because the headers use \c \#if to check environment out and will do nothing in non-Windows environment.
|
||||
However, we still highly recommend you use this pair with platform checker bracket like example does,
|
||||
if your program need to be run on multiple platforms.
|
||||
|
||||
*/
|
||||
}
|
BIN
doc/src/yycc_icon.png
Normal file
BIN
doc/src/yycc_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
216
script/.gitignore
vendored
Normal file
216
script/.gitignore
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
## ===== Myself =====
|
||||
# Exclude VSCode
|
||||
.vscode/
|
||||
|
||||
# Exclude generated files
|
||||
win_build.bat
|
||||
linux_build.sh
|
||||
|
||||
## ===== Python =====
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
#poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
#pdm.lock
|
||||
#pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
#pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Cursor
|
||||
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
||||
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
||||
# refer to https://docs.cursor.com/context/ignore-files
|
||||
.cursorignore
|
||||
.cursorindexingignore
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
@ -1,35 +0,0 @@
|
||||
@ECHO OFF
|
||||
SET README_PATH=%CD%\README.md
|
||||
IF EXIST %README_PATH% (
|
||||
REM DO NOTHING
|
||||
) ELSE (
|
||||
ECHO Error: You must run this script at the root folder of this project!
|
||||
EXIT /b
|
||||
)
|
||||
|
||||
:: Create essential folder
|
||||
MKDIR bin
|
||||
CD bin
|
||||
MKDIR Win32
|
||||
MKDIR x64
|
||||
MKDIR install
|
||||
|
||||
:: Build for Win32
|
||||
CD Win32
|
||||
cmake -G "Visual Studio 16 2019" -A Win32 -DYYCC_BUILD_TESTBENCH=ON ../..
|
||||
cmake --build . --config Debug
|
||||
cmake --install . --prefix=../install --config Debug
|
||||
cmake --build . --config Release
|
||||
cmake --install . --prefix=../install --config Release
|
||||
CD ..
|
||||
|
||||
:: Build for x64
|
||||
CD x64
|
||||
cmake -G "Visual Studio 16 2019" -A x64 -DYYCC_BUILD_TESTBENCH=ON ../..
|
||||
cmake --build . --config Debug
|
||||
cmake --install . --prefix=../install --config Debug
|
||||
cmake --build . --config Release
|
||||
cmake --install . --prefix=../install --config Release
|
||||
CD ..
|
||||
|
||||
ECHO DONE
|
106
script/gen_build_script.py
Normal file
106
script/gen_build_script.py
Normal file
@ -0,0 +1,106 @@
|
||||
import argparse
|
||||
import typing
|
||||
import re
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
import jinja2
|
||||
|
||||
def validate_cpp_ver(ver: str) -> str:
|
||||
if re.match(r'^[0-9]+$', ver) is not None: return ver
|
||||
else: raise argparse.ArgumentTypeError('invalid version of C++ standard.')
|
||||
|
||||
def write_line(f: typing.TextIO, val: str) -> None:
|
||||
f.write(val)
|
||||
f.write('\n')
|
||||
|
||||
# Reference: https://stackoverflow.com/questions/29213106/how-to-securely-escape-command-line-arguments-for-the-cmd-exe-shell-on-windows
|
||||
def escape_for_cmd_exe(arg):
|
||||
meta_re = re.compile(r'([()%!^"<>&|])')
|
||||
return meta_re.sub('^\1', arg)
|
||||
def escape_cmd_argument(arg):
|
||||
if not arg or re.search(r'(["\s])', arg):
|
||||
arg = '"' + arg.replace('"', r'\"') + '"'
|
||||
return escape_for_cmd_exe(arg)
|
||||
def escape_sh_argument(arg):
|
||||
return shlex.quote(arg)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ScriptSettings:
|
||||
pic: bool
|
||||
cpp_version: str
|
||||
build_doc: bool
|
||||
build_testbench: bool
|
||||
|
||||
class TemplateRender:
|
||||
loader: jinja2.BaseLoader
|
||||
environment: jinja2.Environment
|
||||
|
||||
win_template: jinja2.Template
|
||||
linux_template: jinja2.Template
|
||||
|
||||
settings: ScriptSettings
|
||||
|
||||
def __init__(self, settings: ScriptSettings) -> None:
|
||||
self.loader = jinja2.FileSystemLoader(self.__get_dir())
|
||||
self.environment = jinja2.Environment(loader=self.loader)
|
||||
|
||||
self.win_template = self.environment.get_template('win_build.bat.jinja')
|
||||
self.linux_template = self.environment.get_template('linux_build.sh.jinja')
|
||||
|
||||
self.settings = settings
|
||||
|
||||
def __get_dir(self) -> Path:
|
||||
return Path(__file__).resolve().parent
|
||||
|
||||
def __escape_path(self, val: str, is_win: bool) -> str:
|
||||
if is_win: return escape_cmd_argument(val)
|
||||
else: return escape_sh_argument(val)
|
||||
|
||||
def __render(self, template: jinja2.Template, dest_file: str, is_win: bool) -> None:
|
||||
with open(self.__get_dir() / dest_file, 'w', encoding='utf-8') as f:
|
||||
f.write(template.render(
|
||||
repo_root_dir = self.__escape_path(str(self.__get_dir().parent), is_win),
|
||||
cpp_version = self.settings.cpp_version,
|
||||
build_doc = self.settings.build_doc,
|
||||
pic = settings.pic
|
||||
))
|
||||
|
||||
def render_win_script(self) -> None:
|
||||
self.__render(self.win_template, 'win_build.bat', True)
|
||||
|
||||
def render_linux_script(self) -> None:
|
||||
self.__render(self.linux_template, 'linux_build.sh', False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# parse argument
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='YYCC Windows Build Script Generator',
|
||||
description='YYCC Windows Build Script Generator'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-c', '--cpp',
|
||||
action='store', default='17', dest='cpp', type=validate_cpp_ver,
|
||||
help='The version of C++ standard used when building.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-d', '--build-doc',
|
||||
action='store_true', dest='build_doc',
|
||||
help='Build YYCC with documentation.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--pic',
|
||||
action='store_true', dest='pic',
|
||||
help='Enable Position Independent Code flag on non-Windows platform. This is crucial for compiling dynamic library using this library.'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# build settings
|
||||
settings = ScriptSettings(args.cpp, args.build_doc, args.pic)
|
||||
# build template render and render result
|
||||
render = TemplateRender(settings)
|
||||
render.render_win_script()
|
||||
render.render_linux_script()
|
||||
|
||||
|
17
script/linux_build.sh.jinja
Normal file
17
script/linux_build.sh.jinja
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Navigate to project root directory
|
||||
cd {{ repo_root_dir }}
|
||||
|
||||
# Create build and install directory
|
||||
mkdir build
|
||||
mkdir install
|
||||
|
||||
# Build as release version
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD={{ cpp_version }} {{ '-DCMAKE_POSITION_INDEPENDENT_CODE=True' if pic }} {{ '-DYYCC_BUILD_DOC=ON' if build_doc }} {{ '-DYYCC_BUILD_TESTBENCH=ON' if build_testbench }} ../.. --fresh
|
||||
cmake --build .
|
||||
cmake --install . --prefix ../install
|
||||
|
||||
# Exit to original path
|
||||
cd ..
|
||||
echo "YYCC Linux CMake build done"
|
2
script/pycodec/.gitignore
vendored
Normal file
2
script/pycodec/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Exclude result
|
||||
*.cpp
|
7
script/pycodec/README.md
Normal file
7
script/pycodec/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# PyCodec
|
||||
|
||||
This directory contain all stuff related to PyCodec.
|
||||
|
||||
PyCodec use different encoding system on different OS. In Windows it use Win32 functions, and it will use Iconv in other OS. So we need a table converting PyCodec universal encoding name to Windows Code Page or Iconv Code Name. These relation was stored in CSV file and Python script will render it into C++ source code.
|
||||
|
||||
For the format of CSV file, each line is a record. The first item in record is the standard PyCodec name. The second item is corresponding Windows Code Page. If there is no corresponding Code Page, it can be empty. The third item is corresponding Iconv Code Name. It also can be empty with same case. Then, the count of remain columns is variables after forth item (inclusive). All of them is the alias of this standard PyCodec name.
|
54
script/pycodec/conv_encoding_table.py
Normal file
54
script/pycodec/conv_encoding_table.py
Normal file
@ -0,0 +1,54 @@
|
||||
import csv
|
||||
from pathlib import Path
|
||||
import jinja2
|
||||
|
||||
|
||||
class LanguageToken:
|
||||
name: str
|
||||
alias: tuple[str, ...]
|
||||
code_page: str | None
|
||||
iconv_code: str | None
|
||||
|
||||
def __init__(self, row: list[str]):
|
||||
"""Init language token from CSV row."""
|
||||
self.name = row[0].lower()
|
||||
code_page = row[1]
|
||||
self.code_page = None if len(code_page) == 0 else code_page
|
||||
iconv_code = row[2]
|
||||
self.iconv_code = None if len(iconv_code) == 0 else iconv_code
|
||||
# For alias, we strip and to lower them first, and remove all empty entries
|
||||
alias = row[3:]
|
||||
self.alias = tuple(
|
||||
filter(lambda x: len(x) != 0,
|
||||
map(lambda x: x.strip().lower(), alias)))
|
||||
|
||||
|
||||
def _get_self_dir() -> Path:
|
||||
return Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def _extract_tokens() -> list[LanguageToken]:
|
||||
rv: list[LanguageToken] = []
|
||||
csv_file = _get_self_dir() / 'encoding_table.csv'
|
||||
|
||||
with open(csv_file, 'r', encoding='utf-8', newline='') as f:
|
||||
reader = csv.reader(f, delimiter='\t')
|
||||
for row in reader:
|
||||
rv.append(LanguageToken(row))
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def _render_cpp(tokens: list[LanguageToken]) -> None:
|
||||
loader = jinja2.FileSystemLoader(_get_self_dir())
|
||||
environment = jinja2.Environment(loader=loader)
|
||||
template = environment.get_template('encoding_table.cpp.jinja')
|
||||
|
||||
cpp_file = _get_self_dir() / 'encoding_table.cpp'
|
||||
with open(cpp_file, 'w', encoding='utf-8') as f:
|
||||
f.write(template.render(tokens=tokens))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
tokens = _extract_tokens()
|
||||
_render_cpp(tokens)
|
23
script/pycodec/encoding_table.cpp.jinja
Normal file
23
script/pycodec/encoding_table.cpp.jinja
Normal file
@ -0,0 +1,23 @@
|
||||
static const std::map<std::u8string_view, std::u8string_view> ALIAS_MAP {
|
||||
{% for token in tokens -%}
|
||||
{% for alias in token.alias -%}
|
||||
{ u8"{{ alias }}"sv, u8"{{ token.name }}"sv },
|
||||
{% endfor -%}
|
||||
{% endfor -%}
|
||||
};
|
||||
|
||||
static const std::map<std::u8string_view, CodePage> WINCP_MAP {
|
||||
{% for token in tokens -%}
|
||||
{% if token.code_page is not none -%}
|
||||
{ u8"{{ token.name }}"sv, static_cast<CodePage>({{ token.code_page }}u) },
|
||||
{% endif -%}
|
||||
{% endfor -%}
|
||||
};
|
||||
|
||||
static const std::map<std::u8string_view, std::string_view> ICONV_MAP {
|
||||
{% for token in tokens -%}
|
||||
{% if token.iconv_code is not none -%}
|
||||
{ u8"{{ token.name }}"sv, "{{ token.iconv_code }}"sv },
|
||||
{% endif -%}
|
||||
{% endfor -%}
|
||||
};
|
97
script/pycodec/encoding_table.csv
Normal file
97
script/pycodec/encoding_table.csv
Normal file
@ -0,0 +1,97 @@
|
||||
ascii 437 ASCII 646 us-ascii
|
||||
big5 950 BIG5 big5-tw csbig5
|
||||
big5hkscs BIG5-HKSCS big5-hkscs hkscs
|
||||
cp037 037 IBM037 IBM039
|
||||
cp273 273 IBM273 csIBM273
|
||||
cp424 EBCDIC-CP-HE IBM424
|
||||
cp437 437 437 IBM437
|
||||
cp500 500 EBCDIC-CP-BE EBCDIC-CP-CH IBM500
|
||||
cp720 720
|
||||
cp737 737
|
||||
cp775 775 IBM775
|
||||
cp850 850 CP850 850 IBM850
|
||||
cp852 852 852 IBM852
|
||||
cp855 855 855 IBM855
|
||||
cp856
|
||||
cp857 857 857 IBM857
|
||||
cp858 858 858 IBM858
|
||||
cp860 860 860 IBM860
|
||||
cp861 861 861 CP-IS IBM861
|
||||
cp862 862 CP862 862 IBM862
|
||||
cp863 863 863 IBM863
|
||||
cp864 864 IBM864
|
||||
cp865 865 865 IBM865
|
||||
cp866 866 CP866 866 IBM866
|
||||
cp869 869 869 CP-GR IBM869
|
||||
cp874 874 CP874
|
||||
cp875 875
|
||||
cp932 932 CP932 932 ms932 mskanji ms-kanji windows-31j
|
||||
cp949 949 CP949 949 ms949 uhc
|
||||
cp950 950 CP950 950 ms950
|
||||
cp1006
|
||||
cp1026 1026 ibm1026
|
||||
cp1125 1125 ibm1125 cp866u ruscii
|
||||
cp1140 1140 ibm1140
|
||||
cp1250 1250 CP1250 windows-1250
|
||||
cp1251 1251 CP1251 windows-1251
|
||||
cp1252 1252 CP1252 windows-1252
|
||||
cp1253 1253 CP1253 windows-1253
|
||||
cp1254 1254 CP1254 windows-1254
|
||||
cp1255 1255 CP1255 windows-1255
|
||||
cp1256 1256 CP1256 windows-1256
|
||||
cp1257 1257 CP1257 windows-1257
|
||||
cp1258 1258 CP1258 windows-1258
|
||||
euc_jp 20932 EUC-JP eucjp ujis u-jis
|
||||
euc_jis_2004 jisx0213 eucjis2004
|
||||
euc_jisx0213 eucjisx0213
|
||||
euc_kr 51949 EUC-KR euckr korean ksc5601 ks_c-5601 ks_c-5601-1987 ksx1001 ks_x-1001
|
||||
gb2312 936 CP936 chinese csiso58gb231280 euc-cn euccn eucgb2312-cn gb2312-1980 gb2312-80 iso-ir-58
|
||||
gbk 936 GBK 936 cp936 ms936
|
||||
gb18030 54936 GB18030 gb18030-2000
|
||||
hz 52936 HZ hzgb hz-gb hz-gb-2312
|
||||
iso2022_jp 50220 ISO-2022-JP csiso2022jp iso2022jp iso-2022-jp
|
||||
iso2022_jp_1 ISO-2022-JP-1 iso2022jp-1 iso-2022-jp-1
|
||||
iso2022_jp_2 ISO-2022-JP-2 iso2022jp-2 iso-2022-jp-2
|
||||
iso2022_jp_2004 iso2022jp-2004 iso-2022-jp-2004
|
||||
iso2022_jp_3 iso2022jp-3 iso-2022-jp-3
|
||||
iso2022_jp_ext iso2022jp-ext iso-2022-jp-ext
|
||||
iso2022_kr 50225 ISO-2022-KR csiso2022kr iso2022kr iso-2022-kr
|
||||
latin_1 28591 ISO-8859-1 iso-8859-1 iso8859-1 8859 cp819 latin latin1 L1
|
||||
iso8859_2 28592 ISO-8859-2 iso-8859-2 latin2 L2
|
||||
iso8859_3 28593 ISO-8859-3 iso-8859-3 latin3 L3
|
||||
iso8859_4 28594 ISO-8859-4 iso-8859-4 latin4 L4
|
||||
iso8859_5 28595 ISO-8859-5 iso-8859-5 cyrillic
|
||||
iso8859_6 28596 ISO-8859-6 iso-8859-6 arabic
|
||||
iso8859_7 28597 ISO-8859-7 iso-8859-7 greek greek8
|
||||
iso8859_8 28598 ISO-8859-8 iso-8859-8 hebrew
|
||||
iso8859_9 28599 ISO-8859-9 iso-8859-9 latin5 L5
|
||||
iso8859_10 ISO-8859-10 iso-8859-10 latin6 L6
|
||||
iso8859_11 ISO-8859-11 iso-8859-11 thai
|
||||
iso8859_13 28603 ISO-8859-13 iso-8859-13 latin7 L7
|
||||
iso8859_14 ISO-8859-14 iso-8859-14 latin8 L8
|
||||
iso8859_15 28605 ISO-8859-15 iso-8859-15 latin9 L9
|
||||
iso8859_16 ISO-8859-16 iso-8859-16 latin10 L10
|
||||
johab 1361 JOHAB cp1361 ms1361
|
||||
koi8_r
|
||||
koi8_t KOI8-T
|
||||
koi8_u
|
||||
kz1048 kz_1048 strk1048_2002 rk1048
|
||||
mac_cyrillic 10007 MacCyrillic maccyrillic
|
||||
mac_greek 10006 MacGreek macgreek
|
||||
mac_iceland 10079 MacIceland maciceland
|
||||
mac_latin2 maclatin2 maccentraleurope mac_centeuro
|
||||
mac_roman MacRoman macroman macintosh
|
||||
mac_turkish 10081 MacTurkish macturkish
|
||||
ptcp154 PT154 csptcp154 pt154 cp154 cyrillic-asian
|
||||
shift_jis 932 SHIFT_JIS csshiftjis shiftjis sjis s_jis
|
||||
shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
|
||||
shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213
|
||||
utf_32 UTF-32 U32 utf32
|
||||
utf_32_be UTF-32BE UTF-32BE
|
||||
utf_32_le UTF-32LE UTF-32LE
|
||||
utf_16 UTF16 U16 utf16
|
||||
utf_16_be UTF-16BE UTF-16BE
|
||||
utf_16_le UTF-16LE UTF-16LE
|
||||
utf_7 65000 UTF-7 U7 unicode-1-1-utf-7
|
||||
utf_8 65001 UTF-8 U8 UTF utf8 utf-8 cp65001
|
||||
utf_8_sig
|
|
7
script/pyproject.toml
Normal file
7
script/pyproject.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[project]
|
||||
name = "script"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"jinja2==3.1.2",
|
||||
]
|
74
script/uv.lock
generated
Normal file
74
script/uv.lock
generated
Normal file
@ -0,0 +1,74 @@
|
||||
version = 1
|
||||
revision = 2
|
||||
requires-python = ">=3.11"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/ff/75c28576a1d900e87eb6335b063fab47a8ef3c8b4d88524c4bf78f670cce/Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", size = 268239, upload-time = "2022-04-28T17:21:27.579Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61", size = 133101, upload-time = "2022-04-28T17:21:25.336Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "script"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "jinja2" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "jinja2", specifier = "==3.1.2" }]
|
85
script/win_build.bat.jinja
Normal file
85
script/win_build.bat.jinja
Normal file
@ -0,0 +1,85 @@
|
||||
@ECHO OFF
|
||||
:: Navigate to project root directory
|
||||
CD /d {{ repo_root_dir }}
|
||||
:: Create build directory and enter it
|
||||
MKDIR bin
|
||||
CD bin
|
||||
MKDIR cpp{{ cpp_version }}
|
||||
CD cpp{{ cpp_version }}
|
||||
|
||||
:: Create internal build directory
|
||||
MKDIR Win32
|
||||
MKDIR x64
|
||||
MKDIR documentation
|
||||
:: Create internal install directory
|
||||
MKDIR install
|
||||
CD install
|
||||
MKDIR Win32_Debug
|
||||
MKDIR Win32_Release
|
||||
MKDIR x64_Debug
|
||||
MKDIR x64_Release
|
||||
CD ..
|
||||
:: Create internal MSVC specific install directory
|
||||
MKDIR msvc_install
|
||||
CD msvc_install
|
||||
MKDIR bin
|
||||
MKDIR include
|
||||
MKDIR lib
|
||||
MKDIR share
|
||||
CD bin
|
||||
MKDIR Win32
|
||||
MKDIR x64
|
||||
CD ..
|
||||
CD lib
|
||||
MKDIR Win32\Debug
|
||||
MKDIR Win32\Release
|
||||
MKDIR x64\Debug
|
||||
MKDIR x64\Release
|
||||
CD ..
|
||||
CD ..
|
||||
|
||||
:: Build for Win32
|
||||
CD Win32
|
||||
cmake -A Win32 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_TESTBENCH=ON ../../..
|
||||
cmake --build . --config Debug
|
||||
cmake --install . --prefix=../install/Win32_Debug --config Debug
|
||||
cmake --build . --config RelWithDebInfo
|
||||
cmake --install . --prefix=../install/Win32_Release --config RelWithDebInfo
|
||||
CD ..
|
||||
:: Build for x64
|
||||
CD x64
|
||||
cmake -A x64 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_TESTBENCH=ON ../../..
|
||||
cmake --build . --config Debug
|
||||
cmake --install . --prefix=../install/x64_Debug --config Debug
|
||||
cmake --build . --config RelWithDebInfo
|
||||
cmake --install . --prefix=../install/x64_Release --config RelWithDebInfo
|
||||
CD ..
|
||||
|
||||
{% if build_doc %}
|
||||
:: Build for documentation
|
||||
CD documentation
|
||||
cmake -A x64 -DCMAKE_CXX_STANDARD={{ cpp_version }} -DYYCC_BUILD_DOC=ON ../../..
|
||||
cmake --build . --config RelWithDebInfo
|
||||
cmake --build . --target YYCCDocumentation
|
||||
cmake --install . --prefix=../install/x64_Release --config RelWithDebInfo
|
||||
CD ..
|
||||
{% endif %}
|
||||
|
||||
:: Copy header files
|
||||
XCOPY install\x64_Release\include msvc_install\include\ /E /Y
|
||||
:: Copy binary files
|
||||
COPY install\Win32_Release\bin\YYCCTestbench.exe msvc_install\bin\Win32\YYCCTestbench.exe /Y
|
||||
COPY install\x64_Release\bin\YYCCTestbench.exe msvc_install\bin\x64\YYCCTestbench.exe /Y
|
||||
:: Copy library files
|
||||
COPY install\Win32_Debug\lib\YYCCommonplace.lib msvc_install\lib\Win32\Debug\YYCCommonplace.lib /Y
|
||||
COPY install\Win32_Release\lib\YYCCommonplace.lib msvc_install\lib\Win32\Release\YYCCommonplace.lib /Y
|
||||
COPY install\x64_Debug\lib\YYCCommonplace.lib msvc_install\lib\x64\Debug\YYCCommonplace.lib /Y
|
||||
COPY install\x64_Release\lib\YYCCommonplace.lib msvc_install\lib\x64\Release\YYCCommonplace.lib /Y
|
||||
{% if build_doc %}
|
||||
:: Copy documentation files
|
||||
XCOPY install\x64_Release\share msvc_install\share\ /E /Y
|
||||
{% endif %}
|
||||
|
||||
:: Leave build directory and report
|
||||
CD ..\..
|
||||
ECHO Windows CMake Build Done
|
@ -1,41 +1,78 @@
|
||||
# Configure version file
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/yycc/version.hpp.in
|
||||
${CMAKE_CURRENT_LIST_DIR}/yycc/version.hpp
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Create static library
|
||||
add_library(YYCCommonplace STATIC "")
|
||||
# Setup static library sources
|
||||
target_sources(YYCCommonplace
|
||||
PRIVATE
|
||||
# Sources
|
||||
COMHelper.cpp
|
||||
ConsoleHelper.cpp
|
||||
DialogHelper.cpp
|
||||
EncodingHelper.cpp
|
||||
ExceptionHelper.cpp
|
||||
FsPathPatch.cpp
|
||||
IOHelper.cpp
|
||||
StringHelper.cpp
|
||||
WinFctHelper.cpp
|
||||
yycc/string/reinterpret.cpp
|
||||
yycc/string/op.cpp
|
||||
yycc/patch/fopen.cpp
|
||||
yycc/rust/panic.cpp
|
||||
yycc/rust/env.cpp
|
||||
yycc/windows/com.cpp
|
||||
yycc/windows/dialog.cpp
|
||||
yycc/windows/winfct.cpp
|
||||
yycc/encoding/stl.cpp
|
||||
yycc/encoding/windows.cpp
|
||||
yycc/encoding/iconv.cpp
|
||||
|
||||
yycc/carton/pycodec.cpp
|
||||
yycc/carton/termcolor.cpp
|
||||
yycc/carton/wcwidth.cpp
|
||||
yycc/carton/tabulate.cpp
|
||||
)
|
||||
target_sources(YYCCommonplace
|
||||
PUBLIC
|
||||
FILE_SET HEADERS
|
||||
FILES
|
||||
# Headers
|
||||
# Common headers
|
||||
COMHelper.hpp
|
||||
ConsoleHelper.hpp
|
||||
DialogHelper.hpp
|
||||
EncodingHelper.hpp
|
||||
ExceptionHelper.hpp
|
||||
FsPathPatch.hpp
|
||||
IOHelper.hpp
|
||||
ParserHelper.hpp
|
||||
StringHelper.hpp
|
||||
WinFctHelper.hpp
|
||||
# Windows including guard pair
|
||||
WinImportPrefix.hpp
|
||||
WinImportSuffix.hpp
|
||||
# Misc
|
||||
YYCCInternal.hpp
|
||||
YYCCommonplace.hpp
|
||||
yycc.hpp
|
||||
yycc/version.hpp
|
||||
yycc/macro/version_cmp.hpp
|
||||
yycc/macro/os_detector.hpp
|
||||
yycc/macro/stl_detector.hpp
|
||||
yycc/macro/endian_detector.hpp
|
||||
yycc/macro/compiler_detector.hpp
|
||||
yycc/macro/ptr_size_detector.hpp
|
||||
yycc/macro/class_copy_move.hpp
|
||||
yycc/flag_enum.hpp
|
||||
yycc/string/reinterpret.hpp
|
||||
yycc/string/op.hpp
|
||||
yycc/patch/ptr_pad.hpp
|
||||
yycc/patch/fopen.hpp
|
||||
yycc/num/parse.hpp
|
||||
yycc/num/stringify.hpp
|
||||
yycc/num/safe_cast.hpp
|
||||
yycc/num/safe_op.hpp
|
||||
yycc/num/op.hpp
|
||||
yycc/rust/prelude.hpp
|
||||
yycc/rust/primitive.hpp
|
||||
yycc/rust/panic.hpp
|
||||
yycc/rust/option.hpp
|
||||
yycc/rust/result.hpp
|
||||
yycc/rust/env.hpp
|
||||
yycc/windows/import_guard_head.hpp
|
||||
yycc/windows/import_guard_tail.hpp
|
||||
yycc/windows/com.hpp
|
||||
yycc/windows/dialog.hpp
|
||||
yycc/windows/winfct.hpp
|
||||
yycc/constraint.hpp
|
||||
yycc/constraint/builder.hpp
|
||||
yycc/encoding/stl.hpp
|
||||
yycc/encoding/windows.hpp
|
||||
yycc/encoding/iconv.hpp
|
||||
|
||||
yycc/carton/pycodec.hpp
|
||||
yycc/carton/termcolor.hpp
|
||||
yycc/carton/wcwidth.hpp
|
||||
yycc/carton/tabulate.hpp
|
||||
)
|
||||
# Setup header infomations
|
||||
target_include_directories(YYCCommonplace
|
||||
@ -43,35 +80,66 @@ PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
|
||||
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
# Link Iconv if we have import it
|
||||
if (Iconv_FOUND)
|
||||
target_link_libraries(YYCCommonplace
|
||||
PRIVATE
|
||||
Iconv::Iconv
|
||||
)
|
||||
endif ()
|
||||
# Link with DbgHelp.lib on Windows
|
||||
target_link_libraries(YYCCommonplace
|
||||
PRIVATE
|
||||
$<$<BOOL:${WIN32}>:DbgHelp.lib>
|
||||
)
|
||||
# Setup C++ standard
|
||||
set_target_properties(YYCCommonplace
|
||||
PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED 17
|
||||
CXX_EXTENSION OFF
|
||||
)
|
||||
# Order Unicode charset for private using
|
||||
# Setup macros
|
||||
target_compile_definitions(YYCCommonplace
|
||||
PRIVATE
|
||||
PUBLIC
|
||||
# Iconv environment macro
|
||||
$<$<BOOL:${Iconv_FOUND}>:YYCC_FEAT_ICONV>
|
||||
# OS macro
|
||||
$<$<PLATFORM_ID:Windows>:YYCC_OS_WINDOWS>
|
||||
$<$<PLATFORM_ID:Linux>:YYCC_OS_LINUX>
|
||||
$<$<PLATFORM_ID:Android>:YYCC_OS_LINUX> # We brutally think Android as Linux.
|
||||
$<$<PLATFORM_ID:Darwin>:YYCC_OS_MACOS>
|
||||
$<$<PLATFORM_ID:iOS>:YYCC_OS_MACOS> # We brutally think iOS as macOS.
|
||||
# Compiler macro
|
||||
$<$<CXX_COMPILER_ID:GNU>:YYCC_CC_GCC>
|
||||
$<$<CXX_COMPILER_ID:Clang>:YYCC_CC_CLANG>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:YYCC_CC_MSVC>
|
||||
# Endian macro
|
||||
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},LITTLE_ENDIAN>:YYCC_ENDIAN_LITTLE>
|
||||
$<$<STREQUAL:${CMAKE_CXX_BYTE_ORDER},BIG_ENDIAN>:YYCC_ENDIAN_BIG>
|
||||
# Pointer size macro
|
||||
$<$<EQUAL:${CMAKE_SIZEOF_VOID_P},4>:YYCC_PTRSIZE_32>
|
||||
$<$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>:YYCC_PTRSIZE_64>
|
||||
# Use Unicode charset on MSVC
|
||||
$<$<CXX_COMPILER_ID:MSVC>:UNICODE>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_UNICODE>
|
||||
# Fix MSVC shit
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_DEPRECATE>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_CRT_NONSTDC_NO_WARNINGS>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:_CRT_NONSTDC_NO_DEPRECATE>
|
||||
# Fix Windows header file shit
|
||||
$<$<BOOL:${WIN32}>:WIN32_LEAN_AND_MEAN>
|
||||
$<$<BOOL:${WIN32}>:NOMINMAX>
|
||||
)
|
||||
# Order build as UTF-8 in MSVC
|
||||
target_compile_options(YYCCommonplace
|
||||
PRIVATE
|
||||
PUBLIC
|
||||
# Order build as UTF-8 in MSVC
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/utf-8>
|
||||
# Order preprocessor conformance mode (fix __VA_OPT__ error in MSVC)
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/Zc:preprocessor>
|
||||
# Resolve MSVC __cplusplus macro value error.
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>
|
||||
)
|
||||
|
||||
# Install binary and headers
|
||||
install(TARGETS YYCCommonplace
|
||||
EXPORT YYCCommonplaceTargets
|
||||
LIBRARY DESTINATION ${YYCC_INSTALL_PATH_LIB}
|
||||
ARCHIVE DESTINATION ${YYCC_INSTALL_PATH_LIB}
|
||||
INCLUDES DESTINATION include
|
||||
FILE_SET HEADERS DESTINATION include
|
||||
LIBRARY DESTINATION ${YYCC_INSTALL_LIB_PATH}
|
||||
ARCHIVE DESTINATION ${YYCC_INSTALL_LIB_PATH}
|
||||
INCLUDES DESTINATION ${YYCC_INSTALL_INCLUDE_PATH}
|
||||
FILE_SET HEADERS DESTINATION ${YYCC_INSTALL_INCLUDE_PATH}
|
||||
)
|
||||
|
@ -1,48 +0,0 @@
|
||||
#include "COMHelper.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
namespace YYCC::COMHelper {
|
||||
|
||||
/**
|
||||
* @brief The guard for initialize COM environment.
|
||||
* @details This class will try initializing COM environment by calling CoInitialize when constructing,
|
||||
* and it also will try uninitializing COM environment when destructing.
|
||||
* If initialization failed, uninitialization will not be executed.
|
||||
*/
|
||||
class ComGuard {
|
||||
public:
|
||||
ComGuard() : m_HasInit(false) {
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (SUCCEEDED(hr)) m_HasInit = true;
|
||||
}
|
||||
~ComGuard() {
|
||||
if (m_HasInit) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsInitialized() const {
|
||||
return m_HasInit;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_HasInit;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The instance of COM environment guard.
|
||||
* @details Dialog related function need COM environment,
|
||||
* so we need initializing COM environment when loading this module,
|
||||
* and uninitializing COM environment when we no longer use this module.
|
||||
* So we use a static instance in here.
|
||||
* And make it be const so no one can change it.
|
||||
*/
|
||||
static const ComGuard c_ComGuard {};
|
||||
|
||||
bool IsInitialized() {
|
||||
return c_ComGuard.IsInitialized();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief COM fucntions related namespace.
|
||||
* @details
|
||||
* This namespace is Windows specific and is unavailable on other platforms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 std-based smart pointer type were created.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
namespace YYCC::COMHelper {
|
||||
|
||||
/**
|
||||
* @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
|
||||
*/
|
||||
class ComPtrDeleter {
|
||||
public:
|
||||
ComPtrDeleter() {}
|
||||
void operator() (IUnknown* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
com_ptr->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
|
||||
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
|
||||
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
|
||||
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
|
||||
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
|
||||
|
||||
/**
|
||||
* @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
|
||||
*/
|
||||
class CoTaskMemDeleter {
|
||||
public:
|
||||
CoTaskMemDeleter() {}
|
||||
void operator() (void* com_ptr) {
|
||||
if (com_ptr != nullptr) {
|
||||
CoTaskMemFree(com_ptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
|
||||
|
||||
/**
|
||||
* @brief Check whether COM environment has been initialized.
|
||||
* @return True if it is, otherwise false.
|
||||
* @remarks
|
||||
* This function will call corresponding function of COM Guard.
|
||||
* Do not remove this function and you must preserve at least one reference to this function in final program.
|
||||
* Some compiler will try to drop COM Guard in final program if no reference to it and it will cause the initialization of COM environment failed.
|
||||
* This is the reason why I order you do the things said above.
|
||||
*/
|
||||
bool IsInitialized();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,175 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief The namespace providing universal Console visiting functions like C-Sharp Console class.
|
||||
* @details
|
||||
* \par Why this Namespace
|
||||
* Windows console doesn't support UTF8 very well.
|
||||
* The standard input output functions can not work properly on Windows with UTF8.
|
||||
* 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.
|
||||
* \par
|
||||
* 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 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.
|
||||
* \par
|
||||
* All IO functions this namespace provided are UTF8-based.
|
||||
* It also means that input output string should always be UTF8 encoded.
|
||||
*
|
||||
* \par 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.
|
||||
* \par
|
||||
* Due to implementation, if you decide to use this function,
|
||||
* you should give up using any other function to read stdin stream,
|
||||
* such as std::gets() and 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.
|
||||
* \par
|
||||
* 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.
|
||||
*
|
||||
* \par 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.
|
||||
* \par
|
||||
* 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.
|
||||
* \par
|
||||
* 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 std::fprintf(), otherwise the functions with embedded "Write" will
|
||||
* only write plain string like 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.
|
||||
*/
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
#define YYCC_COLORHDR_BLACK "\033[30m"
|
||||
#define YYCC_COLORHDR_RED "\033[31m"
|
||||
#define YYCC_COLORHDR_GREEN "\033[32m"
|
||||
#define YYCC_COLORHDR_YELLOW "\033[33m"
|
||||
#define YYCC_COLORHDR_BLUE "\033[34m"
|
||||
#define YYCC_COLORHDR_MAGENTA "\033[35m"
|
||||
#define YYCC_COLORHDR_CYAN "\033[36m"
|
||||
#define YYCC_COLORHDR_WHITE "\033[37m"
|
||||
|
||||
#define YYCC_COLORHDR_LIGHT_BLACK "\033[90m"
|
||||
#define YYCC_COLORHDR_LIGHT_RED "\033[91m"
|
||||
#define YYCC_COLORHDR_LIGHT_GREEN "\033[92m"
|
||||
#define YYCC_COLORHDR_LIGHT_YELLOW "\033[93m"
|
||||
#define YYCC_COLORHDR_LIGHT_BLUE "\033[94m"
|
||||
#define YYCC_COLORHDR_LIGHT_MAGENTA "\033[95m"
|
||||
#define YYCC_COLORHDR_LIGHT_CYAN "\033[96m"
|
||||
#define YYCC_COLORHDR_LIGHT_WHITE "\033[97m"
|
||||
|
||||
#define YYCC_COLORTAIL "\033[0m"
|
||||
|
||||
|
||||
#define YYCC_COLOR_BLACK(T) "\033[30m" T "\033[0m"
|
||||
#define YYCC_COLOR_RED(T) "\033[31m" T "\033[0m"
|
||||
#define YYCC_COLOR_GREEN(T) "\033[32m" T "\033[0m"
|
||||
#define YYCC_COLOR_YELLOW(T) "\033[33m" T "\033[0m"
|
||||
#define YYCC_COLOR_BLUE(T) "\033[34m" T "\033[0m"
|
||||
#define YYCC_COLOR_MAGENTA(T) "\033[35m" T "\033[0m"
|
||||
#define YYCC_COLOR_CYAN(T) "\033[36m" T "\033[0m"
|
||||
#define YYCC_COLOR_WHITE(T) "\033[37m" T "\033[0m"
|
||||
|
||||
#define YYCC_COLOR_LIGHT_BLACK(T) "\033[90m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_RED(T) "\033[91m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_GREEN(T) "\033[92m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_YELLOW(T) "\033[93m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_BLUE(T) "\033[94m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_MAGENTA(T) "\033[95m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_CYAN(T) "\033[96m" T "\033[0m"
|
||||
#define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m"
|
||||
|
||||
/**
|
||||
* @brief Enable Windows console color support.
|
||||
* @details This actually is enable virtual console feature for stdout and stderr.
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks This function only works on Windows and do nothing on other platforms such as Linux,
|
||||
* because we assume all terminals existing on other platform support color feature as default.
|
||||
*/
|
||||
bool EnableColorfulConsole();
|
||||
|
||||
/**
|
||||
* @brief Universal console read function
|
||||
* @return
|
||||
* The UTF8 encoded string this function read. EOL is excluded.
|
||||
* Empty string if user just press Enter key or function failed.
|
||||
* @remarks
|
||||
* This function is more like C# Console.ReadLine().
|
||||
* It read user input with UTF8 encoding until reaching EOL.
|
||||
* \par
|
||||
* This function also can be used as ordering user press Enter key by
|
||||
* simply calling this function and ignoring its return value.
|
||||
*/
|
||||
std::string ReadLine();
|
||||
|
||||
/**
|
||||
* @brief Universal console write function with format feature.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments to be formatted.
|
||||
*/
|
||||
void Format(const char* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Universal console write function with format and auto EOL feature.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments to be formatted.
|
||||
*/
|
||||
void FormatLine(const char* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Universal console write function.
|
||||
* @param[in] u8_strl The string to be written.
|
||||
*/
|
||||
void Write(const char* u8_strl);
|
||||
/**
|
||||
* @brief Universal console write function with auto EOL feature.
|
||||
* @param[in] u8_strl The string to be written.
|
||||
*/
|
||||
void WriteLine(const char* u8_strl);
|
||||
|
||||
/**
|
||||
* @brief Universal console error write function with format and feature.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments to be formatted.
|
||||
*/
|
||||
void ErrFormat(const char* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Universal console error write function with format and auto EOL feature.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments to be formatted.
|
||||
*/
|
||||
void ErrFormatLine(const char* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Universal console error write function.
|
||||
* @param[in] u8_strl The string to be written.
|
||||
*/
|
||||
void ErrWrite(const char* u8_strl);
|
||||
/**
|
||||
* @brief Universal console error write function with auto EOL feature.
|
||||
* @param[in] u8_strl The string to be written.
|
||||
*/
|
||||
void ErrWriteLine(const char* u8_strl);
|
||||
|
||||
}
|
@ -1,221 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include "COMHelper.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
namespace YYCC::DialogHelper {
|
||||
|
||||
/**
|
||||
* @brief The class represent the file types region in file dialog
|
||||
* @details THis class is specific for Windows use, not user oriented.
|
||||
*/
|
||||
class WinFileFilters {
|
||||
friend class FileFilters;
|
||||
friend class WinFileDialog;
|
||||
public:
|
||||
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
|
||||
|
||||
UINT GetFilterCount() const {
|
||||
return static_cast<UINT>(m_WinFilters.size());
|
||||
}
|
||||
const COMDLG_FILTERSPEC* GetFilterSpecs() const {
|
||||
return m_WinDataStruct.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
using WinFilterModes = std::wstring;
|
||||
using WinFilterName = std::wstring;
|
||||
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
|
||||
|
||||
std::vector<WinFilterPair> m_WinFilters;
|
||||
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
|
||||
|
||||
void Clear() {
|
||||
m_WinDataStruct.reset();
|
||||
m_WinFilters.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class represent the file types region in file dialog.
|
||||
* @details This class is user oriented. User can use function manipulate file types
|
||||
* and final generation function will produce Windows-understood data struct from this.
|
||||
*/
|
||||
class FileFilters {
|
||||
public:
|
||||
FileFilters() : m_Filters() {}
|
||||
|
||||
/**
|
||||
* @brief Add a filter pair in file types list.
|
||||
* @param filter_name[in] The friendly name of the filter.
|
||||
* @param il[in] A C++ initialize list.
|
||||
* Every entries must be `const char*` represent a single filter pattern.
|
||||
* The list at least should have one valid pattern.
|
||||
* This function will not validate these filter patterns, so please write them carefully.
|
||||
* @return True if added success, otherwise false.
|
||||
* @remarks This function allow you register multiple filter patterns for single friendly name.
|
||||
* For example: `Add("Microsoft Word (*.doc; *.docx)", {"*.doc", "*.docx"})`
|
||||
*/
|
||||
bool Add(const char* filter_name, std::initializer_list<const char*> il);
|
||||
/**
|
||||
* @brief Clear filter pairs for following re-use.
|
||||
*/
|
||||
void Clear() { m_Filters.clear(); }
|
||||
/**
|
||||
* @brief Get the count of added filter pairs.
|
||||
* @return The count of already added filter pairs.
|
||||
*/
|
||||
size_t Count() const { return m_Filters.size(); }
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param win_result[out] The class holding the generated filter data struct.
|
||||
* @return True if generation is success, otherwise false.
|
||||
* @remarks User should not call this function, this function is used in internal code.
|
||||
*/
|
||||
bool Generate(WinFileFilters& win_result) const;
|
||||
|
||||
protected:
|
||||
using FilterModes = std::vector<std::string>;
|
||||
using FilterName = std::string;
|
||||
using FilterPair = std::pair<FilterName, FilterModes>;
|
||||
|
||||
std::vector<FilterPair> m_Filters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class represent the file dialog
|
||||
* @details THis class is specific for Windows use, not user oriented.
|
||||
*/
|
||||
class WinFileDialog {
|
||||
friend class FileDialog;
|
||||
public:
|
||||
WinFileDialog() :
|
||||
m_WinOwner(NULL),
|
||||
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
|
||||
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
|
||||
m_WinInitDirectory(nullptr) {}
|
||||
|
||||
bool HasOwner() const { return m_WinOwner != NULL; }
|
||||
HWND GetOwner() const { return m_WinOwner; }
|
||||
|
||||
const WinFileFilters& GetFileTypes() const { return m_WinFileTypes; }
|
||||
UINT GetDefaultFileTypeIndex() const { return m_WinDefaultFileTypeIndex; }
|
||||
|
||||
bool HasTitle() const { return m_HasTitle; }
|
||||
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
|
||||
bool HasInitFileName() const { return m_HasInitFileName; }
|
||||
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); }
|
||||
|
||||
bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
|
||||
IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); }
|
||||
|
||||
protected:
|
||||
HWND m_WinOwner;
|
||||
WinFileFilters m_WinFileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks This is 1-based index according to Windows specification.
|
||||
* In other words, you should plus 1 for this index when generating this struct from
|
||||
* user oriented file dialog parameters.
|
||||
*/
|
||||
UINT m_WinDefaultFileTypeIndex;
|
||||
bool m_HasTitle, m_HasInitFileName;
|
||||
std::wstring m_WinTitle, m_WinInitFileName;
|
||||
COMHelper::SmartIShellItem m_WinInitDirectory;
|
||||
|
||||
void Clear() {
|
||||
m_WinOwner = nullptr;
|
||||
m_WinFileTypes.Clear();
|
||||
m_WinDefaultFileTypeIndex = 0u;
|
||||
m_HasTitle = m_HasInitFileName = false;
|
||||
m_WinTitle.clear();
|
||||
m_WinInitFileName.clear();
|
||||
m_WinInitDirectory.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class represent the file dialog.
|
||||
* @details This class is user oriented. User can use function manipulate file dialog properties
|
||||
* and final generation function will produce Windows-understood data struct from this.
|
||||
*/
|
||||
class FileDialog {
|
||||
public:
|
||||
FileDialog() :
|
||||
m_Owner(NULL),
|
||||
m_FileTypes(),
|
||||
m_DefaultFileTypeIndex(0u),
|
||||
m_Title(), m_InitFileName(), m_InitDirectory(),
|
||||
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
|
||||
|
||||
void SetOwner(HWND owner) { m_Owner = owner; }
|
||||
void SetTitle(const char* title) {
|
||||
if (m_HasTitle = title != nullptr)
|
||||
m_Title = title;
|
||||
}
|
||||
FileFilters& ConfigreFileTypes() {
|
||||
return m_FileTypes;
|
||||
}
|
||||
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
|
||||
void SetInitFileName(const char* init_filename) {
|
||||
if (m_HasInitFileName = init_filename != nullptr)
|
||||
m_InitFileName = init_filename;
|
||||
}
|
||||
void SetInitDirectory(const char* init_dir) {
|
||||
if (m_HasInitDirectory = init_dir != nullptr)
|
||||
m_InitDirectory = init_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear file dialog parameters for following re-use.
|
||||
*/
|
||||
void Clear() {
|
||||
m_Owner = nullptr;
|
||||
m_HasTitle = m_HasInitFileName = m_HasInitDirectory = false;
|
||||
m_Title.clear();
|
||||
m_InitFileName.clear();
|
||||
m_InitDirectory.clear();
|
||||
m_FileTypes.Clear();
|
||||
m_DefaultFileTypeIndex = 0u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param win_result[out] The class holding the generated filter data struct.
|
||||
* @return True if generation is success, otherwise false.
|
||||
* @remarks User should not call this function, this function is used in internal code.
|
||||
*/
|
||||
bool Generate(WinFileDialog& win_result) const;
|
||||
|
||||
protected:
|
||||
HWND m_Owner;
|
||||
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
|
||||
std::string m_Title, m_InitFileName, m_InitDirectory;
|
||||
FileFilters m_FileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks Although Windows notice that this is a 1-based index,
|
||||
* but for universal experience, we order this is 0-based index.
|
||||
*/
|
||||
size_t m_DefaultFileTypeIndex;
|
||||
};
|
||||
|
||||
bool OpenFileDialog(const FileDialog& params, std::string& ret);
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<std::string>& ret);
|
||||
bool SaveFileDialog(const FileDialog& params, std::string& ret);
|
||||
|
||||
bool OpenFolderDialog(const FileDialog& params, std::string& ret);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,193 +0,0 @@
|
||||
#include "EncodingHelper.hpp"
|
||||
|
||||
#include <locale>
|
||||
|
||||
namespace YYCC::EncodingHelper {
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
bool WcharToChar(const wchar_t* src, std::string& dest, UINT codepage) {
|
||||
int count, write_result;
|
||||
|
||||
//converter to CHAR
|
||||
count = WideCharToMultiByte(codepage, 0, reinterpret_cast<LPCWCH>(src), -1, NULL, 0, NULL, NULL);
|
||||
if (count <= 0) return false;
|
||||
|
||||
dest.resize(count - 1);
|
||||
write_result = WideCharToMultiByte(codepage, 0, reinterpret_cast<LPCWCH>(src), -1, reinterpret_cast<LPSTR>(dest.data()), count, NULL, NULL);
|
||||
if (write_result <= 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
bool WcharToUTF8(const wchar_t* src, std::string& dest) {
|
||||
return WcharToChar(src, dest, CP_UTF8);
|
||||
}
|
||||
std::string WcharToChar(const wchar_t* src, UINT codepage) {
|
||||
std::string ret;
|
||||
if (!WcharToChar(src, ret, codepage)) ret.clear();
|
||||
return ret;
|
||||
}
|
||||
std::string WcharToUTF8(const wchar_t* src) {
|
||||
return WcharToChar(src, CP_UTF8);
|
||||
}
|
||||
|
||||
bool CharToWchar(const char* src, std::wstring& dest, UINT codepage) {
|
||||
int wcount, write_result;
|
||||
|
||||
// convert to WCHAR
|
||||
wcount = MultiByteToWideChar(codepage, 0, reinterpret_cast<LPCCH>(src), -1, NULL, 0);
|
||||
if (wcount <= 0) return false;
|
||||
|
||||
dest.resize(wcount - 1);
|
||||
write_result = MultiByteToWideChar(codepage, 0, reinterpret_cast<LPCCH>(src), -1, reinterpret_cast<LPWSTR>(dest.data()), wcount);
|
||||
if (write_result <= 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
bool UTF8ToWchar(const char* src, std::wstring& dest) {
|
||||
return CharToWchar(src, dest, CP_UTF8);
|
||||
}
|
||||
std::wstring CharToWchar(const char* src, UINT codepage) {
|
||||
std::wstring ret;
|
||||
if (!CharToWchar(src, ret, codepage)) ret.clear();
|
||||
return ret;
|
||||
}
|
||||
std::wstring UTF8ToWchar(const char* src) {
|
||||
return CharToWchar(src, CP_UTF8);
|
||||
}
|
||||
|
||||
bool CharToChar(const char* src, std::string& dest, UINT src_codepage, UINT dest_codepage) {
|
||||
std::wstring intermediary;
|
||||
if (!CharToWchar(src, intermediary, src_codepage)) return false;
|
||||
if (!WcharToChar(intermediary.c_str(), dest, dest_codepage)) return false;
|
||||
return true;
|
||||
}
|
||||
std::string CharToChar(const char* src, UINT src_codepage, UINT dest_codepage) {
|
||||
std::string ret;
|
||||
if (!CharToChar(src, ret, src_codepage, dest_codepage)) ret.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_char8_t)
|
||||
using CodecvtUTF8Char_t = char8_t;
|
||||
#else
|
||||
using CodecvtUTF8Char_t = char;
|
||||
#endif
|
||||
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
|
||||
using CodecvtFacet_t = std::codecvt<_TChar, CodecvtUTF8Char_t, std::mbstate_t>;
|
||||
|
||||
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
|
||||
static bool UTF8ToUTFOther(const char* _src, std::basic_string<_TChar>& dest) {
|
||||
// Reference:
|
||||
// https://zh.cppreference.com/w/cpp/locale/codecvt/in
|
||||
|
||||
// init src string
|
||||
if (_src == nullptr) return false;
|
||||
std::string src(_src);
|
||||
|
||||
// init locale and get codecvt facet
|
||||
// same reason in UTFOtherToUTF8 to keeping reference to locale
|
||||
const auto& this_locale = std::locale::classic();
|
||||
const auto& this_codecvt = std::use_facet<CodecvtFacet_t<_TChar>>(this_locale);
|
||||
|
||||
// convertion preparation
|
||||
std::mbstate_t mb{};
|
||||
dest.resize(src.size());
|
||||
const CodecvtUTF8Char_t* intern_from = reinterpret_cast<const CodecvtUTF8Char_t*>(src.c_str()),
|
||||
*intern_from_end = reinterpret_cast<const CodecvtUTF8Char_t*>(src.c_str() + src.size()),
|
||||
*intern_from_next = nullptr;
|
||||
_TChar* extern_to = dest.data(),
|
||||
*extern_to_end = dest.data() + dest.size(),
|
||||
*extern_to_next = nullptr;
|
||||
// do convertion
|
||||
auto result = this_codecvt.in(
|
||||
mb,
|
||||
intern_from, intern_from_end, intern_from_next,
|
||||
extern_to, extern_to_end, extern_to_next
|
||||
);
|
||||
|
||||
// check result
|
||||
if (result != CodecvtFacet_t<_TChar>::ok)
|
||||
return false;
|
||||
// resize result and return
|
||||
dest.resize(extern_to_next - dest.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UTF8ToUTF16(const char* src, std::u16string& dest) {
|
||||
return UTF8ToUTFOther<char16_t>(src, dest);
|
||||
}
|
||||
std::u16string UTF8ToUTF16(const char* src) {
|
||||
std::u16string ret;
|
||||
if (!UTF8ToUTF16(src, ret)) ret.clear();
|
||||
return ret;
|
||||
}
|
||||
bool UTF8ToUTF32(const char* src, std::u32string& dest) {
|
||||
return UTF8ToUTFOther<char32_t>(src, dest);
|
||||
}
|
||||
std::u32string UTF8ToUTF32(const char* src) {
|
||||
std::u32string ret;
|
||||
if (!UTF8ToUTF32(src, ret)) ret.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename _TChar, std::enable_if_t<std::is_same_v<_TChar, char16_t> || std::is_same_v<_TChar, char32_t>, int> = 0>
|
||||
static bool UTFOtherToUTF8(const _TChar* _src, std::string& dest) {
|
||||
// Reference:
|
||||
// https://zh.cppreference.com/w/cpp/locale/codecvt/out
|
||||
|
||||
// initialize src string
|
||||
if (_src == nullptr) return false;
|
||||
std::basic_string<_TChar> src(_src);
|
||||
|
||||
// init locale and get codecvt facet
|
||||
// the reference to locale must be preserved until convertion done.
|
||||
// because the life time of codecvt facet is equal to the reference to locale.
|
||||
const auto& this_locale = std::locale::classic();
|
||||
const auto& this_codecvt = std::use_facet<CodecvtFacet_t<_TChar>>(this_locale);
|
||||
|
||||
// do convertion preparation
|
||||
std::mbstate_t mb{};
|
||||
dest.resize(src.size() * this_codecvt.max_length());
|
||||
const _TChar* intern_from = src.c_str(),
|
||||
*intern_from_end = src.c_str() + src.size(),
|
||||
*intern_from_next = nullptr;
|
||||
CodecvtUTF8Char_t* extern_to = reinterpret_cast<CodecvtUTF8Char_t*>(dest.data()),
|
||||
*extern_to_end = reinterpret_cast<CodecvtUTF8Char_t*>(dest.data() + dest.size()),
|
||||
*extern_to_next = nullptr;
|
||||
// do convertion
|
||||
auto result = this_codecvt.out(
|
||||
mb,
|
||||
intern_from, intern_from_end, intern_from_next,
|
||||
extern_to, extern_to_end, extern_to_next
|
||||
);
|
||||
|
||||
// check result
|
||||
if (result != CodecvtFacet_t<_TChar>::ok)
|
||||
return false;
|
||||
// resize result and retuen
|
||||
dest.resize(extern_to_next - reinterpret_cast<CodecvtUTF8Char_t*>(dest.data()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UTF16ToUTF8(const char16_t* src, std::string& dest) {
|
||||
return UTFOtherToUTF8<char16_t>(src, dest);
|
||||
}
|
||||
std::string UTF16ToUTF8(const char16_t* src) {
|
||||
std::string ret;
|
||||
if (!UTF16ToUTF8(src, ret)) ret.clear();
|
||||
return ret;
|
||||
}
|
||||
bool UTF32ToUTF8(const char32_t* src, std::string& dest) {
|
||||
return UTFOtherToUTF8<char32_t>(src, dest);
|
||||
}
|
||||
std::string UTF32ToUTF8(const char32_t* src) {
|
||||
std::string ret;
|
||||
if (!UTF32ToUTF8(src, ret)) ret.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The namespace handling encoding issues.
|
||||
* @details
|
||||
* \par Windows Encoding Convertion
|
||||
* This namespace provides the convertion between wchar_t, UTF8 and code-page-based string:
|
||||
* The function name has following format: \c AAAToBBB.
|
||||
* AAA is the source string and BBB is target string.
|
||||
* AAA and BBB has following possible value:
|
||||
* \li \c Char: Code-page-based string. Usually it will add a code page parameter for function to get the code page of this string. For code page, please see Microsoft document.
|
||||
* \li \c UTF8: UTF8 string.
|
||||
* \li \c Wchar: wchar_t string.
|
||||
* \par
|
||||
* For example: \c WcharToUTF8 will perform the convertion from wchar_t to UTF8,
|
||||
* and \c CharToChar will perform the convertion between 2 code-page-based string and caller can specify individual code page for these 2 string.
|
||||
* \par
|
||||
* These functions are Windows specific and are unavailable on other platforms.
|
||||
* Becasue Windows use wchar_t string as its function arguments for globalization, and this library use UTF8 everywhere.
|
||||
* So it should have a bidirectional way to do convertion between wchar_t string and UTF8 string.
|
||||
*
|
||||
* \par UTF32, UTF16 and UTF8 Convertion
|
||||
* This namespace also provide the convertion among UTF32, UTF16 and UTF8.
|
||||
* These convertion functions are suit for all platforms, not Windows oriented.
|
||||
* \par
|
||||
* Due to implementation, this library assume all non-Windows system use UTF8 as their C locale.
|
||||
* Otherwise these functions will produce wrong result.
|
||||
*
|
||||
* \par Function Parameters
|
||||
* We provide these encoding convertion functions with following 2 types:
|
||||
* \li Function returns \c bool and its parameter order source string pointer and a corresponding \c std::basic_string container for receiving result.
|
||||
* \li Function returns corresponding \c std::basic_string result, and its parameter only order source string pointer.
|
||||
* \par
|
||||
* For these 2 declarations, both of them will not throw any exception and do not accept nullptr as source string.
|
||||
* The only difference is that the way to indicate convertion error.
|
||||
* \par
|
||||
* First declaration will return false to indicate there is an error when doing convertion. Please note that the content of string container passing in may still be changed!
|
||||
* Last declaration will return empty string to indicate error. Please note if you pass empty string in, they still will output empty string but it doesn't mean an error.
|
||||
* So last declaration is used in the scenario that we don't care whether the convertion success did. For example, output something to console.
|
||||
*
|
||||
*/
|
||||
namespace YYCC::EncodingHelper {
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
bool WcharToChar(const wchar_t* src, std::string& dest, UINT codepage);
|
||||
bool WcharToUTF8(const wchar_t* src, std::string& dest);
|
||||
std::string WcharToChar(const wchar_t* src, UINT codepage);
|
||||
std::string WcharToUTF8(const wchar_t* src);
|
||||
|
||||
bool CharToWchar(const char* src, std::wstring& dest, UINT codepage);
|
||||
bool UTF8ToWchar(const char* src, std::wstring& dest);
|
||||
std::wstring CharToWchar(const char* src, UINT codepage);
|
||||
std::wstring UTF8ToWchar(const char* src);
|
||||
|
||||
bool CharToChar(const char* src, std::string& dest, UINT src_codepage, UINT dest_codepage);
|
||||
std::string CharToChar(const char* src, UINT src_codepage, UINT dest_codepage);
|
||||
|
||||
#endif
|
||||
|
||||
bool UTF8ToUTF16(const char* src, std::u16string& dest);
|
||||
std::u16string UTF8ToUTF16(const char* src);
|
||||
bool UTF8ToUTF32(const char* src, std::u32string& dest);
|
||||
std::u32string UTF8ToUTF32(const char* src);
|
||||
|
||||
bool UTF16ToUTF8(const char16_t* src, std::string& dest);
|
||||
std::string UTF16ToUTF8(const char16_t* src);
|
||||
bool UTF32ToUTF8(const char32_t* src, std::string& dest);
|
||||
std::string UTF32ToUTF8(const char32_t* src);
|
||||
|
||||
}
|
@ -1,428 +0,0 @@
|
||||
#include "ExceptionHelper.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include "WinFctHelper.hpp"
|
||||
#include "ConsoleHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include "IOHelper.hpp"
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "FsPathPatch.hpp"
|
||||
#include <filesystem>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
namespace YYCC::ExceptionHelper {
|
||||
|
||||
/**
|
||||
* @brief True if the exception handler already registered, otherwise false.
|
||||
* @details
|
||||
* This variable is designed to prevent multiple register operation
|
||||
* because unhandled exception handler should only be registered once.
|
||||
* \n
|
||||
* Register function should check whether this variable is false before registering,
|
||||
* and set this variable to true after registing.
|
||||
* Unregister as well as should do the same check.
|
||||
*/
|
||||
static bool g_IsRegistered = false;
|
||||
/**
|
||||
* @brief True if a exception handler is running, otherwise false.
|
||||
* @details
|
||||
* This variable is served for blocking possible infinity recursive exception handling.
|
||||
* \n
|
||||
* When entering unhandled exception handler, we must check whether this variable is true.
|
||||
* If it is true, it mean that there is another unhandled exception handler running.
|
||||
* Then we should exit immediately.
|
||||
* Otherwise, this variable should be set to true indicating we are processing unhandled exception.
|
||||
* After processing exception, at the end of unhandled exception handler,
|
||||
* we should restore this value to false.
|
||||
*
|
||||
*/
|
||||
static bool g_IsProcessing = false;
|
||||
/**
|
||||
* @brief The backup of original exception handler.
|
||||
* @details
|
||||
* This variable was set when registering unhandled exception handler.
|
||||
* And will be used when unregistering for restoring.
|
||||
*/
|
||||
static LPTOP_LEVEL_EXCEPTION_FILTER g_ProcBackup;
|
||||
|
||||
#pragma region Exception Handler Implementation
|
||||
|
||||
/**
|
||||
* @brief Get human-readable exception string from given exception code.
|
||||
* @param[in] code Exception code
|
||||
* @return The const string pointer to corresponding exception explanation string.
|
||||
*/
|
||||
static const char* UExceptionGetCodeName(DWORD code) {
|
||||
switch (code) {
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
return "access violation";
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
return "array index out of bound";
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
return "breakpoint reached";
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
return "misaligned data access";
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
return "operand had denormal value";
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
return "floating-point division by zero";
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
return "no decimal fraction representation for value";
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
return "invalid floating-point operation";
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
return "floating-point overflow";
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
return "floating-point stack corruption";
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
return "floating-point underflow";
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
return "illegal instruction";
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
return "inaccessible page";
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
return "integer division by zero";
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
return "integer overflow";
|
||||
case EXCEPTION_INVALID_DISPOSITION:
|
||||
return "documentation says this should never happen";
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
return "can't continue after a noncontinuable exception";
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
return "attempted to execute a privileged instruction";
|
||||
case EXCEPTION_SINGLE_STEP:
|
||||
return "one instruction has been executed";
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
return "stack overflow";
|
||||
default:
|
||||
return "unknown exception";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Error log (including backtrace) used output function with format feature
|
||||
* @details
|
||||
* This function will format message first.
|
||||
* And write them into given file stream and stderr.
|
||||
* @param[in] fs
|
||||
* The file stream where we write.
|
||||
* If it is nullptr, function will skip writing for file stream.
|
||||
* @param[in] fmt The format string.
|
||||
* @param[in] ... The argument to be formatted.
|
||||
*/
|
||||
static void UExceptionErrLogFormatLine(std::FILE* fs, const char* fmt, ...) {
|
||||
// write to file
|
||||
if (fs != nullptr) {
|
||||
va_list arg1;
|
||||
va_start(arg1, fmt);
|
||||
std::vfprintf(fs, fmt, arg1);
|
||||
std::fputs("\n", fs);
|
||||
va_end(arg1);
|
||||
}
|
||||
// write to stderr
|
||||
va_list arg2;
|
||||
va_start(arg2, fmt);
|
||||
ConsoleHelper::ErrWriteLine(YYCC::StringHelper::VPrintf(fmt, arg2).c_str());
|
||||
va_end(arg2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Error log (including backtrace) used output function
|
||||
* @details
|
||||
* This function will write given string into given file stream and stderr.
|
||||
* @param[in] fs
|
||||
* The file stream where we write.
|
||||
* If it is nullptr, function will skip writing for file stream.
|
||||
* @param[in] strl The string to be written.
|
||||
*/
|
||||
static void UExceptionErrLogWriteLine(std::FILE* fs, const char* strl) {
|
||||
// write to file
|
||||
if (fs != nullptr) {
|
||||
std::fputs(strl, fs);
|
||||
std::fputs("\n", fs);
|
||||
}
|
||||
// write to stderr
|
||||
ConsoleHelper::ErrWriteLine(strl);
|
||||
}
|
||||
|
||||
static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) {
|
||||
// setup loading symbol options
|
||||
SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); // lazy load symbol, and load line number.
|
||||
|
||||
// setup handle
|
||||
HANDLE process = GetCurrentProcess();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
|
||||
// init symbol
|
||||
if (!SymInitialize(process, 0, TRUE)) {
|
||||
// fail to init. return
|
||||
UExceptionErrLogWriteLine(fs, "Fail to initialize symbol handle for process!");
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== CORE DUMP ==========
|
||||
// prepare frame. setup correct fields
|
||||
// references:
|
||||
// https://github.com/rust-lang/backtrace-rs/blob/9ed25b581cfd2ee60e5a3b9054fd023bf6dced90/src/backtrace/dbghelp.rs
|
||||
// https://sourceforge.net/p/predef/wiki/Architectures/
|
||||
DWORD machine_type = 0;
|
||||
STACKFRAME64 frame;
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
#if defined(_M_IX86) || defined(__i386__)
|
||||
// x86
|
||||
machine_type = IMAGE_FILE_MACHINE_I386;
|
||||
frame.AddrPC.Offset = context->Eip;
|
||||
frame.AddrStack.Offset = context->Esp;
|
||||
frame.AddrFrame.Offset = context->Ebp;
|
||||
#elif defined(_M_AMD64) || defined(__amd64__)
|
||||
// amd64
|
||||
machine_type = IMAGE_FILE_MACHINE_AMD64;
|
||||
frame.AddrPC.Offset = context->Rip;
|
||||
frame.AddrStack.Offset = context->Rsp;
|
||||
frame.AddrFrame.Offset = context->Rbp;
|
||||
#elif defined(_M_ARM) || defined(__arm__)
|
||||
// arm (32bit)
|
||||
machine_type = IMAGE_FILE_MACHINE_ARMNT;
|
||||
frame.AddrPC.Offset = context->Pc;
|
||||
frame.AddrStack.Offset = context->Sp;
|
||||
frame.AddrFrame.Offset = context->R11;
|
||||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||||
// arm64
|
||||
machine_type = IMAGE_FILE_MACHINE_ARM64;
|
||||
frame.AddrPC.Offset = context->Pc;
|
||||
frame.AddrStack.Offset = context->Sp;
|
||||
frame.AddrFrame.Offset = context->DUMMYUNIONNAME.DUMMYSTRUCTNAME.Fp;
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
//IA-64 anybody?
|
||||
|
||||
#endif
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
|
||||
// stack walker
|
||||
while (StackWalk64(machine_type, process, thread, &frame, context,
|
||||
0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
|
||||
|
||||
// depth breaker
|
||||
--maxdepth;
|
||||
if (maxdepth < 0) {
|
||||
UExceptionErrLogWriteLine(fs, "..."); // indicate there are some frames not listed
|
||||
break;
|
||||
}
|
||||
|
||||
// get module name
|
||||
const char* module_name = "<unknown module>";
|
||||
std::string module_name_raw;
|
||||
DWORD64 module_base;
|
||||
if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
|
||||
if (WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name_raw)) {
|
||||
module_name = module_name_raw.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
// get source file and line
|
||||
const char* source_file = "<unknown source>";
|
||||
DWORD64 source_file_line = 0;
|
||||
DWORD dwDisplacement;
|
||||
IMAGEHLP_LINE64 winline;
|
||||
winline.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &dwDisplacement, &winline)) {
|
||||
source_file = winline.FileName;
|
||||
source_file_line = winline.LineNumber;
|
||||
}
|
||||
|
||||
// write to file
|
||||
UExceptionErrLogFormatLine(fs, "0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIXPTR "]\t%s#L%" PRIu64,
|
||||
frame.AddrPC.Offset, // memory adress
|
||||
module_name, frame.AddrPC.Offset - module_base, // module name + relative address
|
||||
source_file, source_file_line // source file + source line
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// ========== END CORE DUMP ==========
|
||||
|
||||
// free symbol
|
||||
SymCleanup(process);
|
||||
}
|
||||
|
||||
static void UExceptionErrorLog(const std::string& u8_filename, LPEXCEPTION_POINTERS info) {
|
||||
// open file stream if we have file name
|
||||
std::FILE* fs = nullptr;
|
||||
if (!u8_filename.empty()) {
|
||||
fs = IOHelper::UTF8FOpen(u8_filename.c_str(), "wb");
|
||||
}
|
||||
|
||||
// record exception type first
|
||||
PEXCEPTION_RECORD rec = info->ExceptionRecord;
|
||||
UExceptionErrLogFormatLine(fs, "Unhandled exception occured at 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR ": %s (%" PRIu32 ").",
|
||||
rec->ExceptionAddress,
|
||||
UExceptionGetCodeName(rec->ExceptionCode),
|
||||
rec->ExceptionCode
|
||||
);
|
||||
|
||||
// special proc for 2 exceptions
|
||||
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
|
||||
if (rec->NumberParameters >= 2) {
|
||||
const char* op =
|
||||
rec->ExceptionInformation[0] == 0 ? "read" :
|
||||
rec->ExceptionInformation[0] == 1 ? "written" : "executed";
|
||||
UExceptionErrLogFormatLine(fs, "The data at memory address 0x%" PRI_XPTR_LEFT_PADDING PRIxPTR " could not be %s.",
|
||||
rec->ExceptionInformation[1], op);
|
||||
}
|
||||
}
|
||||
|
||||
// output stacktrace
|
||||
UExceptionBacktrace(fs, info->ContextRecord, 1024);
|
||||
|
||||
// close file if necessary
|
||||
if (fs != nullptr) {
|
||||
std::fclose(fs);
|
||||
}
|
||||
}
|
||||
|
||||
static void UExceptionCoreDump(const std::string& u8_filename, LPEXCEPTION_POINTERS info) {
|
||||
// convert file encoding
|
||||
std::wstring filename;
|
||||
if (u8_filename.empty())
|
||||
return; // if no given file name, return
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filename.c_str(), filename))
|
||||
return; // if convertion failed, return
|
||||
|
||||
// open file and write
|
||||
HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION exception_info;
|
||||
exception_info.ThreadId = GetCurrentThreadId();
|
||||
exception_info.ExceptionPointers = info;
|
||||
exception_info.ClientPointers = TRUE;
|
||||
MiniDumpWriteDump(
|
||||
GetCurrentProcess(), GetCurrentProcessId(), hFile,
|
||||
MiniDumpNormal,
|
||||
&exception_info,
|
||||
NULL, NULL
|
||||
);
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
}
|
||||
|
||||
static bool UExceptionFetchRecordPath(std::string& log_path, std::string& coredump_path) {
|
||||
// build two file names like: "module.dll.1234.log" and "module.dll.1234.dmp".
|
||||
// "module.dll" is the name of current module. "1234" is current process id.
|
||||
// get self module name
|
||||
std::string u8_self_module_name;
|
||||
{
|
||||
// get module handle
|
||||
HMODULE hSelfModule = YYCC::WinFctHelper::GetCurrentModule();
|
||||
if (hSelfModule == nullptr)
|
||||
return false;
|
||||
// get full path of self module
|
||||
std::string u8_self_module_path;
|
||||
if (!YYCC::WinFctHelper::GetModuleFileName(hSelfModule, u8_self_module_path))
|
||||
return false;
|
||||
// extract file name from full path by std::filesystem::path
|
||||
std::filesystem::path self_module_path(FsPathPatch::FromUTF8Path(u8_self_module_path.c_str()));
|
||||
u8_self_module_name = FsPathPatch::ToUTF8Path(self_module_path.filename());
|
||||
}
|
||||
// then get process id
|
||||
DWORD process_id = GetCurrentProcessId();
|
||||
// conbine them as a file name prefix
|
||||
std::string u8_filename_prefix;
|
||||
if (!YYCC::StringHelper::Printf(u8_filename_prefix, "%s.%" PRIu32, u8_self_module_name.c_str(), process_id))
|
||||
return false;
|
||||
// then get file name for log and minidump
|
||||
std::string u8_log_filename = u8_filename_prefix + ".log";
|
||||
std::string u8_coredump_filename = u8_filename_prefix + ".dmp";
|
||||
|
||||
// fetch crash report path
|
||||
// get local appdata folder
|
||||
std::string u8_localappdata_path;
|
||||
if (!WinFctHelper::GetLocalAppData(u8_localappdata_path))
|
||||
return false;
|
||||
// convert to std::filesystem::path
|
||||
std::filesystem::path crash_report_path(FsPathPatch::FromUTF8Path(u8_localappdata_path.c_str()));
|
||||
// slash into crash report folder
|
||||
crash_report_path /= FsPathPatch::FromUTF8Path("CrashDumps");
|
||||
// use create function to make sure it is existing
|
||||
std::filesystem::create_directories(crash_report_path);
|
||||
|
||||
// build log path and coredump path
|
||||
// build std::filesystem::path first
|
||||
std::filesystem::path log_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_log_filename.c_str());
|
||||
std::filesystem::path coredump_filepath = crash_report_path / FsPathPatch::FromUTF8Path(u8_coredump_filename.c_str());
|
||||
// output to result
|
||||
log_path = FsPathPatch::ToUTF8Path(log_filepath);
|
||||
coredump_path = FsPathPatch::ToUTF8Path(coredump_filepath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
|
||||
// detect loop calling
|
||||
if (g_IsProcessing) goto end_proc;
|
||||
// start process
|
||||
g_IsProcessing = true;
|
||||
|
||||
// core implementation
|
||||
{
|
||||
// fetch error report path first
|
||||
std::string log_path, coredump_path;
|
||||
if (!UExceptionFetchRecordPath(log_path, coredump_path)) {
|
||||
// fail to fetch path, clear them.
|
||||
// we still can handle crash without them
|
||||
log_path.clear();
|
||||
coredump_path.clear();
|
||||
// and tell user we can not output file
|
||||
ConsoleHelper::ErrWriteLine("Crash occurs, but we can not create crash log and coredump!");
|
||||
} else {
|
||||
// okey. output file path to tell user the path where you can find.
|
||||
ConsoleHelper::ErrFormatLine("Crash Log: %s", log_path.c_str());
|
||||
ConsoleHelper::ErrFormatLine("Crash Coredump: %s", coredump_path.c_str());
|
||||
}
|
||||
|
||||
// write crash log
|
||||
UExceptionErrorLog(log_path, info);
|
||||
// write crash coredump
|
||||
UExceptionCoreDump(coredump_path, info);
|
||||
|
||||
}
|
||||
|
||||
// end process
|
||||
failed:
|
||||
g_IsProcessing = false;
|
||||
// if backup proc can be run, run it
|
||||
// otherwise directly return.
|
||||
end_proc:
|
||||
if (g_ProcBackup != nullptr) {
|
||||
return g_ProcBackup(info);
|
||||
} else {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
void Register() {
|
||||
if (g_IsRegistered) return;
|
||||
g_ProcBackup = SetUnhandledExceptionFilter(UExceptionImpl);
|
||||
g_IsRegistered = true;
|
||||
}
|
||||
|
||||
void Unregister() {
|
||||
if (!g_IsRegistered) return;
|
||||
SetUnhandledExceptionFilter(g_ProcBackup);
|
||||
g_IsRegistered = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
/**
|
||||
* @brief Windows specific unhandled exception processor.
|
||||
* @details
|
||||
* This namespace is Windows specific. On other platforms, the whole namespace is unavailable.
|
||||
*
|
||||
* This namespace allow user register unhandled exception handler on Windows
|
||||
* to output error log into \c stderr and log file, and generate coredump if possible.
|
||||
* This is useful for bug tracing on Windows, especially most Windows user are naive and don't know how to report bug.
|
||||
*
|
||||
*/
|
||||
namespace YYCC::ExceptionHelper {
|
||||
|
||||
/**
|
||||
* @brief Register unhandled exception handler
|
||||
* @details
|
||||
* This function will set an internal function as unhandled exception handler on Windows.
|
||||
*
|
||||
* When unhandled exception raised,
|
||||
* That internal function will output error stacktrace in standard output
|
||||
* and log file (located in temp folder), and also generate a dump file
|
||||
* in temp folder (for convenient debugging of developer when reporting bugs) if it can.
|
||||
*
|
||||
* This function usually is called at the start of program.
|
||||
*/
|
||||
void Register();
|
||||
/**
|
||||
* @brief Unregister unhandled exception handler
|
||||
* @details
|
||||
* The reverse operation of Register().
|
||||
*
|
||||
* This function and Register() should always be used as a pair.
|
||||
* You must call this function if you have called Register() before.
|
||||
*
|
||||
* This function usually is called at the end of program.
|
||||
*/
|
||||
void Unregister();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,41 +0,0 @@
|
||||
#include "FsPathPatch.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace YYCC::FsPathPatch {
|
||||
|
||||
std::filesystem::path FromUTF8Path(const char* u8_path) {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// convert path to wchar
|
||||
std::wstring wpath;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_path, wpath))
|
||||
throw std::invalid_argument("Fail to convert given UTF8 string.");
|
||||
|
||||
// call microsoft specified fopen which support wchar as argument.
|
||||
return std::filesystem::path(wpath);
|
||||
|
||||
#else
|
||||
return std::filesystem::path(u8_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string ToUTF8Path(const std::filesystem::path& path) {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// get and convert to utf8
|
||||
std::string u8_path;
|
||||
if (!YYCC::EncodingHelper::WcharToUTF8(path.c_str(), u8_path))
|
||||
throw std::invalid_argument("Fail to convert to UTF8 string.");
|
||||
|
||||
// return utf8 path
|
||||
return u8_path;
|
||||
|
||||
#else
|
||||
return path.string();
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
/**
|
||||
* @brief The patch namespace resolving \c std::filesystem::path encoding issue.
|
||||
* @details
|
||||
* This patch is Windows oriented.
|
||||
* 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.
|
||||
*
|
||||
* 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 passing UTF8 char sequence to \c std::filesystem::path 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 namespace always use UTF8 as its argument. There is no ambiguous issue.
|
||||
* You should use the functions provided by this namespace on any platforms
|
||||
* instead of vanilla \c std::filesystem::path functions.
|
||||
*/
|
||||
namespace YYCC::FsPathPatch {
|
||||
|
||||
/**
|
||||
* @brief Constructs the path from a UTF8 character sequence
|
||||
* @param[in] u8_path UTF8 path string for building this std::filesystem::path.
|
||||
* @return std::filesystem::path instance.
|
||||
* @exception std::invalid_argument Fail to parse given UTF8 string (maybe invalid?).
|
||||
*/
|
||||
std::filesystem::path FromUTF8Path(const char* u8_path);
|
||||
|
||||
/**
|
||||
* @brief Returns the UTF8 representation of the pathname
|
||||
* @param path[in] The string to be output.
|
||||
* @return UTF8 encoded string representing given path.
|
||||
* @exception std::invalid_argument Fail to parse to UTF8 string.
|
||||
*/
|
||||
std::string ToUTF8Path(const std::filesystem::path& path);
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
#include "IOHelper.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::IOHelper {
|
||||
|
||||
FILE* UTF8FOpen(const char* u8_filepath, const char* u8_mode) {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// convert mode and file path to wchar
|
||||
std::wstring wmode, wpath;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_mode, wmode))
|
||||
return nullptr;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filepath, wpath))
|
||||
return nullptr;
|
||||
|
||||
// call microsoft specified fopen which support wchar as argument.
|
||||
return _wfopen(wpath.c_str(), wmode.c_str());
|
||||
|
||||
#else
|
||||
return std::fopen(u8_filepath, u8_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
|
||||
namespace YYCC::IOHelper {
|
||||
|
||||
#if UINTPTR_MAX == UINT32_MAX
|
||||
#define PRI_XPTR_LEFT_PADDING "08"
|
||||
#elif UINTPTR_MAX == UINT64_MAX
|
||||
/**
|
||||
* @brief The left-padding zero format string of HEX-printed pointer type.
|
||||
* @details
|
||||
* When printing a pointer with HEX style, we always hope it can be left-padded with some zero for easy reading.
|
||||
* In different architecture, the size of this padding is differnet too so we create this macro.
|
||||
*
|
||||
* In 32-bit environment, it will be "08" meaning left pad zero until 8 number position.
|
||||
* In 64-bit environment, it will be "016" meaning left pad zero until 16 number position.
|
||||
*/
|
||||
#define PRI_XPTR_LEFT_PADDING "016"
|
||||
#else
|
||||
#error "Not supported pointer size."
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The UTF8 version of std::fopen.
|
||||
* @param[in] u8_filepath The UTF8 encoded path to the file to be opened.
|
||||
* @param[in] u8_mode UTF8 encoded mode string of the file to be opened.
|
||||
* @remarks
|
||||
* This function is suit for Windows because std::fopen do not support UTF8 on Windows.
|
||||
* On other platforms, this function will delegate request directly to std::fopen.
|
||||
* @return FILE* of the file to be opened, or nullptr if failed.
|
||||
*/
|
||||
FILE* UTF8FOpen(const char* u8_filepath, const char* u8_mode);
|
||||
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include <string>
|
||||
#include <cinttypes>
|
||||
#include <type_traits>
|
||||
#include <stdexcept>
|
||||
#include <charconv>
|
||||
#include <array>
|
||||
|
||||
namespace YYCC::ParserHelper {
|
||||
|
||||
// Reference: https://zh.cppreference.com/w/cpp/utility/from_chars
|
||||
|
||||
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
|
||||
bool TryParse(const std::string& strl, _Ty& num) {
|
||||
auto [ptr, ec] = std::from_chars(strl.c_str(), strl.c_str() + strl.size(), num, std::chars_format::general);
|
||||
if (ec == std::errc()) {
|
||||
// check whether the full string is matched
|
||||
return ptr == strl.c_str() + strl.size();
|
||||
} else if (ec == std::errc::invalid_argument) {
|
||||
// given string is invalid
|
||||
return false;
|
||||
} else if (ec == std::errc::result_out_of_range) {
|
||||
// given string is out of range
|
||||
return false;
|
||||
} else {
|
||||
// unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
bool TryParse(const std::string& strl, _Ty& num, int base = 10) {
|
||||
auto [ptr, ec] = std::from_chars(strl.c_str(), strl.c_str() + strl.size(), num, base);
|
||||
if (ec == std::errc()) {
|
||||
// check whether the full string is matched
|
||||
return ptr == strl.c_str() + strl.size();
|
||||
} else if (ec == std::errc::invalid_argument) {
|
||||
// given string is invalid
|
||||
return false;
|
||||
} else if (ec == std::errc::result_out_of_range) {
|
||||
// given string is out of range
|
||||
return false;
|
||||
} else {
|
||||
// unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
|
||||
bool TryParse(const std::string& strl, _Ty& num) {
|
||||
if (strl == "true") num = true;
|
||||
else if (strl == "false") num = false;
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
|
||||
_Ty Parse(const std::string& strl) {
|
||||
_Ty ret;
|
||||
TryParse(strl, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Reference: https://en.cppreference.com/w/cpp/utility/to_chars
|
||||
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
std::string ToString(_Ty num) {
|
||||
std::array<char, 64> buffer;
|
||||
auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), num);
|
||||
if (ec == std::errc()) {
|
||||
return std::string(buffer.data(), ptr - buffer.data());
|
||||
} else if (ec == std::errc::value_too_large) {
|
||||
// too short buffer
|
||||
// this should not happend
|
||||
throw std::out_of_range("ToString() buffer is not sufficient.");
|
||||
} else {
|
||||
// unreachable
|
||||
throw std::runtime_error("unreachable code.");
|
||||
}
|
||||
}
|
||||
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
|
||||
std::string ToString(_Ty num) {
|
||||
if (num) return std::string("true");
|
||||
else return std::string("false");
|
||||
}
|
||||
|
||||
}
|
@ -1,217 +0,0 @@
|
||||
#include "StringHelper.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace YYCC::StringHelper {
|
||||
|
||||
bool Printf(std::string& strl, const char* format, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
bool ret = VPrintf(strl, format, argptr);
|
||||
va_end(argptr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VPrintf(std::string& strl, const char* format, va_list argptr) {
|
||||
va_list args1;
|
||||
va_copy(args1, argptr);
|
||||
va_list args2;
|
||||
va_copy(args2, argptr);
|
||||
|
||||
// the return value is desired char count without NULL terminal.
|
||||
// minus number means error
|
||||
int count = std::vsnprintf(nullptr, 0, format, args1);
|
||||
if (count < 0) {
|
||||
// invalid length returned by vsnprintf.
|
||||
return false;
|
||||
}
|
||||
va_end(args1);
|
||||
|
||||
// resize std::string to desired count.
|
||||
// and pass its length + 1 to std::vsnprintf,
|
||||
// because std::vsnprintf only can write "buf_size - 1" chars with a trailing NULL.
|
||||
// however std::vsnprintf already have a trailing NULL, so we plus 1 for it.
|
||||
strl.resize(count);
|
||||
int write_result = std::vsnprintf(strl.data(), strl.size() + 1, format, args2);
|
||||
va_end(args2);
|
||||
|
||||
if (write_result < 0 || write_result > count) {
|
||||
// invalid write result in vsnprintf.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::string Printf(const char* format, ...) {
|
||||
std::string ret;
|
||||
|
||||
va_list argptr;
|
||||
va_start(argptr, format);
|
||||
VPrintf(ret, format, argptr);
|
||||
va_end(argptr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string VPrintf(const char* format, va_list argptr) {
|
||||
std::string ret;
|
||||
|
||||
va_list argcpy;
|
||||
va_copy(argcpy, argptr);
|
||||
VPrintf(ret, format, argcpy);
|
||||
va_end(argcpy);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Replace(std::string& strl, const char* _from_strl, const char* _to_strl) {
|
||||
// Reference: https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
|
||||
|
||||
// check requirements
|
||||
// from string and to string should not be nullptr.
|
||||
if (_from_strl == nullptr || _to_strl == nullptr) return;
|
||||
// from string should not be empty
|
||||
std::string from_strl(_from_strl);
|
||||
std::string to_strl(_to_strl);
|
||||
if (from_strl.empty()) return;
|
||||
|
||||
// start replace one by one
|
||||
size_t start_pos = 0;
|
||||
while ((start_pos = strl.find(from_strl, start_pos)) != std::string::npos) {
|
||||
strl.replace(start_pos, from_strl.size(), to_strl);
|
||||
start_pos += to_strl.size(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||
}
|
||||
}
|
||||
|
||||
std::string Replace(const char* _strl, const char* _from_strl, const char* _to_strl) {
|
||||
// prepare result
|
||||
std::string strl;
|
||||
// if given string is not nullptr, assign it and process it.
|
||||
if (_strl != nullptr) {
|
||||
strl = _strl;
|
||||
Replace(strl, _from_strl, _to_strl);
|
||||
}
|
||||
// return value
|
||||
return strl;
|
||||
}
|
||||
|
||||
std::string Join(JoinDataProvider fct_data, const char* decilmer) {
|
||||
std::string ret;
|
||||
bool is_first = true;
|
||||
const char* element;
|
||||
|
||||
// fetch element
|
||||
while ((element = fct_data()) != nullptr) {
|
||||
// insert decilmer
|
||||
if (is_first) is_first = false;
|
||||
else {
|
||||
// only insert non-nullptr decilmer.
|
||||
if (decilmer != nullptr)
|
||||
ret.append(decilmer);
|
||||
}
|
||||
|
||||
// insert element
|
||||
ret.append(element);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Join(const std::vector<std::string>& data, const char* decilmer, bool reversed) {
|
||||
if (reversed) {
|
||||
auto iter = data.crbegin();
|
||||
auto stop = data.crend();
|
||||
return Join([&iter, &stop]() -> const char* {
|
||||
// if we reach tail, return nullptr
|
||||
if (iter == stop) return nullptr;
|
||||
// otherwise fetch data, inc iterator and return.
|
||||
const char* ret = iter->c_str();
|
||||
++iter;
|
||||
return ret;
|
||||
}, decilmer);
|
||||
} else {
|
||||
auto iter = data.cbegin();
|
||||
auto stop = data.cend();
|
||||
return Join([&iter, &stop]() -> const char* {
|
||||
// if we reach tail, return nullptr
|
||||
if (iter == stop) return nullptr;
|
||||
// otherwise fetch data, inc iterator and return.
|
||||
const char* ret = iter->c_str();
|
||||
++iter;
|
||||
return ret;
|
||||
}, decilmer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<bool bIsToLower>
|
||||
void GeneralStringLowerUpper(std::string& strl) {
|
||||
// References:
|
||||
// https://en.cppreference.com/w/cpp/algorithm/transform
|
||||
// https://en.cppreference.com/w/cpp/string/byte/tolower
|
||||
std::transform(
|
||||
strl.cbegin(), strl.cend(), strl.begin(),
|
||||
[](unsigned char c) -> char {
|
||||
if constexpr (bIsToLower) return std::tolower(c);
|
||||
else return std::toupper(c);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
std::string Lower(const char* strl) {
|
||||
std::string ret;
|
||||
if (strl == nullptr) return ret;
|
||||
else ret = strl;
|
||||
Lower(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Lower(std::string& strl) {
|
||||
GeneralStringLowerUpper<true>(strl);
|
||||
}
|
||||
|
||||
std::string Upper(const char* strl) {
|
||||
// same as Lower, just replace char transform function.
|
||||
std::string ret;
|
||||
if (strl == nullptr) return ret;
|
||||
else ret = strl;
|
||||
Upper(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Upper(std::string& strl) {
|
||||
GeneralStringLowerUpper<false>(strl);
|
||||
}
|
||||
|
||||
std::vector<std::string> Split(const char* _strl, const char* _decilmer) {
|
||||
// Reference:
|
||||
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
|
||||
|
||||
// prepare return value
|
||||
std::vector<std::string> elems;
|
||||
|
||||
// if the string need to be splitted is nullptr, return empty result.
|
||||
if (_strl == nullptr) return elems;
|
||||
std::string strl(_strl);
|
||||
// if decilmer is nullptr, or decilmer is zero length, return original string
|
||||
std::string decilmer;
|
||||
if (_decilmer == nullptr || (decilmer = _decilmer, decilmer.empty())) {
|
||||
elems.push_back(strl);
|
||||
return elems;
|
||||
}
|
||||
|
||||
// start spliting
|
||||
std::size_t previous = 0, current;
|
||||
while ((current = strl.find(decilmer.c_str(), previous)) != std::string::npos) {
|
||||
elems.push_back(strl.substr(previous, current - previous));
|
||||
previous = current + decilmer.size();
|
||||
}
|
||||
// try insert last part but prevent possible out of range exception
|
||||
if (previous <= strl.size()) {
|
||||
elems.push_back(strl.substr(previous));
|
||||
}
|
||||
return elems;
|
||||
}
|
||||
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <cstdarg>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace YYCC::StringHelper {
|
||||
|
||||
bool Printf(std::string& strl, const char* format, ...);
|
||||
bool VPrintf(std::string& strl, const char* format, va_list argptr);
|
||||
|
||||
std::string Printf(const char* format, ...);
|
||||
std::string VPrintf(const char* format, va_list argptr);
|
||||
|
||||
void Replace(std::string& strl, const char* _from_strl, const char* _to_strl);
|
||||
std::string Replace(const char* _strl, const char* _from_strl, const char* _to_strl);
|
||||
|
||||
/**
|
||||
* @brief The data provider of general Join function.
|
||||
* This function pointer return non-null string pointer to represent a element of joined series.
|
||||
* otherwise return nullptr to terminate the joining process.
|
||||
*/
|
||||
using JoinDataProvider = std::function<const char* ()>;
|
||||
/**
|
||||
* @brief General Join function.
|
||||
* @details This function use function pointer as a general data provider interface,
|
||||
* so this function suit for all types container, the user only need write a little bit adaption code.
|
||||
* @param fct_data[in] The function pointer to data provider.
|
||||
* @param decilmer[in] The decilmer.
|
||||
* @return A std::string instance which containing the join result.
|
||||
*/
|
||||
std::string Join(JoinDataProvider fct_data, const char* decilmer);
|
||||
/**
|
||||
* @brief Specialized Join function for common used container.
|
||||
* @param data
|
||||
* @param decilmer
|
||||
* @param reversed
|
||||
* @return
|
||||
*/
|
||||
std::string Join(const std::vector<std::string>& data, const char* decilmer, bool reversed = false);
|
||||
|
||||
/**
|
||||
* @brief Transform string to lower.
|
||||
* @param strl
|
||||
* @return
|
||||
*/
|
||||
std::string Lower(const char* strl);
|
||||
void Lower(std::string& strl);
|
||||
/**
|
||||
* @brief Transform string to upper.
|
||||
* @param strl
|
||||
* @return
|
||||
*/
|
||||
std::string Upper(const char* strl);
|
||||
void Upper(std::string& strl);
|
||||
|
||||
/**
|
||||
* @brief General Split function.
|
||||
* @param _strl[in] The string need to be splitting.
|
||||
* If this is nullptr, the result will be empty.
|
||||
* @param _decilmer[in] The decilmer for splitting.
|
||||
* If decilmer is nullptr or zero length, the result will only have 1 element which is original string.
|
||||
* @return
|
||||
* @remarks This function may be low performance because it just a homebrew Split functon.
|
||||
* It can works in most toy cases but not suit for high performance scenario.
|
||||
* Also, this function will produce a copy of original string because it is not zero copy.
|
||||
*/
|
||||
std::vector<std::string> Split(const char* _strl, const char* _decilmer);
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
#include "WinFctHelper.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "COMHelper.hpp"
|
||||
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
HMODULE GetCurrentModule() {
|
||||
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
|
||||
HMODULE hModule = NULL;
|
||||
GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter.
|
||||
(LPCWSTR)GetCurrentModule,
|
||||
&hModule);
|
||||
|
||||
return hModule;
|
||||
}
|
||||
|
||||
bool GetTempDirectory(std::string& ret) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD expected_size;
|
||||
|
||||
// fetch temp folder
|
||||
while (true) {
|
||||
if ((expected_size = GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
|
||||
// failed, set to empty
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected_size > static_cast<DWORD>(wpath.size())) {
|
||||
// buffer is too short, need enlarge and do fetching again
|
||||
wpath.resize(expected_size);
|
||||
} else {
|
||||
// ok. shrink to real length, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(expected_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str(), ret);
|
||||
}
|
||||
|
||||
bool GetModuleFileName(HINSTANCE hModule, std::string& ret) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD copied_size;
|
||||
|
||||
while (true) {
|
||||
if ((copied_size = GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
|
||||
// failed, return
|
||||
return false;
|
||||
}
|
||||
|
||||
// check insufficient buffer
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
// buffer is not enough, enlarge it and try again.
|
||||
wpath.resize(wpath.size() + MAX_PATH);
|
||||
} else {
|
||||
// ok, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(copied_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath.c_str(), ret);
|
||||
}
|
||||
|
||||
bool GetLocalAppData(std::string& ret) {
|
||||
// fetch path
|
||||
LPWSTR _known_path;
|
||||
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &_known_path);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR known_path(_known_path);
|
||||
|
||||
// convert to utf8
|
||||
return YYCC::EncodingHelper::WcharToUTF8(known_path.get(), ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The helper providing assistance to Win32 functions.
|
||||
* @details
|
||||
* This helper is Windows specific.
|
||||
* If current environment is not Windows, the whole namespace will be unavailable.
|
||||
*/
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
/**
|
||||
* @brief Get Windows used HANDLE for current module.
|
||||
* @details
|
||||
* If your target is EXE, the current module simply is your program self.
|
||||
* However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL.
|
||||
*
|
||||
* This function is frequently used by DLL.
|
||||
* Because some design need the HANDLE of current module, not the host EXE loading your DLL.
|
||||
* For example, you may want to get the name of your built DLL at runtime, then you should pass current module HANDLE, not the HANDLE of EXE.
|
||||
* Or, if you want to get the path to your DLL, you also should pass current module HANDLE.
|
||||
* @return A Windows HANDLE pointing to current module, NULL if failed.
|
||||
*/
|
||||
HMODULE GetCurrentModule();
|
||||
|
||||
/**
|
||||
* @brief Get path to Windows temp folder.
|
||||
* @param[out] ret
|
||||
* The variable receiving UTF8 encoded path to Windows temp folder.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetTempDirectory(std::string& ret);
|
||||
|
||||
/**
|
||||
* @brief Get the file name of given module HANDLE
|
||||
* @param[in] hModule
|
||||
* The HANDLE to the module where we want get file name.
|
||||
* It is same as the HANDLE parameter of \c GetModuleFileName.
|
||||
* @param[out] ret
|
||||
* The variable receiving UTF8 encoded file name of given module.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetModuleFileName(HINSTANCE hModule, std::string& ret);
|
||||
|
||||
/**
|
||||
* @brief Get the path to LOCALAPPDATA.
|
||||
* @details LOCALAPPDATA usually was used as putting local app data files
|
||||
* @param[out] ret
|
||||
* The variable receiving UTF8 encoded path to LOCALAPPDATA.
|
||||
* @return
|
||||
*/
|
||||
bool GetLocalAppData(std::string& ret);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,19 +0,0 @@
|
||||
// It is by design that no pragma once or #if to prevent deplicated including.
|
||||
// Because this header is the part of wrapper, not a real header.
|
||||
// #pragma once
|
||||
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// Define 2 macros to disallow Windows generate MIN and MAX macros
|
||||
// which cause std::min and std::max can not function as normal.
|
||||
#if !defined(WIN32_LEAN_AND_MEAN)
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#if !defined(NOMINMAX)
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,20 +0,0 @@
|
||||
// It is by design that no pragma once or #if to prevent deplicated including.
|
||||
// Because this header is the part of wrapper, not a real header.
|
||||
// #pragma once
|
||||
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// Windows also will generate following macros
|
||||
// which may cause the function sign is different in Windows and other platforms.
|
||||
// So we simply remove them.
|
||||
// Because #undef will not throw error if there are no matched macro,
|
||||
// so we simply #undef them directly.
|
||||
#undef GetObject
|
||||
#undef GetClassName
|
||||
#undef LoadImage
|
||||
#undef GetTempPath
|
||||
#undef GetModuleFileName
|
||||
|
||||
#endif
|
@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Define operating system macros
|
||||
#define YYCC_OS_WINDOWS 2
|
||||
#define YYCC_OS_LINUX 3
|
||||
// Check current operating system.
|
||||
#if defined(_WIN32)
|
||||
#define YYCC_OS YYCC_OS_WINDOWS
|
||||
#else
|
||||
#define YYCC_OS YYCC_OS_LINUX
|
||||
#endif
|
||||
|
||||
// If we are in Windows,
|
||||
// we need add 2 macros to disable Windows shitty warnings and errors of
|
||||
// depracted functions and not secure functions.
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#if !defined(_CRT_NONSTDC_NO_DEPRECATE)
|
||||
#define _CRT_NONSTDC_NO_DEPRECATE
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//// Decide the char type we used
|
||||
//#include <string>
|
||||
//namespace YYCC {
|
||||
//#if defined(__cpp_char8_t)
|
||||
// using u8char = char8_t;
|
||||
// using u8string = std::std::string
|
||||
//#else
|
||||
// using u8char = char;
|
||||
// using u8string = std::string;
|
||||
//#endif
|
||||
//}
|
348
src/YYCCLegacy/ArgParser.cpp
Normal file
348
src/YYCCLegacy/ArgParser.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
#include "ArgParser.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "ConsoleHelper.hpp"
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <processenv.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
#endif
|
||||
|
||||
namespace YYCC::ArgParser {
|
||||
|
||||
#pragma region Arguments List
|
||||
|
||||
ArgumentList ArgumentList::CreateFromStd(int argc, char* argv[]) {
|
||||
std::vector<yycc_u8string> args;
|
||||
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
|
||||
if (argv[i] != nullptr)
|
||||
args.emplace_back(yycc_u8string(YYCC::EncodingHelper::ToUTF8(argv[i])));
|
||||
}
|
||||
return ArgumentList(std::move(args));
|
||||
}
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
ArgumentList ArgumentList::CreateFromWin32() {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw
|
||||
|
||||
// prepare list
|
||||
std::vector<yycc_u8string> args;
|
||||
|
||||
// try fetching from Win32 functions
|
||||
int argc;
|
||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (argv != NULL) {
|
||||
for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self)
|
||||
if (argv[i] != nullptr) {
|
||||
yycc_u8string u8_argv;
|
||||
if (YYCC::EncodingHelper::WcharToUTF8(argv[i], u8_argv))
|
||||
args.emplace_back(std::move(u8_argv));
|
||||
}
|
||||
}
|
||||
}
|
||||
LocalFree(argv);
|
||||
|
||||
// return result
|
||||
return ArgumentList(std::move(args));
|
||||
}
|
||||
#endif
|
||||
|
||||
ArgumentList::ArgumentList(std::vector<yycc_u8string>&& arguments) :
|
||||
m_Arguments(arguments), m_ArgumentsCursor(0u) {}
|
||||
|
||||
void ArgumentList::Prev() {
|
||||
if (m_ArgumentsCursor == 0u)
|
||||
throw std::runtime_error("attempt to move on the head of iterator.");
|
||||
--m_ArgumentsCursor;
|
||||
}
|
||||
|
||||
void ArgumentList::Next() {
|
||||
if (IsEOF()) throw std::runtime_error("attempt to move on the tail of iterator.");
|
||||
++m_ArgumentsCursor;
|
||||
}
|
||||
|
||||
const yycc_u8string& ArgumentList::Argument() const {
|
||||
if (IsEOF()) throw std::runtime_error("attempt to get data on the tail of iterator.");
|
||||
return m_Arguments[m_ArgumentsCursor];
|
||||
}
|
||||
|
||||
bool ArgumentList::IsSwitch(bool* is_long_name, yycc_u8string* long_name, yycc_char8_t* short_name) const {
|
||||
// check eof first
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
// check long name first, then check short name
|
||||
if (IsLongNameSwitch(long_name)) {
|
||||
if (is_long_name != nullptr) *is_long_name = true;
|
||||
return true;
|
||||
}
|
||||
if (IsShortNameSwitch(short_name)) {
|
||||
if (is_long_name != nullptr) *is_long_name = false;
|
||||
return true;
|
||||
}
|
||||
// not matched
|
||||
return false;
|
||||
}
|
||||
bool ArgumentList::IsLongNameSwitch(yycc_u8string* name_part) const {
|
||||
// fetch current parameter
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
|
||||
// find double slash
|
||||
if (param.find(AbstractArgument::DOUBLE_DASH) != 0u) return false;
|
||||
// check gotten long name
|
||||
yycc_u8string_view long_name = yycc_u8string_view(param).substr(2u);
|
||||
if (!AbstractArgument::IsLegalLongName(long_name)) return false;
|
||||
// set checked long name if possible and return
|
||||
if (name_part != nullptr)
|
||||
*name_part = long_name;
|
||||
return true;
|
||||
}
|
||||
bool ArgumentList::IsShortNameSwitch(yycc_char8_t* name_part) const {
|
||||
// fetch current parameter
|
||||
if (IsEOF()) throw std::runtime_error("attempt to fetch data on the tail of iterator.");
|
||||
const yycc_u8string& param = m_Arguments[m_ArgumentsCursor];
|
||||
// if the length is not exactly equal to 2,
|
||||
// or it not starts with dash,
|
||||
// it is impossible a short name
|
||||
if (param.size() != 2u || param[0] != AbstractArgument::DASH) return false;
|
||||
// check gotten short name
|
||||
yycc_char8_t short_name = param[1];
|
||||
if (!AbstractArgument::IsLegalShortName(short_name)) return false;
|
||||
// set checked short name if possible and return
|
||||
if (name_part != nullptr)
|
||||
*name_part = short_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArgumentList::IsValue(yycc_u8string* val) const {
|
||||
bool is_value = !IsSwitch();
|
||||
if (is_value && val != nullptr)
|
||||
*val = m_Arguments[m_ArgumentsCursor];
|
||||
return is_value;
|
||||
}
|
||||
|
||||
bool ArgumentList::IsEOF() const { return m_ArgumentsCursor >= m_Arguments.size(); }
|
||||
|
||||
void ArgumentList::Reset() { m_ArgumentsCursor = 0u; }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Abstract Argument
|
||||
|
||||
const yycc_u8string AbstractArgument::DOUBLE_DASH = YYCC_U8("--");
|
||||
const yycc_char8_t AbstractArgument::DASH = YYCC_U8_CHAR('-');
|
||||
const yycc_char8_t AbstractArgument::NO_SHORT_NAME = YYCC_U8_CHAR(0);
|
||||
const yycc_char8_t AbstractArgument::MIN_SHORT_NAME = YYCC_U8_CHAR('!');
|
||||
const yycc_char8_t AbstractArgument::MAX_SHORT_NAME = YYCC_U8_CHAR('~');
|
||||
|
||||
bool AbstractArgument::IsLegalShortName(yycc_char8_t short_name) {
|
||||
if (short_name == AbstractArgument::DASH || // dash is not allowed
|
||||
short_name < AbstractArgument::MIN_SHORT_NAME || short_name > AbstractArgument::MAX_SHORT_NAME) { // non-display ASCII chars are not allowed
|
||||
return false;
|
||||
}
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
bool AbstractArgument::IsLegalLongName(const yycc_u8string_view& long_name) {
|
||||
// empty is not allowed
|
||||
if (long_name.empty()) return false;
|
||||
// non-display ASCII chars are not allowed
|
||||
for (const auto& val : long_name) {
|
||||
if (val < AbstractArgument::MIN_SHORT_NAME || val > AbstractArgument::MAX_SHORT_NAME)
|
||||
return false;
|
||||
}
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
|
||||
AbstractArgument::AbstractArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description, const yycc_char8_t* argument_example,
|
||||
bool is_optional) :
|
||||
m_LongName(), m_ShortName(AbstractArgument::NO_SHORT_NAME), m_Description(), m_ArgumentExample(),
|
||||
m_IsOptional(is_optional), m_IsCaptured(false) {
|
||||
|
||||
// try to assign long name and check it
|
||||
if (long_name != nullptr) {
|
||||
m_LongName = long_name;
|
||||
if (!AbstractArgument::IsLegalLongName(m_LongName))
|
||||
throw std::invalid_argument("Given long name is invalid.");
|
||||
}
|
||||
// try to assign short name and check it
|
||||
if (short_name != AbstractArgument::NO_SHORT_NAME) {
|
||||
m_ShortName = short_name;
|
||||
if (!AbstractArgument::IsLegalShortName(m_ShortName))
|
||||
throw std::invalid_argument("Given short name is invalid.");
|
||||
}
|
||||
// check short name and long name existence
|
||||
if (!HasShortName() && !HasLongName())
|
||||
throw std::invalid_argument("you must specify an one of long name or short name.");
|
||||
|
||||
// try to assign other string values
|
||||
if (description != nullptr) m_Description = description;
|
||||
if (argument_example != nullptr) m_ArgumentExample = argument_example;
|
||||
}
|
||||
|
||||
AbstractArgument::~AbstractArgument() {}
|
||||
|
||||
bool AbstractArgument::HasLongName() const { return !m_LongName.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetLongName() const { return m_LongName; }
|
||||
bool AbstractArgument::HasShortName() const { return m_ShortName != NO_SHORT_NAME; }
|
||||
yycc_char8_t AbstractArgument::GetShortName() const { return m_ShortName; }
|
||||
bool AbstractArgument::HasDescription() const { return !m_Description.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetDescription() const { return m_Description; }
|
||||
bool AbstractArgument::HasArgumentExample() const { return !m_ArgumentExample.empty(); }
|
||||
const yycc_u8string& AbstractArgument::GetArgumentExample() const { return m_ArgumentExample; }
|
||||
bool AbstractArgument::IsOptional() const { return m_IsOptional; }
|
||||
|
||||
bool AbstractArgument::IsCaptured() const { return m_IsCaptured; }
|
||||
void AbstractArgument::SetCaptured(bool is_captured) { m_IsCaptured = is_captured; }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Option Context
|
||||
|
||||
OptionContext::OptionContext(
|
||||
const yycc_char8_t* summary, const yycc_char8_t* description,
|
||||
std::initializer_list<AbstractArgument*> arguments) :
|
||||
m_Summary(), m_Description() {
|
||||
// assign summary and description
|
||||
if (summary != nullptr) m_Summary = summary;
|
||||
if (description != nullptr) m_Description = description;
|
||||
|
||||
// insert argument list and check them
|
||||
for (auto* arg : arguments) {
|
||||
// insert into long name map if necessary
|
||||
if (arg->HasLongName()) {
|
||||
auto result = m_LongNameMap.try_emplace(arg->GetLongName(), arg);
|
||||
if (!result.second) throw std::invalid_argument("duplicated long name!");
|
||||
}
|
||||
// insert into short name map if necessary
|
||||
if (arg->HasShortName()) {
|
||||
auto result = m_ShortNameMap.try_emplace(arg->GetShortName(), arg);
|
||||
if (!result.second) throw std::invalid_argument("duplicated short name!");
|
||||
}
|
||||
// insert into argument list
|
||||
m_Arguments.emplace_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
OptionContext::~OptionContext() {}
|
||||
|
||||
bool OptionContext::Parse(ArgumentList& al) {
|
||||
// reset argument list first
|
||||
al.Reset();
|
||||
|
||||
// prepare variables and start loop
|
||||
yycc_u8string long_name;
|
||||
yycc_char8_t short_name;
|
||||
bool is_long_name;
|
||||
while (!al.IsEOF()) {
|
||||
// if we can not find any switches, return with error
|
||||
if (!al.IsSwitch(&is_long_name, &long_name, &short_name)) return false;
|
||||
|
||||
// find corresponding argument by long name or short name.
|
||||
// if we can not find it, return with error.
|
||||
AbstractArgument* arg;
|
||||
if (is_long_name) {
|
||||
auto finder = m_LongNameMap.find(long_name);
|
||||
if (finder == m_LongNameMap.end()) return false;
|
||||
arg = finder->second;
|
||||
} else {
|
||||
auto finder = m_ShortNameMap.find(short_name);
|
||||
if (finder == m_ShortNameMap.end()) return false;
|
||||
arg = finder->second;
|
||||
}
|
||||
|
||||
// if this argument has been captured, raise error
|
||||
if (arg->IsCaptured()) return false;
|
||||
// call user parse function of found argument
|
||||
if (arg->Parse(al)) {
|
||||
// success. mark it is captured
|
||||
arg->SetCaptured(true);
|
||||
} else {
|
||||
// failed, return error
|
||||
return false;
|
||||
}
|
||||
|
||||
// move to next argument
|
||||
al.Next();
|
||||
}
|
||||
|
||||
// after processing all argument,
|
||||
// we should check whether all non-optional argument are captured.
|
||||
for (const auto* arg : m_Arguments) {
|
||||
if (!arg->IsOptional() && !arg->IsCaptured())
|
||||
return false;
|
||||
}
|
||||
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
|
||||
void OptionContext::Reset() {
|
||||
for (auto* arg : m_Arguments) {
|
||||
// clear user data and unset captured
|
||||
arg->Reset();
|
||||
arg->SetCaptured(false);
|
||||
}
|
||||
}
|
||||
|
||||
void OptionContext::Help() const {
|
||||
// print summary and description if necessary
|
||||
if (!m_Summary.empty())
|
||||
YYCC::ConsoleHelper::WriteLine(m_Summary.c_str());
|
||||
if (!m_Description.empty())
|
||||
YYCC::ConsoleHelper::WriteLine(m_Description.c_str());
|
||||
|
||||
// blank line
|
||||
YYCC::ConsoleHelper::WriteLine(YYCC_U8(""));
|
||||
|
||||
// print argument list
|
||||
for (const auto* arg : m_Arguments) {
|
||||
yycc_u8string argstr;
|
||||
|
||||
// print indent
|
||||
argstr += YYCC_U8("\t");
|
||||
// print optional head
|
||||
bool is_optional = arg->IsOptional();
|
||||
if (is_optional) argstr += YYCC_U8("[");
|
||||
|
||||
// switch name
|
||||
bool short_name = arg->HasShortName(), long_name = arg->HasLongName();
|
||||
if (short_name) {
|
||||
argstr += YYCC_U8("-");
|
||||
argstr += arg->GetShortName();
|
||||
}
|
||||
if (long_name) {
|
||||
if (short_name) argstr += YYCC_U8(", ");
|
||||
argstr += YYCC_U8("--");
|
||||
argstr += arg->GetLongName();
|
||||
}
|
||||
|
||||
// argument example
|
||||
if (arg->HasArgumentExample()) {
|
||||
argstr += YYCC_U8(" ");
|
||||
argstr += arg->GetArgumentExample();
|
||||
}
|
||||
|
||||
// optional tail
|
||||
if (is_optional) argstr += YYCC_U8("]");
|
||||
|
||||
// argument description
|
||||
if (arg->HasDescription()) {
|
||||
// eol and double indent
|
||||
argstr += YYCC_U8("\n\t\t");
|
||||
// description
|
||||
argstr += arg->GetDescription();
|
||||
}
|
||||
|
||||
// write into console
|
||||
YYCC::ConsoleHelper::WriteLine(argstr.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
465
src/YYCCLegacy/ArgParser.hpp
Normal file
465
src/YYCCLegacy/ArgParser.hpp
Normal file
@ -0,0 +1,465 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "Constraints.hpp"
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "ParserHelper.hpp"
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
/**
|
||||
* @brief Universal argument parser.
|
||||
* @details
|
||||
* For how to use this namespace, please see \ref arg_parser.
|
||||
*/
|
||||
namespace YYCC::ArgParser {
|
||||
|
||||
/**
|
||||
* @brief The advanced wrapper of the list containing command line arguments.
|
||||
* @details
|
||||
* This class is used by OptionContext and argument class internally for convenience.
|
||||
* It should not be constrcuted directly.
|
||||
* Programmer should choose proper static creation function to create instance of this class.
|
||||
*/
|
||||
class ArgumentList {
|
||||
public:
|
||||
/**
|
||||
* @brief Create argument list from the parameters of standard C main function.
|
||||
* @param[in] argc The argument count passed to standard C main function.
|
||||
* @param[in] argv The argument value passed to standard C main function.
|
||||
* @return Extracted argument list instance.
|
||||
* @remarks
|
||||
* First item in command line will be stripped,
|
||||
* because in most cases it points to executable self
|
||||
* and should not be seen as a part of arguments.
|
||||
*/
|
||||
static ArgumentList CreateFromStd(int argc, char* argv[]);
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
/**
|
||||
* @brief Create argument list from Win32 function.
|
||||
* @details
|
||||
* @return Extracted argument list instance.
|
||||
* @remarks
|
||||
* First item in command line will be stripped,
|
||||
* because in most cases it points to executable self
|
||||
* and should not be seen as a part of arguments.
|
||||
* \par
|
||||
* Programmer should use this function instead of CreateFromStd(),
|
||||
* because that function involve encoding issue on Windows, especially command line including non-ASCII chars.
|
||||
* Only this function guaranteen that return correct argument list on Windows.
|
||||
*/
|
||||
static ArgumentList CreateFromWin32();
|
||||
#endif
|
||||
private:
|
||||
/**
|
||||
* @brief Constructor of ArgumentList used internally.
|
||||
* @param[in] arguments
|
||||
* Underlying argument list.
|
||||
* This argument list should remove first executable name before passing it to there.
|
||||
*/
|
||||
ArgumentList(std::vector<yycc_u8string>&& arguments);
|
||||
public:
|
||||
YYCC_DEF_CLS_COPY_MOVE(ArgumentList);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Move to previous argument.
|
||||
* @exception std::runtime_error Try moving at the head of argument list.
|
||||
*/
|
||||
void Prev();
|
||||
/**
|
||||
* @brief Move to next argument.
|
||||
* @exception std::runtime_error Try moving at the tail of argument list.
|
||||
*/
|
||||
void Next();
|
||||
/**
|
||||
* @brief Get the string of current argument.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return The constant reference to the string of current argument.
|
||||
*/
|
||||
const yycc_u8string& Argument() const;
|
||||
/**
|
||||
* @brief Check whether current argument is a option / switch.
|
||||
* @param[out] is_long_name
|
||||
* It will be set true if this argument is long name, otherwise short name.
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @param[out] long_name
|
||||
* The container holding matched long name if it is (double dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @param[out] short_name
|
||||
* The variable holding matched short name if it is (dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return
|
||||
* True if it is, otherwise false.
|
||||
* If this function return false, all given parameters are in undefined status.
|
||||
*/
|
||||
bool IsSwitch(
|
||||
bool* is_long_name = nullptr,
|
||||
yycc_u8string* long_name = nullptr,
|
||||
yycc_char8_t* short_name = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether current argument is a value.
|
||||
* @param[out] val
|
||||
* The variable holding value if it is.
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @exception std::runtime_error Try fetching data at the tail of argument list.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsValue(yycc_u8string* val = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether we are at the tail of argument list.
|
||||
* @details
|
||||
* Please note EOF is a special state that you can not fetch data from it.
|
||||
* EOF is the next element of the last element of argument list.
|
||||
* It more like \c end() in most C++ container.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsEOF() const;
|
||||
/**
|
||||
* @brief Reset cursor to the head of argument list for reuse.
|
||||
*/
|
||||
void Reset();
|
||||
private:
|
||||
/**
|
||||
* @brief Check whether current argument is long name option / switch.
|
||||
* @details This function is used by IsSwitch() internally.
|
||||
* @param[out] name_part
|
||||
* The container holding matched long name if it is (double dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsLongNameSwitch(yycc_u8string* name_part = nullptr) const;
|
||||
/**
|
||||
* @brief Check whether current argument is short name option / switch.
|
||||
* @details This function is used by IsSwitch() internally.
|
||||
* @param[out] name_part
|
||||
* The variable holding matched short name if it is (dash stripped).
|
||||
* nullptr if you don't want to receive this infomation.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsShortNameSwitch(yycc_char8_t* name_part = nullptr) const;
|
||||
|
||||
private:
|
||||
std::vector<yycc_u8string> m_Arguments;
|
||||
size_t m_ArgumentsCursor;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The base class of every argument.
|
||||
* @details Programmer can inherit this class and implement essential functions to create custom argument.
|
||||
*/
|
||||
class AbstractArgument {
|
||||
friend class OptionContext;
|
||||
|
||||
// Long name and short name constants and checker.
|
||||
public:
|
||||
static const yycc_u8string DOUBLE_DASH; ///< The constant value representing double dash (\c --)
|
||||
static const yycc_char8_t DASH; ///< The constant value representing dash (\c -)
|
||||
static const yycc_char8_t NO_SHORT_NAME; ///< The constant value representing that there is not short value.
|
||||
static const yycc_char8_t MIN_SHORT_NAME; ///< The constant value representing the minimum value of valid ASCII chars in short and long name.
|
||||
static const yycc_char8_t MAX_SHORT_NAME; ///< The constant value representing the maximum value of valid ASCII chars in short and long name.
|
||||
/**
|
||||
* @brief Check whether given short name is valid.
|
||||
* @details
|
||||
* An ASCII code of valid short name
|
||||
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
|
||||
* It also can not be #DASH.
|
||||
* @param[in] short_name Short name for checking.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
static bool IsLegalShortName(yycc_char8_t short_name);
|
||||
/**
|
||||
* @brief Check whether given long name is valid.
|
||||
* @details
|
||||
* An ASCII code of every item in valid long name
|
||||
* should not lower than #MIN_SHORT_NAME or higher than #MAX_SHORT_NAME.
|
||||
* However it can be #DASH. This is different with short name.
|
||||
* @param[in] long_name Long name for checking.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
static bool IsLegalLongName(const yycc_u8string_view& long_name);
|
||||
|
||||
// Constructor & destructor
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional
|
||||
* True if this argument is optional argument.
|
||||
* Optional argument can be absent in argument list.
|
||||
* Non-optional argument must be presented in argument list,
|
||||
* otherwise parser will fail.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
AbstractArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name = AbstractArgument::NO_SHORT_NAME,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false);
|
||||
virtual ~AbstractArgument();
|
||||
YYCC_DEL_CLS_COPY_MOVE(AbstractArgument);
|
||||
|
||||
// ===== User Implementation =====
|
||||
protected:
|
||||
/**
|
||||
* @brief User implemented custom parse function
|
||||
* @param[in] al The argument list for parsing.
|
||||
* @return True if parse is success, otherwise false.
|
||||
* @remarks
|
||||
* When enter this function, argument list points to switch self.
|
||||
* After success parsing, you should point it to the argument this function last accepted.
|
||||
* For exmaple, for command line "-i 114514",
|
||||
* when enter this function, this argument list point to "-i",
|
||||
* and you should set it to "114514" when exiting this function.
|
||||
*/
|
||||
virtual bool Parse(ArgumentList& al) = 0;
|
||||
/**
|
||||
* @brief User implemented custom reset function
|
||||
* @remarks
|
||||
* In this function, user should claer its stored value if is has.
|
||||
* You don't need clar capture state. That is done by library self.
|
||||
*/
|
||||
virtual void Reset() = 0;
|
||||
|
||||
// ===== Basic Infos =====
|
||||
public:
|
||||
/// @brief Check whether this argument specify long name.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasLongName() const;
|
||||
/// @brief Get specified long name.
|
||||
/// @return Specified long name.
|
||||
const yycc_u8string& GetLongName() const;
|
||||
/// @brief Check whether this argument specify short name.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasShortName() const;
|
||||
/// @brief Get specified short name.
|
||||
/// @return Specified short name.
|
||||
yycc_char8_t GetShortName() const;
|
||||
/// @brief Check whether this argument specify description.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasDescription() const;
|
||||
/// @brief Get specified description.
|
||||
/// @return Specified description.
|
||||
const yycc_u8string& GetDescription() const;
|
||||
/// @brief Check whether this argument specify example.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool HasArgumentExample() const;
|
||||
/// @brief Get specified example.
|
||||
/// @return Specified example.
|
||||
const yycc_u8string& GetArgumentExample() const;
|
||||
/// @brief Check whether this argument is optional.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool IsOptional() const;
|
||||
private:
|
||||
yycc_u8string m_LongName;
|
||||
yycc_char8_t m_ShortName;
|
||||
yycc_u8string m_Description;
|
||||
yycc_u8string m_ArgumentExample;
|
||||
bool m_IsOptional;
|
||||
|
||||
// ===== Capture State =====
|
||||
public:
|
||||
/// @brief Check whether this argument has been captured.
|
||||
/// @return True if it is, otherwise false.
|
||||
bool IsCaptured() const;
|
||||
private:
|
||||
/**
|
||||
* @brief Set capture state of this argument.
|
||||
* @details This function is used internally by OptionContext.
|
||||
* @param[in] is_captured New states of captured.
|
||||
*/
|
||||
void SetCaptured(bool is_captured);
|
||||
bool m_IsCaptured;
|
||||
};
|
||||
|
||||
/// @brief The core of argument parser, also manage all arguments.
|
||||
class OptionContext {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct option context.
|
||||
* @param[in] summary The summary of this application which will be printed in help text.
|
||||
* @param[in] description The description of this application which will be printed in help text.
|
||||
* @param[in] arguments The initializer list including pointers to all arguments.
|
||||
*/
|
||||
OptionContext(
|
||||
const yycc_char8_t* summary, const yycc_char8_t* description,
|
||||
std::initializer_list<AbstractArgument*> arguments);
|
||||
~OptionContext();
|
||||
YYCC_DEL_CLS_COPY_MOVE(OptionContext);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Start a parse.
|
||||
* @param[in] al The reference to ArgumentList for parsing.
|
||||
* @return
|
||||
* True if success, otherwise false.
|
||||
* If this function return false, you should not visit any arguments it managed.
|
||||
*/
|
||||
bool Parse(ArgumentList& al);
|
||||
/**
|
||||
* @brief Reset all managed argument to default state thus you can start another parsing.
|
||||
*/
|
||||
void Reset();
|
||||
/**
|
||||
* @brief Print help text in \c stdout.
|
||||
*/
|
||||
void Help() const;
|
||||
|
||||
private:
|
||||
yycc_u8string m_Summary;
|
||||
yycc_u8string m_Description;
|
||||
|
||||
std::vector<AbstractArgument*> m_Arguments;
|
||||
std::map<yycc_u8string, AbstractArgument*> m_LongNameMap;
|
||||
std::map<yycc_char8_t, AbstractArgument*> m_ShortNameMap;
|
||||
};
|
||||
|
||||
#pragma region Argument Presets
|
||||
|
||||
/**
|
||||
* @brief Arithmetic (integral, floating point. except bool) type argument
|
||||
* @tparam _Ty The internal stored type belongs to arithmetic type.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
|
||||
class NumberArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an arithmetic argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional True if this argument is optional argument.
|
||||
* @param[in] constraint The constraint applied to this argument.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
NumberArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false,
|
||||
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
|
||||
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
|
||||
virtual ~NumberArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(NumberArgument);
|
||||
|
||||
public:
|
||||
/// @brief Get stored data in argument.
|
||||
_Ty Get() const {
|
||||
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
|
||||
return m_Data;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override {
|
||||
// try get corresponding value
|
||||
yycc_u8string strval;
|
||||
al.Next();
|
||||
if (al.IsEOF() || !al.IsValue(&strval)) {
|
||||
al.Prev();
|
||||
return false;
|
||||
}
|
||||
// try parsing value
|
||||
if (!YYCC::ParserHelper::TryParse<_Ty>(strval, m_Data)) return false;
|
||||
// check constraint
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
virtual void Reset() override {
|
||||
std::memset(&m_Data, 0, sizeof(m_Data));
|
||||
}
|
||||
|
||||
protected:
|
||||
_Ty m_Data;
|
||||
Constraints::Constraint<_Ty> m_Constraint;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A simple switch type argument which do not store any value.
|
||||
*/
|
||||
class SwitchArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor an switch argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
SwitchArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr) :
|
||||
// bool switch must be optional, because it is false if no given switch.
|
||||
// bool switch doesn't have argument, so it doesn't have example property.
|
||||
AbstractArgument(long_name, short_name, description, nullptr, true) {}
|
||||
virtual ~SwitchArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(SwitchArgument);
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override { return true; } // simply return true because no value to store.
|
||||
virtual void Reset() override {} // nothing need to be reset.
|
||||
};
|
||||
|
||||
/// @brief String type argument
|
||||
class StringArgument : public AbstractArgument {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor a string argument
|
||||
* @param[in] long_name The long name of this argument. nullptr if no long name.
|
||||
* @param[in] short_name The short name of this argument. #NO_SHORT_NAME if no short name.
|
||||
* @param[in] description The description of this argument to indroduce what this argument does. nullptr if no description.
|
||||
* @param[in] argument_example The example string of this argument's value. nullptr if no example.
|
||||
* @param[in] is_optional True if this argument is optional argument.
|
||||
* @param[in] constraint The constraint applied to this argument.
|
||||
* @exception std::invalid_argument Given short name or long name are invalid.
|
||||
*/
|
||||
StringArgument(
|
||||
const yycc_char8_t* long_name, yycc_char8_t short_name,
|
||||
const yycc_char8_t* description = nullptr, const yycc_char8_t* argument_example = nullptr,
|
||||
bool is_optional = false,
|
||||
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
|
||||
AbstractArgument(long_name, short_name, description, argument_example, is_optional), m_Data(), m_Constraint(constraint) {}
|
||||
virtual ~StringArgument() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(StringArgument);
|
||||
|
||||
public:
|
||||
/// @brief Get stored data in argument.
|
||||
const yycc_u8string& Get() const {
|
||||
if (!IsCaptured()) throw std::runtime_error("try fetching data from a not captured argument.");
|
||||
return m_Data;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool Parse(ArgumentList& al) override {
|
||||
// try get corresponding value
|
||||
al.Next();
|
||||
if (al.IsEOF() || !al.IsValue(&m_Data)) {
|
||||
al.Prev();
|
||||
return false;
|
||||
}
|
||||
// check constraint
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
// okey
|
||||
return true;
|
||||
}
|
||||
virtual void Reset() override {
|
||||
m_Data.clear();
|
||||
}
|
||||
|
||||
protected:
|
||||
yycc_u8string m_Data;
|
||||
Constraints::Constraint<yycc_u8string> m_Constraint;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
}
|
186
src/YYCCLegacy/ConfigManager.cpp
Normal file
186
src/YYCCLegacy/ConfigManager.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include "ConfigManager.hpp"
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "IOHelper.hpp"
|
||||
#include "EnumHelper.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace YYCC::ConfigManager {
|
||||
|
||||
#pragma region Abstract Setting
|
||||
|
||||
AbstractSetting::AbstractSetting(const yycc_u8string_view& name) : m_Name(name), m_RawData() {
|
||||
if (m_Name.empty())
|
||||
throw std::invalid_argument("the name of setting should not be empty");
|
||||
}
|
||||
|
||||
AbstractSetting::~AbstractSetting() {}
|
||||
|
||||
const yycc_u8string& AbstractSetting::GetName() const { return m_Name; }
|
||||
|
||||
void AbstractSetting::ResizeData(size_t new_size) { m_RawData.resize(new_size); }
|
||||
const void* AbstractSetting::GetDataPtr() const { return m_RawData.data(); }
|
||||
void* AbstractSetting::GetDataPtr() { return m_RawData.data(); }
|
||||
size_t AbstractSetting::GetDataSize() const { return m_RawData.size(); }
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Core Manager
|
||||
|
||||
CoreManager::CoreManager(
|
||||
const yycc_u8string_view& cfg_file_path,
|
||||
uint64_t version_identifier,
|
||||
std::initializer_list<AbstractSetting*> settings) :
|
||||
m_CfgFilePath(cfg_file_path), m_VersionIdentifier(version_identifier), m_Settings() {
|
||||
// Mark: no need to check cfg file path
|
||||
// it will be checked at creating file handle
|
||||
|
||||
// assign settings
|
||||
for (auto* setting : settings) {
|
||||
auto result = m_Settings.try_emplace(setting->GetName(), setting);
|
||||
if (!result.second) {
|
||||
// if not inserted because duplicated, raise exception
|
||||
throw std::invalid_argument("Duplicated setting name");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigLoadResult CoreManager::Load() {
|
||||
// prepare result variables
|
||||
ConfigLoadResult ret = ConfigLoadResult::OK;
|
||||
|
||||
// reset all settings first
|
||||
Reset();
|
||||
|
||||
// get file handle
|
||||
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("rb")));
|
||||
if (fs.get() == nullptr) {
|
||||
// if we fail to get, it means that we do not have corresponding cfg file.
|
||||
// all settings should be reset to default value.
|
||||
ret = ConfigLoadResult::Created;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// fetch version info
|
||||
uint64_t version_info;
|
||||
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
|
||||
ret = ConfigLoadResult::Created;
|
||||
return ret;
|
||||
}
|
||||
// check version
|
||||
// if read version is greater than we expected,
|
||||
// it means that this cfg file is created by the program higer than this.
|
||||
// we should not read anything from it.
|
||||
// however, for compaitibility reason, we allow read old cfg data.
|
||||
if (version_info > m_VersionIdentifier) {
|
||||
ret = ConfigLoadResult::ForwardNew;
|
||||
return ret;
|
||||
} else if (version_info < m_VersionIdentifier) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::Migrated);
|
||||
}
|
||||
|
||||
// fetch setting item from file
|
||||
yycc_u8string name_cache;
|
||||
while (true) {
|
||||
// try fetch setting name
|
||||
// fetch name length
|
||||
size_t name_length;
|
||||
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
|
||||
// we also check whether reach EOF at there.
|
||||
if (std::feof(fs.get())) break;
|
||||
else {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
// fetch name body
|
||||
name_cache.resize(name_length);
|
||||
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// get setting data length
|
||||
size_t data_length;
|
||||
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// get matched setting first
|
||||
const auto& found = m_Settings.find(name_cache);
|
||||
if (found != m_Settings.end()) {
|
||||
// found. read data for it
|
||||
found->second->ResizeData(data_length);
|
||||
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
// call user defined load function
|
||||
// if fail to parse, reset to default value
|
||||
if (!found->second->UserLoad()) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::ItemError);
|
||||
found->second->UserReset();
|
||||
}
|
||||
} else {
|
||||
// fail to find. skip this unknown setting
|
||||
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) {
|
||||
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool CoreManager::Save() {
|
||||
// get file handle
|
||||
IOHelper::SmartStdFile fs(IOHelper::UTF8FOpen(m_CfgFilePath.c_str(), YYCC_U8("wb")));
|
||||
// if we fail to get, return false.
|
||||
if (fs == nullptr) return false;
|
||||
|
||||
// write config data
|
||||
uint64_t version_info = m_VersionIdentifier;
|
||||
if (std::fwrite(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info))
|
||||
return false;
|
||||
|
||||
// iterate all data for writing
|
||||
for (const auto& pair : m_Settings) {
|
||||
// do user defined save
|
||||
// if failed, skip this setting
|
||||
if (!pair.second->UserSave())
|
||||
continue;
|
||||
|
||||
// write setting name
|
||||
// write name length
|
||||
size_t name_length = pair.first.size();
|
||||
if (std::fwrite(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length))
|
||||
return false;
|
||||
// write name body
|
||||
if (std::fwrite(pair.first.c_str(), 1u, name_length, fs.get()) != name_length)
|
||||
return false;
|
||||
|
||||
// write setting daat
|
||||
// write data length
|
||||
size_t data_length = pair.second->GetDataSize();
|
||||
if (std::fwrite(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length))
|
||||
return false;
|
||||
// write data body
|
||||
if (std::fwrite(pair.second->GetDataPtr(), 1u, data_length, fs.get()) != data_length)
|
||||
return false;
|
||||
}
|
||||
|
||||
// all settings done, return true
|
||||
return true;
|
||||
}
|
||||
|
||||
void CoreManager::Reset() {
|
||||
for (const auto& pair : m_Settings) {
|
||||
pair.second->UserReset();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
}
|
269
src/YYCCLegacy/ConfigManager.hpp
Normal file
269
src/YYCCLegacy/ConfigManager.hpp
Normal file
@ -0,0 +1,269 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "Constraints.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
/**
|
||||
* @brief Universal configuration manager
|
||||
* @details For how to use this namespace, please see \ref config_manager.
|
||||
*/
|
||||
namespace YYCC::ConfigManager {
|
||||
|
||||
/**
|
||||
* @brief The load result of loading config.
|
||||
*/
|
||||
enum class ConfigLoadResult {
|
||||
OK = 0, ///< Success load configs.
|
||||
Created = 1 << 0, ///< Given file is not existing, we create all configs in default values.
|
||||
ForwardNew = 1 << 1, ///< Detect the config file created by higher version. We create all configs in default values.
|
||||
Migrated = 1 << 2, ///< Detect the config file created by lower version. We try migrate configs written in it.
|
||||
BrokenFile = 1 << 3, ///< Given file has bad format. Thus some configs are kept as its default values.
|
||||
ItemError = 1 << 4 ///< Some config can not be recognized from the data read from file so they are reset to default value.
|
||||
};
|
||||
using UnderlyingConfigLoadResult_t = std::underlying_type_t<ConfigLoadResult>;
|
||||
|
||||
/// @brief The base class of every setting.
|
||||
/// @details Programmer can inherit this class and implement essential functions to create custom setting.
|
||||
class AbstractSetting {
|
||||
friend class CoreManager;
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a setting
|
||||
* @param[in] name The name of this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
AbstractSetting(const yycc_u8string_view& name);
|
||||
virtual ~AbstractSetting();
|
||||
YYCC_DEL_CLS_COPY_MOVE(AbstractSetting);
|
||||
|
||||
// Name interface
|
||||
public:
|
||||
/// @brief Get name of this setting.
|
||||
/// @details Name was used in storing setting in file.
|
||||
const yycc_u8string& GetName() const;
|
||||
private:
|
||||
yycc_u8string m_Name;
|
||||
|
||||
// User Implementations
|
||||
protected:
|
||||
/// @brief User implemented custom load function
|
||||
/// @remarks
|
||||
/// In this function, programmer should read data from internal buffer
|
||||
/// and store it to its own another internal variables.
|
||||
/// @return True if success, otherwise false.
|
||||
virtual bool UserLoad() = 0;
|
||||
/// @brief User implemented custom save function
|
||||
/// @remarks
|
||||
/// In this function, programmer should write data,
|
||||
/// which is stored in another variavle by it own, to internal buffer.
|
||||
/// @return True if success, otherwise false.
|
||||
virtual bool UserSave() = 0;
|
||||
/// @brief User implemented custom reset function
|
||||
/// @remarks In this function, programmer should reset its internal variable to default value.
|
||||
virtual void UserReset() = 0;
|
||||
|
||||
// Buffer related functions
|
||||
protected:
|
||||
/// @brief Resize internal buffer to given size.
|
||||
/// @remarks It is usually used in UserSave.
|
||||
/// @param[in] new_size The new size of internal buffer.
|
||||
void ResizeData(size_t new_size);
|
||||
/// @brief Get data pointer to internal buffer.
|
||||
/// @remarks It is usually used in UserLoad.
|
||||
const void* GetDataPtr() const;
|
||||
/// @brief Get mutable data pointer to internal buffer.
|
||||
/// @remarks It is usually used in UserSave.
|
||||
void* GetDataPtr();
|
||||
/// @brief Get the length of internal buffer.
|
||||
size_t GetDataSize() const;
|
||||
private:
|
||||
std::vector<uint8_t> m_RawData;
|
||||
};
|
||||
|
||||
/// @brief Settings manager and config file reader writer.
|
||||
class CoreManager {
|
||||
public:
|
||||
/**
|
||||
* @brief Build core manager.
|
||||
* @param[in] cfg_file_path The path to config file.
|
||||
* @param[in] version_identifier The identifier of version. Higher is newer. Lower config will try doing migration.
|
||||
* @param[in] settings An initializer list containing pointers to all managed settings.
|
||||
*/
|
||||
CoreManager(
|
||||
const yycc_u8string_view& cfg_file_path,
|
||||
uint64_t version_identifier,
|
||||
std::initializer_list<AbstractSetting*> settings);
|
||||
~CoreManager() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(CoreManager);
|
||||
|
||||
// Core functions
|
||||
public:
|
||||
/// @brief Load settings from file.
|
||||
/// @details Before loading, all settings will be reset to default value first.
|
||||
/// @return What happend when loading config. This function always success.
|
||||
ConfigLoadResult Load();
|
||||
/// @brief Save settings to file.
|
||||
/// @return True if success, otherwise false.
|
||||
bool Save();
|
||||
/// @brief Reset all settings to default value.
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
yycc_u8string m_CfgFilePath;
|
||||
uint64_t m_VersionIdentifier;
|
||||
std::map<yycc_u8string, AbstractSetting*> m_Settings;
|
||||
};
|
||||
|
||||
#pragma region Setting Presets
|
||||
|
||||
/**
|
||||
* @brief Arithmetic (integral, floating point, bool) and enum type setting
|
||||
* @tparam _Ty The internal stored type belongs to arithmetic type.
|
||||
*/
|
||||
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> || std::is_enum_v<_Ty>, int> = 0>
|
||||
class NumberSetting : public AbstractSetting {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct arithmetic type setting.
|
||||
* @param[in] name The name of this setting.
|
||||
* @param[in] default_value The default value of this setting.
|
||||
* @param[in] constraint The constraint applied to this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
NumberSetting(
|
||||
const yycc_u8string_view& name, _Ty default_value,
|
||||
Constraints::Constraint<_Ty> constraint = Constraints::Constraint<_Ty> {}) :
|
||||
AbstractSetting(name), m_Data(default_value), m_DefaultData(default_value), m_Constraint(constraint) {}
|
||||
virtual ~NumberSetting() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(NumberSetting);
|
||||
|
||||
/// @brief Get stored data in setting.
|
||||
_Ty Get() const { return m_Data; }
|
||||
/**
|
||||
* @brief Set data to setting.
|
||||
* @param[in] new_data The new data.
|
||||
* @return True if success, otherwise false (given value is invalid)
|
||||
*/
|
||||
bool Set(_Ty new_data) {
|
||||
// validate data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data))
|
||||
return false;
|
||||
// assign data
|
||||
m_Data = new_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool UserLoad() override {
|
||||
// read data
|
||||
if (sizeof(m_Data) != GetDataSize())
|
||||
return false;
|
||||
m_Data = *reinterpret_cast<const _Ty*>(GetDataPtr());
|
||||
// check data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
virtual bool UserSave() override {
|
||||
// write data
|
||||
ResizeData(sizeof(m_Data));
|
||||
*reinterpret_cast<_Ty*>(GetDataPtr()) = m_Data;
|
||||
return true;
|
||||
}
|
||||
virtual void UserReset() override {
|
||||
m_Data = m_DefaultData;
|
||||
}
|
||||
|
||||
_Ty m_Data, m_DefaultData;
|
||||
Constraints::Constraint<_Ty> m_Constraint;
|
||||
};
|
||||
|
||||
/// @brief String type setting
|
||||
class StringSetting : public AbstractSetting {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct string setting
|
||||
* @param[in] name The name of this setting.
|
||||
* @param[in] default_value The default value of this setting.
|
||||
* @param[in] constraint The constraint applied to this setting.
|
||||
* @exception std::invalid_argument Name of setting is empty.
|
||||
*/
|
||||
StringSetting(
|
||||
const yycc_u8string_view& name, const yycc_u8string_view& default_value,
|
||||
Constraints::Constraint<yycc_u8string> constraint = Constraints::Constraint<yycc_u8string> {}) :
|
||||
AbstractSetting(name), m_Data(), m_DefaultData(), m_Constraint(constraint) {
|
||||
m_Data = default_value;
|
||||
m_DefaultData = default_value;
|
||||
}
|
||||
virtual ~StringSetting() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(StringSetting);
|
||||
|
||||
/// @brief Get reference to stored string.
|
||||
const yycc_u8string& Get() const { return m_Data; }
|
||||
/**
|
||||
* @brief Set string data to setting.
|
||||
* @param[in] new_data The new string data.
|
||||
* @return True if success, otherwise false (given value is invalid)
|
||||
*/
|
||||
bool Set(const yycc_u8string_view& new_data) {
|
||||
// check data validation
|
||||
yycc_u8string new_data_cache(new_data);
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(new_data_cache))
|
||||
return false;
|
||||
// assign data
|
||||
m_Data = std::move(new_data_cache);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool UserLoad() override {
|
||||
// read string length
|
||||
size_t string_length;
|
||||
if (GetDataSize() < sizeof(string_length))
|
||||
return false;
|
||||
string_length = *reinterpret_cast<const size_t*>(GetDataPtr());
|
||||
// read string body
|
||||
if (GetDataSize() != sizeof(string_length) + string_length)
|
||||
return false;
|
||||
m_Data.assign(
|
||||
reinterpret_cast<const yycc_char8_t*>(static_cast<const uint8_t*>(GetDataPtr()) + sizeof(string_length)),
|
||||
string_length
|
||||
);
|
||||
// check data
|
||||
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(m_Data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
virtual bool UserSave() override {
|
||||
// allocate result buffer
|
||||
size_t string_length = m_Data.size();
|
||||
ResizeData(sizeof(string_length) + string_length);
|
||||
// get pointer
|
||||
uint8_t* ptr = static_cast<uint8_t*>(GetDataPtr());
|
||||
// assign string length
|
||||
*reinterpret_cast<size_t*>(ptr) = string_length;
|
||||
// assign string body
|
||||
std::memcpy(ptr + sizeof(string_length), m_Data.data(), string_length);
|
||||
return true;
|
||||
}
|
||||
virtual void UserReset() override {
|
||||
m_Data = m_DefaultData;
|
||||
}
|
||||
|
||||
yycc_u8string m_Data, m_DefaultData;
|
||||
Constraints::Constraint<yycc_u8string> m_Constraint;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
#include <iostream>
|
||||
|
||||
// Include Windows used headers in Windows.
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <io.h>
|
||||
@ -16,7 +16,7 @@
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
#pragma region Windows Specific Functions
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
static bool RawEnableColorfulConsole(FILE* fs) {
|
||||
if (!_isatty(_fileno(fs))) return false;
|
||||
@ -54,7 +54,7 @@ namespace YYCC::ConsoleHelper {
|
||||
*/
|
||||
|
||||
template<bool _bIsConsole>
|
||||
static std::string WinConsoleRead(HANDLE hStdIn) {
|
||||
static yycc_u8string WinConsoleRead(HANDLE hStdIn) {
|
||||
using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>;
|
||||
|
||||
// Prepare an internal buffer because the read data may not be fully used.
|
||||
@ -113,21 +113,21 @@ namespace YYCC::ConsoleHelper {
|
||||
}
|
||||
|
||||
// post-process for return value
|
||||
std::string real_return_buffer;
|
||||
yycc_u8string real_return_buffer;
|
||||
if constexpr (_bIsConsole) {
|
||||
// console mode need convert wchar to utf8
|
||||
YYCC::EncodingHelper::WcharToUTF8(return_buffer.c_str(), real_return_buffer);
|
||||
YYCC::EncodingHelper::WcharToUTF8(return_buffer, real_return_buffer);
|
||||
} else {
|
||||
// non-console just copt the result
|
||||
real_return_buffer = return_buffer;
|
||||
real_return_buffer = EncodingHelper::ToUTF8(return_buffer);
|
||||
}
|
||||
// every mode need delete \r words
|
||||
YYCC::StringHelper::Replace(real_return_buffer, "\r", "");
|
||||
YYCC::StringHelper::Replace(real_return_buffer, YYCC_U8("\r"), YYCC_U8(""));
|
||||
// return value
|
||||
return real_return_buffer;
|
||||
}
|
||||
|
||||
static void WinConsoleWrite(const std::string& strl, bool to_stderr) {
|
||||
static void WinConsoleWrite(const yycc_u8string& strl, bool to_stderr) {
|
||||
// Prepare some Win32 variables
|
||||
// fetch stdout handle first
|
||||
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
|
||||
@ -139,7 +139,7 @@ namespace YYCC::ConsoleHelper {
|
||||
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
|
||||
// console handle, use WriteConsoleW.
|
||||
// convert utf8 string to wide char first
|
||||
std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl.c_str()));
|
||||
std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl));
|
||||
size_t wstrl_size = wstrl.size();
|
||||
// write string with size check
|
||||
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
@ -149,7 +149,7 @@ namespace YYCC::ConsoleHelper {
|
||||
// anything else, use WriteFile instead.
|
||||
// WriteFile do not need extra convertion, because it is direct writing.
|
||||
// check whether string length is overflow
|
||||
size_t strl_size = strl.size() * sizeof(std::string::value_type);
|
||||
size_t strl_size = strl.size() * sizeof(yycc_u8string::value_type);
|
||||
// write string with size check
|
||||
if (strl_size <= std::numeric_limits<DWORD>::max()) {
|
||||
WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
|
||||
@ -161,7 +161,7 @@ namespace YYCC::ConsoleHelper {
|
||||
#pragma endregion
|
||||
|
||||
bool EnableColorfulConsole() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
bool ret = true;
|
||||
ret &= RawEnableColorfulConsole(stdout);
|
||||
@ -176,8 +176,8 @@ namespace YYCC::ConsoleHelper {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string ReadLine() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
yycc_u8string ReadLine() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
// get stdin mode
|
||||
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
@ -188,24 +188,24 @@ namespace YYCC::ConsoleHelper {
|
||||
} else {
|
||||
return WinConsoleRead<false>(hStdIn);
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
// in linux, directly use C++ function to fetch.
|
||||
std::string cmd;
|
||||
if (std::getline(std::cin, cmd).fail()) cmd.clear();
|
||||
return cmd;
|
||||
return EncodingHelper::ToUTF8(cmd);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
template<bool bNeedFmt, bool bIsErr, bool bHasEOL>
|
||||
static void RawWrite(const char* u8_fmt, va_list argptr) {
|
||||
static void RawWrite(const yycc_char8_t* u8_fmt, va_list argptr) {
|
||||
// Buiild string need to be written first
|
||||
// If no format string or plain string for writing, return.
|
||||
if (u8_fmt == nullptr) return;
|
||||
// Build or simply copy string
|
||||
std::string strl;
|
||||
yycc_u8string strl;
|
||||
if constexpr (bNeedFmt) {
|
||||
// treat as format string
|
||||
va_list argcpy;
|
||||
@ -218,62 +218,62 @@ namespace YYCC::ConsoleHelper {
|
||||
}
|
||||
// Checkout whether add EOL
|
||||
if constexpr (bHasEOL) {
|
||||
strl += "\n";
|
||||
strl += YYCC_U8("\n");
|
||||
}
|
||||
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// call Windows specific writer
|
||||
WinConsoleWrite(strl, bIsErr);
|
||||
#else
|
||||
// in linux, directly use C function to write.
|
||||
std::fputs(strl.c_str(), bIsErr ? stderr : stdout);
|
||||
std::fputs(EncodingHelper::ToOrdinary(strl.c_str()), bIsErr ? stderr : stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Format(const char* u8_fmt, ...) {
|
||||
void Format(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, false, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void FormatLine(const char* u8_fmt, ...) {
|
||||
void FormatLine(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, false, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void Write(const char* u8_strl) {
|
||||
void Write(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, false, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void WriteLine(const char* u8_strl) {
|
||||
void WriteLine(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, false, true>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ErrFormat(const char* u8_fmt, ...) {
|
||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, true, false>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ErrFormatLine(const char* u8_fmt, ...) {
|
||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...) {
|
||||
va_list argptr;
|
||||
va_start(argptr, u8_fmt);
|
||||
RawWrite<true, true, true>(u8_fmt, argptr);
|
||||
va_end(argptr);
|
||||
}
|
||||
|
||||
void ErrWrite(const char* u8_strl) {
|
||||
void ErrWrite(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, true, false>(u8_strl, empty);
|
||||
}
|
||||
|
||||
void ErrWriteLine(const char* u8_strl) {
|
||||
void ErrWriteLine(const yycc_char8_t* u8_strl) {
|
||||
va_list empty{};
|
||||
RawWrite<false, true, true>(u8_strl, empty);
|
||||
}
|
92
src/YYCCLegacy/ConsoleHelper.hpp
Normal file
92
src/YYCCLegacy/ConsoleHelper.hpp
Normal file
@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief The helper providing universal C\# style console function and other console related stuff
|
||||
* @details
|
||||
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
|
||||
*/
|
||||
namespace YYCC::ConsoleHelper {
|
||||
|
||||
/**
|
||||
* @brief Enable console color support for Windows.
|
||||
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks
|
||||
* This function only works on Windows and do nothing on other platforms such as Linux,
|
||||
* because we assume all terminals existing on other platform support color feature as default.
|
||||
*/
|
||||
bool EnableColorfulConsole();
|
||||
|
||||
/**
|
||||
* @brief Reads the next line of UTF8 characters from the standard input stream.
|
||||
* @return
|
||||
* The next line of UTF8 characters from the input stream.
|
||||
* Empty string if user just press Enter key or function failed.
|
||||
*/
|
||||
yycc_u8string ReadLine();
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void Format(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard output stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void FormatLine(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void Write(const yycc_char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard output stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void WriteLine(const yycc_char8_t* u8_strl);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the text representation of the specified object,
|
||||
* followed by the current line terminator,
|
||||
* to the standard error stream using the specified format information.
|
||||
* @param[in] u8_fmt The format string.
|
||||
* @param[in] ... The arguments of format string.
|
||||
*/
|
||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...);
|
||||
/**
|
||||
* @brief Writes the specified string value to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ErrWrite(const yycc_char8_t* u8_strl);
|
||||
/**
|
||||
* @brief
|
||||
* Writes the specified string value, followed by the current line terminator,
|
||||
* to the standard error stream.
|
||||
* @param[in] u8_strl The value to write.
|
||||
*/
|
||||
void ErrWriteLine(const yycc_char8_t* u8_strl);
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
#include "DialogHelper.hpp"
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
@ -8,16 +8,16 @@ namespace YYCC::DialogHelper {
|
||||
|
||||
#pragma region FileFilters
|
||||
|
||||
bool FileFilters::Add(const char* filter_name, std::initializer_list<const char*> il) {
|
||||
bool FileFilters::Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il) {
|
||||
// assign filter name
|
||||
if (filter_name == nullptr) return false;
|
||||
FilterName name(filter_name);
|
||||
|
||||
// assign filter patterns
|
||||
FilterModes modes;
|
||||
for (const char* pattern : il) {
|
||||
for (const yycc_char8_t* pattern : il) {
|
||||
if (pattern != nullptr)
|
||||
modes.emplace_back(std::string(pattern));
|
||||
modes.emplace_back(yycc_u8string(pattern));
|
||||
}
|
||||
|
||||
// check filter patterns
|
||||
@ -36,13 +36,14 @@ namespace YYCC::DialogHelper {
|
||||
for (const auto& it : m_Filters) {
|
||||
// convert name to wchar
|
||||
WinFileFilters::WinFilterName name;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first.c_str(), name))
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(it.first, name))
|
||||
return false;
|
||||
|
||||
// convert pattern and join them
|
||||
std::string joined_modes(YYCC::StringHelper::Join(it.second, ";"));
|
||||
const auto& filter_modes = it.second;
|
||||
yycc_u8string joined_modes(YYCC::StringHelper::Join(filter_modes.begin(), filter_modes.end(), YYCC_U8(";")));
|
||||
WinFileFilters::WinFilterModes modes;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes.c_str(), modes))
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(joined_modes, modes))
|
||||
return false;
|
||||
|
||||
// append new pair
|
||||
@ -94,12 +95,12 @@ namespace YYCC::DialogHelper {
|
||||
|
||||
// build title and init file name
|
||||
if (m_HasTitle) {
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_Title.c_str(), win_result.m_WinTitle))
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_Title, win_result.m_WinTitle))
|
||||
return false;
|
||||
win_result.m_HasTitle = true;
|
||||
}
|
||||
if (m_HasInitFileName) {
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitFileName.c_str(), win_result.m_WinInitFileName))
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitFileName, win_result.m_WinInitFileName))
|
||||
return false;
|
||||
win_result.m_HasInitFileName = true;
|
||||
}
|
||||
@ -108,7 +109,7 @@ namespace YYCC::DialogHelper {
|
||||
if (m_HasInitDirectory) {
|
||||
// convert to wpath
|
||||
std::wstring w_init_directory;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitDirectory.c_str(), w_init_directory))
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(m_InitDirectory, w_init_directory))
|
||||
return false;
|
||||
|
||||
// fetch IShellItem*
|
||||
@ -143,7 +144,7 @@ namespace YYCC::DialogHelper {
|
||||
* @return True if success, otherwise false.
|
||||
* @remarks This is an assist function of CommonFileDialog.
|
||||
*/
|
||||
static bool ExtractDisplayName(IShellItem* item, std::string& ret) {
|
||||
static bool ExtractDisplayName(IShellItem* item, yycc_u8string& ret) {
|
||||
// fetch display name from IShellItem*
|
||||
LPWSTR _name;
|
||||
HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name);
|
||||
@ -168,7 +169,7 @@ namespace YYCC::DialogHelper {
|
||||
* @remarks This function is the real underlying function of all dialog functions.
|
||||
*/
|
||||
template<CommonFileDialogType EDialogType>
|
||||
static bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) {
|
||||
static bool CommonFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
|
||||
// prepare result variable
|
||||
HRESULT hr;
|
||||
@ -289,7 +290,7 @@ namespace YYCC::DialogHelper {
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
|
||||
// extract display name
|
||||
std::string result_name;
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name))
|
||||
return false;
|
||||
|
||||
@ -326,7 +327,7 @@ namespace YYCC::DialogHelper {
|
||||
COMHelper::SmartIShellItem result_item(_item);
|
||||
|
||||
// extract display name
|
||||
std::string result_name;
|
||||
yycc_u8string result_name;
|
||||
if (!ExtractDisplayName(result_item.get(), result_name))
|
||||
return false;
|
||||
|
||||
@ -347,24 +348,24 @@ namespace YYCC::DialogHelper {
|
||||
|
||||
#pragma region Wrapper Functions
|
||||
|
||||
bool OpenFileDialog(const FileDialog& params, std::string& ret) {
|
||||
std::vector<std::string> cache;
|
||||
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::OpenFile>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<std::string>& ret) {
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret) {
|
||||
return CommonFileDialog<CommonFileDialogType::OpenMultipleFiles>(params, ret);
|
||||
}
|
||||
bool SaveFileDialog(const FileDialog& params, std::string& ret) {
|
||||
std::vector<std::string> cache;
|
||||
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::SaveFile>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
||||
}
|
||||
|
||||
bool OpenFolderDialog(const FileDialog& params, std::string& ret) {
|
||||
std::vector<std::string> cache;
|
||||
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret) {
|
||||
std::vector<yycc_u8string> cache;
|
||||
bool isok = CommonFileDialog<CommonFileDialogType::OpenFolder>(params, cache);
|
||||
if (isok) ret = cache.front();
|
||||
return isok;
|
312
src/YYCCLegacy/DialogHelper.hpp
Normal file
312
src/YYCCLegacy/DialogHelper.hpp
Normal file
@ -0,0 +1,312 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "COMHelper.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <shlobj_core.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The namespace providing Windows universal dialog features.
|
||||
* @details
|
||||
* This namespace only available on Windows platform.
|
||||
* See also \ref dialog_helper.
|
||||
*/
|
||||
namespace YYCC::DialogHelper {
|
||||
|
||||
/**
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileFilters {
|
||||
friend class FileFilters;
|
||||
friend class WinFileDialog;
|
||||
public:
|
||||
WinFileFilters() : m_WinFilters(), m_WinDataStruct(nullptr) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(WinFileFilters);
|
||||
|
||||
/// @brief Get the count of available file filters
|
||||
UINT GetFilterCount() const {
|
||||
return static_cast<UINT>(m_WinFilters.size());
|
||||
}
|
||||
/// @brief Get pointer to Windows used file filters declarations
|
||||
const COMDLG_FILTERSPEC* GetFilterSpecs() const {
|
||||
return m_WinDataStruct.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
using WinFilterModes = std::wstring;
|
||||
using WinFilterName = std::wstring;
|
||||
using WinFilterPair = std::pair<WinFilterName, WinFilterModes>;
|
||||
|
||||
std::vector<WinFilterPair> m_WinFilters;
|
||||
std::unique_ptr<COMDLG_FILTERSPEC[]> m_WinDataStruct;
|
||||
|
||||
/// @brief Clear all current file filters
|
||||
void Clear() {
|
||||
m_WinDataStruct.reset();
|
||||
m_WinFilters.clear();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file types region in file dialog.
|
||||
* @details
|
||||
* This class is served for programmer using.
|
||||
* But you don't need create it on your own.
|
||||
* You can simply fetch it by FileDialog::ConfigreFileTypes ,
|
||||
* because this class is a part of FileDialog.
|
||||
*/
|
||||
class FileFilters {
|
||||
public:
|
||||
FileFilters() : m_Filters() {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(FileFilters);
|
||||
|
||||
/**
|
||||
* @brief Add a filter pair in file types list.
|
||||
* @param[in] filter_name The friendly name of the filter.
|
||||
* @param[in] il
|
||||
* A C++ initialize list containing acceptable file filter pattern.
|
||||
* Every entries must be `const yycc_char8_t*` representing a single filter pattern.
|
||||
* The list at least should have one valid pattern.
|
||||
* This function will not validate these filter patterns, so please write them carefully.
|
||||
* @return True if added success, otherwise false.
|
||||
* @remarks
|
||||
* This function allow you register multiple filter patterns for single friendly name.
|
||||
* For example: <TT>Add(u8"Microsoft Word (*.doc; *.docx)", {u8"*.doc", u8"*.docx"})</TT>
|
||||
*/
|
||||
bool Add(const yycc_char8_t* filter_name, std::initializer_list<const yycc_char8_t*> il);
|
||||
/**
|
||||
* @brief Get the count of added filter pairs.
|
||||
* @return The count of already added filter pairs.
|
||||
*/
|
||||
size_t Count() const { return m_Filters.size(); }
|
||||
|
||||
/// @brief Clear filter pairs for following re-use.
|
||||
void Clear() { m_Filters.clear(); }
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param[out] win_result The class receiving the generated filter data struct.
|
||||
* @return True if generation success, otherwise false.
|
||||
* @remarks
|
||||
* Programmer should not call this function,
|
||||
* this function is used as YYCC internal code.
|
||||
*/
|
||||
bool Generate(WinFileFilters& win_result) const;
|
||||
|
||||
protected:
|
||||
using FilterModes = std::vector<yycc_u8string>;
|
||||
using FilterName = yycc_u8string;
|
||||
using FilterPair = std::pair<FilterName, FilterModes>;
|
||||
|
||||
std::vector<FilterPair> m_Filters;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for Windows used.
|
||||
* Programmer should \b not create this class manually.
|
||||
*/
|
||||
class WinFileDialog {
|
||||
friend class FileDialog;
|
||||
public:
|
||||
WinFileDialog() :
|
||||
m_WinOwner(NULL),
|
||||
m_WinFileTypes(), m_WinDefaultFileTypeIndex(0u),
|
||||
m_HasTitle(false), m_HasInitFileName(false), m_WinTitle(), m_WinInitFileName(),
|
||||
m_WinInitDirectory(nullptr) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(WinFileDialog);
|
||||
|
||||
/// @brief Get whether this dialog has owner.
|
||||
bool HasOwner() const { return m_WinOwner != NULL; }
|
||||
/// @brief Get the \c HWND of dialog owner.
|
||||
HWND GetOwner() const { return m_WinOwner; }
|
||||
|
||||
/// @brief Get the struct holding Windows used file filters data.
|
||||
const WinFileFilters& GetFileTypes() const { return m_WinFileTypes; }
|
||||
/// @brief Get the index of default selected file filter.
|
||||
UINT GetDefaultFileTypeIndex() const { return m_WinDefaultFileTypeIndex; }
|
||||
|
||||
/// @brief Get whether dialog has custom title.
|
||||
bool HasTitle() const { return m_HasTitle; }
|
||||
/// @brief Get custom title of dialog.
|
||||
const wchar_t* GetTitle() const { return m_WinTitle.c_str(); }
|
||||
/// @brief Get whether dialog has custom initial file name.
|
||||
bool HasInitFileName() const { return m_HasInitFileName; }
|
||||
/// @brief Get custom initial file name of dialog
|
||||
const wchar_t* GetInitFileName() const { return m_WinInitFileName.c_str(); }
|
||||
|
||||
/// @brief Get whether dialog has custom initial directory.
|
||||
bool HasInitDirectory() const { return m_WinInitDirectory.get() != nullptr; }
|
||||
/// @brief Get custom initial directory of dialog.
|
||||
IShellItem* GetInitDirectory() const { return m_WinInitDirectory.get(); }
|
||||
|
||||
protected:
|
||||
HWND m_WinOwner;
|
||||
WinFileFilters m_WinFileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* This is 1-based index according to Windows specification.
|
||||
* In other words, when generating this struct from FileDialog to this struct this field should plus 1.
|
||||
* Because the same field located in FileDialog is 0-based index.
|
||||
*/
|
||||
UINT m_WinDefaultFileTypeIndex;
|
||||
bool m_HasTitle, m_HasInitFileName;
|
||||
std::wstring m_WinTitle, m_WinInitFileName;
|
||||
COMHelper::SmartIShellItem m_WinInitDirectory;
|
||||
|
||||
/// @brief Clear all data and reset them to default value.
|
||||
void Clear() {
|
||||
m_WinOwner = nullptr;
|
||||
m_WinFileTypes.Clear();
|
||||
m_WinDefaultFileTypeIndex = 0u;
|
||||
m_HasTitle = m_HasInitFileName = false;
|
||||
m_WinTitle.clear();
|
||||
m_WinInitFileName.clear();
|
||||
m_WinInitDirectory.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The class representing the file dialog.
|
||||
* @details
|
||||
* This class is served for programming using to describe every aspectes of the dialog.
|
||||
* For how to use this struct, see \ref dialog_helper.
|
||||
*/
|
||||
class FileDialog {
|
||||
public:
|
||||
FileDialog() :
|
||||
m_Owner(NULL),
|
||||
m_FileTypes(),
|
||||
m_DefaultFileTypeIndex(0u),
|
||||
m_Title(), m_InitFileName(), m_InitDirectory(),
|
||||
m_HasTitle(false), m_HasInitFileName(false), m_HasInitDirectory(false) {}
|
||||
YYCC_DEL_CLS_COPY_MOVE(FileDialog);
|
||||
|
||||
/**
|
||||
* @brief Set the owner of dialog.
|
||||
* @param[in] owner The \c HWND pointing to the owner of dialog, or NULL to remove owner.
|
||||
*/
|
||||
void SetOwner(HWND owner) { m_Owner = owner; }
|
||||
/**
|
||||
* @brief Set custom title of dialog
|
||||
* @param[in] title The string pointer to custom title, or nullptr to remove it.
|
||||
*/
|
||||
void SetTitle(const yycc_char8_t* title) {
|
||||
if (m_HasTitle = title != nullptr)
|
||||
m_Title = title;
|
||||
}
|
||||
/**
|
||||
* @brief Fetch the struct describing file filters for future configuration.
|
||||
* @return The reference to the struct describing file filters.
|
||||
*/
|
||||
FileFilters& ConfigreFileTypes() {
|
||||
return m_FileTypes;
|
||||
}
|
||||
/**
|
||||
* @brief Set the index of default selected file filter.
|
||||
* @param[in] idx
|
||||
* The index to default one.
|
||||
* This must be a valid index in file filters.
|
||||
*/
|
||||
void SetDefaultFileTypeIndex(size_t idx) { m_DefaultFileTypeIndex = idx; }
|
||||
/**
|
||||
* @brief Set the initial file name of dialog
|
||||
* @details If set, the file name will always be same one when opening dialog.
|
||||
* @param[in] init_filename String pointer to initial file name, or nullptr to remove it.
|
||||
*/
|
||||
void SetInitFileName(const yycc_char8_t* init_filename) {
|
||||
if (m_HasInitFileName = init_filename != nullptr)
|
||||
m_InitFileName = init_filename;
|
||||
}
|
||||
/**
|
||||
* @brief Set the initial directory of dialog
|
||||
* @details If set, the opended directory will always be the same one when opening dialog
|
||||
* @param[in] init_dir
|
||||
* String pointer to initial directory.
|
||||
* Invalid path or nullptr will remove this feature.
|
||||
*/
|
||||
void SetInitDirectory(const yycc_char8_t* init_dir) {
|
||||
if (m_HasInitDirectory = init_dir != nullptr)
|
||||
m_InitDirectory = init_dir;
|
||||
}
|
||||
|
||||
/// @brief Clear file dialog parameters for following re-use.
|
||||
void Clear() {
|
||||
m_Owner = nullptr;
|
||||
m_HasTitle = m_HasInitFileName = m_HasInitDirectory = false;
|
||||
m_Title.clear();
|
||||
m_InitFileName.clear();
|
||||
m_InitDirectory.clear();
|
||||
m_FileTypes.Clear();
|
||||
m_DefaultFileTypeIndex = 0u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate Windows dialog system used data struct.
|
||||
* @param[out] win_result The class receiving the generated filter data struct.
|
||||
* @return True if generation is success, otherwise false.
|
||||
* @remarks
|
||||
* Programmer should not call this function.
|
||||
* This function is used as YYCC internal code.
|
||||
*/
|
||||
bool Generate(WinFileDialog& win_result) const;
|
||||
|
||||
protected:
|
||||
HWND m_Owner;
|
||||
bool m_HasTitle, m_HasInitFileName, m_HasInitDirectory;
|
||||
yycc_u8string m_Title, m_InitFileName, m_InitDirectory;
|
||||
FileFilters m_FileTypes;
|
||||
/**
|
||||
* @brief The default selected file type in dialog
|
||||
* @remarks
|
||||
* The index Windows used is 1-based index.
|
||||
* But for universal experience, we order this is 0-based index.
|
||||
* And do convertion when generating Windows used struct.
|
||||
*/
|
||||
size_t m_DefaultFileTypeIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected file.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenFileDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select multiple file to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret The list of full path of user selected files.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenMultipleFileDialog(const FileDialog& params, std::vector<yycc_u8string>& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single file to save.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected file.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool SaveFileDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
/**
|
||||
* @brief Open the dialog which order user select single directory to open.
|
||||
* @param[in] params The configuration of dialog.
|
||||
* @param[out] ret Full path to user selected directory.
|
||||
* @return False if user calcel the operation or something went wrong, otherwise true.
|
||||
*/
|
||||
bool OpenFolderDialog(const FileDialog& params, yycc_u8string& ret);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
563
src/YYCCLegacy/ExceptionHelper.cpp
Normal file
563
src/YYCCLegacy/ExceptionHelper.cpp
Normal file
@ -0,0 +1,563 @@
|
||||
#include "ExceptionHelper.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "WinFctHelper.hpp"
|
||||
#include "ConsoleHelper.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include "IOHelper.hpp"
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "StdPatch.hpp"
|
||||
#include <filesystem>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cinttypes>
|
||||
#include <mutex>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
namespace YYCC::ExceptionHelper {
|
||||
|
||||
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS);
|
||||
class ExceptionRegister {
|
||||
public:
|
||||
ExceptionRegister() :
|
||||
m_CoreMutex(),
|
||||
m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr),
|
||||
m_UserCallback(nullptr),
|
||||
m_SingletonMutex(NULL) {}
|
||||
~ExceptionRegister() {
|
||||
Unregister();
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Try to register unhandled exception handler.
|
||||
*/
|
||||
void Register(ExceptionCallback callback) {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
// if we have registered, return
|
||||
if (m_IsRegistered) return;
|
||||
|
||||
// check singleton
|
||||
// build mutex string first
|
||||
yycc_u8string mutex_name;
|
||||
if (!StringHelper::Printf(mutex_name, YYCC_U8("Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}"), GetCurrentProcessId()))
|
||||
return;
|
||||
std::wstring mutex_wname;
|
||||
if (!EncodingHelper::UTF8ToWchar(mutex_name, mutex_wname))
|
||||
return;
|
||||
// create mutex
|
||||
m_SingletonMutex = CreateMutexW(NULL, FALSE, mutex_wname.c_str());
|
||||
DWORD errcode = GetLastError();
|
||||
// check whether be created
|
||||
if (m_SingletonMutex == NULL)
|
||||
return;
|
||||
if (errcode == ERROR_ALREADY_EXISTS) {
|
||||
CloseHandle(m_SingletonMutex);
|
||||
m_SingletonMutex = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// okey, we can register it.
|
||||
// backup old handler
|
||||
m_PrevProcHandler = SetUnhandledExceptionFilter(UExceptionImpl);
|
||||
// set user callback
|
||||
m_UserCallback = callback;
|
||||
// mark registered
|
||||
m_IsRegistered = true;
|
||||
}
|
||||
/**
|
||||
* @brief Try to unregister unhandled exception handler.
|
||||
*/
|
||||
void Unregister() {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
// if we are not registered, skip
|
||||
if (!m_IsRegistered) return;
|
||||
|
||||
// unregister handler
|
||||
// reset user callback
|
||||
m_UserCallback = nullptr;
|
||||
// restore old handler
|
||||
SetUnhandledExceptionFilter(m_PrevProcHandler);
|
||||
m_PrevProcHandler = nullptr;
|
||||
|
||||
// release singleton handler
|
||||
if (m_SingletonMutex != NULL) {
|
||||
CloseHandle(m_SingletonMutex);
|
||||
m_SingletonMutex = NULL;
|
||||
}
|
||||
|
||||
// mark unregistered
|
||||
m_IsRegistered = false;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Check whether handler is registered.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsRegistered() const {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
return m_IsRegistered;
|
||||
}
|
||||
/**
|
||||
* @brief Check whether we are processing unhandled exception.
|
||||
* @return True if it is, otherwise false.
|
||||
*/
|
||||
bool IsProcessing() const {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
return m_IsProcessing;
|
||||
}
|
||||
/**
|
||||
* @brief Get the old unhandled exception handler before registering.
|
||||
* @return The fucntion pointer to old unhandled exception handler. May be nullptr.
|
||||
*/
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER GetPrevProcHandler() const {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
return m_PrevProcHandler;
|
||||
}
|
||||
/**
|
||||
* @brief Get user specified callback.
|
||||
* @return The function pointer to user callback. nullptr if no associated callback.
|
||||
*/
|
||||
ExceptionCallback GetUserCallback() const {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
return m_UserCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Try to start process unhandled exception.
|
||||
* @return True if you can start to process.
|
||||
* False means there is already a process running. You should not process it now.
|
||||
*/
|
||||
bool StartProcessing() {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
if (m_IsProcessing) return false;
|
||||
else {
|
||||
m_IsProcessing = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Mark current process of unhandled exception has done.
|
||||
* @details This should only be called when StartProcessing() return true.
|
||||
*/
|
||||
void StopProcessing() {
|
||||
std::lock_guard<std::mutex> locker(m_CoreMutex);
|
||||
m_IsProcessing = false;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The core mutex for keeping this class is in synchronized.
|
||||
*/
|
||||
mutable std::mutex m_CoreMutex;
|
||||
|
||||
/**
|
||||
* @brief Whether we have registered unhandled exception handler.
|
||||
* True if it is, otherwise false.
|
||||
*/
|
||||
bool m_IsRegistered;
|
||||
/**
|
||||
* @brief Whether we are processing unhandled exception.
|
||||
* True if it is, otherwise false.
|
||||
*/
|
||||
bool m_IsProcessing;
|
||||
/**
|
||||
* @brief User defined callback.
|
||||
* @details It will be called at the tail of unhandled exception handler, because it may raise exception.
|
||||
* We must make sure all log and coredump have been done before calling it.
|
||||
*/
|
||||
ExceptionCallback m_UserCallback;
|
||||
/**
|
||||
* @brief The backup of old unhandled exception handler.
|
||||
*/
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER m_PrevProcHandler;
|
||||
/**
|
||||
* @brief The Windows mutex handle for singleton implementation.
|
||||
* Because we may have many DLLs using YYCC in the same process.
|
||||
* But the unhandled exception handler only need to be registered once.
|
||||
*/
|
||||
HANDLE m_SingletonMutex;
|
||||
};
|
||||
|
||||
/// @brief Core register singleton.
|
||||
static ExceptionRegister g_ExceptionRegister;
|
||||
|
||||
#pragma region Exception Handler Implementation
|
||||
|
||||
/**
|
||||
* @brief Get human-readable exception string from given exception code.
|
||||
* @param[in] code Exception code
|
||||
* @return The const string pointer to corresponding exception explanation string.
|
||||
*/
|
||||
static const yycc_char8_t* UExceptionGetCodeName(DWORD code) {
|
||||
switch (code) {
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
return YYCC_U8("access violation");
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
return YYCC_U8("array index out of bound");
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
return YYCC_U8("breakpoint reached");
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
return YYCC_U8("misaligned data access");
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
return YYCC_U8("operand had denormal value");
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
return YYCC_U8("floating-point division by zero");
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
return YYCC_U8("no decimal fraction representation for value");
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
return YYCC_U8("invalid floating-point operation");
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
return YYCC_U8("floating-point overflow");
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
return YYCC_U8("floating-point stack corruption");
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
return YYCC_U8("floating-point underflow");
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
return YYCC_U8("illegal instruction");
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
return YYCC_U8("inaccessible page");
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
return YYCC_U8("integer division by zero");
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
return YYCC_U8("integer overflow");
|
||||
case EXCEPTION_INVALID_DISPOSITION:
|
||||
return YYCC_U8("documentation says this should never happen");
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
return YYCC_U8("can't continue after a noncontinuable exception");
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
return YYCC_U8("attempted to execute a privileged instruction");
|
||||
case EXCEPTION_SINGLE_STEP:
|
||||
return YYCC_U8("one instruction has been executed");
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
return YYCC_U8("stack overflow");
|
||||
default:
|
||||
return YYCC_U8("unknown exception");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Error log (including backtrace) used output function
|
||||
* @details
|
||||
* This function will write given string into given file stream and stderr.
|
||||
* @param[in] fs
|
||||
* The file stream where we write.
|
||||
* If it is nullptr, function will skip writing for file stream.
|
||||
* @param[in] strl The string to be written.
|
||||
*/
|
||||
static void UExceptionErrLogWriteLine(std::FILE* fs, const yycc_char8_t* strl) {
|
||||
// write to file
|
||||
if (fs != nullptr) {
|
||||
std::fputs(EncodingHelper::ToOrdinary(strl), fs);
|
||||
std::fputs("\n", fs);
|
||||
}
|
||||
// write to stderr
|
||||
ConsoleHelper::ErrWriteLine(strl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Error log (including backtrace) used output function with format feature
|
||||
* @details
|
||||
* This function will format message first.
|
||||
* And write them into given file stream and stderr.
|
||||
* @param[in] fs
|
||||
* The file stream where we write.
|
||||
* If it is nullptr, function will skip writing for file stream.
|
||||
* @param[in] fmt The format string.
|
||||
* @param[in] ... The argument to be formatted.
|
||||
*/
|
||||
static void UExceptionErrLogFormatLine(std::FILE* fs, const yycc_char8_t* fmt, ...) {
|
||||
// do format first
|
||||
va_list arg;
|
||||
va_start(arg, fmt);
|
||||
auto fmt_result = YYCC::StringHelper::VPrintf(fmt, arg);
|
||||
va_end(arg);
|
||||
// write to file and console
|
||||
UExceptionErrLogWriteLine(fs, fmt_result.c_str());
|
||||
}
|
||||
|
||||
static void UExceptionBacktrace(FILE* fs, LPCONTEXT context, int maxdepth) {
|
||||
// setup loading symbol options
|
||||
SymSetOptions(SymGetOptions() | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); // lazy load symbol, and load line number.
|
||||
|
||||
// setup handle
|
||||
HANDLE process = GetCurrentProcess();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
|
||||
// init symbol
|
||||
if (!SymInitialize(process, 0, TRUE)) {
|
||||
// fail to init. return
|
||||
UExceptionErrLogWriteLine(fs, YYCC_U8("Fail to initialize symbol handle for process!"));
|
||||
return;
|
||||
}
|
||||
|
||||
// ========== CORE DUMP ==========
|
||||
// prepare frame. setup correct fields
|
||||
// references:
|
||||
// https://github.com/rust-lang/backtrace-rs/blob/9ed25b581cfd2ee60e5a3b9054fd023bf6dced90/src/backtrace/dbghelp.rs
|
||||
// https://sourceforge.net/p/predef/wiki/Architectures/
|
||||
DWORD machine_type = 0;
|
||||
STACKFRAME64 frame;
|
||||
memset(&frame, 0, sizeof(frame));
|
||||
#if defined(_M_IX86) || defined(__i386__)
|
||||
// x86
|
||||
machine_type = IMAGE_FILE_MACHINE_I386;
|
||||
frame.AddrPC.Offset = context->Eip;
|
||||
frame.AddrStack.Offset = context->Esp;
|
||||
frame.AddrFrame.Offset = context->Ebp;
|
||||
#elif defined(_M_AMD64) || defined(__amd64__)
|
||||
// amd64
|
||||
machine_type = IMAGE_FILE_MACHINE_AMD64;
|
||||
frame.AddrPC.Offset = context->Rip;
|
||||
frame.AddrStack.Offset = context->Rsp;
|
||||
frame.AddrFrame.Offset = context->Rbp;
|
||||
#elif defined(_M_ARM) || defined(__arm__)
|
||||
// arm (32bit)
|
||||
machine_type = IMAGE_FILE_MACHINE_ARMNT;
|
||||
frame.AddrPC.Offset = context->Pc;
|
||||
frame.AddrStack.Offset = context->Sp;
|
||||
frame.AddrFrame.Offset = context->R11;
|
||||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||||
// arm64
|
||||
machine_type = IMAGE_FILE_MACHINE_ARM64;
|
||||
frame.AddrPC.Offset = context->Pc;
|
||||
frame.AddrStack.Offset = context->Sp;
|
||||
frame.AddrFrame.Offset = context->DUMMYUNIONNAME.DUMMYSTRUCTNAME.Fp;
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
//IA-64 anybody?
|
||||
|
||||
#endif
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
|
||||
// stack walker
|
||||
while (StackWalk64(machine_type, process, thread, &frame, context,
|
||||
0, SymFunctionTableAccess64, SymGetModuleBase64, 0)) {
|
||||
|
||||
// depth breaker
|
||||
--maxdepth;
|
||||
if (maxdepth < 0) {
|
||||
UExceptionErrLogWriteLine(fs, YYCC_U8("...")); // indicate there are some frames not listed
|
||||
break;
|
||||
}
|
||||
|
||||
// get module name
|
||||
const yycc_char8_t* no_module_name = YYCC_U8("<unknown module>");
|
||||
yycc_u8string module_name(no_module_name);
|
||||
DWORD64 module_base;
|
||||
if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
|
||||
if (!WinFctHelper::GetModuleFileName((HINSTANCE)module_base, module_name)) {
|
||||
module_name = no_module_name;
|
||||
}
|
||||
}
|
||||
|
||||
// get source file and line
|
||||
const yycc_char8_t* source_file = YYCC_U8("<unknown source>");
|
||||
DWORD64 source_file_line = 0;
|
||||
DWORD dwDisplacement;
|
||||
IMAGEHLP_LINE64 winline;
|
||||
winline.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
if (SymGetLineFromAddr64(process, frame.AddrPC.Offset, &dwDisplacement, &winline)) {
|
||||
source_file = EncodingHelper::ToUTF8(winline.FileName); // TODO: check whether there is UNICODE file name.
|
||||
source_file_line = winline.LineNumber;
|
||||
}
|
||||
|
||||
// write to file
|
||||
// MARK: should not use PRIXPTR to print adddress.
|
||||
// because Windows always use DWORD64 as the type of address.
|
||||
// use PRIX64 instead.
|
||||
UExceptionErrLogFormatLine(fs, YYCC_U8("0x%" PRI_XPTR_LEFT_PADDING PRIX64 "[%s+0x%" PRI_XPTR_LEFT_PADDING PRIX64 "]\t%s#L%" PRIu64),
|
||||
frame.AddrPC.Offset, // memory adress
|
||||
module_name.c_str(), frame.AddrPC.Offset - module_base, // module name + relative address
|
||||
source_file, source_file_line // source file + source line
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// ========== END CORE DUMP ==========
|
||||
|
||||
// free symbol
|
||||
SymCleanup(process);
|
||||
}
|
||||
|
||||
static void UExceptionErrorLog(const yycc_u8string& u8_filename, LPEXCEPTION_POINTERS info) {
|
||||
// open file stream if we have file name
|
||||
std::FILE* fs = nullptr;
|
||||
if (!u8_filename.empty()) {
|
||||
fs = IOHelper::UTF8FOpen(u8_filename.c_str(), YYCC_U8("wb"));
|
||||
}
|
||||
|
||||
// record exception type first
|
||||
PEXCEPTION_RECORD rec = info->ExceptionRecord;
|
||||
UExceptionErrLogFormatLine(fs, YYCC_U8("Unhandled exception occured at 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR ": %s (%" PRIu32 ")."),
|
||||
rec->ExceptionAddress,
|
||||
UExceptionGetCodeName(rec->ExceptionCode),
|
||||
rec->ExceptionCode
|
||||
);
|
||||
|
||||
// special proc for 2 exceptions
|
||||
if (rec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || rec->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) {
|
||||
if (rec->NumberParameters >= 2) {
|
||||
const yycc_char8_t* op =
|
||||
rec->ExceptionInformation[0] == 0 ? YYCC_U8("read") :
|
||||
rec->ExceptionInformation[0] == 1 ? YYCC_U8("written") : YYCC_U8("executed");
|
||||
UExceptionErrLogFormatLine(fs, YYCC_U8("The data at memory address 0x%" PRI_XPTR_LEFT_PADDING PRIxPTR " could not be %s."),
|
||||
rec->ExceptionInformation[1], op);
|
||||
}
|
||||
}
|
||||
|
||||
// output stacktrace
|
||||
UExceptionBacktrace(fs, info->ContextRecord, 1024);
|
||||
|
||||
// close file if necessary
|
||||
if (fs != nullptr) {
|
||||
std::fclose(fs);
|
||||
}
|
||||
}
|
||||
|
||||
static void UExceptionCoreDump(const yycc_u8string& u8_filename, LPEXCEPTION_POINTERS info) {
|
||||
// convert file encoding
|
||||
std::wstring filename;
|
||||
if (u8_filename.empty())
|
||||
return; // if no given file name, return
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(u8_filename, filename))
|
||||
return; // if convertion failed, return
|
||||
|
||||
// open file and write
|
||||
HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION exception_info;
|
||||
exception_info.ThreadId = GetCurrentThreadId();
|
||||
exception_info.ExceptionPointers = info;
|
||||
exception_info.ClientPointers = TRUE;
|
||||
MiniDumpWriteDump(
|
||||
GetCurrentProcess(), GetCurrentProcessId(), hFile,
|
||||
MiniDumpNormal,
|
||||
&exception_info,
|
||||
NULL, NULL
|
||||
);
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
}
|
||||
|
||||
static bool UExceptionFetchRecordPath(yycc_u8string& log_path, yycc_u8string& coredump_path) {
|
||||
// build two file names like: "error.exe.1234.log" and "error.exe.1234.dmp".
|
||||
// "error.exe" is the name of current process. "1234" is current process id.
|
||||
// get process name
|
||||
yycc_u8string u8_process_name;
|
||||
{
|
||||
// get full path of process
|
||||
yycc_u8string u8_process_path;
|
||||
if (!YYCC::WinFctHelper::GetModuleFileName(NULL, u8_process_path))
|
||||
return false;
|
||||
// extract file name from full path by std::filesystem::path
|
||||
std::filesystem::path process_path(StdPatch::ToStdPath(u8_process_path));
|
||||
u8_process_name = StdPatch::ToUTF8Path(process_path.filename());
|
||||
}
|
||||
// then get process id
|
||||
DWORD process_id = GetCurrentProcessId();
|
||||
// conbine them as a file name prefix
|
||||
yycc_u8string u8_filename_prefix;
|
||||
if (!YYCC::StringHelper::Printf(u8_filename_prefix, YYCC_U8("%s.%" PRIu32), u8_process_name.c_str(), process_id))
|
||||
return false;
|
||||
// then get file name for log and minidump
|
||||
yycc_u8string u8_log_filename = u8_filename_prefix + YYCC_U8(".log");
|
||||
yycc_u8string u8_coredump_filename = u8_filename_prefix + YYCC_U8(".dmp");
|
||||
|
||||
// fetch crash report path
|
||||
// get local appdata folder
|
||||
yycc_u8string u8_localappdata_path;
|
||||
if (!WinFctHelper::GetLocalAppData(u8_localappdata_path))
|
||||
return false;
|
||||
// convert to std::filesystem::path
|
||||
std::filesystem::path crash_report_path(StdPatch::ToStdPath(u8_localappdata_path));
|
||||
// slash into crash report folder
|
||||
crash_report_path /= StdPatch::ToStdPath(YYCC_U8("CrashDumps"));
|
||||
// use create function to make sure it is existing
|
||||
std::filesystem::create_directories(crash_report_path);
|
||||
|
||||
// build log path and coredump path
|
||||
// build std::filesystem::path first
|
||||
std::filesystem::path log_filepath = crash_report_path / StdPatch::ToStdPath(u8_log_filename);
|
||||
std::filesystem::path coredump_filepath = crash_report_path / StdPatch::ToStdPath(u8_coredump_filename);
|
||||
// output to result
|
||||
log_path = StdPatch::ToUTF8Path(log_filepath);
|
||||
coredump_path = StdPatch::ToUTF8Path(coredump_filepath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static LONG WINAPI UExceptionImpl(LPEXCEPTION_POINTERS info) {
|
||||
// try to start process current unhandled exception
|
||||
// to prevent any possible recursive calling.
|
||||
if (!g_ExceptionRegister.StartProcessing()) goto end_proc;
|
||||
|
||||
// core implementation
|
||||
{
|
||||
// fetch error report path first
|
||||
yycc_u8string log_path, coredump_path;
|
||||
if (!UExceptionFetchRecordPath(log_path, coredump_path)) {
|
||||
// fail to fetch path, clear them.
|
||||
// we still can handle crash without them
|
||||
log_path.clear();
|
||||
coredump_path.clear();
|
||||
// and tell user we can not output file
|
||||
ConsoleHelper::ErrWriteLine(YYCC_U8("Crash occurs, but we can not create crash log and coredump!"));
|
||||
} else {
|
||||
// okey. output file path to tell user the path where you can find.
|
||||
ConsoleHelper::ErrFormatLine(YYCC_U8("Crash Log: %s"), log_path.c_str());
|
||||
ConsoleHelper::ErrFormatLine(YYCC_U8("Crash Coredump: %s"), coredump_path.c_str());
|
||||
}
|
||||
|
||||
// write crash log
|
||||
UExceptionErrorLog(log_path, info);
|
||||
// write crash coredump
|
||||
UExceptionCoreDump(coredump_path, info);
|
||||
|
||||
// call user callback
|
||||
ExceptionCallback user_callback = g_ExceptionRegister.GetUserCallback();
|
||||
if (user_callback != nullptr)
|
||||
user_callback(log_path, coredump_path);
|
||||
}
|
||||
|
||||
// stop process
|
||||
g_ExceptionRegister.StopProcessing();
|
||||
|
||||
end_proc:
|
||||
// if backup proc can be run, run it
|
||||
// otherwise directly return.
|
||||
auto prev_proc = g_ExceptionRegister.GetPrevProcHandler();
|
||||
if (prev_proc != nullptr) {
|
||||
return prev_proc(info);
|
||||
} else {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
void Register(ExceptionCallback callback) {
|
||||
g_ExceptionRegister.Register(callback);
|
||||
}
|
||||
|
||||
void Unregister() {
|
||||
g_ExceptionRegister.Unregister();
|
||||
}
|
||||
|
||||
#if defined(YYCC_DEBUG_UE_FILTER)
|
||||
long __stdcall DebugCallUExceptionImpl(void* data) {
|
||||
return UExceptionImpl(static_cast<LPEXCEPTION_POINTERS>(data));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
64
src/YYCCLegacy/ExceptionHelper.hpp
Normal file
64
src/YYCCLegacy/ExceptionHelper.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
/**
|
||||
* @brief Windows specific unhandled exception processor.
|
||||
* @details
|
||||
* This namespace is Windows specific. On other platforms, the whole namespace is unavailable.
|
||||
* For how to utilize this namespace, please see \ref exception_helper.
|
||||
*
|
||||
*/
|
||||
namespace YYCC::ExceptionHelper {
|
||||
|
||||
/**
|
||||
* @brief The callback function prototype which will be called when unhandled exception happened after registering.
|
||||
* @details
|
||||
* During registering unhandled exception handler,
|
||||
* caller can optionally provide a function pointer matching this prorotype to register.
|
||||
* Then it will be called if unhandled exception hanppened.
|
||||
*
|
||||
* This callback will provide 2 readonly arguments.
|
||||
* First is the path to error log file.
|
||||
* Second is the path to core dump file.
|
||||
* These pathes may be empty if internal handler fail to create them.
|
||||
*
|
||||
* This callback is convenient for programmer using an explicit way to tell user an exception happened.
|
||||
* Because in default, handler will only write error log to \c stderr and file.
|
||||
* It will be totally invisible on a GUI application.
|
||||
*/
|
||||
using ExceptionCallback = void(*)(const yycc_u8string& log_path, const yycc_u8string& coredump_path);
|
||||
|
||||
/**
|
||||
* @brief Register unhandled exception handler
|
||||
* @details
|
||||
* This function will set an internal function as unhandled exception handler on Windows.
|
||||
*
|
||||
* When unhandled exception raised,
|
||||
* That internal function will output error stacktrace in standard output,
|
||||
* and generate log file and dump file in \c \%APPDATA\%/CrashDumps folder if it is possible.
|
||||
* (for convenient debugging of developer when reporting bugs.)
|
||||
*
|
||||
* This function usually is called at the start of program.
|
||||
* @param[in] callback User defined callback called when unhandled exception happened. nullptr if no callback.
|
||||
*/
|
||||
void Register(ExceptionCallback callback = nullptr);
|
||||
/**
|
||||
* @brief Unregister unhandled exception handler
|
||||
* @details
|
||||
* The reverse operation of Register().
|
||||
*
|
||||
* This function and Register() should always be used as a pair.
|
||||
* You must call this function to release reources if you have called Register().
|
||||
*
|
||||
* This function usually is called at the end of program.
|
||||
*/
|
||||
void Unregister();
|
||||
|
||||
#if defined(YYCC_DEBUG_UE_FILTER)
|
||||
long __stdcall DebugCallUExceptionImpl(void*);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
115
src/YYCCLegacy/WinFctHelper.cpp
Normal file
115
src/YYCCLegacy/WinFctHelper.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include "WinFctHelper.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "COMHelper.hpp"
|
||||
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
HMODULE GetCurrentModule() {
|
||||
// Reference: https://stackoverflow.com/questions/557081/how-do-i-get-the-hmodule-for-the-currently-executing-code
|
||||
HMODULE hModule = NULL;
|
||||
::GetModuleHandleExW(
|
||||
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, // get address and do not inc ref counter.
|
||||
(LPCWSTR)GetCurrentModule,
|
||||
&hModule);
|
||||
|
||||
return hModule;
|
||||
}
|
||||
|
||||
bool GetTempDirectory(yycc_u8string& ret) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD expected_size;
|
||||
|
||||
// fetch temp folder
|
||||
while (true) {
|
||||
if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
|
||||
// failed, set to empty
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected_size > static_cast<DWORD>(wpath.size())) {
|
||||
// buffer is too short, need enlarge and do fetching again
|
||||
wpath.resize(expected_size);
|
||||
} else {
|
||||
// ok. shrink to real length, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(expected_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath, ret);
|
||||
}
|
||||
|
||||
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret) {
|
||||
// create wchar buffer for receiving the temp path.
|
||||
std::wstring wpath(MAX_PATH + 1u, L'\0');
|
||||
DWORD copied_size;
|
||||
|
||||
while (true) {
|
||||
if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
|
||||
// failed, return
|
||||
return false;
|
||||
}
|
||||
|
||||
// check insufficient buffer
|
||||
if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
// buffer is not enough, enlarge it and try again.
|
||||
wpath.resize(wpath.size() + MAX_PATH);
|
||||
} else {
|
||||
// ok, break while
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// resize result
|
||||
wpath.resize(copied_size);
|
||||
// convert to utf8 and return
|
||||
return YYCC::EncodingHelper::WcharToUTF8(wpath, ret);
|
||||
}
|
||||
|
||||
bool GetLocalAppData(yycc_u8string& ret) {
|
||||
// check whether com initialized
|
||||
if (!COMHelper::IsInitialized()) return false;
|
||||
|
||||
// fetch path
|
||||
LPWSTR _known_path;
|
||||
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &_known_path);
|
||||
if (FAILED(hr)) return false;
|
||||
COMHelper::SmartLPWSTR known_path(_known_path);
|
||||
|
||||
// convert to utf8
|
||||
return YYCC::EncodingHelper::WcharToUTF8(known_path.get(), ret);
|
||||
}
|
||||
|
||||
bool IsValidCodePage(UINT code_page) {
|
||||
CPINFOEXW cpinfo;
|
||||
return ::GetCPInfoExW(code_page, 0, &cpinfo);
|
||||
}
|
||||
|
||||
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists) {
|
||||
std::wstring wExistingFileName, wNewFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
|
||||
return ::CopyFileW(wExistingFileName.c_str(), wNewFileName.c_str(), bFailIfExists);
|
||||
}
|
||||
|
||||
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName) {
|
||||
std::wstring wExistingFileName, wNewFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpExistingFileName, wExistingFileName)) return FALSE;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpNewFileName, wNewFileName)) return FALSE;
|
||||
return ::MoveFileW(wExistingFileName.c_str(), wNewFileName.c_str());
|
||||
}
|
||||
|
||||
BOOL DeleteFile(const yycc_u8string_view& lpFileName) {
|
||||
std::wstring wFileName;
|
||||
if (!YYCC::EncodingHelper::UTF8ToWchar(lpFileName, wFileName)) return FALSE;
|
||||
return ::DeleteFileW(wFileName.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
106
src/YYCCLegacy/WinFctHelper.hpp
Normal file
106
src/YYCCLegacy/WinFctHelper.hpp
Normal file
@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
#include "YYCCInternal.hpp"
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "WinImportPrefix.hpp"
|
||||
#include <Windows.h>
|
||||
#include "WinImportSuffix.hpp"
|
||||
|
||||
/**
|
||||
* @brief The helper providing assistance of Win32 functions.
|
||||
* @details
|
||||
* This helper is Windows specific.
|
||||
* If current environment is not Windows, the whole namespace will be unavailable.
|
||||
* See also \ref win_fct_helper
|
||||
*/
|
||||
namespace YYCC::WinFctHelper {
|
||||
|
||||
/**
|
||||
* @brief Get Windows used HANDLE for current module.
|
||||
* @details
|
||||
* If your target is EXE, the current module simply is your program self.
|
||||
* However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL.
|
||||
*
|
||||
* This function is frequently used by DLL.
|
||||
* Because some design need the HANDLE of current module, not the host EXE loading your DLL.
|
||||
* For example, you may want to get the path of your built DLL, or fetch resources from your DLL at runtime,
|
||||
* then you should pass current module HANDLE, not NULL or the HANDLE of EXE.
|
||||
* @return A Windows HANDLE pointing to current module, NULL if failed.
|
||||
*/
|
||||
HMODULE GetCurrentModule();
|
||||
|
||||
/**
|
||||
* @brief Get path to Windows temporary folder.
|
||||
* @details Windows temporary folder usually is the target of \%TEMP\%.
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to Windows temp folder.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetTempDirectory(yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Get the file name of given module HANDLE
|
||||
* @param[in] hModule
|
||||
* The HANDLE to the module where you want to get file name.
|
||||
* It is same as the HANDLE parameter of Win32 \c GetModuleFileName.
|
||||
* @param[out] ret The variable receiving UTF8 encoded file name of given module.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetModuleFileName(HINSTANCE hModule, yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Get the path to \%LOCALAPPDATA\%.
|
||||
* @details \%LOCALAPPDATA\% usually was used as putting local app data files
|
||||
* @param[out] ret The variable receiving UTF8 encoded path to LOCALAPPDATA.
|
||||
* @return True if success, otherwise false.
|
||||
*/
|
||||
bool GetLocalAppData(yycc_u8string& ret);
|
||||
|
||||
/**
|
||||
* @brief Check whether given code page number is a valid one.
|
||||
* @param[in] code_page The code page number.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
bool IsValidCodePage(UINT code_page);
|
||||
|
||||
/**
|
||||
* @brief Copies an existing file to a new file.
|
||||
* @param lpExistingFileName The name of an existing file.
|
||||
* @param lpNewFileName The name of the new file.
|
||||
* @param bFailIfExists
|
||||
* If this parameter is TRUE and the new file specified by \c lpNewFileName already exists, the function fails.
|
||||
* If this parameter is FALSE and the new file already exists, the function overwrites the existing file and succeeds.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c CopyFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfilew
|
||||
*/
|
||||
BOOL CopyFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName, BOOL bFailIfExists);
|
||||
|
||||
/**
|
||||
* @brief Moves an existing file or a directory, including its children.
|
||||
* @param lpExistingFileName The current name of the file or directory on the local computer.
|
||||
* @param lpNewFileName
|
||||
* The new name for the file or directory. The new name must not already exist.
|
||||
* A new file may be on a different file system or drive. A new directory must be on the same drive.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c MoveFile: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefilew
|
||||
*/
|
||||
BOOL MoveFile(const yycc_u8string_view& lpExistingFileName, const yycc_u8string_view& lpNewFileName);
|
||||
|
||||
/**
|
||||
* @brief Deletes an existing file.
|
||||
* @param lpFileName The name of the file to be deleted.
|
||||
* @return
|
||||
* If the function succeeds, the return value is nonzero.
|
||||
* If the function fails, the return value is zero. To get extended error information, call \c GetLastError.
|
||||
* @remarks Same as Windows \c DeleteFile: https://learn.microsoft.com/e-us/windows/win32/api/winbase/nf-winbase-deletefile
|
||||
*/
|
||||
BOOL DeleteFile(const yycc_u8string_view& lpFileName);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "YYCCInternal.hpp"
|
||||
|
||||
#include "StringHelper.hpp"
|
||||
#include "EncodingHelper.hpp"
|
||||
#include "ConsoleHelper.hpp"
|
||||
#include "COMHelper.hpp"
|
||||
#include "DialogHelper.hpp"
|
||||
#include "ParserHelper.hpp"
|
||||
#include "ExceptionHelper.hpp"
|
||||
#include "IOHelper.hpp"
|
||||
#include "WinFctHelper.hpp"
|
||||
#include "FsPathPatch.hpp"
|
26
src/yycc.hpp
Normal file
26
src/yycc.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* @file
|
||||
* When you use YYCCommonplace, please make sure that you include this header first,
|
||||
* before including any other headers of YYCC.
|
||||
* This header contain essential check macros and version infos.
|
||||
* They are crucial before using YYCC.
|
||||
*/
|
||||
|
||||
// Library version
|
||||
#include "yycc/version.hpp"
|
||||
|
||||
// Detect essential macros
|
||||
// Operating System macros
|
||||
#include "yycc/macro/os_detector.hpp"
|
||||
// Compiler macros
|
||||
#include "yycc/macro/compiler_detector.hpp"
|
||||
// Endian macros
|
||||
#include "yycc/macro/endian_detector.hpp"
|
||||
// Pointer size macros
|
||||
#include "yycc/macro/ptr_size_detector.hpp"
|
||||
// STL macros
|
||||
#include "yycc/macro/stl_detector.hpp"
|
||||
|
||||
namespace yycc {}
|
0
src/yycc/carton/binstore/kernel.hpp
Normal file
0
src/yycc/carton/binstore/kernel.hpp
Normal file
0
src/yycc/carton/clap/kernel.hpp
Normal file
0
src/yycc/carton/clap/kernel.hpp
Normal file
591
src/yycc/carton/pycodec.cpp
Normal file
591
src/yycc/carton/pycodec.cpp
Normal file
@ -0,0 +1,591 @@
|
||||
#include "pycodec.hpp"
|
||||
|
||||
#include "../string/op.hpp"
|
||||
#include <map>
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
namespace op = ::yycc::string::op;
|
||||
|
||||
namespace yycc::carton::pycodec {
|
||||
|
||||
/// @brief Error occurs when fetching token from encoding name.
|
||||
enum class FetchError {
|
||||
NoSuchName, ///< Given name can not be resolved.
|
||||
};
|
||||
|
||||
/// @brief The Result type used when fetching token from encoding name.
|
||||
template<typename T>
|
||||
using FetchResult = std::expected<T, FetchError>;
|
||||
|
||||
#pragma region Encoding Name
|
||||
|
||||
// clang-format off
|
||||
static const std::map<std::u8string_view, std::u8string_view> ALIAS_MAP{
|
||||
{u8"646"sv, u8"ascii"sv},
|
||||
{u8"us-ascii"sv, u8"ascii"sv},
|
||||
{u8"big5-tw"sv, u8"big5"sv},
|
||||
{u8"csbig5"sv, u8"big5"sv},
|
||||
{u8"big5-hkscs"sv, u8"big5hkscs"sv},
|
||||
{u8"hkscs"sv, u8"big5hkscs"sv},
|
||||
{u8"ibm037"sv, u8"cp037"sv},
|
||||
{u8"ibm039"sv, u8"cp037"sv},
|
||||
{u8"273"sv, u8"cp273"sv},
|
||||
{u8"ibm273"sv, u8"cp273"sv},
|
||||
{u8"csibm273"sv, u8"cp273"sv},
|
||||
{u8"ebcdic-cp-he"sv, u8"cp424"sv},
|
||||
{u8"ibm424"sv, u8"cp424"sv},
|
||||
{u8"437"sv, u8"cp437"sv},
|
||||
{u8"ibm437"sv, u8"cp437"sv},
|
||||
{u8"ebcdic-cp-be"sv, u8"cp500"sv},
|
||||
{u8"ebcdic-cp-ch"sv, u8"cp500"sv},
|
||||
{u8"ibm500"sv, u8"cp500"sv},
|
||||
{u8"ibm775"sv, u8"cp775"sv},
|
||||
{u8"850"sv, u8"cp850"sv},
|
||||
{u8"ibm850"sv, u8"cp850"sv},
|
||||
{u8"852"sv, u8"cp852"sv},
|
||||
{u8"ibm852"sv, u8"cp852"sv},
|
||||
{u8"855"sv, u8"cp855"sv},
|
||||
{u8"ibm855"sv, u8"cp855"sv},
|
||||
{u8"857"sv, u8"cp857"sv},
|
||||
{u8"ibm857"sv, u8"cp857"sv},
|
||||
{u8"858"sv, u8"cp858"sv},
|
||||
{u8"ibm858"sv, u8"cp858"sv},
|
||||
{u8"860"sv, u8"cp860"sv},
|
||||
{u8"ibm860"sv, u8"cp860"sv},
|
||||
{u8"861"sv, u8"cp861"sv},
|
||||
{u8"cp-is"sv, u8"cp861"sv},
|
||||
{u8"ibm861"sv, u8"cp861"sv},
|
||||
{u8"862"sv, u8"cp862"sv},
|
||||
{u8"ibm862"sv, u8"cp862"sv},
|
||||
{u8"863"sv, u8"cp863"sv},
|
||||
{u8"ibm863"sv, u8"cp863"sv},
|
||||
{u8"ibm864"sv, u8"cp864"sv},
|
||||
{u8"865"sv, u8"cp865"sv},
|
||||
{u8"ibm865"sv, u8"cp865"sv},
|
||||
{u8"866"sv, u8"cp866"sv},
|
||||
{u8"ibm866"sv, u8"cp866"sv},
|
||||
{u8"869"sv, u8"cp869"sv},
|
||||
{u8"cp-gr"sv, u8"cp869"sv},
|
||||
{u8"ibm869"sv, u8"cp869"sv},
|
||||
{u8"932"sv, u8"cp932"sv},
|
||||
{u8"ms932"sv, u8"cp932"sv},
|
||||
{u8"mskanji"sv, u8"cp932"sv},
|
||||
{u8"ms-kanji"sv, u8"cp932"sv},
|
||||
{u8"windows-31j"sv, u8"cp932"sv},
|
||||
{u8"949"sv, u8"cp949"sv},
|
||||
{u8"ms949"sv, u8"cp949"sv},
|
||||
{u8"uhc"sv, u8"cp949"sv},
|
||||
{u8"950"sv, u8"cp950"sv},
|
||||
{u8"ms950"sv, u8"cp950"sv},
|
||||
{u8"ibm1026"sv, u8"cp1026"sv},
|
||||
{u8"1125"sv, u8"cp1125"sv},
|
||||
{u8"ibm1125"sv, u8"cp1125"sv},
|
||||
{u8"cp866u"sv, u8"cp1125"sv},
|
||||
{u8"ruscii"sv, u8"cp1125"sv},
|
||||
{u8"ibm1140"sv, u8"cp1140"sv},
|
||||
{u8"windows-1250"sv, u8"cp1250"sv},
|
||||
{u8"windows-1251"sv, u8"cp1251"sv},
|
||||
{u8"windows-1252"sv, u8"cp1252"sv},
|
||||
{u8"windows-1253"sv, u8"cp1253"sv},
|
||||
{u8"windows-1254"sv, u8"cp1254"sv},
|
||||
{u8"windows-1255"sv, u8"cp1255"sv},
|
||||
{u8"windows-1256"sv, u8"cp1256"sv},
|
||||
{u8"windows-1257"sv, u8"cp1257"sv},
|
||||
{u8"windows-1258"sv, u8"cp1258"sv},
|
||||
{u8"eucjp"sv, u8"euc_jp"sv},
|
||||
{u8"ujis"sv, u8"euc_jp"sv},
|
||||
{u8"u-jis"sv, u8"euc_jp"sv},
|
||||
{u8"jisx0213"sv, u8"euc_jis_2004"sv},
|
||||
{u8"eucjis2004"sv, u8"euc_jis_2004"sv},
|
||||
{u8"eucjisx0213"sv, u8"euc_jisx0213"sv},
|
||||
{u8"euckr"sv, u8"euc_kr"sv},
|
||||
{u8"korean"sv, u8"euc_kr"sv},
|
||||
{u8"ksc5601"sv, u8"euc_kr"sv},
|
||||
{u8"ks_c-5601"sv, u8"euc_kr"sv},
|
||||
{u8"ks_c-5601-1987"sv, u8"euc_kr"sv},
|
||||
{u8"ksx1001"sv, u8"euc_kr"sv},
|
||||
{u8"ks_x-1001"sv, u8"euc_kr"sv},
|
||||
{u8"chinese"sv, u8"gb2312"sv},
|
||||
{u8"csiso58gb231280"sv, u8"gb2312"sv},
|
||||
{u8"euc-cn"sv, u8"gb2312"sv},
|
||||
{u8"euccn"sv, u8"gb2312"sv},
|
||||
{u8"eucgb2312-cn"sv, u8"gb2312"sv},
|
||||
{u8"gb2312-1980"sv, u8"gb2312"sv},
|
||||
{u8"gb2312-80"sv, u8"gb2312"sv},
|
||||
{u8"iso-ir-58"sv, u8"gb2312"sv},
|
||||
{u8"936"sv, u8"gbk"sv},
|
||||
{u8"cp936"sv, u8"gbk"sv},
|
||||
{u8"ms936"sv, u8"gbk"sv},
|
||||
{u8"gb18030-2000"sv, u8"gb18030"sv},
|
||||
{u8"hzgb"sv, u8"hz"sv},
|
||||
{u8"hz-gb"sv, u8"hz"sv},
|
||||
{u8"hz-gb-2312"sv, u8"hz"sv},
|
||||
{u8"csiso2022jp"sv, u8"iso2022_jp"sv},
|
||||
{u8"iso2022jp"sv, u8"iso2022_jp"sv},
|
||||
{u8"iso-2022-jp"sv, u8"iso2022_jp"sv},
|
||||
{u8"iso2022jp-1"sv, u8"iso2022_jp_1"sv},
|
||||
{u8"iso-2022-jp-1"sv, u8"iso2022_jp_1"sv},
|
||||
{u8"iso2022jp-2"sv, u8"iso2022_jp_2"sv},
|
||||
{u8"iso-2022-jp-2"sv, u8"iso2022_jp_2"sv},
|
||||
{u8"iso2022jp-2004"sv, u8"iso2022_jp_2004"sv},
|
||||
{u8"iso-2022-jp-2004"sv, u8"iso2022_jp_2004"sv},
|
||||
{u8"iso2022jp-3"sv, u8"iso2022_jp_3"sv},
|
||||
{u8"iso-2022-jp-3"sv, u8"iso2022_jp_3"sv},
|
||||
{u8"iso2022jp-ext"sv, u8"iso2022_jp_ext"sv},
|
||||
{u8"iso-2022-jp-ext"sv, u8"iso2022_jp_ext"sv},
|
||||
{u8"csiso2022kr"sv, u8"iso2022_kr"sv},
|
||||
{u8"iso2022kr"sv, u8"iso2022_kr"sv},
|
||||
{u8"iso-2022-kr"sv, u8"iso2022_kr"sv},
|
||||
{u8"iso-8859-1"sv, u8"latin_1"sv},
|
||||
{u8"iso8859-1"sv, u8"latin_1"sv},
|
||||
{u8"8859"sv, u8"latin_1"sv},
|
||||
{u8"cp819"sv, u8"latin_1"sv},
|
||||
{u8"latin"sv, u8"latin_1"sv},
|
||||
{u8"latin1"sv, u8"latin_1"sv},
|
||||
{u8"l1"sv, u8"latin_1"sv},
|
||||
{u8"iso-8859-2"sv, u8"iso8859_2"sv},
|
||||
{u8"latin2"sv, u8"iso8859_2"sv},
|
||||
{u8"l2"sv, u8"iso8859_2"sv},
|
||||
{u8"iso-8859-3"sv, u8"iso8859_3"sv},
|
||||
{u8"latin3"sv, u8"iso8859_3"sv},
|
||||
{u8"l3"sv, u8"iso8859_3"sv},
|
||||
{u8"iso-8859-4"sv, u8"iso8859_4"sv},
|
||||
{u8"latin4"sv, u8"iso8859_4"sv},
|
||||
{u8"l4"sv, u8"iso8859_4"sv},
|
||||
{u8"iso-8859-5"sv, u8"iso8859_5"sv},
|
||||
{u8"cyrillic"sv, u8"iso8859_5"sv},
|
||||
{u8"iso-8859-6"sv, u8"iso8859_6"sv},
|
||||
{u8"arabic"sv, u8"iso8859_6"sv},
|
||||
{u8"iso-8859-7"sv, u8"iso8859_7"sv},
|
||||
{u8"greek"sv, u8"iso8859_7"sv},
|
||||
{u8"greek8"sv, u8"iso8859_7"sv},
|
||||
{u8"iso-8859-8"sv, u8"iso8859_8"sv},
|
||||
{u8"hebrew"sv, u8"iso8859_8"sv},
|
||||
{u8"iso-8859-9"sv, u8"iso8859_9"sv},
|
||||
{u8"latin5"sv, u8"iso8859_9"sv},
|
||||
{u8"l5"sv, u8"iso8859_9"sv},
|
||||
{u8"iso-8859-10"sv, u8"iso8859_10"sv},
|
||||
{u8"latin6"sv, u8"iso8859_10"sv},
|
||||
{u8"l6"sv, u8"iso8859_10"sv},
|
||||
{u8"iso-8859-11"sv, u8"iso8859_11"sv},
|
||||
{u8"thai"sv, u8"iso8859_11"sv},
|
||||
{u8"iso-8859-13"sv, u8"iso8859_13"sv},
|
||||
{u8"latin7"sv, u8"iso8859_13"sv},
|
||||
{u8"l7"sv, u8"iso8859_13"sv},
|
||||
{u8"iso-8859-14"sv, u8"iso8859_14"sv},
|
||||
{u8"latin8"sv, u8"iso8859_14"sv},
|
||||
{u8"l8"sv, u8"iso8859_14"sv},
|
||||
{u8"iso-8859-15"sv, u8"iso8859_15"sv},
|
||||
{u8"latin9"sv, u8"iso8859_15"sv},
|
||||
{u8"l9"sv, u8"iso8859_15"sv},
|
||||
{u8"iso-8859-16"sv, u8"iso8859_16"sv},
|
||||
{u8"latin10"sv, u8"iso8859_16"sv},
|
||||
{u8"l10"sv, u8"iso8859_16"sv},
|
||||
{u8"cp1361"sv, u8"johab"sv},
|
||||
{u8"ms1361"sv, u8"johab"sv},
|
||||
{u8"kz_1048"sv, u8"kz1048"sv},
|
||||
{u8"strk1048_2002"sv, u8"kz1048"sv},
|
||||
{u8"rk1048"sv, u8"kz1048"sv},
|
||||
{u8"maccyrillic"sv, u8"mac_cyrillic"sv},
|
||||
{u8"macgreek"sv, u8"mac_greek"sv},
|
||||
{u8"maciceland"sv, u8"mac_iceland"sv},
|
||||
{u8"maclatin2"sv, u8"mac_latin2"sv},
|
||||
{u8"maccentraleurope"sv, u8"mac_latin2"sv},
|
||||
{u8"mac_centeuro"sv, u8"mac_latin2"sv},
|
||||
{u8"macroman"sv, u8"mac_roman"sv},
|
||||
{u8"macintosh"sv, u8"mac_roman"sv},
|
||||
{u8"macturkish"sv, u8"mac_turkish"sv},
|
||||
{u8"csptcp154"sv, u8"ptcp154"sv},
|
||||
{u8"pt154"sv, u8"ptcp154"sv},
|
||||
{u8"cp154"sv, u8"ptcp154"sv},
|
||||
{u8"cyrillic-asian"sv, u8"ptcp154"sv},
|
||||
{u8"csshiftjis"sv, u8"shift_jis"sv},
|
||||
{u8"shiftjis"sv, u8"shift_jis"sv},
|
||||
{u8"sjis"sv, u8"shift_jis"sv},
|
||||
{u8"s_jis"sv, u8"shift_jis"sv},
|
||||
{u8"shiftjis2004"sv, u8"shift_jis_2004"sv},
|
||||
{u8"sjis_2004"sv, u8"shift_jis_2004"sv},
|
||||
{u8"sjis2004"sv, u8"shift_jis_2004"sv},
|
||||
{u8"shiftjisx0213"sv, u8"shift_jisx0213"sv},
|
||||
{u8"sjisx0213"sv, u8"shift_jisx0213"sv},
|
||||
{u8"s_jisx0213"sv, u8"shift_jisx0213"sv},
|
||||
{u8"u32"sv, u8"utf_32"sv},
|
||||
{u8"utf32"sv, u8"utf_32"sv},
|
||||
{u8"utf-32be"sv, u8"utf_32_be"sv},
|
||||
{u8"utf-32le"sv, u8"utf_32_le"sv},
|
||||
{u8"u16"sv, u8"utf_16"sv},
|
||||
{u8"utf16"sv, u8"utf_16"sv},
|
||||
{u8"utf-16be"sv, u8"utf_16_be"sv},
|
||||
{u8"utf-16le"sv, u8"utf_16_le"sv},
|
||||
{u8"u7"sv, u8"utf_7"sv},
|
||||
{u8"unicode-1-1-utf-7"sv, u8"utf_7"sv},
|
||||
{u8"u8"sv, u8"utf_8"sv},
|
||||
{u8"utf"sv, u8"utf_8"sv},
|
||||
{u8"utf8"sv, u8"utf_8"sv},
|
||||
{u8"utf-8"sv, u8"utf_8"sv},
|
||||
{u8"cp65001"sv, u8"utf_8"sv},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @brief Resolve encoding name alias and fetch real encoding name.
|
||||
* @param[in] lang The encoding name for finding.
|
||||
* @return
|
||||
* The given encoding name if given name not present in alias map.
|
||||
* Otherwise the found encoding name by given name.
|
||||
*/
|
||||
static std::u8string resolve_encoding_alias(const std::u8string_view& enc_name) {
|
||||
auto name = op::to_lower(enc_name);
|
||||
auto finder = ALIAS_MAP.find(name);
|
||||
if (finder == ALIAS_MAP.end()) {
|
||||
// not found, use original encoding name.
|
||||
return std::u8string(enc_name);
|
||||
} else {
|
||||
// found, use found encoding name.
|
||||
return std::u8string(finder->second);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
|
||||
using CodePage = YYCC_PYCODEC_BACKEND_NS::CodePage;
|
||||
|
||||
// clang-format off
|
||||
static const std::map<std::u8string_view, CodePage> WINCP_MAP {
|
||||
{ u8"ascii"sv, static_cast<CodePage>(437u) },
|
||||
{ u8"big5"sv, static_cast<CodePage>(950u) },
|
||||
{ u8"cp037"sv, static_cast<CodePage>(037u) },
|
||||
{ u8"cp437"sv, static_cast<CodePage>(437u) },
|
||||
{ u8"cp500"sv, static_cast<CodePage>(500u) },
|
||||
{ u8"cp720"sv, static_cast<CodePage>(720u) },
|
||||
{ u8"cp737"sv, static_cast<CodePage>(737u) },
|
||||
{ u8"cp775"sv, static_cast<CodePage>(775u) },
|
||||
{ u8"cp850"sv, static_cast<CodePage>(850u) },
|
||||
{ u8"cp852"sv, static_cast<CodePage>(852u) },
|
||||
{ u8"cp855"sv, static_cast<CodePage>(855u) },
|
||||
{ u8"cp857"sv, static_cast<CodePage>(857u) },
|
||||
{ u8"cp858"sv, static_cast<CodePage>(858u) },
|
||||
{ u8"cp860"sv, static_cast<CodePage>(860u) },
|
||||
{ u8"cp861"sv, static_cast<CodePage>(861u) },
|
||||
{ u8"cp862"sv, static_cast<CodePage>(862u) },
|
||||
{ u8"cp863"sv, static_cast<CodePage>(863u) },
|
||||
{ u8"cp864"sv, static_cast<CodePage>(864u) },
|
||||
{ u8"cp865"sv, static_cast<CodePage>(865u) },
|
||||
{ u8"cp866"sv, static_cast<CodePage>(866u) },
|
||||
{ u8"cp869"sv, static_cast<CodePage>(869u) },
|
||||
{ u8"cp874"sv, static_cast<CodePage>(874u) },
|
||||
{ u8"cp875"sv, static_cast<CodePage>(875u) },
|
||||
{ u8"cp932"sv, static_cast<CodePage>(932u) },
|
||||
{ u8"cp949"sv, static_cast<CodePage>(949u) },
|
||||
{ u8"cp950"sv, static_cast<CodePage>(950u) },
|
||||
{ u8"cp1026"sv, static_cast<CodePage>(1026u) },
|
||||
{ u8"cp1140"sv, static_cast<CodePage>(1140u) },
|
||||
{ u8"cp1250"sv, static_cast<CodePage>(1250u) },
|
||||
{ u8"cp1251"sv, static_cast<CodePage>(1251u) },
|
||||
{ u8"cp1252"sv, static_cast<CodePage>(1252u) },
|
||||
{ u8"cp1253"sv, static_cast<CodePage>(1253u) },
|
||||
{ u8"cp1254"sv, static_cast<CodePage>(1254u) },
|
||||
{ u8"cp1255"sv, static_cast<CodePage>(1255u) },
|
||||
{ u8"cp1256"sv, static_cast<CodePage>(1256u) },
|
||||
{ u8"cp1257"sv, static_cast<CodePage>(1257u) },
|
||||
{ u8"cp1258"sv, static_cast<CodePage>(1258u) },
|
||||
{ u8"euc_jp"sv, static_cast<CodePage>(20932u) },
|
||||
{ u8"euc_kr"sv, static_cast<CodePage>(51949u) },
|
||||
{ u8"gb2312"sv, static_cast<CodePage>(936u) },
|
||||
{ u8"gbk"sv, static_cast<CodePage>(936u) },
|
||||
{ u8"gb18030"sv, static_cast<CodePage>(54936u) },
|
||||
{ u8"hz"sv, static_cast<CodePage>(52936u) },
|
||||
{ u8"iso2022_jp"sv, static_cast<CodePage>(50220u) },
|
||||
{ u8"iso2022_kr"sv, static_cast<CodePage>(50225u) },
|
||||
{ u8"latin_1"sv, static_cast<CodePage>(28591u) },
|
||||
{ u8"iso8859_2"sv, static_cast<CodePage>(28592u) },
|
||||
{ u8"iso8859_3"sv, static_cast<CodePage>(28593u) },
|
||||
{ u8"iso8859_4"sv, static_cast<CodePage>(28594u) },
|
||||
{ u8"iso8859_5"sv, static_cast<CodePage>(28595u) },
|
||||
{ u8"iso8859_6"sv, static_cast<CodePage>(28596u) },
|
||||
{ u8"iso8859_7"sv, static_cast<CodePage>(28597u) },
|
||||
{ u8"iso8859_8"sv, static_cast<CodePage>(28598u) },
|
||||
{ u8"iso8859_9"sv, static_cast<CodePage>(28599u) },
|
||||
{ u8"iso8859_13"sv, static_cast<CodePage>(28603u) },
|
||||
{ u8"iso8859_15"sv, static_cast<CodePage>(28605u) },
|
||||
{ u8"johab"sv, static_cast<CodePage>(1361u) },
|
||||
{ u8"mac_cyrillic"sv, static_cast<CodePage>(10007u) },
|
||||
{ u8"mac_greek"sv, static_cast<CodePage>(10006u) },
|
||||
{ u8"mac_iceland"sv, static_cast<CodePage>(10079u) },
|
||||
{ u8"mac_turkish"sv, static_cast<CodePage>(10081u) },
|
||||
{ u8"shift_jis"sv, static_cast<CodePage>(932u) },
|
||||
{ u8"utf_7"sv, static_cast<CodePage>(65000u) },
|
||||
{ u8"utf_8"sv, static_cast<CodePage>(65001u) },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static FetchResult<CodePage> fetch_code_page(const std::u8string_view& enc_name) {
|
||||
// resolve alias
|
||||
std::u8string resolved_name = resolve_encoding_alias(enc_name);
|
||||
// find code page
|
||||
op::lower(resolved_name);
|
||||
auto finder = WINCP_MAP.find(resolved_name);
|
||||
if (finder == WINCP_MAP.end()) return std::unexpected(FetchError::NoSuchName);
|
||||
// okey, we found it.
|
||||
return finder->second;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// clang-format off
|
||||
static const std::map<std::u8string_view, std::string_view> ICONV_MAP{
|
||||
{u8"ascii"sv, "ASCII"sv},
|
||||
{u8"big5"sv, "BIG5"sv},
|
||||
{u8"big5hkscs"sv, "BIG5-HKSCS"sv},
|
||||
{u8"cp850"sv, "CP850"sv},
|
||||
{u8"cp862"sv, "CP862"sv},
|
||||
{u8"cp866"sv, "CP866"sv},
|
||||
{u8"cp874"sv, "CP874"sv},
|
||||
{u8"cp932"sv, "CP932"sv},
|
||||
{u8"cp949"sv, "CP949"sv},
|
||||
{u8"cp950"sv, "CP950"sv},
|
||||
{u8"cp1250"sv, "CP1250"sv},
|
||||
{u8"cp1251"sv, "CP1251"sv},
|
||||
{u8"cp1252"sv, "CP1252"sv},
|
||||
{u8"cp1253"sv, "CP1253"sv},
|
||||
{u8"cp1254"sv, "CP1254"sv},
|
||||
{u8"cp1255"sv, "CP1255"sv},
|
||||
{u8"cp1256"sv, "CP1256"sv},
|
||||
{u8"cp1257"sv, "CP1257"sv},
|
||||
{u8"cp1258"sv, "CP1258"sv},
|
||||
{u8"euc_jp"sv, "EUC-JP"sv},
|
||||
{u8"euc_kr"sv, "EUC-KR"sv},
|
||||
{u8"gb2312"sv, "CP936"sv},
|
||||
{u8"gbk"sv, "GBK"sv},
|
||||
{u8"gb18030"sv, "GB18030"sv},
|
||||
{u8"hz"sv, "HZ"sv},
|
||||
{u8"iso2022_jp"sv, "ISO-2022-JP"sv},
|
||||
{u8"iso2022_jp_1"sv, "ISO-2022-JP-1"sv},
|
||||
{u8"iso2022_jp_2"sv, "ISO-2022-JP-2"sv},
|
||||
{u8"iso2022_kr"sv, "ISO-2022-KR"sv},
|
||||
{u8"latin_1"sv, "ISO-8859-1"sv},
|
||||
{u8"iso8859_2"sv, "ISO-8859-2"sv},
|
||||
{u8"iso8859_3"sv, "ISO-8859-3"sv},
|
||||
{u8"iso8859_4"sv, "ISO-8859-4"sv},
|
||||
{u8"iso8859_5"sv, "ISO-8859-5"sv},
|
||||
{u8"iso8859_6"sv, "ISO-8859-6"sv},
|
||||
{u8"iso8859_7"sv, "ISO-8859-7"sv},
|
||||
{u8"iso8859_8"sv, "ISO-8859-8"sv},
|
||||
{u8"iso8859_9"sv, "ISO-8859-9"sv},
|
||||
{u8"iso8859_10"sv, "ISO-8859-10"sv},
|
||||
{u8"iso8859_11"sv, "ISO-8859-11"sv},
|
||||
{u8"iso8859_13"sv, "ISO-8859-13"sv},
|
||||
{u8"iso8859_14"sv, "ISO-8859-14"sv},
|
||||
{u8"iso8859_15"sv, "ISO-8859-15"sv},
|
||||
{u8"iso8859_16"sv, "ISO-8859-16"sv},
|
||||
{u8"johab"sv, "JOHAB"sv},
|
||||
{u8"koi8_t"sv, "KOI8-T"sv},
|
||||
{u8"mac_cyrillic"sv, "MacCyrillic"sv},
|
||||
{u8"mac_greek"sv, "MacGreek"sv},
|
||||
{u8"mac_iceland"sv, "MacIceland"sv},
|
||||
{u8"mac_roman"sv, "MacRoman"sv},
|
||||
{u8"mac_turkish"sv, "MacTurkish"sv},
|
||||
{u8"ptcp154"sv, "PT154"sv},
|
||||
{u8"shift_jis"sv, "SHIFT_JIS"sv},
|
||||
{u8"utf_32"sv, "UTF-32"sv},
|
||||
{u8"utf_32_be"sv, "UTF-32BE"sv},
|
||||
{u8"utf_32_le"sv, "UTF-32LE"sv},
|
||||
{u8"utf_16"sv, "UTF16"sv},
|
||||
{u8"utf_16_be"sv, "UTF-16BE"sv},
|
||||
{u8"utf_16_le"sv, "UTF-16LE"sv},
|
||||
{u8"utf_7"sv, "UTF-7"sv},
|
||||
{u8"utf_8"sv, "UTF-8"sv},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static FetchResult<std::string_view> fetch_iconv_name(const std::u8string_view& enc_name) {
|
||||
// resolve alias
|
||||
std::u8string resolved_name = resolve_encoding_alias(enc_name);
|
||||
// find code page
|
||||
op::lower(resolved_name);
|
||||
auto finder = ICONV_MAP.find(resolved_name);
|
||||
if (finder == ICONV_MAP.end()) return std::unexpected(FetchError::NoSuchName);
|
||||
// okey, we found it.
|
||||
return finder->second;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Misc
|
||||
|
||||
ConvError::ConvError(const ConvBackendError& err) : inner(err) {}
|
||||
|
||||
ConvError::ConvError(const ConvFrontendError& err) : inner(err) {}
|
||||
|
||||
ConvError::ConvError(ConvBackendError&& err) noexcept : inner(std::move(err)) {}
|
||||
|
||||
ConvError::ConvError(ConvFrontendError&& err) noexcept : inner(std::move(err)) {}
|
||||
|
||||
bool is_valid_encoding_name(const EncodingName& name) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return fetch_code_page(name).has_value();
|
||||
#else
|
||||
return fetch_iconv_name(name).has_value();
|
||||
#endif
|
||||
}
|
||||
|
||||
// YYC MARK:
|
||||
// Define a macro for following class ctor
|
||||
// We only need initialize member if we are in Iconv environment.
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
#define CTOR_INITLIST_TYPE1
|
||||
#else
|
||||
#define CTOR_INITLIST_TYPE1 : inner()
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Char -> UTF8
|
||||
|
||||
CharToUtf8::CharToUtf8(const EncodingName& name) : inner(std::nullopt) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
auto rv = fetch_code_page(name);
|
||||
if (rv.has_value()) inner = rv.value();
|
||||
#else
|
||||
auto rv = fetch_iconv_name(name);
|
||||
if (rv.has_value()) inner = YYCC_PYCODEC_BACKEND_NS::CharToUtf8(rv.value());
|
||||
#endif
|
||||
}
|
||||
|
||||
CharToUtf8::~CharToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> CharToUtf8::to_utf8(const std::string_view& src) {
|
||||
if (!inner.has_value()) return std::unexpected(ConvFrontendError::NoSuchName);
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src, inner.value());
|
||||
#else
|
||||
return inner.value().to_utf8(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8 -> Char
|
||||
|
||||
Utf8ToChar::Utf8ToChar(const EncodingName& name) : inner(std::nullopt) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
auto rv = fetch_code_page(name);
|
||||
if (rv.has_value()) inner = rv.value();
|
||||
#else
|
||||
auto rv = fetch_iconv_name(name);
|
||||
if (rv.has_value()) inner = YYCC_PYCODEC_BACKEND_NS::Utf8ToChar(rv.value());
|
||||
#endif
|
||||
}
|
||||
|
||||
Utf8ToChar::~Utf8ToChar() {}
|
||||
|
||||
ConvResult<std::string> Utf8ToChar::to_char(const std::u8string_view& src) {
|
||||
if (!inner.has_value()) return std::unexpected(ConvFrontendError::NoSuchName);
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_char(src, inner.value());
|
||||
#else
|
||||
return inner.value().to_char(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region WChar -> UTF8
|
||||
|
||||
WcharToUtf8::WcharToUtf8() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
WcharToUtf8::~WcharToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> WcharToUtf8::to_utf8(const std::wstring_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
|
||||
#else
|
||||
return inner.to_utf8(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8 -> WChar
|
||||
|
||||
Utf8ToWchar::Utf8ToWchar() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf8ToWchar::~Utf8ToWchar() {}
|
||||
|
||||
ConvResult<std::wstring> Utf8ToWchar::to_wchar(const std::u8string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_wchar(src);
|
||||
#else
|
||||
return inner.to_wchar(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8 -> UTF16
|
||||
|
||||
Utf8ToUtf16::Utf8ToUtf16() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf8ToUtf16::~Utf8ToUtf16() {}
|
||||
|
||||
ConvResult<std::u16string> Utf8ToUtf16::to_utf16(const std::u8string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf16(src);
|
||||
#else
|
||||
return inner.to_utf16(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF16 -> UTF8
|
||||
|
||||
Utf16ToUtf8::Utf16ToUtf8() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf16ToUtf8::~Utf16ToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> Utf16ToUtf8::to_utf8(const std::u16string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
|
||||
#else
|
||||
return inner.to_utf8(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8 -> UTF32
|
||||
|
||||
Utf8ToUtf32::Utf8ToUtf32() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf8ToUtf32::~Utf8ToUtf32() {}
|
||||
|
||||
ConvResult<std::u32string> Utf8ToUtf32::to_utf32(const std::u8string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf32(src);
|
||||
#else
|
||||
return inner.to_utf32(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF32 -> UTF8
|
||||
|
||||
Utf32ToUtf8::Utf32ToUtf8() CTOR_INITLIST_TYPE1 {}
|
||||
|
||||
Utf32ToUtf8::~Utf32ToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> Utf32ToUtf8::to_utf8(const std::u32string_view& src) {
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
return YYCC_PYCODEC_BACKEND_NS::to_utf8(src);
|
||||
#else
|
||||
return inner.to_utf8(src);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::carton::pycodec
|
204
src/yycc/carton/pycodec.hpp
Normal file
204
src/yycc/carton/pycodec.hpp
Normal file
@ -0,0 +1,204 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#include "../macro/class_copy_move.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <expected>
|
||||
|
||||
// Choose the backend of PyCodec module
|
||||
#if defined(YYCC_FEAT_ICONV)
|
||||
// We try Iconv first in any cases.
|
||||
#include "../encoding/iconv.hpp"
|
||||
#define YYCC_PYCODEC_ICONV_BACKEND
|
||||
#define YYCC_PYCODEC_BACKEND_NS ::yycc::encoding::iconv
|
||||
#elif defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||
// If we can not use Iconv, we try to fallback to Windows implementation.
|
||||
#include "../encoding/windows.hpp"
|
||||
#define YYCC_PYCODEC_WIN32_BACKEND
|
||||
#define YYCC_PYCODEC_BACKEND_NS ::yycc::encoding::windows
|
||||
#else
|
||||
// No viable implementation.
|
||||
#error "Can not find viable encoding convertion solution in current environment for PyCodec module."
|
||||
#endif
|
||||
|
||||
namespace yycc::carton::pycodec {
|
||||
|
||||
/// @brief The universal name of encoding.
|
||||
using EncodingName = std::u8string_view;
|
||||
|
||||
/// @brief The alias to error type of backend.
|
||||
using ConvBackendError = YYCC_PYCODEC_BACKEND_NS::ConvError;
|
||||
|
||||
/// @brief The error occurs in this module self.
|
||||
enum class ConvFrontendError {
|
||||
NoSuchName, ///< Can not find suitable backend token for given encoding name.
|
||||
};
|
||||
|
||||
/// @brief The possible error occurs in this module.
|
||||
class ConvError {
|
||||
public:
|
||||
ConvError(const ConvBackendError& err);
|
||||
ConvError(const ConvFrontendError& err);
|
||||
ConvError(ConvBackendError&& err) noexcept;
|
||||
ConvError(ConvFrontendError&& err) noexcept;
|
||||
YYCC_DEFAULT_COPY_MOVE(ConvError)
|
||||
|
||||
private:
|
||||
std::variant<ConvBackendError, ConvFrontendError> inner;
|
||||
};
|
||||
|
||||
/// @brief The result type of this module.
|
||||
template<typename T>
|
||||
using ConvResult = std::expected<T, ConvError>;
|
||||
|
||||
/**
|
||||
* @brief Check whether given name is a valid encoding name in PyCodec.
|
||||
* @param[in] name The name to be checked.
|
||||
* @return True if it is valid, otherwise false.
|
||||
*/
|
||||
bool is_valid_encoding_name(const EncodingName& name);
|
||||
|
||||
/// @brief Char -> UTF8
|
||||
class CharToUtf8 {
|
||||
public:
|
||||
CharToUtf8(const EncodingName& name);
|
||||
~CharToUtf8();
|
||||
YYCC_DELETE_COPY(CharToUtf8)
|
||||
YYCC_DEFAULT_MOVE(CharToUtf8)
|
||||
|
||||
public:
|
||||
ConvResult<std::u8string> to_utf8(const std::string_view& src);
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
std::optional<YYCC_PYCODEC_BACKEND_NS::CodePage> inner;
|
||||
#else
|
||||
std::optional<YYCC_PYCODEC_BACKEND_NS::CharToUtf8> inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// @brief UTF8 -> Char
|
||||
class Utf8ToChar {
|
||||
public:
|
||||
Utf8ToChar(const EncodingName& name);
|
||||
~Utf8ToChar();
|
||||
YYCC_DELETE_COPY(Utf8ToChar)
|
||||
YYCC_DEFAULT_MOVE(Utf8ToChar)
|
||||
|
||||
public:
|
||||
ConvResult<std::string> to_char(const std::u8string_view& src);
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_WIN32_BACKEND)
|
||||
std::optional<YYCC_PYCODEC_BACKEND_NS::CodePage> inner;
|
||||
#else
|
||||
std::optional<YYCC_PYCODEC_BACKEND_NS::Utf8ToChar> inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// @brief WChar -> UTF8
|
||||
class WcharToUtf8 {
|
||||
public:
|
||||
WcharToUtf8();
|
||||
~WcharToUtf8();
|
||||
YYCC_DELETE_COPY(WcharToUtf8)
|
||||
YYCC_DEFAULT_MOVE(WcharToUtf8)
|
||||
|
||||
public:
|
||||
ConvResult<std::u8string> to_utf8(const std::wstring_view& src);
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
|
||||
YYCC_PYCODEC_BACKEND_NS::WcharToUtf8 inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// @brief UTF8 -> WChar
|
||||
class Utf8ToWchar {
|
||||
public:
|
||||
Utf8ToWchar();
|
||||
~Utf8ToWchar();
|
||||
YYCC_DELETE_COPY(Utf8ToWchar)
|
||||
YYCC_DEFAULT_MOVE(Utf8ToWchar)
|
||||
|
||||
public:
|
||||
ConvResult<std::wstring> to_wchar(const std::u8string_view& src);
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
|
||||
YYCC_PYCODEC_BACKEND_NS::Utf8ToWchar inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// @brief UTF8 -> UTF16
|
||||
class Utf8ToUtf16 {
|
||||
public:
|
||||
Utf8ToUtf16();
|
||||
~Utf8ToUtf16();
|
||||
YYCC_DELETE_COPY(Utf8ToUtf16)
|
||||
YYCC_DEFAULT_MOVE(Utf8ToUtf16)
|
||||
|
||||
public:
|
||||
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
|
||||
YYCC_PYCODEC_BACKEND_NS::Utf8ToUtf16 inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// @brief UTF16 -> UTF8
|
||||
class Utf16ToUtf8 {
|
||||
public:
|
||||
Utf16ToUtf8();
|
||||
~Utf16ToUtf8();
|
||||
YYCC_DELETE_COPY(Utf16ToUtf8)
|
||||
YYCC_DEFAULT_MOVE(Utf16ToUtf8)
|
||||
|
||||
public:
|
||||
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
|
||||
YYCC_PYCODEC_BACKEND_NS::Utf16ToUtf8 inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// @brief UTF8 -> UTF32
|
||||
class Utf8ToUtf32 {
|
||||
public:
|
||||
Utf8ToUtf32();
|
||||
~Utf8ToUtf32();
|
||||
YYCC_DELETE_COPY(Utf8ToUtf32)
|
||||
YYCC_DEFAULT_MOVE(Utf8ToUtf32)
|
||||
|
||||
public:
|
||||
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
|
||||
YYCC_PYCODEC_BACKEND_NS::Utf8ToUtf32 inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// @brief UTF32 -> UTF8
|
||||
class Utf32ToUtf8 {
|
||||
public:
|
||||
Utf32ToUtf8();
|
||||
~Utf32ToUtf8();
|
||||
YYCC_DELETE_COPY(Utf32ToUtf8)
|
||||
YYCC_DEFAULT_MOVE(Utf32ToUtf8)
|
||||
|
||||
public:
|
||||
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
|
||||
|
||||
private:
|
||||
#if defined(YYCC_PYCODEC_ICONV_BACKEND)
|
||||
YYCC_PYCODEC_BACKEND_NS::Utf32ToUtf8 inner;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace yycc::carton::pycodec
|
192
src/yycc/carton/tabulate.cpp
Normal file
192
src/yycc/carton/tabulate.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include "tabulate.hpp"
|
||||
#include "wcwidth.hpp"
|
||||
#include "../num/safe_op.hpp"
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <stdexcept>
|
||||
#include <ranges>
|
||||
|
||||
#define WCWIDTH ::yycc::carton::wcwidth
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
#define SAFEOP ::yycc::num::safe_op
|
||||
|
||||
namespace yycc::carton::tabulate {
|
||||
|
||||
#pragma region Tabulate Width
|
||||
|
||||
TabulateWidth::TabulateWidth(size_t n) : widths(n, 0u) {}
|
||||
|
||||
TabulateWidth::~TabulateWidth() {}
|
||||
|
||||
size_t TabulateWidth::get_column_count() const {
|
||||
return widths.size();
|
||||
}
|
||||
|
||||
size_t TabulateWidth::get_column_width(size_t column_index) const {
|
||||
return widths.at(column_index);
|
||||
}
|
||||
|
||||
void TabulateWidth::update_column_width(size_t column_index, size_t new_size) {
|
||||
auto& width = widths.at(column_index);
|
||||
width = std::max(width, new_size);
|
||||
}
|
||||
|
||||
void TabulateWidth::clear() {
|
||||
std::fill(widths.begin(), widths.end(), 0u);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Tabulate Cell
|
||||
|
||||
TabulateCell::TabulateCell(const std::u8string_view& text) : text(text), text_width(WCWIDTH::wcswidth(text).value_or(0u)) {}
|
||||
|
||||
TabulateCell::~TabulateCell() {}
|
||||
|
||||
const std::u8string& TabulateCell::get_text() const {
|
||||
return text;
|
||||
}
|
||||
|
||||
size_t TabulateCell::get_text_width() const {
|
||||
return text_width;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Tabulate
|
||||
|
||||
/// @brief Default separator literal for Tabulate.
|
||||
static constexpr char8_t SEPARATOR_BAR[] = u8"---";
|
||||
/// @brief A stupid size_t ZERO literal to trigger template type deduce for std::views::iota.
|
||||
static constexpr size_t ZERO = 0;
|
||||
|
||||
Tabulate::Tabulate(size_t n) :
|
||||
n(n), header_display(true), bar_display(true), prefix_string(), rows_widths(n), header_widths(n), header(n, TabulateCell(u8"")),
|
||||
bar(SEPARATOR_BAR), rows() {}
|
||||
|
||||
Tabulate::~Tabulate() {}
|
||||
|
||||
void Tabulate::print(std::ostream& dst) const {
|
||||
// Get column count
|
||||
auto n = this->get_column_count();
|
||||
|
||||
// Create width recorder for final printing
|
||||
// according to whether we show table header and separator bar.
|
||||
auto widths = this->rows_widths;
|
||||
if (this->header_display) {
|
||||
for (auto index : std::views::iota(ZERO, n)) {
|
||||
widths.update_column_width(index, this->header_widths.get_column_width(index));
|
||||
}
|
||||
}
|
||||
if (this->bar_display) {
|
||||
auto bar_width = this->bar.get_text_width();
|
||||
for (auto index : std::views::iota(ZERO, n)) {
|
||||
widths.update_column_width(index, bar_width);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the maximum space char count to build a string filled with spaces,
|
||||
// for the convenient about following printing.
|
||||
size_t max_space = 1;
|
||||
for (auto index : std::views::iota(ZERO, n)) {
|
||||
max_space = std::max(max_space, widths.get_column_width(index));
|
||||
}
|
||||
std::u8string spaces(max_space, u8' ');
|
||||
std::u8string_view spaces_view(spaces);
|
||||
|
||||
// Print table
|
||||
// Define a convenient macro
|
||||
#define CVT(data) REINTERPRET::as_ordinary_view(data)
|
||||
// Show header
|
||||
if (this->header_display) {
|
||||
dst << CVT(this->prefix_string);
|
||||
for (const auto [index, item] : std::views::enumerate(header)) {
|
||||
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), item.get_text_width());
|
||||
dst << CVT(item.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
|
||||
}
|
||||
dst << std::endl;
|
||||
}
|
||||
// Show bar
|
||||
if (this->bar_display) {
|
||||
dst << CVT(this->prefix_string);
|
||||
auto bar_width = this->bar.get_text_width();
|
||||
for (auto index : std::views::iota(ZERO, n)) {
|
||||
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), bar_width);
|
||||
dst << CVT(this->bar.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
|
||||
}
|
||||
dst << std::endl;
|
||||
}
|
||||
// Show data
|
||||
for (const auto& row : this->rows) {
|
||||
dst << CVT(this->prefix_string);
|
||||
for (const auto [index, item] : std::views::enumerate(row)) {
|
||||
auto diff = SAFEOP::saturating_sub(widths.get_column_width(index), item.get_text_width());
|
||||
dst << CVT(item.get_text()) << CVT(spaces_view.substr(0, diff)) << " ";
|
||||
}
|
||||
dst << std::endl;
|
||||
}
|
||||
// Undef macro
|
||||
#undef CVT
|
||||
}
|
||||
|
||||
size_t Tabulate::get_column_count() const {
|
||||
return this->n;
|
||||
}
|
||||
|
||||
void Tabulate::show_header(bool show_header) {
|
||||
this->header_display = show_header;
|
||||
}
|
||||
|
||||
void Tabulate::show_bar(bool show_bar) {
|
||||
this->bar_display = show_bar;
|
||||
}
|
||||
|
||||
void Tabulate::set_prefix(const std::u8string_view& prefix) {
|
||||
this->prefix_string = prefix;
|
||||
}
|
||||
|
||||
void Tabulate::set_header(std::initializer_list<std::u8string_view> hdr) {
|
||||
// Check data size.
|
||||
if (hdr.size() != get_column_count()) {
|
||||
throw std::invalid_argument("the size of given header is not equal to column count");
|
||||
}
|
||||
|
||||
// Change header data and update header width recorder.
|
||||
header.clear();
|
||||
header_widths.clear();
|
||||
for (const auto [index, item] : std::views::enumerate(hdr)) {
|
||||
auto cell = header.emplace_back(item);
|
||||
header_widths.update_column_width(index, cell.get_text_width());
|
||||
}
|
||||
}
|
||||
|
||||
void Tabulate::set_bar(const std::u8string_view& bar) {
|
||||
this->bar = bar;
|
||||
}
|
||||
|
||||
void Tabulate::add_row(std::initializer_list<std::u8string_view> row) {
|
||||
// Check data size.
|
||||
if (row.size() != get_column_count()) {
|
||||
throw std::invalid_argument("the size of given row is not equal to column count");
|
||||
}
|
||||
|
||||
// Prepare inserted row, and update data width recorder.
|
||||
Row inserted_row;
|
||||
inserted_row.reserve(row.size());
|
||||
for (const auto [index, item] : std::views::enumerate(row)) {
|
||||
auto cell = inserted_row.emplace_back(item);
|
||||
rows_widths.update_column_width(index, cell.get_text_width());
|
||||
}
|
||||
|
||||
// Insert row
|
||||
rows.emplace_back(std::move(inserted_row));
|
||||
}
|
||||
|
||||
void Tabulate::clear() {
|
||||
// Clear data and data width recorder.
|
||||
rows.clear();
|
||||
rows_widths.clear();
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::carton::tabulate
|
198
src/yycc/carton/tabulate.hpp
Normal file
198
src/yycc/carton/tabulate.hpp
Normal file
@ -0,0 +1,198 @@
|
||||
#pragma once
|
||||
#include "../macro/class_copy_move.hpp"
|
||||
#include <initializer_list>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace yycc::carton::tabulate {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Assistant class recording column width.
|
||||
*/
|
||||
class TabulateWidth {
|
||||
public:
|
||||
/**
|
||||
* @brief Create width recorder with given column count.
|
||||
* @param[in] n Column count.
|
||||
*/
|
||||
TabulateWidth(size_t n);
|
||||
~TabulateWidth();
|
||||
YYCC_DEFAULT_COPY_MOVE(TabulateWidth)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get column count.
|
||||
* @return Column count.
|
||||
*/
|
||||
size_t get_column_count() const;
|
||||
/**
|
||||
* @brief Get column width of given index.
|
||||
* @param[in] column_index Column index for fetching width.
|
||||
* @return Column width of given index.
|
||||
*/
|
||||
size_t get_column_width(size_t column_index) const;
|
||||
/**
|
||||
* @brief Update column width of given index with given value.
|
||||
* @details The width of column will be updated to the maximum between given value and old value.
|
||||
* @param[in] column_index Column index for updating width.
|
||||
* @param[in] new_size New width value.
|
||||
*/
|
||||
void update_column_width(size_t column_index, size_t new_size);
|
||||
/**
|
||||
* @brief Clear all width data
|
||||
* @details All width data will be reset to zero.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
std::vector<size_t> widths;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Assistant class holding table cell data.
|
||||
* @details
|
||||
* This class holds the data of table cell.
|
||||
* Also make a cache for the width this cell's text occupied in console,
|
||||
* to avoid duplicated calculation for occupied width.
|
||||
*/
|
||||
class TabulateCell {
|
||||
public:
|
||||
/**
|
||||
* @brief Build cell with given text.
|
||||
* @param[in] text Data of cell.
|
||||
*/
|
||||
TabulateCell(const std::u8string_view& text);
|
||||
~TabulateCell();
|
||||
YYCC_DEFAULT_COPY_MOVE(TabulateCell)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the text of cell.
|
||||
* @return The text of cell.
|
||||
*/
|
||||
const std::u8string& get_text() const;
|
||||
/**
|
||||
* @brief Get width this cell's text occupied in console.
|
||||
* @return The width this cell occupied.
|
||||
*/
|
||||
size_t get_text_width() const;
|
||||
|
||||
private:
|
||||
/// @brief The data of cell.
|
||||
std::u8string text;
|
||||
/// @brief The width cache of this data occupied in console.
|
||||
size_t text_width;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The type representing one row of data in table.
|
||||
*/
|
||||
using Row = std::vector<TabulateCell>;
|
||||
/**
|
||||
* @private
|
||||
* @brief The type representing row collection in table.
|
||||
*/
|
||||
using Rows = std::vector<Row>;
|
||||
|
||||
/**
|
||||
* @brief Main class of Tabulate
|
||||
*/
|
||||
class Tabulate {
|
||||
public:
|
||||
/**
|
||||
* @brief Create Tabulate class with given column count.
|
||||
* @details
|
||||
* In default, the separator bar of table is 3 dash.
|
||||
* Header and separator bar are also shown in default.
|
||||
* @param[in] n Column count of table.
|
||||
*/
|
||||
Tabulate(size_t n);
|
||||
~Tabulate();
|
||||
YYCC_DELETE_COPY_MOVE(Tabulate)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Print table into given stream.
|
||||
* @details In default, stream is \c stdout.
|
||||
* @param[in] dst The stream printed into.
|
||||
*/
|
||||
void print(std::ostream& dst = std::cout) const;
|
||||
|
||||
/**
|
||||
* @brief Get the column count of table.
|
||||
* @return Column count of table.
|
||||
*/
|
||||
size_t get_column_count() const;
|
||||
/**
|
||||
* @brief Change whether show table header when printing.
|
||||
* @param[in] show_header True for showing, otherwise false.
|
||||
*/
|
||||
void show_header(bool show_header);
|
||||
/**
|
||||
* @brief Change whether show separator bar when printing.
|
||||
* @param[in] show_bar True for showing, otherwise false.
|
||||
*/
|
||||
void show_bar(bool show_bar);
|
||||
/**
|
||||
* @brief Modify the prefix string of table.
|
||||
* @details
|
||||
* The prefix string of table is the string
|
||||
* which will be printed before each lines of output.
|
||||
* It is usually used for indent.
|
||||
* @param[in] prefix The prefix string.
|
||||
*/
|
||||
void set_prefix(const std::u8string_view& prefix);
|
||||
/**
|
||||
* @brief Modify the header of table.
|
||||
* @param[in] hdr An initializer list holding header texts one by one.
|
||||
* @exception std::invalid_argument The size of given header is mismatch with column count.
|
||||
*/
|
||||
void set_header(std::initializer_list<std::u8string_view> hdr);
|
||||
/**
|
||||
* @brief Modify separator bar string of table.
|
||||
* @param[in] bar New separator bar string.
|
||||
*/
|
||||
void set_bar(const std::u8string_view& bar);
|
||||
/**
|
||||
* @brief Add one data row into table.
|
||||
* @param[in] row An initializer list holding row texts one by one.
|
||||
* @exception std::invalid_argument The size of given header is mismatch with column count.
|
||||
*/
|
||||
void add_row(std::initializer_list<std::u8string_view> row);
|
||||
/**
|
||||
* @brief Clear all data rows of table.
|
||||
* @details Table header and separator bar will not be changed.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private:
|
||||
|
||||
/// @brief The column count of table.
|
||||
size_t n;
|
||||
|
||||
/// @brief Whether showing table header.
|
||||
bool header_display;
|
||||
/// @brief Whether showing table separator bar between header and data.
|
||||
bool bar_display;
|
||||
/// @brief The prefix string presented in each lines of output table.
|
||||
std::u8string prefix_string;
|
||||
|
||||
/// @brief Width recorder for header.
|
||||
TabulateWidth header_widths;
|
||||
/// @brief Width recorder for data.
|
||||
TabulateWidth rows_widths;
|
||||
|
||||
/// @brief The header of table.
|
||||
Row header;
|
||||
/// @brief The separator bar of table.
|
||||
TabulateCell bar;
|
||||
/// @brief The data of table.
|
||||
Rows rows;
|
||||
};
|
||||
|
||||
}
|
234
src/yycc/carton/termcolor.cpp
Normal file
234
src/yycc/carton/termcolor.cpp
Normal file
@ -0,0 +1,234 @@
|
||||
#include "termcolor.hpp"
|
||||
#include "../flag_enum.hpp"
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <stdexcept>
|
||||
#include <bit>
|
||||
|
||||
#define FLAG_ENUM ::yycc::flag_enum
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
namespace yycc::carton::termcolor {
|
||||
|
||||
#pragma region Lowlevel Functions
|
||||
|
||||
const std::u8string_view foreground(Color color) {
|
||||
switch (color) {
|
||||
case Color::Default:
|
||||
return u8""sv;
|
||||
case Color::Black:
|
||||
return u8"\033[30m"sv;
|
||||
case Color::Red:
|
||||
return u8"\033[31m"sv;
|
||||
case Color::Green:
|
||||
return u8"\033[32m"sv;
|
||||
case Color::Yellow:
|
||||
return u8"\033[33m"sv;
|
||||
case Color::Blue:
|
||||
return u8"\033[34m"sv;
|
||||
case Color::Magenta:
|
||||
return u8"\033[35m"sv;
|
||||
case Color::Cyan:
|
||||
return u8"\033[36m"sv;
|
||||
case Color::White:
|
||||
return u8"\033[37m"sv;
|
||||
case Color::LightBlack:
|
||||
return u8"\033[90m"sv;
|
||||
case Color::LightRed:
|
||||
return u8"\033[91m"sv;
|
||||
case Color::LightGreen:
|
||||
return u8"\033[92m"sv;
|
||||
case Color::LightYellow:
|
||||
return u8"\033[93m"sv;
|
||||
case Color::LightBlue:
|
||||
return u8"\033[94m"sv;
|
||||
case Color::LightMagenta:
|
||||
return u8"\033[95m"sv;
|
||||
case Color::LightCyan:
|
||||
return u8"\033[96m"sv;
|
||||
case Color::LightWhite:
|
||||
return u8"\033[97m"sv;
|
||||
default:
|
||||
throw std::invalid_argument("invalid color kind");
|
||||
}
|
||||
}
|
||||
|
||||
const std::u8string_view background(Color color) {
|
||||
switch (color) {
|
||||
case Color::Default:
|
||||
return u8""sv;
|
||||
case Color::Black:
|
||||
return u8"\033[40m"sv;
|
||||
case Color::Red:
|
||||
return u8"\033[41m"sv;
|
||||
case Color::Green:
|
||||
return u8"\033[42m"sv;
|
||||
case Color::Yellow:
|
||||
return u8"\033[43m"sv;
|
||||
case Color::Blue:
|
||||
return u8"\033[44m"sv;
|
||||
case Color::Magenta:
|
||||
return u8"\033[45m"sv;
|
||||
case Color::Cyan:
|
||||
return u8"\033[46m"sv;
|
||||
case Color::White:
|
||||
return u8"\033[47m"sv;
|
||||
case Color::LightBlack:
|
||||
return u8"\033[100m"sv;
|
||||
case Color::LightRed:
|
||||
return u8"\033[101m"sv;
|
||||
case Color::LightGreen:
|
||||
return u8"\033[102m"sv;
|
||||
case Color::LightYellow:
|
||||
return u8"\033[103m"sv;
|
||||
case Color::LightBlue:
|
||||
return u8"\033[104m"sv;
|
||||
case Color::LightMagenta:
|
||||
return u8"\033[105m"sv;
|
||||
case Color::LightCyan:
|
||||
return u8"\033[106m"sv;
|
||||
case Color::LightWhite:
|
||||
return u8"\033[107m"sv;
|
||||
default:
|
||||
throw std::invalid_argument("invalid color kind");
|
||||
}
|
||||
}
|
||||
|
||||
const std::u8string_view style(Attribute attr) {
|
||||
// Return for Default first because it can not pass following test
|
||||
if (attr == Attribute::Default) {
|
||||
return u8""sv;
|
||||
}
|
||||
|
||||
// Check whether it only has one flag
|
||||
if (!std::has_single_bit(FLAG_ENUM::integer(attr))) {
|
||||
throw std::invalid_argument("style() only accept single flag attribute");
|
||||
}
|
||||
|
||||
// Return result
|
||||
switch (attr) {
|
||||
case Attribute::Bold:
|
||||
return u8"\033[1m"sv;
|
||||
case Attribute::Dark:
|
||||
return u8"\033[2m"sv;
|
||||
case Attribute::Italic:
|
||||
return u8"\033[3m"sv;
|
||||
case Attribute::Underline:
|
||||
return u8"\033[4m"sv;
|
||||
case Attribute::Blink:
|
||||
return u8"\033[5m"sv;
|
||||
case Attribute::Reverse:
|
||||
return u8"\033[6m"sv;
|
||||
case Attribute::Concealed:
|
||||
return u8"\033[7m"sv;
|
||||
default:
|
||||
throw std::invalid_argument("invalid attribute kind");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The possible maximum length of ANSI Escape Sequence used in this module.
|
||||
* @details This const value is used for computing reserved size of final built string.
|
||||
*/
|
||||
static constexpr size_t ANSI_ESC_LEN = sizeof(u8"\033[000m") - 1;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Count how many single flags combine given attributes.
|
||||
* @details
|
||||
* For function styles() involving multiple font style ANSI Escape Sequence,
|
||||
* this function may be useful for computing desired size of final result,
|
||||
* to reduce useless memory re-allocation.
|
||||
* @param[in] attrs Attributes for counting.
|
||||
* @return The count of single flag.
|
||||
*/
|
||||
static size_t count_attribute_flags(Attribute attrs) {
|
||||
return static_cast<size_t>(std::popcount(FLAG_ENUM::integer(attrs)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief Append multiple font styles into given string.
|
||||
* @details
|
||||
* This function will decompose given font styles into single flag.
|
||||
* And append its components one by one into given string.
|
||||
* If there is enough reserved space in given string,
|
||||
* there is no memory re-allocation happened.
|
||||
* @remarks
|
||||
* This function is served for styles() and colored().
|
||||
* @param[in] s The string to be appended.
|
||||
* @param[in] attrs The attributes for writting.
|
||||
*/
|
||||
static void append_styles(std::u8string& s, Attribute attrs) {
|
||||
#define CHECK_ATTR(probe) \
|
||||
if (FLAG_ENUM::has(attrs, probe)) s.append(termcolor::style(probe));
|
||||
|
||||
if (attrs != Attribute::Default) {
|
||||
CHECK_ATTR(Attribute::Bold);
|
||||
CHECK_ATTR(Attribute::Dark);
|
||||
CHECK_ATTR(Attribute::Italic);
|
||||
CHECK_ATTR(Attribute::Blink);
|
||||
CHECK_ATTR(Attribute::Reverse);
|
||||
CHECK_ATTR(Attribute::Concealed);
|
||||
}
|
||||
|
||||
#undef CHECK_ATTR
|
||||
}
|
||||
|
||||
std::u8string styles(Attribute attrs) {
|
||||
// Prepare the result string
|
||||
std::u8string rv;
|
||||
rv.reserve(count_attribute_flags(attrs) * ANSI_ESC_LEN);
|
||||
// Append styles and return
|
||||
append_styles(rv, attrs);
|
||||
return rv;
|
||||
}
|
||||
|
||||
const std::u8string_view reset() {
|
||||
return u8"\033[0m"sv;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Highlevel Functions
|
||||
|
||||
std::u8string colored(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
|
||||
// Calculate the expected size of result string.
|
||||
// final count = styles count + 1 (foreground) + 1 (background) + 1 (reset)
|
||||
std::u8string rv;
|
||||
size_t ansi_esc_count = count_attribute_flags(styles) + 3;
|
||||
rv.reserve(ansi_esc_count * ANSI_ESC_LEN + words.size());
|
||||
|
||||
// Append data one by one
|
||||
rv.append(termcolor::foreground(foreground));
|
||||
rv.append(termcolor::background(background));
|
||||
append_styles(rv, styles);
|
||||
rv.append(words);
|
||||
rv.append(termcolor::reset());
|
||||
|
||||
// Return result
|
||||
return rv;
|
||||
}
|
||||
|
||||
void cprint(const std::u8string_view& words, Color foreground, Color background, Attribute styles, std::ostream& dst) {
|
||||
dst << REINTERPRET::as_ordinary_view(colored(words, foreground, background, styles));
|
||||
}
|
||||
|
||||
void ecprint(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
|
||||
cprint(words, foreground, background, styles, std::cerr);
|
||||
}
|
||||
|
||||
void cprintln(const std::u8string_view& words, Color foreground, Color background, Attribute styles, std::ostream& dst) {
|
||||
cprint(words, foreground, background, styles, dst);
|
||||
dst << std::endl;
|
||||
}
|
||||
|
||||
void ecprintln(const std::u8string_view& words, Color foreground, Color background, Attribute styles) {
|
||||
cprintln(words, foreground, background, styles, std::cerr);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::carton::termcolor
|
166
src/yycc/carton/termcolor.hpp
Normal file
166
src/yycc/carton/termcolor.hpp
Normal file
@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* @brief The namespace for terminal font color and style.
|
||||
* @details
|
||||
* This namespace provides functions to generate ANSI escape sequence for terminal font color and style.
|
||||
* It also provides functions to add color and style for given string with ANSI Escape Sequence.
|
||||
*
|
||||
* This namespace is basically the immitation of the Python package with same name.
|
||||
*/
|
||||
namespace yycc::carton::termcolor {
|
||||
|
||||
#pragma region Lowlevel Functions
|
||||
|
||||
/**
|
||||
* @brief The color of font.
|
||||
*/
|
||||
enum class Color {
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
LightBlack,
|
||||
LightRed,
|
||||
LightGreen,
|
||||
LightYellow,
|
||||
LightBlue,
|
||||
LightMagenta,
|
||||
LightCyan,
|
||||
LightWhite,
|
||||
Default
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get ANSI escape sequence for foreground color
|
||||
* @param[in] color The color to generate sequence for
|
||||
* @return Gotten ANSI escape sequence
|
||||
*/
|
||||
const std::u8string_view foreground(Color color);
|
||||
/**
|
||||
* @brief Get ANSI escape sequence for background color
|
||||
* @param[in] color The color to generate sequence for
|
||||
* @return Gotten ANSI escape sequence
|
||||
*/
|
||||
const std::u8string_view background(Color color);
|
||||
|
||||
/**
|
||||
* @brief The attribute of font
|
||||
* @remarks We define this enum as unsigned integral, so that we can use \c std::has_single_bit.
|
||||
*/
|
||||
enum class Attribute : uint32_t {
|
||||
Default = 0,
|
||||
Bold = 1 << 0,
|
||||
Dark = 1 << 1,
|
||||
Italic = 1 << 2,
|
||||
Underline = 1 << 3,
|
||||
Blink = 1 << 4,
|
||||
Reverse = 1 << 5,
|
||||
Concealed = 1 << 6
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get ANSI escape sequence for text style
|
||||
* @details
|
||||
* Please note that this function only support single attribute flag.
|
||||
* If you want to use multiple attributes, please use styles() instead.
|
||||
*
|
||||
* However, the difference between this function and styles() is that
|
||||
* there is no memory allocation in this function.
|
||||
* It may have better performance that styles().
|
||||
* @param[in] attr Single attribute to generate sequence for
|
||||
* @return Gotten ANSI escape sequence
|
||||
* @throws std::invalid_argument if attribute is not a single flag
|
||||
*/
|
||||
const std::u8string_view style(Attribute attr);
|
||||
/**
|
||||
* @brief Generates ANSI escape sequence for multiple text styles
|
||||
* @param[in] attrs Combination of attributes to generate sequences for
|
||||
* @return Generated ANSI escape sequence
|
||||
*/
|
||||
std::u8string styles(Attribute attrs);
|
||||
/**
|
||||
* @brief Get ANSI escape sequence for reset style
|
||||
* @return Gotten ANSI reset sequence
|
||||
*/
|
||||
const std::u8string_view reset();
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Highlevel Functions
|
||||
|
||||
/**
|
||||
* @brief Add color and style for given string with ANSI Escape Sequence.
|
||||
* @param[in] words The words to be decorated.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
* @return Decorated words.
|
||||
*/
|
||||
std::u8string colored(const std::u8string_view& words,
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default);
|
||||
|
||||
/**
|
||||
* @brief Print words into stream with given styles.
|
||||
* @param[in] words The words to be printed.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
* @param[in] dst The stream written into. \c stdout in default.
|
||||
*/
|
||||
void cprint(const std::u8string_view& words = std::u8string_view(u8""),
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default,
|
||||
std::ostream& dst = std::cout);
|
||||
|
||||
/**
|
||||
* @brief Print words into \c stderr with given styles.
|
||||
* @param[in] words The words to be printed.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
*/
|
||||
void ceprint(const std::u8string_view& words = std::u8string_view(u8""),
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default);
|
||||
|
||||
/**
|
||||
* @brief Print words into stream with given styles and break line.
|
||||
* @param[in] words The words to be printed.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
* @param[in] dst The stream written into. \c stdout in default.
|
||||
*/
|
||||
void cprintln(const std::u8string_view& words = std::u8string_view(u8""),
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default,
|
||||
std::ostream& dst = std::cout);
|
||||
|
||||
/**
|
||||
* @brief Print words into \c stderr with given styles and break line.
|
||||
* @param[in] words The words to be printed.
|
||||
* @param[in] foreground The foreground of words.
|
||||
* @param[in] background The background of words.
|
||||
* @param[in] styles The font style of words.
|
||||
*/
|
||||
void ceprintln(const std::u8string_view& words = std::u8string_view(u8""),
|
||||
Color foreground = Color::Default,
|
||||
Color background = Color::Default,
|
||||
Attribute styles = Attribute::Default);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::carton::termcolor
|
478
src/yycc/carton/wcwidth.cpp
Normal file
478
src/yycc/carton/wcwidth.cpp
Normal file
@ -0,0 +1,478 @@
|
||||
#include "wcwidth.hpp"
|
||||
#include "../encoding/stl.hpp"
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
#define ENC ::yycc::encoding::stl
|
||||
|
||||
namespace yycc::carton::wcwidth {
|
||||
|
||||
using Boundary = std::pair<char32_t, char32_t>;
|
||||
using BoundaryVector = std::vector<Boundary>;
|
||||
|
||||
// YYC MARK:
|
||||
// Following table and code are copied from Python package "wcwidth".
|
||||
// Although the code of this package are also copied from the original "wcwidth" C implementation.
|
||||
//
|
||||
// I do not need so much exact measurement.
|
||||
// I just want a "it works" wcwdith in all platforms.
|
||||
// So these tables are coming from the table with lowest UNICODE version
|
||||
// (original package provides different tables for different UNICODE versions).
|
||||
|
||||
// clang-format off
|
||||
static const BoundaryVector ZERO_WIDTH{
|
||||
{U'\x00000', U'\x00000'}, // (nil)
|
||||
{U'\x000ad', U'\x000ad'}, // Soft Hyphen
|
||||
{U'\x00300', U'\x0036f'}, // Combining Grave Accent ..Combining Latin Small Le
|
||||
{U'\x00483', U'\x00486'}, // Combining Cyrillic Titlo..Combining Cyrillic Psili
|
||||
{U'\x00488', U'\x00489'}, // Combining Cyrillic Hundr..Combining Cyrillic Milli
|
||||
{U'\x00591', U'\x005b9'}, // Hebrew Accent Etnahta ..Hebrew Point Holam
|
||||
{U'\x005bb', U'\x005bd'}, // Hebrew Point Qubuts ..Hebrew Point Meteg
|
||||
{U'\x005bf', U'\x005bf'}, // Hebrew Point Rafe
|
||||
{U'\x005c1', U'\x005c2'}, // Hebrew Point Shin Dot ..Hebrew Point Sin Dot
|
||||
{U'\x005c4', U'\x005c5'}, // Hebrew Mark Upper Dot ..Hebrew Mark Lower Dot
|
||||
{U'\x005c7', U'\x005c7'}, // Hebrew Point Qamats Qatan
|
||||
{U'\x00600', U'\x00603'}, // Arabic Number Sign ..Arabic Sign Safha
|
||||
{U'\x00610', U'\x00615'}, // Arabic Sign Sallallahou ..Arabic Small High Tah
|
||||
{U'\x0064b', U'\x0065e'}, // Arabic Fathatan ..Arabic Fatha With Two Do
|
||||
{U'\x00670', U'\x00670'}, // Arabic Letter Superscript Alef
|
||||
{U'\x006d6', U'\x006e4'}, // Arabic Small High Ligatu..Arabic Small High Madda
|
||||
{U'\x006e7', U'\x006e8'}, // Arabic Small High Yeh ..Arabic Small High Noon
|
||||
{U'\x006ea', U'\x006ed'}, // Arabic Empty Centre Low ..Arabic Small Low Meem
|
||||
{U'\x0070f', U'\x0070f'}, // Syriac Abbreviation Mark
|
||||
{U'\x00711', U'\x00711'}, // Syriac Letter Superscript Alaph
|
||||
{U'\x00730', U'\x0074a'}, // Syriac Pthaha Above ..Syriac Barrekh
|
||||
{U'\x007a6', U'\x007b0'}, // Thaana Abafili ..Thaana Sukun
|
||||
{U'\x00901', U'\x00903'}, // Devanagari Sign Candrabi..Devanagari Sign Visarga
|
||||
{U'\x0093c', U'\x0093c'}, // Devanagari Sign Nukta
|
||||
{U'\x0093e', U'\x0094d'}, // Devanagari Vowel Sign Aa..Devanagari Sign Virama
|
||||
{U'\x00951', U'\x00954'}, // Devanagari Stress Sign U..Devanagari Acute Accent
|
||||
{U'\x00962', U'\x00963'}, // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo
|
||||
{U'\x00981', U'\x00983'}, // Bengali Sign Candrabindu..Bengali Sign Visarga
|
||||
{U'\x009bc', U'\x009bc'}, // Bengali Sign Nukta
|
||||
{U'\x009be', U'\x009c4'}, // Bengali Vowel Sign Aa ..Bengali Vowel Sign Vocal
|
||||
{U'\x009c7', U'\x009c8'}, // Bengali Vowel Sign E ..Bengali Vowel Sign Ai
|
||||
{U'\x009cb', U'\x009cd'}, // Bengali Vowel Sign O ..Bengali Sign Virama
|
||||
{U'\x009d7', U'\x009d7'}, // Bengali Au Length Mark
|
||||
{U'\x009e2', U'\x009e3'}, // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal
|
||||
{U'\x00a01', U'\x00a03'}, // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Visarga
|
||||
{U'\x00a3c', U'\x00a3c'}, // Gurmukhi Sign Nukta
|
||||
{U'\x00a3e', U'\x00a42'}, // Gurmukhi Vowel Sign Aa ..Gurmukhi Vowel Sign Uu
|
||||
{U'\x00a47', U'\x00a48'}, // Gurmukhi Vowel Sign Ee ..Gurmukhi Vowel Sign Ai
|
||||
{U'\x00a4b', U'\x00a4d'}, // Gurmukhi Vowel Sign Oo ..Gurmukhi Sign Virama
|
||||
{U'\x00a70', U'\x00a71'}, // Gurmukhi Tippi ..Gurmukhi Addak
|
||||
{U'\x00a81', U'\x00a83'}, // Gujarati Sign Candrabind..Gujarati Sign Visarga
|
||||
{U'\x00abc', U'\x00abc'}, // Gujarati Sign Nukta
|
||||
{U'\x00abe', U'\x00ac5'}, // Gujarati Vowel Sign Aa ..Gujarati Vowel Sign Cand
|
||||
{U'\x00ac7', U'\x00ac9'}, // Gujarati Vowel Sign E ..Gujarati Vowel Sign Cand
|
||||
{U'\x00acb', U'\x00acd'}, // Gujarati Vowel Sign O ..Gujarati Sign Virama
|
||||
{U'\x00ae2', U'\x00ae3'}, // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca
|
||||
{U'\x00b01', U'\x00b03'}, // Oriya Sign Candrabindu ..Oriya Sign Visarga
|
||||
{U'\x00b3c', U'\x00b3c'}, // Oriya Sign Nukta
|
||||
{U'\x00b3e', U'\x00b43'}, // Oriya Vowel Sign Aa ..Oriya Vowel Sign Vocalic
|
||||
{U'\x00b47', U'\x00b48'}, // Oriya Vowel Sign E ..Oriya Vowel Sign Ai
|
||||
{U'\x00b4b', U'\x00b4d'}, // Oriya Vowel Sign O ..Oriya Sign Virama
|
||||
{U'\x00b56', U'\x00b57'}, // Oriya Ai Length Mark ..Oriya Au Length Mark
|
||||
{U'\x00b82', U'\x00b82'}, // Tamil Sign Anusvara
|
||||
{U'\x00bbe', U'\x00bc2'}, // Tamil Vowel Sign Aa ..Tamil Vowel Sign Uu
|
||||
{U'\x00bc6', U'\x00bc8'}, // Tamil Vowel Sign E ..Tamil Vowel Sign Ai
|
||||
{U'\x00bca', U'\x00bcd'}, // Tamil Vowel Sign O ..Tamil Sign Virama
|
||||
{U'\x00bd7', U'\x00bd7'}, // Tamil Au Length Mark
|
||||
{U'\x00c01', U'\x00c03'}, // Telugu Sign Candrabindu ..Telugu Sign Visarga
|
||||
{U'\x00c3e', U'\x00c44'}, // Telugu Vowel Sign Aa ..Telugu Vowel Sign Vocali
|
||||
{U'\x00c46', U'\x00c48'}, // Telugu Vowel Sign E ..Telugu Vowel Sign Ai
|
||||
{U'\x00c4a', U'\x00c4d'}, // Telugu Vowel Sign O ..Telugu Sign Virama
|
||||
{U'\x00c55', U'\x00c56'}, // Telugu Length Mark ..Telugu Ai Length Mark
|
||||
{U'\x00c82', U'\x00c83'}, // Kannada Sign Anusvara ..Kannada Sign Visarga
|
||||
{U'\x00cbc', U'\x00cbc'}, // Kannada Sign Nukta
|
||||
{U'\x00cbe', U'\x00cc4'}, // Kannada Vowel Sign Aa ..Kannada Vowel Sign Vocal
|
||||
{U'\x00cc6', U'\x00cc8'}, // Kannada Vowel Sign E ..Kannada Vowel Sign Ai
|
||||
{U'\x00cca', U'\x00ccd'}, // Kannada Vowel Sign O ..Kannada Sign Virama
|
||||
{U'\x00cd5', U'\x00cd6'}, // Kannada Length Mark ..Kannada Ai Length Mark
|
||||
{U'\x00d02', U'\x00d03'}, // Malayalam Sign Anusvara ..Malayalam Sign Visarga
|
||||
{U'\x00d3e', U'\x00d43'}, // Malayalam Vowel Sign Aa ..Malayalam Vowel Sign Voc
|
||||
{U'\x00d46', U'\x00d48'}, // Malayalam Vowel Sign E ..Malayalam Vowel Sign Ai
|
||||
{U'\x00d4a', U'\x00d4d'}, // Malayalam Vowel Sign O ..Malayalam Sign Virama
|
||||
{U'\x00d57', U'\x00d57'}, // Malayalam Au Length Mark
|
||||
{U'\x00d82', U'\x00d83'}, // Sinhala Sign Anusvaraya ..Sinhala Sign Visargaya
|
||||
{U'\x00dca', U'\x00dca'}, // Sinhala Sign Al-lakuna
|
||||
{U'\x00dcf', U'\x00dd4'}, // Sinhala Vowel Sign Aela-..Sinhala Vowel Sign Ketti
|
||||
{U'\x00dd6', U'\x00dd6'}, // Sinhala Vowel Sign Diga Paa-pilla
|
||||
{U'\x00dd8', U'\x00ddf'}, // Sinhala Vowel Sign Gaett..Sinhala Vowel Sign Gayan
|
||||
{U'\x00df2', U'\x00df3'}, // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga
|
||||
{U'\x00e31', U'\x00e31'}, // Thai Character Mai Han-akat
|
||||
{U'\x00e34', U'\x00e3a'}, // Thai Character Sara I ..Thai Character Phinthu
|
||||
{U'\x00e47', U'\x00e4e'}, // Thai Character Maitaikhu..Thai Character Yamakkan
|
||||
{U'\x00eb1', U'\x00eb1'}, // Lao Vowel Sign Mai Kan
|
||||
{U'\x00eb4', U'\x00eb9'}, // Lao Vowel Sign I ..Lao Vowel Sign Uu
|
||||
{U'\x00ebb', U'\x00ebc'}, // Lao Vowel Sign Mai Kon ..Lao Semivowel Sign Lo
|
||||
{U'\x00ec8', U'\x00ecd'}, // Lao Tone Mai Ek ..Lao Niggahita
|
||||
{U'\x00f18', U'\x00f19'}, // Tibetan Astrological Sig..Tibetan Astrological Sig
|
||||
{U'\x00f35', U'\x00f35'}, // Tibetan Mark Ngas Bzung Nyi Zla
|
||||
{U'\x00f37', U'\x00f37'}, // Tibetan Mark Ngas Bzung Sgor Rtags
|
||||
{U'\x00f39', U'\x00f39'}, // Tibetan Mark Tsa -phru
|
||||
{U'\x00f3e', U'\x00f3f'}, // Tibetan Sign Yar Tshes ..Tibetan Sign Mar Tshes
|
||||
{U'\x00f71', U'\x00f84'}, // Tibetan Vowel Sign Aa ..Tibetan Mark Halanta
|
||||
{U'\x00f86', U'\x00f87'}, // Tibetan Sign Lci Rtags ..Tibetan Sign Yang Rtags
|
||||
{U'\x00f90', U'\x00f97'}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
|
||||
{U'\x00f99', U'\x00fbc'}, // Tibetan Subjoined Letter..Tibetan Subjoined Letter
|
||||
{U'\x00fc6', U'\x00fc6'}, // Tibetan Symbol Padma Gdan
|
||||
{U'\x0102c', U'\x01032'}, // Myanmar Vowel Sign Aa ..Myanmar Vowel Sign Ai
|
||||
{U'\x01036', U'\x01039'}, // Myanmar Sign Anusvara ..Myanmar Sign Virama
|
||||
{U'\x01056', U'\x01059'}, // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal
|
||||
{U'\x01160', U'\x011ff'}, // Hangul Jungseong Filler ..Hangul Jongseong Ssangni
|
||||
{U'\x0135f', U'\x0135f'}, // Ethiopic Combining Gemination Mark
|
||||
{U'\x01712', U'\x01714'}, // Tagalog Vowel Sign I ..Tagalog Sign Virama
|
||||
{U'\x01732', U'\x01734'}, // Hanunoo Vowel Sign I ..Hanunoo Sign Pamudpod
|
||||
{U'\x01752', U'\x01753'}, // Buhid Vowel Sign I ..Buhid Vowel Sign U
|
||||
{U'\x01772', U'\x01773'}, // Tagbanwa Vowel Sign I ..Tagbanwa Vowel Sign U
|
||||
{U'\x017b4', U'\x017d3'}, // Khmer Vowel Inherent Aq ..Khmer Sign Bathamasat
|
||||
{U'\x017dd', U'\x017dd'}, // Khmer Sign Atthacan
|
||||
{U'\x0180b', U'\x0180d'}, // Mongolian Free Variation..Mongolian Free Variation
|
||||
{U'\x018a9', U'\x018a9'}, // Mongolian Letter Ali Gali Dagalga
|
||||
{U'\x01920', U'\x0192b'}, // Limbu Vowel Sign A ..Limbu Subjoined Letter W
|
||||
{U'\x01930', U'\x0193b'}, // Limbu Small Letter Ka ..Limbu Sign Sa-i
|
||||
{U'\x019b0', U'\x019c0'}, // New Tai Lue Vowel Sign V..New Tai Lue Vowel Sign I
|
||||
{U'\x019c8', U'\x019c9'}, // New Tai Lue Tone Mark-1 ..New Tai Lue Tone Mark-2
|
||||
{U'\x01a17', U'\x01a1b'}, // Buginese Vowel Sign I ..Buginese Vowel Sign Ae
|
||||
{U'\x01dc0', U'\x01dc3'}, // Combining Dotted Grave A..Combining Suspension Mar
|
||||
{U'\x0200b', U'\x0200f'}, // Zero Width Space ..Right-to-left Mark
|
||||
{U'\x02028', U'\x0202e'}, // Line Separator ..Right-to-left Override
|
||||
{U'\x02060', U'\x02063'}, // Word Joiner ..Invisible Separator
|
||||
{U'\x0206a', U'\x0206f'}, // Inhibit Symmetric Swappi..Nominal Digit Shapes
|
||||
{U'\x020d0', U'\x020eb'}, // Combining Left Harpoon A..Combining Long Double So
|
||||
{U'\x0302a', U'\x0302f'}, // Ideographic Level Tone M..Hangul Double Dot Tone M
|
||||
{U'\x03099', U'\x0309a'}, // Combining Katakana-hirag..Combining Katakana-hirag
|
||||
{U'\x0a802', U'\x0a802'}, // Syloti Nagri Sign Dvisvara
|
||||
{U'\x0a806', U'\x0a806'}, // Syloti Nagri Sign Hasanta
|
||||
{U'\x0a80b', U'\x0a80b'}, // Syloti Nagri Sign Anusvara
|
||||
{U'\x0a823', U'\x0a827'}, // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign
|
||||
{U'\x0d7b0', U'\x0d7ff'}, // Hangul Jungseong O-yeo ..(nil)
|
||||
{U'\x0fb1e', U'\x0fb1e'}, // Hebrew Point Judeo-spanish Varika
|
||||
{U'\x0fe00', U'\x0fe0f'}, // Variation Selector-1 ..Variation Selector-16
|
||||
{U'\x0fe20', U'\x0fe23'}, // Combining Ligature Left ..Combining Double Tilde R
|
||||
{U'\x0feff', U'\x0feff'}, // Zero Width No-break Space
|
||||
{U'\x0fff9', U'\x0fffb'}, // Interlinear Annotation A..Interlinear Annotation T
|
||||
{U'\x10a01', U'\x10a03'}, // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo
|
||||
{U'\x10a05', U'\x10a06'}, // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O
|
||||
{U'\x10a0c', U'\x10a0f'}, // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga
|
||||
{U'\x10a38', U'\x10a3a'}, // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo
|
||||
{U'\x10a3f', U'\x10a3f'}, // Kharoshthi Virama
|
||||
{U'\x1d165', U'\x1d169'}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{U'\x1d16d', U'\x1d182'}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{U'\x1d185', U'\x1d18b'}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{U'\x1d1aa', U'\x1d1ad'}, // Musical Symbol Combining..Musical Symbol Combining
|
||||
{U'\x1d242', U'\x1d244'}, // Combining Greek Musical ..Combining Greek Musical
|
||||
{U'\xe0001', U'\xe0001'}, // Language Tag
|
||||
{U'\xe0020', U'\xe007f'}, // Tag Space ..Cancel Tag
|
||||
{U'\xe0100', U'\xe01ef'}, // Variation Selector-17 ..Variation Selector-256
|
||||
};
|
||||
|
||||
static const BoundaryVector WIDE_EAST_ASIAN{
|
||||
{U'\x01100', U'\x01159'}, // Hangul Choseong Kiyeok ..Hangul Choseong Yeorinhi
|
||||
{U'\x0115f', U'\x0115f'}, // Hangul Choseong Filler
|
||||
{U'\x02329', U'\x0232a'}, // Left-pointing Angle Brac..Right-pointing Angle Bra
|
||||
{U'\x02e80', U'\x02e99'}, // Cjk Radical Repeat ..Cjk Radical Rap
|
||||
{U'\x02e9b', U'\x02ef3'}, // Cjk Radical Choke ..Cjk Radical C-simplified
|
||||
{U'\x02f00', U'\x02fd5'}, // Kangxi Radical One ..Kangxi Radical Flute
|
||||
{U'\x02ff0', U'\x02ffb'}, // Ideographic Description ..Ideographic Description
|
||||
{U'\x03000', U'\x03029'}, // Ideographic Space ..Hangzhou Numeral Nine
|
||||
{U'\x03030', U'\x0303e'}, // Wavy Dash ..Ideographic Variation In
|
||||
{U'\x03041', U'\x03096'}, // Hiragana Letter Small A ..Hiragana Letter Small Ke
|
||||
{U'\x0309b', U'\x030ff'}, // Katakana-hiragana Voiced..Katakana Digraph Koto
|
||||
{U'\x03105', U'\x0312c'}, // Bopomofo Letter B ..Bopomofo Letter Gn
|
||||
{U'\x03131', U'\x0318e'}, // Hangul Letter Kiyeok ..Hangul Letter Araeae
|
||||
{U'\x03190', U'\x031b7'}, // Ideographic Annotation L..Bopomofo Final Letter H
|
||||
{U'\x031c0', U'\x031cf'}, // Cjk Stroke T ..Cjk Stroke N
|
||||
{U'\x031f0', U'\x0321e'}, // Katakana Letter Small Ku..Parenthesized Korean Cha
|
||||
{U'\x03220', U'\x03243'}, // Parenthesized Ideograph ..Parenthesized Ideograph
|
||||
{U'\x03250', U'\x032fe'}, // Partnership Sign ..Circled Katakana Wo
|
||||
{U'\x03300', U'\x04db5'}, // Square Apaato ..Cjk Unified Ideograph-4d
|
||||
{U'\x04e00', U'\x09fbb'}, // Cjk Unified Ideograph-4e..Cjk Unified Ideograph-9f
|
||||
{U'\x0a000', U'\x0a48c'}, // Yi Syllable It ..Yi Syllable Yyr
|
||||
{U'\x0a490', U'\x0a4c6'}, // Yi Radical Qot ..Yi Radical Ke
|
||||
{U'\x0ac00', U'\x0d7a3'}, // Hangul Syllable Ga ..Hangul Syllable Hih
|
||||
{U'\x0f900', U'\x0fa2d'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
|
||||
{U'\x0fa30', U'\x0fa6a'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
|
||||
{U'\x0fa70', U'\x0fad9'}, // Cjk Compatibility Ideogr..Cjk Compatibility Ideogr
|
||||
{U'\x0fe10', U'\x0fe19'}, // Presentation Form For Ve..Presentation Form For Ve
|
||||
{U'\x0fe30', U'\x0fe52'}, // Presentation Form For Ve..Small Full Stop
|
||||
{U'\x0fe54', U'\x0fe66'}, // Small Semicolon ..Small Equals Sign
|
||||
{U'\x0fe68', U'\x0fe6b'}, // Small Reverse Solidus ..Small Commercial At
|
||||
{U'\x0ff01', U'\x0ff60'}, // Fullwidth Exclamation Ma..Fullwidth Right White Pa
|
||||
{U'\x0ffe0', U'\x0ffe6'}, // Fullwidth Cent Sign ..Fullwidth Won Sign
|
||||
{U'\x20000', U'\x2fffd'}, // Cjk Unified Ideograph-20..(nil)
|
||||
{U'\x30000', U'\x3fffd'}, // Cjk Unified Ideograph-30..(nil)
|
||||
};
|
||||
|
||||
static const BoundaryVector VS16_NARROW_TO_WIDE{
|
||||
{U'\x00023', U'\x00023'}, // Number Sign
|
||||
{U'\x0002a', U'\x0002a'}, // Asterisk
|
||||
{U'\x00030', U'\x00039'}, // Digit Zero ..Digit Nine
|
||||
{U'\x000a9', U'\x000a9'}, // Copyright Sign
|
||||
{U'\x000ae', U'\x000ae'}, // Registered Sign
|
||||
{U'\x0203c', U'\x0203c'}, // Double Exclamation Mark
|
||||
{U'\x02049', U'\x02049'}, // Exclamation Question Mark
|
||||
{U'\x02122', U'\x02122'}, // Trade Mark Sign
|
||||
{U'\x02139', U'\x02139'}, // Information Source
|
||||
{U'\x02194', U'\x02199'}, // Left Right Arrow ..South West Arrow
|
||||
{U'\x021a9', U'\x021aa'}, // Leftwards Arrow With Hoo..Rightwards Arrow With Ho
|
||||
{U'\x02328', U'\x02328'}, // Keyboard
|
||||
{U'\x023cf', U'\x023cf'}, // Eject Symbol
|
||||
{U'\x023ed', U'\x023ef'}, // Black Right-pointing Dou..Black Right-pointing Tri
|
||||
{U'\x023f1', U'\x023f2'}, // Stopwatch ..Timer Clock
|
||||
{U'\x023f8', U'\x023fa'}, // Double Vertical Bar ..Black Circle For Record
|
||||
{U'\x024c2', U'\x024c2'}, // Circled Latin Capital Letter M
|
||||
{U'\x025aa', U'\x025ab'}, // Black Small Square ..White Small Square
|
||||
{U'\x025b6', U'\x025b6'}, // Black Right-pointing Triangle
|
||||
{U'\x025c0', U'\x025c0'}, // Black Left-pointing Triangle
|
||||
{U'\x025fb', U'\x025fc'}, // White Medium Square ..Black Medium Square
|
||||
{U'\x02600', U'\x02604'}, // Black Sun With Rays ..Comet
|
||||
{U'\x0260e', U'\x0260e'}, // Black Telephone
|
||||
{U'\x02611', U'\x02611'}, // Ballot Box With Check
|
||||
{U'\x02618', U'\x02618'}, // Shamrock
|
||||
{U'\x0261d', U'\x0261d'}, // White Up Pointing Index
|
||||
{U'\x02620', U'\x02620'}, // Skull And Crossbones
|
||||
{U'\x02622', U'\x02623'}, // Radioactive Sign ..Biohazard Sign
|
||||
{U'\x02626', U'\x02626'}, // Orthodox Cross
|
||||
{U'\x0262a', U'\x0262a'}, // Star And Crescent
|
||||
{U'\x0262e', U'\x0262f'}, // Peace Symbol ..Yin Yang
|
||||
{U'\x02638', U'\x0263a'}, // Wheel Of Dharma ..White Smiling Face
|
||||
{U'\x02640', U'\x02640'}, // Female Sign
|
||||
{U'\x02642', U'\x02642'}, // Male Sign
|
||||
{U'\x0265f', U'\x02660'}, // Black Chess Pawn ..Black Spade Suit
|
||||
{U'\x02663', U'\x02663'}, // Black Club Suit
|
||||
{U'\x02665', U'\x02666'}, // Black Heart Suit ..Black Diamond Suit
|
||||
{U'\x02668', U'\x02668'}, // Hot Springs
|
||||
{U'\x0267b', U'\x0267b'}, // Black Universal Recycling Symbol
|
||||
{U'\x0267e', U'\x0267e'}, // Permanent Paper Sign
|
||||
{U'\x02692', U'\x02692'}, // Hammer And Pick
|
||||
{U'\x02694', U'\x02697'}, // Crossed Swords ..Alembic
|
||||
{U'\x02699', U'\x02699'}, // Gear
|
||||
{U'\x0269b', U'\x0269c'}, // Atom Symbol ..Fleur-de-lis
|
||||
{U'\x026a0', U'\x026a0'}, // Warning Sign
|
||||
{U'\x026a7', U'\x026a7'}, // Male With Stroke And Male And Female Sign
|
||||
{U'\x026b0', U'\x026b1'}, // Coffin ..Funeral Urn
|
||||
{U'\x026c8', U'\x026c8'}, // Thunder Cloud And Rain
|
||||
{U'\x026cf', U'\x026cf'}, // Pick
|
||||
{U'\x026d1', U'\x026d1'}, // Helmet With White Cross
|
||||
{U'\x026d3', U'\x026d3'}, // Chains
|
||||
{U'\x026e9', U'\x026e9'}, // Shinto Shrine
|
||||
{U'\x026f0', U'\x026f1'}, // Mountain ..Umbrella On Ground
|
||||
{U'\x026f4', U'\x026f4'}, // Ferry
|
||||
{U'\x026f7', U'\x026f9'}, // Skier ..Person With Ball
|
||||
{U'\x02702', U'\x02702'}, // Black Scissors
|
||||
{U'\x02708', U'\x02709'}, // Airplane ..Envelope
|
||||
{U'\x0270c', U'\x0270d'}, // Victory Hand ..Writing Hand
|
||||
{U'\x0270f', U'\x0270f'}, // Pencil
|
||||
{U'\x02712', U'\x02712'}, // Black Nib
|
||||
{U'\x02714', U'\x02714'}, // Heavy Check Mark
|
||||
{U'\x02716', U'\x02716'}, // Heavy Multiplication X
|
||||
{U'\x0271d', U'\x0271d'}, // Latin Cross
|
||||
{U'\x02721', U'\x02721'}, // Star Of David
|
||||
{U'\x02733', U'\x02734'}, // Eight Spoked Asterisk ..Eight Pointed Black Star
|
||||
{U'\x02744', U'\x02744'}, // Snowflake
|
||||
{U'\x02747', U'\x02747'}, // Sparkle
|
||||
{U'\x02763', U'\x02764'}, // Heavy Heart Exclamation ..Heavy Black Heart
|
||||
{U'\x027a1', U'\x027a1'}, // Black Rightwards Arrow
|
||||
{U'\x02934', U'\x02935'}, // Arrow Pointing Rightward..Arrow Pointing Rightward
|
||||
{U'\x02b05', U'\x02b07'}, // Leftwards Black Arrow ..Downwards Black Arrow
|
||||
{U'\x1f170', U'\x1f171'}, // Negative Squared Latin C..Negative Squared Latin C
|
||||
{U'\x1f17e', U'\x1f17f'}, // Negative Squared Latin C..Negative Squared Latin C
|
||||
{U'\x1f321', U'\x1f321'}, // Thermometer
|
||||
{U'\x1f324', U'\x1f32c'}, // White Sun With Small Clo..Wind Blowing Face
|
||||
{U'\x1f336', U'\x1f336'}, // Hot Pepper
|
||||
{U'\x1f37d', U'\x1f37d'}, // Fork And Knife With Plate
|
||||
{U'\x1f396', U'\x1f397'}, // Military Medal ..Reminder Ribbon
|
||||
{U'\x1f399', U'\x1f39b'}, // Studio Microphone ..Control Knobs
|
||||
{U'\x1f39e', U'\x1f39f'}, // Film Frames ..Admission Tickets
|
||||
{U'\x1f3cb', U'\x1f3ce'}, // Weight Lifter ..Racing Car
|
||||
{U'\x1f3d4', U'\x1f3df'}, // Snow Capped Mountain ..Stadium
|
||||
{U'\x1f3f3', U'\x1f3f3'}, // Waving White Flag
|
||||
{U'\x1f3f5', U'\x1f3f5'}, // Rosette
|
||||
{U'\x1f3f7', U'\x1f3f7'}, // Label
|
||||
{U'\x1f43f', U'\x1f43f'}, // Chipmunk
|
||||
{U'\x1f441', U'\x1f441'}, // Eye
|
||||
{U'\x1f4fd', U'\x1f4fd'}, // Film Projector
|
||||
{U'\x1f549', U'\x1f54a'}, // Om Symbol ..Dove Of Peace
|
||||
{U'\x1f56f', U'\x1f570'}, // Candle ..Mantelpiece Clock
|
||||
{U'\x1f573', U'\x1f579'}, // Hole ..Joystick
|
||||
{U'\x1f587', U'\x1f587'}, // Linked Paperclips
|
||||
{U'\x1f58a', U'\x1f58d'}, // Lower Left Ballpoint Pen..Lower Left Crayon
|
||||
{U'\x1f590', U'\x1f590'}, // Raised Hand With Fingers Splayed
|
||||
{U'\x1f5a5', U'\x1f5a5'}, // Desktop Computer
|
||||
{U'\x1f5a8', U'\x1f5a8'}, // Printer
|
||||
{U'\x1f5b1', U'\x1f5b2'}, // Three Button Mouse ..Trackball
|
||||
{U'\x1f5bc', U'\x1f5bc'}, // Frame With Picture
|
||||
{U'\x1f5c2', U'\x1f5c4'}, // Card Index Dividers ..File Cabinet
|
||||
{U'\x1f5d1', U'\x1f5d3'}, // Wastebasket ..Spiral Calendar Pad
|
||||
{U'\x1f5dc', U'\x1f5de'}, // Compression ..Rolled-up Newspaper
|
||||
{U'\x1f5e1', U'\x1f5e1'}, // Dagger Knife
|
||||
{U'\x1f5e3', U'\x1f5e3'}, // Speaking Head In Silhouette
|
||||
{U'\x1f5e8', U'\x1f5e8'}, // Left Speech Bubble
|
||||
{U'\x1f5ef', U'\x1f5ef'}, // Right Anger Bubble
|
||||
{U'\x1f5f3', U'\x1f5f3'}, // Ballot Box With Ballot
|
||||
{U'\x1f5fa', U'\x1f5fa'}, // World Map
|
||||
{U'\x1f6cb', U'\x1f6cb'}, // Couch And Lamp
|
||||
{U'\x1f6cd', U'\x1f6cf'}, // Shopping Bags ..Bed
|
||||
{U'\x1f6e0', U'\x1f6e5'}, // Hammer And Wrench ..Motor Boat
|
||||
{U'\x1f6e9', U'\x1f6e9'}, // Small Airplane
|
||||
{U'\x1f6f0', U'\x1f6f0'}, // Satellite
|
||||
{U'\x1f6f3', U'\x1f6f3'}, // Passenger Ship
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static size_t bisearch(char32_t ucs, const BoundaryVector& table) {
|
||||
// TODO: Use STD algorithm to optimize this function
|
||||
|
||||
// YYC MARK:
|
||||
// Do not change this "int" to "size_t" casually,
|
||||
// because the result of arithmetic operation may be negative.
|
||||
// Do not change this type before using new algorithm.
|
||||
int lbound = 0, ubound = table.size() - 1;
|
||||
|
||||
if (ucs < table.front().first || ucs > table.back().second) return 0;
|
||||
|
||||
while (ubound >= lbound) {
|
||||
int mid = (lbound + ubound) / 2;
|
||||
if (ucs > table[mid].second) lbound = mid + 1;
|
||||
else if (ucs < table[mid].first) ubound = mid - 1;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t wcwidth(char32_t wc) {
|
||||
// TODO: Add lru_cache(maxsize=1000) for this function
|
||||
|
||||
// Small optimize for ASCII
|
||||
if (U'\x20' <= wc && wc < U'\x7F') [[likely]]
|
||||
return 1;
|
||||
|
||||
// C0/C1 control char
|
||||
// NOTE: Not vanilla implementation. Return 0 instead of 1.
|
||||
if ((wc && wc < L'\x20') || (L'\x7F' <= wc && wc < L'\xA0')) return 0;
|
||||
|
||||
// Zero-width char
|
||||
if (bisearch(wc, ZERO_WIDTH)) return 0;
|
||||
|
||||
// Width 1 or 2
|
||||
return 1 + bisearch(wc, WIDE_EAST_ASIAN);
|
||||
}
|
||||
|
||||
enum class WcswidthState {
|
||||
/// Normal character.
|
||||
Normal,
|
||||
/// Under ZWJ control char.
|
||||
/// Ignore the width of next char.
|
||||
ZeroWidthJoiner,
|
||||
/// Under ANSI Escape Sequence.
|
||||
/// Following chars should be treated as escape char.
|
||||
AnsiEscape,
|
||||
/// Under CSI control sequence, a part of ANSI Escape Sequence.
|
||||
/// No width was accumulated before terminal char.
|
||||
AnsiCsiEscape,
|
||||
};
|
||||
|
||||
struct WcswidthContext {
|
||||
/// Current state.
|
||||
WcswidthState state;
|
||||
/// Tract the last computed char.
|
||||
/// It will be used for VS16 char.
|
||||
std::optional<char32_t> last_measured_char;
|
||||
};
|
||||
|
||||
Result<size_t> wcswidth(const std::u32string_view& rhs) {
|
||||
WcswidthContext ctx{WcswidthState::Normal, std::nullopt};
|
||||
size_t width = 0;
|
||||
|
||||
for (char32_t chr : rhs) {
|
||||
// Match char value
|
||||
switch (ctx.state) {
|
||||
case WcswidthState::Normal: {
|
||||
switch (chr) {
|
||||
case U'\x200D': {
|
||||
// ZWJ control char
|
||||
ctx.state = WcswidthState::ZeroWidthJoiner;
|
||||
break;
|
||||
}
|
||||
case U'\xFE0F': {
|
||||
// VS16 control char
|
||||
// If we have a char which was acknowledged and has width,
|
||||
// analyse it instead of this control char.
|
||||
if (ctx.last_measured_char.has_value()) {
|
||||
width += bisearch(ctx.last_measured_char.value(), VS16_NARROW_TO_WIDE);
|
||||
ctx.last_measured_char = std::nullopt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case U'\x1B': {
|
||||
// ANSI escape sequence
|
||||
ctx.state = WcswidthState::AnsiEscape;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Fetch widht for normal char
|
||||
int wcw = wcwidth(chr);
|
||||
// Tract the final non-zero char for VS16 control char
|
||||
if (wcw > 0) ctx.last_measured_char = wcw;
|
||||
// Accumulate width
|
||||
width += wcw;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WcswidthState::ZeroWidthJoiner: {
|
||||
// Eat this char and back to normal state.
|
||||
// This is what ZWJ does.
|
||||
ctx.state = WcswidthState::Normal;
|
||||
break;
|
||||
}
|
||||
case WcswidthState::AnsiEscape: {
|
||||
// Check the second char of escape sequence.
|
||||
// If it is '[', we enter CSI state,
|
||||
// otherwise we eat it and back to normal state.
|
||||
// Additionally, there is a range requirement for this char (0x40-0x5F).
|
||||
if (chr == U'[') {
|
||||
ctx.state = WcswidthState::AnsiCsiEscape;
|
||||
} else if (chr >= U'\x40' && chr <= U'\x5F') {
|
||||
ctx.state = WcswidthState::Normal;
|
||||
} else {
|
||||
return std::unexpected(Error::BadAnsiEscSeq);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WcswidthState::AnsiCsiEscape: {
|
||||
// CSI sequence is aonsisted by variable Parameter Char (count can be zero),
|
||||
// at least one Middle Char and only one Final Char.
|
||||
// So we eat all chars until we reach the terminal char.
|
||||
if (chr >= U'\x40' && chr <= U'\x7E') {
|
||||
// Final Char. Back to normal state.
|
||||
ctx.state = WcswidthState::Normal;
|
||||
} else if (chr >= U'\x30' && chr <= U'\x3F') {
|
||||
; // Parameter Char. Do nothing
|
||||
} else if (chr >= U'\x20' && chr <= U'\x2F') {
|
||||
; // Middle Char. Do nothing
|
||||
} else {
|
||||
return std::unexpected(Error::BadCsiSeq);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
Result<size_t> wcswidth(const std::u8string_view& rhs) {
|
||||
// Cast encoding
|
||||
auto u32str = ENC::to_utf32(rhs);
|
||||
if (!u32str.has_value()) return std::unexpected(Error::BadEncoding);
|
||||
// Call underlying function
|
||||
return wcswidth(u32str.value());
|
||||
}
|
||||
|
||||
} // namespace yycc::carton::wcwidth
|
47
src/yycc/carton/wcwidth.hpp
Normal file
47
src/yycc/carton/wcwidth.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
/**
|
||||
* @brief The namespace replicating Linux-specialized function, "wcswidth", in all platforms.
|
||||
* @details
|
||||
* "wcswdith" is a specialized function in Linux.
|
||||
* It was not included in POSIX standard and only provided on Linux.
|
||||
* This function can fetch how many space which given string occupied in terminal.
|
||||
* This is essential and useful function in our library.
|
||||
* So I create this namespace to make "wcswidth" be available on all platforms.
|
||||
*
|
||||
* "wcswidth" is based on \c wchar_t. In Linux, \c wchar_t is 4-bytes length.
|
||||
* It can represent any characters without surrogate pair.
|
||||
* However, in Windows, \c wchar_t is 2-bytes length.
|
||||
* There is possible surrogate pair within \c wchar_t string, which is inconvenient for our programming.
|
||||
* So in this homebrew namespace, I forcelt use \c char32_t as the basic char type.
|
||||
*
|
||||
* Due to the requirements of mine, this implementation is slightly different with original one.
|
||||
* These differences are list below:
|
||||
*
|
||||
* \li We do not return negative value for Control Char in "wcwidth",
|
||||
* because we need to support the analyse of ANSI Escape Sequence.
|
||||
* \li Due to the previous change, the type of return value of "wcwidth" and "wcswidth"
|
||||
* are changed from \c int to \c size_t because there is no negative return value.
|
||||
* \li "wcswidth" now support ANSI Escape Sequence (e.g. terminal color).
|
||||
* So it can analyse colorful output with correct space.
|
||||
*/
|
||||
namespace yycc::carton::wcwidth {
|
||||
|
||||
/// @brief Error occurs in this module
|
||||
enum class Error {
|
||||
BadEncoding, ///< Given
|
||||
BadAnsiEscSeq, ///< Bad char when processing ANSI Escape Sequence
|
||||
BadCsiSeq, ///< Bad char when processing CSI Sequence.
|
||||
};
|
||||
|
||||
/// @brief Result type for this module
|
||||
template<typename T>
|
||||
using Result = std::expected<T, Error>;
|
||||
|
||||
size_t wcwidth(char32_t wc);
|
||||
Result<size_t> wcswidth(const std::u32string_view& rhs);
|
||||
Result<size_t> wcswidth(const std::u8string_view& rhs);
|
||||
|
||||
} // namespace yycc::carton::wcwidth
|
78
src/yycc/constraint.hpp
Normal file
78
src/yycc/constraint.hpp
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include "macro/class_copy_move.hpp"
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
/// @brief The namespace containing generic constraint concept used varied in other modules.
|
||||
namespace yycc::constraint {
|
||||
|
||||
/// @brief Function prototype used in Constraint for checking whether given value is valid.
|
||||
/// @details Analyze given value, and return true if value is legal, otherwise false.
|
||||
template<typename T>
|
||||
using FnCheck = std::function<bool(const T&)>;
|
||||
/// @brief Function prototype used in Constraint for clamping given value into a valid value.
|
||||
/// @details Analyze given value, return clamped value.
|
||||
template<typename T>
|
||||
using FnClamp = std::function<T(const T&)>;
|
||||
|
||||
/**
|
||||
* @brief The constraint applied to settings to limit its stored value.
|
||||
* @tparam T The data type this constraint need to be processed with.
|
||||
* @details
|
||||
* Constraint class contains various features:
|
||||
* \li Check: Check whether given value is in range.
|
||||
* \li Clamp: Clamp given value into valid value.
|
||||
* Every instances of Constraint can have some, or none of these features.
|
||||
* So it is essential to check whether instance has corresponding features before using it.
|
||||
*/
|
||||
template<typename T>
|
||||
class Constraint {
|
||||
public:
|
||||
Constraint(FnCheck<T>&& fn_check, FnClamp<T>&& fn_clamp) :
|
||||
fn_check(std::move(fn_check)), fn_clamp(std::move(fn_clamp)) {}
|
||||
YYCC_DELETE_COPY(Constraint)
|
||||
YYCC_DEFAULT_MOVE(Constraint)
|
||||
|
||||
/**
|
||||
* @brief Perform Check feature.
|
||||
* @param[in] value The valid for checking.
|
||||
* @return True if valid is okey, otherwise false.
|
||||
* @exception std::logic_error Raised if this feature is not supported.
|
||||
*/
|
||||
bool check(const T& value) const {
|
||||
if (!support_check()) {
|
||||
throw std::logic_error("this Constraint do not support check operation");
|
||||
} else {
|
||||
return fn_check(value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief Perform Clamp feature.
|
||||
* @param[in] value The valid for clamping.
|
||||
* @return The result after clamping.
|
||||
* @exception std::logic_error Raised if this feature is not supported.
|
||||
*/
|
||||
T clamp(const T& value) const {
|
||||
if (!support_clamp()) {
|
||||
throw std::logic_error("this Constraint do not support clamp operation");
|
||||
} else {
|
||||
return fn_clamp(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Check whether this Constraint support Check feature.
|
||||
/// @return True if it support, otherwise false.
|
||||
bool support_check() const noexcept { return this->fn_check != nullptr; }
|
||||
/// @brief Check whether this Constraint support Clamp feature.
|
||||
/// @return True if it support, otherwise false.
|
||||
bool support_clamp() const noexcept { return this->fn_clamp != nullptr; }
|
||||
|
||||
private:
|
||||
/// @brief Pointer to Check feature function.
|
||||
FnCheck<T> fn_check;
|
||||
/// @brief Pointer to Clamp feature function.
|
||||
FnClamp<T> fn_clamp;
|
||||
};
|
||||
|
||||
} // namespace yycc::core::constraint
|
71
src/yycc/constraint/builder.hpp
Normal file
71
src/yycc/constraint/builder.hpp
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "../constraint.hpp"
|
||||
#include <set>
|
||||
|
||||
/// @brief The namespace containing convenient function building common used Constraint instance.
|
||||
namespace yycc::constraint::builder {
|
||||
|
||||
/**
|
||||
* @brief Build Constraint for arithmetic values by minimum and maximum value range.
|
||||
* @tparam T An arithmetic or enum type (except bool) of underlying stored value.
|
||||
* @param[in] min_value The minimum value of range (inclusive).
|
||||
* @param[in] max_value The maximum value of range (inclusive).
|
||||
* @return The generated constraint instance which can be directly applied.
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_arithmetic_v<T> && !std::is_same_v<T, bool>, int> = 0>
|
||||
Constraint<T> min_max_constraint(T min_value, T max_value) {
|
||||
if (min_value > max_value) throw std::invalid_argument("the max value must be equal or greater than min value");
|
||||
|
||||
auto fn_check = [min_value, max_value](const T& val) -> bool { return (val <= max_value) && (val >= min_value); };
|
||||
auto fn_clamp = [min_value, max_value](const T& val) -> T { return std::clamp(val, min_value, max_value); };
|
||||
return Constraint<T>(std::move(fn_check), std::move(fn_clamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get constraint for enum values by enumerating all possible values.
|
||||
* @tparam T An enum type (except bool) of underlying stored value.
|
||||
* @param[in] il An initializer list storing all possible values.
|
||||
* @param[in] default_index The index of default value in given list.
|
||||
* @return The generated constraint instance which can be directly applied.
|
||||
*/
|
||||
template<typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
|
||||
Constraint<T> enum_constraint(const std::initializer_list<T>& il, size_t default_index = 0u) {
|
||||
if (default_index >= il.size()) throw std::invalid_argument("the default index must be a valid index in given list");
|
||||
|
||||
T default_entry = il.begin()[default_index];
|
||||
std::set<T> entries(il);
|
||||
|
||||
auto fn_check = [entries](const T& val) -> bool { return entries.contains(val); };
|
||||
auto fn_clamp = [entries, default_entry](const T& val) -> T {
|
||||
if (entries.contains(val)) return val;
|
||||
else return default_entry;
|
||||
};
|
||||
return Constraint<T>(std::move(fn_check), fn_clamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get constraint for string values by enumerating all possible values.
|
||||
* @param[in] il An initializer list storing all possible values.
|
||||
* @param[in] default_index The index of default value in given list.
|
||||
* @return The generated constraint instance which can be directly applied.
|
||||
*/
|
||||
inline Constraint<std::u8string> strenum_constraint(const std::initializer_list<std::u8string_view>& il, size_t default_index = 0u) {
|
||||
if (default_index >= il.size()) throw std::invalid_argument("the default index must be a valid index in given list");
|
||||
|
||||
std::u8string default_entry = std::u8string(il.begin()[default_index]);
|
||||
std::set<std::u8string> entries;
|
||||
for (const auto& i : il) {
|
||||
entries.emplace(i);
|
||||
}
|
||||
|
||||
auto fn_check = [entries](const std::u8string& val) -> bool { return entries.contains(val); };
|
||||
auto fn_clamp = [entries, default_entry](const std::u8string& val) -> std::u8string {
|
||||
if (entries.contains(val)) return val;
|
||||
else return default_entry;
|
||||
};
|
||||
return Constraint<std::u8string>(std::move(fn_check), fn_clamp);
|
||||
}
|
||||
|
||||
} // namespace yycc::constraint::builder
|
||||
|
||||
#undef NS_YYCC_STRING
|
340
src/yycc/encoding/iconv.cpp
Normal file
340
src/yycc/encoding/iconv.cpp
Normal file
@ -0,0 +1,340 @@
|
||||
#include "iconv.hpp"
|
||||
|
||||
#if defined(YYCC_FEAT_ICONV)
|
||||
|
||||
#include "../macro/endian_detector.hpp"
|
||||
#include <cerrno>
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
#pragma region Iconv Shit Fix
|
||||
|
||||
// YYC MARK:
|
||||
// I don't know what Iconv is for, Iconv put an huge pieces of shit into its header file "iconv.h" (at least for me).
|
||||
// Especially a macro called iconv, which pollutes my namespace name while also can not be disabled because I need to rely on it to access essential functions.
|
||||
// I can't simply redefine it, because I can't make sure that this "iconv" is defined in that way on all platforms.
|
||||
// So I can only write some definitions of functions and types here, and extract the functions and types I need before I declare the namespace.
|
||||
// And at the same time remove those annoying macro definitions. Hopefully, the compiler will optimize these wrapper functions.
|
||||
|
||||
#include <iconv.h>
|
||||
|
||||
typedef iconv_t that_iconv_t;
|
||||
static iconv_t that_iconv_open(const char* tocode, const char* fromcode) {
|
||||
return iconv_open(tocode, fromcode);
|
||||
}
|
||||
static int that_iconv_close(iconv_t cd) {
|
||||
return iconv_close(cd);
|
||||
}
|
||||
static size_t that_iconv(iconv_t cd, const char** inbuf, size_t* inbytesleft, char** outbuf, size_t* outbytesleft) {
|
||||
// YYC MARK:
|
||||
// This is also bullshit. I don't know why the real signature of this function differ with its document written by GNU.
|
||||
// I have to make a "const" cast in there.
|
||||
return iconv(cd, const_cast<char**>(inbuf), inbytesleft, outbuf, outbytesleft);
|
||||
}
|
||||
|
||||
#undef iconv_t
|
||||
#undef iconv_open
|
||||
#undef iconv_close
|
||||
#undef iconv
|
||||
|
||||
#pragma endregion
|
||||
|
||||
namespace yycc::encoding::iconv {
|
||||
|
||||
static const that_iconv_t INVALID_ICONV_TOKEN = reinterpret_cast<that_iconv_t>(-1);
|
||||
|
||||
#pragma region PrivToken
|
||||
|
||||
class PrivToken {
|
||||
public:
|
||||
PrivToken(const CodeName& from_code, const CodeName& to_code) : inner(INVALID_ICONV_TOKEN) {
|
||||
// We must cast them into string container, not string view,
|
||||
// because they may not have NULL terminator.
|
||||
std::string iconv_from_code(from_code);
|
||||
std::string iconv_to_code(to_code);
|
||||
// Call iconv_t creator
|
||||
that_iconv_t descriptor = that_iconv_open(iconv_to_code.c_str(), iconv_from_code.c_str());
|
||||
if (descriptor == INVALID_ICONV_TOKEN) {
|
||||
if (errno == EINVAL) {
|
||||
return;
|
||||
} else {
|
||||
throw std::runtime_error("impossible errno when calling iconv_open()");
|
||||
}
|
||||
}
|
||||
// Setup value
|
||||
this->inner = descriptor;
|
||||
}
|
||||
~PrivToken() {
|
||||
if (this->inner != INVALID_ICONV_TOKEN) {
|
||||
that_iconv_close(this->inner);
|
||||
}
|
||||
}
|
||||
PrivToken(PrivToken&& rhs) noexcept : inner(rhs.inner) {
|
||||
// Reset rhs inner
|
||||
rhs.inner = INVALID_ICONV_TOKEN;
|
||||
}
|
||||
PrivToken& operator=(PrivToken&& rhs) noexcept {
|
||||
// Free self first
|
||||
if (this->inner != INVALID_ICONV_TOKEN) {
|
||||
that_iconv_close(this->inner);
|
||||
}
|
||||
// Copy rhs inner and reset it.
|
||||
this->inner = rhs.inner;
|
||||
rhs.inner = INVALID_ICONV_TOKEN;
|
||||
// Return self
|
||||
return *this;
|
||||
}
|
||||
YYCC_DELETE_COPY(PrivToken)
|
||||
|
||||
bool is_valid() const { return this->inner != INVALID_ICONV_TOKEN; }
|
||||
that_iconv_t get_inner() const { return this->inner; }
|
||||
|
||||
private:
|
||||
that_iconv_t inner;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Token
|
||||
|
||||
Token::Token(const CodeName& from_code, const CodeName& to_code) : inner(nullptr) {
|
||||
this->inner = new PrivToken(from_code, to_code);
|
||||
}
|
||||
|
||||
Token::~Token() {
|
||||
if (this->inner != nullptr) {
|
||||
delete this->inner;
|
||||
}
|
||||
}
|
||||
|
||||
Token::Token(Token&& rhs) noexcept : inner(rhs.inner) {
|
||||
rhs.inner = nullptr;
|
||||
}
|
||||
|
||||
Token& Token::operator=(Token&& rhs) noexcept {
|
||||
this->inner = rhs.inner;
|
||||
rhs.inner = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Token::is_valid() const {
|
||||
return this->inner->is_valid();
|
||||
}
|
||||
|
||||
PrivToken* Token::get_inner() const {
|
||||
return this->inner;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Kernel
|
||||
|
||||
constexpr const size_t ICONV_INC_LEN = 16u;
|
||||
constexpr size_t ICONV_ERR_RV = static_cast<size_t>(-1);
|
||||
|
||||
// Reference: https://stackoverflow.com/questions/13297458/simple-utf8-utf16-string-conversion-with-iconv
|
||||
|
||||
static ConvResult<std::vector<uint8_t>> iconv_kernel(const Token& token, const uint8_t* str_from_buf, size_t str_from_len) {
|
||||
// ===== Check Requirements =====
|
||||
// Prepare return value
|
||||
std::vector<uint8_t> str_to;
|
||||
|
||||
// Unwrap and check iconv_t
|
||||
that_iconv_t cd = token.get_inner()->get_inner();
|
||||
if (cd == INVALID_ICONV_TOKEN) return std::unexpected(ConvError::InvalidCd);
|
||||
|
||||
// Check empty input
|
||||
if (str_from_len == 0u) return str_to;
|
||||
// Check nullptr input variables
|
||||
if (str_from_buf == nullptr) return std::unexpected(ConvError::NullPointer);
|
||||
|
||||
// ===== Do Iconv =====
|
||||
// setup input variables
|
||||
size_t inbytesleft = str_from_len;
|
||||
const char* inbuf = reinterpret_cast<const char*>(str_from_buf);
|
||||
// pre-allocation output variables
|
||||
str_to.resize(str_from_len + ICONV_INC_LEN);
|
||||
size_t outbytesleft = str_to.size();
|
||||
char* outbuf = reinterpret_cast<char*>(str_to.data());
|
||||
|
||||
// conv core
|
||||
size_t nchars = that_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
|
||||
while (nchars == ICONV_ERR_RV && errno == E2BIG) {
|
||||
// record the length has been converted
|
||||
size_t len = outbuf - reinterpret_cast<char*>(str_to.data());
|
||||
|
||||
// resize for container and its variables
|
||||
str_to.resize(str_to.size() + ICONV_INC_LEN);
|
||||
outbytesleft += ICONV_INC_LEN;
|
||||
|
||||
// assign new outbuf from failed position
|
||||
outbuf = reinterpret_cast<char*>(str_to.data()) + len;
|
||||
nchars = that_iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
|
||||
}
|
||||
|
||||
// restore descriptor initial state
|
||||
that_iconv(cd, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
// check error
|
||||
if (nchars == ICONV_ERR_RV) {
|
||||
if (errno == EILSEQ) {
|
||||
return std::unexpected(ConvError::InvalidMbSeq);
|
||||
} else if (errno == EINVAL) {
|
||||
return std::unexpected(ConvError::IncompleteMbSeq);
|
||||
} else {
|
||||
throw std::runtime_error("impossible errno when calling iconv_open()");
|
||||
}
|
||||
} else {
|
||||
// success
|
||||
// compute result data
|
||||
str_to.resize(str_to.size() - outbytesleft);
|
||||
return str_to;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Convertion Class Helper
|
||||
|
||||
// YYC MARK:
|
||||
// If we use UTF16 or UTF32 code name directly, it will produce a BOM at data head.
|
||||
// That's not what we expected.
|
||||
// So we need manually check runtime endian and explicitly specify endian in code name.
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
constexpr auto UTF8_CODENAME_LITERAL = "UTF-8"sv;
|
||||
constexpr auto WCHAR_CODENAME_LITERAL = "WCHAR_T"sv;
|
||||
constexpr auto UTF16_CODENAME_LITERAL =
|
||||
#if defined(YYCC_ENDIAN_LITTLE)
|
||||
"UTF-16LE"sv;
|
||||
#else
|
||||
"UTF-16BE"sv;
|
||||
#endif
|
||||
constexpr auto UTF32_CODENAME_LITERAL =
|
||||
#if defined(YYCC_ENDIAN_LITTLE)
|
||||
"UTF-32LE"sv;
|
||||
#else
|
||||
"UTF-32BE"sv;
|
||||
#endif
|
||||
|
||||
// TODO:
|
||||
// There is a memory copy in this function. Consider optimizing it in future.
|
||||
// A possible solution is that create a std::vector-like wrapper for std::basic_string and std::basic_string_view.
|
||||
// We call them VecString and VecStringView, and use them in "iconv_kernel" instead of real std::vector.
|
||||
// They exposed interface are std::vector-like but its inner is std::basic_string and std::basic_string_view.
|
||||
#define USER_CONVFN(src_char_type, dst_char_type) \
|
||||
auto rv = iconv_kernel(this->token, reinterpret_cast<const uint8_t*>(src.data()), src.size() * sizeof(src_char_type)); \
|
||||
if (rv.has_value()) { \
|
||||
const auto& dst = rv.value(); \
|
||||
if constexpr (sizeof(dst_char_type) > 1u) { \
|
||||
if (dst.size() % sizeof(dst_char_type) != 0u) return std::unexpected(ConvError::BadRv); \
|
||||
} \
|
||||
return std::basic_string<dst_char_type>(reinterpret_cast<const dst_char_type*>(dst.data()), dst.size() / sizeof(dst_char_type)); \
|
||||
} else { \
|
||||
return std::unexpected(rv.error()); \
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Char -> UTF8
|
||||
|
||||
CharToUtf8::CharToUtf8(const CodeName& code_name) : token(code_name, UTF8_CODENAME_LITERAL) {}
|
||||
|
||||
CharToUtf8::~CharToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> CharToUtf8::to_utf8(const std::string_view& src) {
|
||||
USER_CONVFN(char, char8_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8 -> Char
|
||||
|
||||
Utf8ToChar::Utf8ToChar(const CodeName& code_name) : token(UTF8_CODENAME_LITERAL, code_name) {}
|
||||
|
||||
Utf8ToChar::~Utf8ToChar() {}
|
||||
|
||||
ConvResult<std::string> Utf8ToChar::to_char(const std::u8string_view& src) {
|
||||
USER_CONVFN(char8_t, char);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region WChar -> Char
|
||||
|
||||
WcharToUtf8::WcharToUtf8() : token(WCHAR_CODENAME_LITERAL, UTF8_CODENAME_LITERAL) {}
|
||||
|
||||
WcharToUtf8::~WcharToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> WcharToUtf8::to_utf8(const std::wstring_view& src) {
|
||||
USER_CONVFN(wchar_t, char8_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Char -> WChar
|
||||
|
||||
Utf8ToWchar::Utf8ToWchar() : token(UTF8_CODENAME_LITERAL, WCHAR_CODENAME_LITERAL) {}
|
||||
|
||||
Utf8ToWchar::~Utf8ToWchar() {}
|
||||
|
||||
ConvResult<std::wstring> Utf8ToWchar::to_wchar(const std::u8string_view& src) {
|
||||
USER_CONVFN(char8_t, wchar_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8 -> UTF16
|
||||
|
||||
Utf8ToUtf16::Utf8ToUtf16() : token(UTF8_CODENAME_LITERAL, UTF16_CODENAME_LITERAL) {}
|
||||
|
||||
Utf8ToUtf16::~Utf8ToUtf16() {}
|
||||
|
||||
ConvResult<std::u16string> Utf8ToUtf16::to_utf16(const std::u8string_view& src) {
|
||||
USER_CONVFN(char8_t, char16_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF16 -> UTF8
|
||||
|
||||
Utf16ToUtf8::Utf16ToUtf8() : token(UTF16_CODENAME_LITERAL, UTF8_CODENAME_LITERAL) {}
|
||||
|
||||
Utf16ToUtf8::~Utf16ToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> Utf16ToUtf8::to_utf8(const std::u16string_view& src) {
|
||||
USER_CONVFN(char16_t, char8_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF8 -> UTF32
|
||||
|
||||
Utf8ToUtf32::Utf8ToUtf32() : token(UTF8_CODENAME_LITERAL, UTF32_CODENAME_LITERAL) {}
|
||||
|
||||
Utf8ToUtf32::~Utf8ToUtf32() {}
|
||||
|
||||
ConvResult<std::u32string> Utf8ToUtf32::to_utf32(const std::u8string_view& src) {
|
||||
USER_CONVFN(char8_t, char32_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF32 -> UTF8
|
||||
|
||||
Utf32ToUtf8::Utf32ToUtf8() : token(UTF32_CODENAME_LITERAL, UTF8_CODENAME_LITERAL) {}
|
||||
|
||||
Utf32ToUtf8::~Utf32ToUtf8() {}
|
||||
|
||||
ConvResult<std::u8string> Utf32ToUtf8::to_utf8(const std::u32string_view& src) {
|
||||
USER_CONVFN(char32_t, char8_t);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::encoding::iconv
|
||||
|
||||
#endif
|
175
src/yycc/encoding/iconv.hpp
Normal file
175
src/yycc/encoding/iconv.hpp
Normal file
@ -0,0 +1,175 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/class_copy_move.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
namespace yycc::encoding::iconv {
|
||||
|
||||
// YYC MARK:
|
||||
// I don't want to include "iconv.h" in there.
|
||||
// One of reasons is that I want to hide all implementation of Iconv.
|
||||
// Another reason is that "iconv.h" defines some annoying macros which intervene some names in this files.
|
||||
// So I introduce PIMPL design mode. Use a pointer to hide all details in class PrivToken.
|
||||
|
||||
/// @brief The code name type used by Iconv.
|
||||
using CodeName = std::string_view;
|
||||
|
||||
/// @private
|
||||
class PrivToken;
|
||||
|
||||
/// @private
|
||||
class Token {
|
||||
public:
|
||||
Token(const CodeName& from_code, const CodeName& to_code);
|
||||
~Token();
|
||||
Token(Token&& rhs) noexcept;
|
||||
Token& operator=(Token&& rhs) noexcept;
|
||||
YYCC_DELETE_COPY(Token)
|
||||
|
||||
bool is_valid() const;
|
||||
PrivToken* get_inner() const;
|
||||
|
||||
private:
|
||||
PrivToken* inner;
|
||||
};
|
||||
|
||||
/// @brief The possible error occurs in this module.
|
||||
enum class ConvError {
|
||||
InvalidCd, ///< Given token is invalid.
|
||||
NullPointer, ///< Some of essential pointer in argument is nullptr.
|
||||
InvalidMbSeq, ///< An invalid multibyte sequence has been encountered in the input.
|
||||
IncompleteMbSeq, ///< An incomplete multibyte sequence has been encountered in the input.
|
||||
BadRv, ///< The size of encoding convertion is not matched with expected char type.
|
||||
};
|
||||
|
||||
/// @brief The result type in this module.
|
||||
template<typename T>
|
||||
using ConvResult = std::expected<T, ConvError>;
|
||||
|
||||
#if defined(YYCC_FEAT_ICONV)
|
||||
|
||||
/// @brief Char -> UTF8
|
||||
class CharToUtf8 {
|
||||
public:
|
||||
CharToUtf8(const CodeName& code_name);
|
||||
~CharToUtf8();
|
||||
YYCC_DELETE_COPY(CharToUtf8)
|
||||
YYCC_DEFAULT_MOVE(CharToUtf8)
|
||||
|
||||
public:
|
||||
ConvResult<std::u8string> to_utf8(const std::string_view& src);
|
||||
|
||||
private:
|
||||
Token token;
|
||||
};
|
||||
|
||||
/// @brief UTF8 -> Char
|
||||
class Utf8ToChar {
|
||||
public:
|
||||
Utf8ToChar(const CodeName& code_name);
|
||||
~Utf8ToChar();
|
||||
YYCC_DELETE_COPY(Utf8ToChar)
|
||||
YYCC_DEFAULT_MOVE(Utf8ToChar)
|
||||
|
||||
public:
|
||||
ConvResult<std::string> to_char(const std::u8string_view& src);
|
||||
|
||||
private:
|
||||
Token token;
|
||||
};
|
||||
|
||||
/// @brief WChar -> UTF8
|
||||
class WcharToUtf8 {
|
||||
public:
|
||||
WcharToUtf8();
|
||||
~WcharToUtf8();
|
||||
YYCC_DELETE_COPY(WcharToUtf8)
|
||||
YYCC_DEFAULT_MOVE(WcharToUtf8)
|
||||
|
||||
public:
|
||||
ConvResult<std::u8string> to_utf8(const std::wstring_view& src);
|
||||
|
||||
private:
|
||||
Token token;
|
||||
};
|
||||
|
||||
/// @brief UTF8 -> WChar
|
||||
class Utf8ToWchar {
|
||||
public:
|
||||
Utf8ToWchar();
|
||||
~Utf8ToWchar();
|
||||
YYCC_DELETE_COPY(Utf8ToWchar)
|
||||
YYCC_DEFAULT_MOVE(Utf8ToWchar)
|
||||
|
||||
public:
|
||||
ConvResult<std::wstring> to_wchar(const std::u8string_view& src);
|
||||
|
||||
private:
|
||||
Token token;
|
||||
};
|
||||
|
||||
/// @brief UTF8 -> UTF16
|
||||
class Utf8ToUtf16 {
|
||||
public:
|
||||
Utf8ToUtf16();
|
||||
~Utf8ToUtf16();
|
||||
YYCC_DELETE_COPY(Utf8ToUtf16)
|
||||
YYCC_DEFAULT_MOVE(Utf8ToUtf16)
|
||||
|
||||
public:
|
||||
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
|
||||
|
||||
private:
|
||||
Token token;
|
||||
};
|
||||
|
||||
/// @brief UTF16 -> UTF8
|
||||
class Utf16ToUtf8 {
|
||||
public:
|
||||
Utf16ToUtf8();
|
||||
~Utf16ToUtf8();
|
||||
YYCC_DELETE_COPY(Utf16ToUtf8)
|
||||
YYCC_DEFAULT_MOVE(Utf16ToUtf8)
|
||||
|
||||
public:
|
||||
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
|
||||
|
||||
private:
|
||||
Token token;
|
||||
};
|
||||
|
||||
/// @brief UTF8 -> UTF32
|
||||
class Utf8ToUtf32 {
|
||||
public:
|
||||
Utf8ToUtf32();
|
||||
~Utf8ToUtf32();
|
||||
YYCC_DELETE_COPY(Utf8ToUtf32)
|
||||
YYCC_DEFAULT_MOVE(Utf8ToUtf32)
|
||||
|
||||
public:
|
||||
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
|
||||
|
||||
private:
|
||||
Token token;
|
||||
};
|
||||
|
||||
/// @brief UTF32 -> UTF8
|
||||
class Utf32ToUtf8 {
|
||||
public:
|
||||
Utf32ToUtf8();
|
||||
~Utf32ToUtf8();
|
||||
YYCC_DELETE_COPY(Utf32ToUtf8)
|
||||
YYCC_DEFAULT_MOVE(Utf32ToUtf8)
|
||||
|
||||
public:
|
||||
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
|
||||
|
||||
private:
|
||||
Token token;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace yycc::encoding::iconv
|
118
src/yycc/encoding/stl.cpp
Normal file
118
src/yycc/encoding/stl.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include "stl.hpp"
|
||||
#include <locale>
|
||||
|
||||
namespace yycc::encoding::stl {
|
||||
|
||||
#pragma region Generic Converter
|
||||
|
||||
/*
|
||||
* YYC MARK:
|
||||
* According to the documentation introduced in CppReference.
|
||||
* The standard library is guaranteed to provide several specific specializations of \c std::codecvt.
|
||||
* The UTF8 char type in UTF8 related specializations of \c std::codecvt is different in different C++ standard.
|
||||
* But the oldest C++ version YYCC supported is C++ 23, char8_t is the only viable UTF8 char type for \c std::codecvt.
|
||||
* So we can simply and safely use it to correctly trigger specific specializations of \c std::codecv in there.
|
||||
*/
|
||||
|
||||
template<typename TChar>
|
||||
requires(std::is_same_v<TChar, char16_t> || std::is_same_v<TChar, char32_t>)
|
||||
using CodecvtFacet = std::codecvt<TChar, char8_t, std::mbstate_t>;
|
||||
|
||||
template<typename TChar>
|
||||
requires(std::is_same_v<TChar, char16_t> || std::is_same_v<TChar, char32_t>)
|
||||
static ConvResult<std::basic_string<TChar>> generic_to_utf_other(const std::u8string_view& src) {
|
||||
// Reference:
|
||||
// https://en.cppreference.com/w/cpp/locale/codecvt/in
|
||||
|
||||
// prepare return value
|
||||
std::basic_string<TChar> dst;
|
||||
|
||||
// if src is empty, return directly
|
||||
if (src.empty()) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
// init locale and get codecvt facet
|
||||
// same reason in UTFOtherToUTF8 to keeping reference to locale
|
||||
const auto& this_locale = std::locale::classic();
|
||||
const auto& this_codecvt = std::use_facet<CodecvtFacet<TChar>>(this_locale);
|
||||
|
||||
// convertion preparation
|
||||
std::mbstate_t mb{};
|
||||
dst.resize(src.size());
|
||||
const char8_t *intern_from = reinterpret_cast<const char8_t*>(src.data()),
|
||||
*intern_from_end = reinterpret_cast<const char8_t*>(src.data() + src.size()), *intern_from_next = nullptr;
|
||||
TChar *extern_to = dst.data(), *extern_to_end = dst.data() + dst.size(), *extern_to_next = nullptr;
|
||||
// do convertion
|
||||
auto result = this_codecvt.in(mb, intern_from, intern_from_end, intern_from_next, extern_to, extern_to_end, extern_to_next);
|
||||
|
||||
// check result
|
||||
if (result != CodecvtFacet<TChar>::ok) return std::unexpected(ConvError{});
|
||||
// resize result and return
|
||||
dst.resize(extern_to_next - dst.data());
|
||||
return dst;
|
||||
}
|
||||
|
||||
template<typename TChar>
|
||||
requires(std::is_same_v<TChar, char16_t> || std::is_same_v<TChar, char32_t>)
|
||||
static ConvResult<std::u8string> generic_to_utf8(const std::basic_string_view<TChar>& src) {
|
||||
// Reference:
|
||||
// https://en.cppreference.com/w/cpp/locale/codecvt/out
|
||||
|
||||
// prepare return value
|
||||
std::u8string dst;
|
||||
|
||||
// if src is empty, return directly
|
||||
if (src.empty()) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
// init locale and get codecvt facet
|
||||
// the reference to locale must be preserved until convertion done.
|
||||
// because the life time of codecvt facet is equal to the reference to locale.
|
||||
const auto& this_locale = std::locale::classic();
|
||||
const auto& this_codecvt = std::use_facet<CodecvtFacet<TChar>>(this_locale);
|
||||
|
||||
// do convertion preparation
|
||||
std::mbstate_t mb{};
|
||||
dst.resize(src.size() * this_codecvt.max_length());
|
||||
const TChar *intern_from = src.data(), *intern_from_end = src.data() + src.size(), *intern_from_next = nullptr;
|
||||
char8_t *extern_to = reinterpret_cast<char8_t*>(dst.data()), *extern_to_end = reinterpret_cast<char8_t*>(dst.data() + dst.size()),
|
||||
*extern_to_next = nullptr;
|
||||
// do convertion
|
||||
auto result = this_codecvt.out(mb, intern_from, intern_from_end, intern_from_next, extern_to, extern_to_end, extern_to_next);
|
||||
|
||||
// check result
|
||||
if (result != CodecvtFacet<TChar>::ok) return std::unexpected(ConvError{});
|
||||
// resize result and retuen
|
||||
dst.resize(extern_to_next - reinterpret_cast<char8_t*>(dst.data()));
|
||||
return dst;
|
||||
}
|
||||
|
||||
#pragma endregion Converter
|
||||
|
||||
#pragma region
|
||||
|
||||
ConvResult<std::u16string> to_utf16(const std::u8string_view& src) {
|
||||
// UTF8 -> UTF16
|
||||
return generic_to_utf_other<char16_t>(src);
|
||||
}
|
||||
|
||||
ConvResult<std::u8string> to_utf8(const std::u16string_view& src) {
|
||||
// UTF16 -> UTF8
|
||||
return generic_to_utf8<char16_t>(src);
|
||||
}
|
||||
|
||||
ConvResult<std::u32string> to_utf32(const std::u8string_view& src) {
|
||||
// UTF8 -> UTF32
|
||||
return generic_to_utf_other<char32_t>(src);
|
||||
}
|
||||
|
||||
ConvResult<std::u8string> to_utf8(const std::u32string_view& src) {
|
||||
// UTF32 -> UTF8
|
||||
return generic_to_utf8<char32_t>(src);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::encoding::stlcvt
|
43
src/yycc/encoding/stl.hpp
Normal file
43
src/yycc/encoding/stl.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
|
||||
namespace yycc::encoding::stl {
|
||||
|
||||
/// @brief Possible convertion error occurs in this module.
|
||||
struct ConvError {};
|
||||
|
||||
/// @brief The result type of this module.
|
||||
template<typename T>
|
||||
using ConvResult = std::expected<T, ConvError>;
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> UTF16
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF16 -> UTF8
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> UTF32
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF32 -> UTF8
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
|
||||
|
||||
}
|
253
src/yycc/encoding/windows.cpp
Normal file
253
src/yycc/encoding/windows.cpp
Normal file
@ -0,0 +1,253 @@
|
||||
#include "windows.hpp"
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
#include "../string/reinterpret.hpp"
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <cuchar>
|
||||
|
||||
#include "../windows/import_guard_head.hpp"
|
||||
#include <Windows.h>
|
||||
#include "../windows/import_guard_tail.hpp"
|
||||
|
||||
#define NS_YYCC_STRING_REINTERPRET ::yycc::string::reinterpret
|
||||
|
||||
namespace yycc::encoding::windows {
|
||||
|
||||
#pragma region WideCharToMultiByte and MultiByteToWideChar stuff
|
||||
|
||||
// WChar -> Char
|
||||
ConvResult<std::string> to_char(const std::wstring_view& src, CodePage code_page) {
|
||||
// prepare result
|
||||
std::string dst;
|
||||
|
||||
// if src is empty, direct output
|
||||
if (src.empty()) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
// init WideCharToMultiByte used variables
|
||||
// setup src pointer
|
||||
LPCWCH lpWideCharStr = reinterpret_cast<LPCWCH>(src.data());
|
||||
// check whether source string is too large.
|
||||
size_t cSrcSize = src.size();
|
||||
if (cSrcSize > std::numeric_limits<int>::max()) return std::unexpected(ConvError::TooLargeLength);
|
||||
int cchWideChar = static_cast<int>(src.size());
|
||||
|
||||
// do convertion
|
||||
// do a dry-run first to fetch desired size.
|
||||
int desired_size = WideCharToMultiByte(code_page, 0, lpWideCharStr, cchWideChar, NULL, 0, NULL, NULL);
|
||||
if (desired_size <= 0) return std::unexpected(ConvError::NoDesiredSize);
|
||||
// resize dest for receiving result
|
||||
dst.resize(static_cast<size_t>(desired_size));
|
||||
// do real convertion
|
||||
int write_result
|
||||
= WideCharToMultiByte(code_page, 0, lpWideCharStr, cchWideChar, reinterpret_cast<LPSTR>(dst.data()), desired_size, NULL, NULL);
|
||||
if (write_result <= 0) return std::unexpected(ConvError::BadWrittenSize);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Char -> WChar
|
||||
ConvResult<std::wstring> to_wchar(const std::string_view& src, CodePage code_page) {
|
||||
// prepare result
|
||||
std::wstring dst;
|
||||
|
||||
// if src is empty, direct output
|
||||
if (src.empty()) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
// init WideCharToMultiByte used variables
|
||||
// setup src pointer
|
||||
LPCCH lpMultiByteStr = reinterpret_cast<LPCCH>(src.data());
|
||||
// check whether source string is too large.
|
||||
size_t cSrcSize = src.size();
|
||||
if (cSrcSize > std::numeric_limits<int>::max()) return std::unexpected(ConvError::TooLargeLength);
|
||||
int cbMultiByte = static_cast<int>(src.size());
|
||||
|
||||
// do convertion
|
||||
// do a dry-run first to fetch desired size.
|
||||
int desired_size = MultiByteToWideChar(code_page, 0, lpMultiByteStr, cbMultiByte, NULL, 0);
|
||||
if (desired_size <= 0) return std::unexpected(ConvError::NoDesiredSize);
|
||||
// resize dest for receiving result
|
||||
dst.resize(static_cast<size_t>(desired_size));
|
||||
// do real convertion
|
||||
int write_result = MultiByteToWideChar(code_page, 0, lpMultiByteStr, cbMultiByte, reinterpret_cast<LPWSTR>(dst.data()), desired_size);
|
||||
if (write_result <= 0) return std::unexpected(ConvError::BadWrittenSize);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
// Char -> Char
|
||||
ConvResult<std::string> to_char(const std::string_view& src, CodePage src_code_page, CodePage dst_code_page) {
|
||||
auto first_rv = to_wchar(src, src_code_page);
|
||||
return first_rv.and_then([dst_code_page](const auto& src) { return to_char(src, dst_code_page); });
|
||||
}
|
||||
|
||||
// WChar -> UTF8
|
||||
ConvResult<std::u8string> to_utf8(const std::wstring_view& src) {
|
||||
auto rv = to_char(src, CP_UTF8);
|
||||
return rv.transform([](const auto& dst) { return NS_YYCC_STRING_REINTERPRET::as_utf8(dst); });
|
||||
}
|
||||
|
||||
// UTF8 -> WChar
|
||||
ConvResult<std::wstring> to_wchar(const std::u8string_view& src) {
|
||||
return to_wchar(NS_YYCC_STRING_REINTERPRET::as_ordinary_view(src), CP_UTF8);
|
||||
}
|
||||
|
||||
// Char -> UTF8
|
||||
ConvResult<std::u8string> to_utf8(const std::string_view& src, CodePage code_page) {
|
||||
auto rv = to_char(src, code_page, CP_UTF8);
|
||||
return rv.transform([](const auto& dst) { return NS_YYCC_STRING_REINTERPRET::as_utf8(dst); });
|
||||
}
|
||||
|
||||
// UTF8 -> Char
|
||||
ConvResult<std::string> to_char(const std::u8string_view& src, CodePage code_page) {
|
||||
return to_char(NS_YYCC_STRING_REINTERPRET::as_ordinary_view(src), CP_UTF8, code_page);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region UTF stuff
|
||||
|
||||
// YYC MARK:
|
||||
// The convertion between UTF is implemented by c16rtomb, c32rtomb, mbrtoc16 and mbrtoc32.
|
||||
// These function is locale related in C++ standard, but in Microsoft STL, it's only for UTF8.
|
||||
// So we can use them safely in Win32 environment.
|
||||
// Reference:
|
||||
// * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/c16rtomb-c32rtomb1?view=msvc-170
|
||||
// * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/mbrtoc16-mbrtoc323?view=msvc-170
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
// 1 UTF32 unit can produe 4 UTF8 units or 2 UTF16 units in theory.
|
||||
// So we pre-allocate memory for the result to prevent allocating memory multiple times.
|
||||
constexpr size_t MULTIPLE_UTF8_TO_UTF16 = 1u;
|
||||
constexpr size_t MULTIPLE_UTF16_TO_UTF8 = 2u;
|
||||
constexpr size_t MULTIPLE_UTF8_TO_UTF32 = 1u;
|
||||
constexpr size_t MULTIPLE_UTF32_TO_UTF8 = 4u;
|
||||
|
||||
// UTF8 -> UTF16
|
||||
ConvResult<std::u16string> to_utf16(const std::u8string_view& src) {
|
||||
std::u16string dst;
|
||||
dst.reserve(src.size() * MULTIPLE_UTF8_TO_UTF16);
|
||||
|
||||
std::mbstate_t state{}; // zero-initialized to initial state
|
||||
char16_t c16;
|
||||
const char* ptr = reinterpret_cast<const char*>(src.data());
|
||||
const char* end = ptr + src.size();
|
||||
|
||||
// YYC MARK:
|
||||
// Due to the shitty design of mbrtoc16, it forcely assume that passed string is null-terminated.
|
||||
// And the third argument should >= 1.
|
||||
// However, our given string is string view which do not have null-terminated guaranteen.
|
||||
//
|
||||
// So we manually check whether we have reach the tail of string and simulate a fake null terminal.
|
||||
// If string is still processing, we pass given string.
|
||||
// If we have reach the tail of string, we pass our homemade NULL_TERMINAL to this function to make it works normally.
|
||||
//
|
||||
// This is a stupid polyfill, however, it I do not do this,
|
||||
// there is a bug that the second part of surrogate pair will be dropped in final string,
|
||||
// if there is a Unicode character located at the tail of string which need surrogate pair to be presented.
|
||||
static const char NULL_TERMINAL = '\0';
|
||||
while (true) {
|
||||
bool not_tail = ptr < end;
|
||||
const char* new_ptr = not_tail ? ptr : &NULL_TERMINAL;
|
||||
size_t new_size = not_tail ? end - ptr : sizeof(NULL_TERMINAL);
|
||||
size_t rc = std::mbrtoc16(&c16, new_ptr, new_size, &state);
|
||||
if (!rc) break;
|
||||
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::EncodeUtf8);
|
||||
else if (rc == (size_t) -2) return std::unexpected(ConvError::IncompleteUtf8);
|
||||
else if (rc == (size_t) -3) dst.push_back(c16); // from earlier surrogate pair
|
||||
else {
|
||||
dst.push_back(c16);
|
||||
ptr += rc;
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
// UTF16 -> UTF8
|
||||
ConvResult<std::u8string> to_utf8(const std::u16string_view& src) {
|
||||
std::u8string dst;
|
||||
dst.reserve(src.size() * MULTIPLE_UTF16_TO_UTF8);
|
||||
|
||||
std::mbstate_t state{};
|
||||
char mbout[MB_LEN_MAX]{};
|
||||
size_t rc = 1; // Assign it to ONE to avoid mismatching surrogate pair checker when string is empty.
|
||||
for (char16_t c : src) {
|
||||
rc = std::c16rtomb(mbout, c, &state);
|
||||
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::InvalidUtf16);
|
||||
else dst.append(reinterpret_cast<char8_t*>(mbout), rc);
|
||||
}
|
||||
|
||||
if (rc == 0) {
|
||||
// YYC MARK:
|
||||
// If rc is zero after processing all chars,
|
||||
// it means that we are aborted when processing an UTF16 surrogate pair.
|
||||
// We should report it as an error.
|
||||
return std::unexpected(ConvError::InvalidUtf16);
|
||||
}
|
||||
|
||||
// Okey, return result.
|
||||
return dst;
|
||||
}
|
||||
|
||||
// UTF8 -> UTF32
|
||||
ConvResult<std::u32string> to_utf32(const std::u8string_view& src) {
|
||||
std::u32string dst;
|
||||
dst.reserve(src.size() * MULTIPLE_UTF8_TO_UTF32);
|
||||
|
||||
std::mbstate_t state{};
|
||||
char32_t c32;
|
||||
const char* ptr = reinterpret_cast<const char*>(src.data());
|
||||
const char* end = ptr + src.size();
|
||||
|
||||
while (ptr < end) {
|
||||
// YYC MARK:
|
||||
// There is no surrogate pair in UTF32,
|
||||
// so we do not need do that stupid things in UTF8 to UTF32 functions.
|
||||
size_t rc = std::mbrtoc32(&c32, ptr, end - ptr, &state);
|
||||
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::EncodeUtf8);
|
||||
else if (rc == (size_t) -2) return std::unexpected(ConvError::IncompleteUtf8);
|
||||
else if (rc == (size_t) -3) throw std::runtime_error("no surrogates in UTF-32");
|
||||
else dst.push_back(c32);
|
||||
|
||||
ptr += rc;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
// UTF32 -> UTF8
|
||||
ConvResult<std::u8string> to_utf8(const std::u32string_view& src) {
|
||||
std::u8string dst;
|
||||
dst.reserve(src.size() * MULTIPLE_UTF32_TO_UTF8);
|
||||
|
||||
std::mbstate_t state{};
|
||||
char mbout[MB_LEN_MAX]{};
|
||||
for (char32_t c : src) {
|
||||
size_t rc = std::c32rtomb(mbout, c, &state);
|
||||
|
||||
if (rc == (size_t) -1) return std::unexpected(ConvError::InvalidUtf32);
|
||||
else dst.append(reinterpret_cast<char8_t*>(mbout), rc);
|
||||
}
|
||||
|
||||
// YYC MARK:
|
||||
// There is no surrogate pair for UTF32,
|
||||
// so this "if" statement only presented in UTF16 to UTF8 function.
|
||||
// In this function, we directly return value.
|
||||
return dst;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::encoding::windows
|
||||
|
||||
#endif
|
129
src/yycc/encoding/windows.hpp
Normal file
129
src/yycc/encoding/windows.hpp
Normal file
@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
#include "../macro/os_detector.hpp"
|
||||
#include "../macro/stl_detector.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <expected>
|
||||
#include <cstdint>
|
||||
|
||||
namespace yycc::encoding::windows {
|
||||
|
||||
/// @brief The type of Windows code page.
|
||||
using CodePage = uint32_t;
|
||||
|
||||
/// @brief The possible error kind occurs in this module.
|
||||
enum class ConvError {
|
||||
TooLargeLength, ///< The length of given string is too large exceeding the maximum capacity of Win32 function.
|
||||
NoDesiredSize, ///< Can not compute the desired size of result string.
|
||||
BadWrittenSize, ///< The size of written data is not matched with expected size.
|
||||
InvalidUtf32, ///< Given char is invalid in UTF32.
|
||||
InvalidUtf16, ///< Given char is invalid in UTF16.
|
||||
EncodeUtf8, ///< Error occurs when encoding UTF8.
|
||||
IncompleteUtf8, ///< Given UTF8 string is incomplete.
|
||||
};
|
||||
|
||||
/// @brief The result type in this module.
|
||||
template<typename T>
|
||||
using ConvResult = std::expected<T, ConvError>;
|
||||
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
|
||||
/**
|
||||
* @brief WChar -> Char
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] code_page The code page of native string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::string> to_char(const std::wstring_view& src, CodePage code_page);
|
||||
|
||||
/**
|
||||
* @brief Char -> WChar
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] code_page The code page of native string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::wstring> to_wchar(const std::string_view& src, CodePage code_page);
|
||||
|
||||
/**
|
||||
* @brief Char -> Char
|
||||
* @details This is the combination of "WChar -> Char" and "Char -> WChar"
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] src_code_page The code page of source string.
|
||||
* @param[in] dst_code_page The code page of destination string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::string> to_char(const std::string_view& src, CodePage src_code_page, CodePage dst_code_page);
|
||||
|
||||
/**
|
||||
* @brief WChar -> UTF8
|
||||
* @details This is the specialization of "WChar -> Char"
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::wstring_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> WChar
|
||||
* @details This is the specialization of "Char -> WChar"
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::wstring> to_wchar(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief Char -> UTF8
|
||||
* @details This is the specialization of "Char -> Char"
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] code_page The code page of native string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::string_view& src, CodePage code_page);
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> Char
|
||||
* @details This is the specialization of "Char -> Char"
|
||||
* @param[in] src The string to be converted.
|
||||
* @param[in] code_page The code page of native string.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::string> to_char(const std::u8string_view& src, CodePage code_page);
|
||||
|
||||
// YYC MARK:
|
||||
// UTF convertion only works on Microsoft STL.
|
||||
// See implementation for more details
|
||||
|
||||
#if defined(YYCC_STL_MSSTL)
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> UTF16
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u16string> to_utf16(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF16 -> UTF8
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::u16string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF8 -> UTF32
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u32string> to_utf32(const std::u8string_view& src);
|
||||
|
||||
/**
|
||||
* @brief UTF32 -> UTF8
|
||||
* @param[in] src The string to be converted.
|
||||
* @return The converted string, or error occurring.
|
||||
*/
|
||||
ConvResult<std::u8string> to_utf8(const std::u32string_view& src);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace yycc::encoding::windows
|
207
src/yycc/flag_enum.hpp
Normal file
207
src/yycc/flag_enum.hpp
Normal file
@ -0,0 +1,207 @@
|
||||
#pragma once
|
||||
#include <type_traits>
|
||||
|
||||
/**
|
||||
* @brief The namespace for convenient C++ enum class logic operations.
|
||||
* @details
|
||||
* C++ enum class statement is a modern way to declare enum in C++.
|
||||
* But it lack essential logic operations which is commonly used by programmer.
|
||||
* So we create this helper to resolve this issue.
|
||||
*/
|
||||
namespace yycc::flag_enum {
|
||||
|
||||
// Reference:
|
||||
// Enum operator overload: https://stackoverflow.com/a/71107019
|
||||
// Constexpr operator overload: https://stackoverflow.com/a/17746099
|
||||
|
||||
// YYC MARK:
|
||||
// Currently, the solution of "Constexpr operator overload" is not used.
|
||||
// We use explicit way, "Enum operator overload".
|
||||
|
||||
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
// inline constexpr TEnum operator|(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) | static_cast<ut>(rhs));
|
||||
// }
|
||||
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
// inline constexpr TEnum operator|=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs | rhs;
|
||||
// return lhs;
|
||||
// }
|
||||
|
||||
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
// inline constexpr TEnum operator&(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) & static_cast<ut>(rhs));
|
||||
// }
|
||||
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
// inline constexpr TEnum operator&=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs & rhs;
|
||||
// return lhs;
|
||||
// }
|
||||
|
||||
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
// inline constexpr TEnum operator^(TEnum lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(static_cast<ut>(lhs) ^ static_cast<ut>(rhs));
|
||||
// }
|
||||
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
// inline constexpr TEnum operator^=(TEnum& lhs, TEnum rhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// lhs = lhs ^ rhs;
|
||||
// return lhs;
|
||||
// }
|
||||
|
||||
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
// inline constexpr TEnum operator~(TEnum lhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<TEnum>(~(static_cast<ut>(lhs)));
|
||||
// }
|
||||
|
||||
// template<typename TEnum, std::enable_if_t<std::is_enum_v<TEnum>, int> = 0>
|
||||
// inline constexpr bool operator bool(TEnum lhs) {
|
||||
// using ut = std::underlying_type_t<TEnum>;
|
||||
// return static_cast<bool>(static_cast<ut>(lhs));
|
||||
// }
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @brief The helper struct to check all given template argument are the same enum type.
|
||||
* @tparam TEnum The template parameter to be checked (first one).
|
||||
* @tparam Ts The template parameter to be checked.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
struct AllSameEnum {
|
||||
public:
|
||||
// YYC MARK:
|
||||
// Please note that we must use std::is_same, not std::is_same_v!
|
||||
// That's std::conjunction_v required.
|
||||
static constexpr bool value = std::is_enum_v<std::remove_cv_t<TEnum>>
|
||||
&& std::conjunction_v<std::is_same<std::remove_cv_t<TEnum>, std::remove_cv_t<Ts>>...>;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @brief The convenient calling to all_enum_values::value to check enum template parameter.
|
||||
* @tparam TEnum The template parameter to be checked (first one).
|
||||
* @tparam Ts The template parameter to be checked.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
inline constexpr bool ALL_SAME_ENUM = AllSameEnum<TEnum, Ts...>::value;
|
||||
|
||||
/**
|
||||
* @brief Merge given enum flags like performing <TT>e1 | e2 | ... | en</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] val The first enum flag to be merged.
|
||||
* @param[in] val_left Left enum flags to be merged.
|
||||
* @return The merged enum flag.
|
||||
* @remarks
|
||||
* This function use recursive expansion to get final merge result.
|
||||
* So there is no difference of each arguments.
|
||||
* We independ first argument just served for expansion.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
requires(ALL_SAME_ENUM<TEnum, Ts...>)
|
||||
constexpr TEnum merge(TEnum val, Ts... val_left) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
ut result = static_cast<ut>(val);
|
||||
if constexpr (sizeof...(val_left) > 0) {
|
||||
result |= static_cast<ut>(merge(val_left...));
|
||||
}
|
||||
return static_cast<TEnum>(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reverse given enum flags like performing <TT>~(e)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] e The list of enum flags to be inversed.
|
||||
* @return The inversed enum flag.
|
||||
*/
|
||||
template<typename TEnum>
|
||||
requires(std::is_enum_v<TEnum>)
|
||||
constexpr TEnum invert(TEnum e) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<TEnum>(~(static_cast<ut>(e)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Use specified enum flag to mask given enum flag like performing <TT>e1 &= e2</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flags to be masked.
|
||||
* @param[in] e2 The mask enum flag.
|
||||
*/
|
||||
template<typename TEnum>
|
||||
requires(std::is_enum_v<TEnum>)
|
||||
constexpr void mask(TEnum& e1, TEnum e2) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(e2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add multiple enum flags to given enum flag like performing <TT>e1 |= (e2 | e3 | ... | en)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flag which flags add on.
|
||||
* @param[in] vals The enum flag to be added.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
requires(ALL_SAME_ENUM<TEnum, Ts...>)
|
||||
constexpr void add(TEnum& e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) | static_cast<ut>(merge(vals...)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove multiple enum flags from given enum flag like performing <TT>e1 &= ~(e2 | e3 | ... | en)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in,out] e1 The enum flag which flags removed from.
|
||||
* @param[in] vals The enum flag to be removed.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
requires(ALL_SAME_ENUM<TEnum, Ts...>)
|
||||
constexpr void remove(TEnum& e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
e1 = static_cast<TEnum>(static_cast<ut>(e1) & static_cast<ut>(invert(merge(vals...))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether given enum flag has any of specified multiple enum flags (OR) like performing <TT>bool(e1 & (e2 | e3 | ... | en))</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param[in] e1 The enum flag where we check.
|
||||
* @param[in] vals The enum flags for checking.
|
||||
* @return True if it has any of given flags (OR), otherwise false.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
requires(ALL_SAME_ENUM<TEnum, Ts...>)
|
||||
constexpr bool has(TEnum e1, Ts... vals) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<bool>(static_cast<ut>(e1) & static_cast<ut>(merge(vals...)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cast given enum flags to its equvalent boolean value like performing <TT>bool(e)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param e The enum flags to be cast.
|
||||
* @return The equvalent bool value of given enum flag.
|
||||
*/
|
||||
template<typename TEnum, typename... Ts>
|
||||
requires(ALL_SAME_ENUM<TEnum, Ts...>)
|
||||
constexpr bool boolean(TEnum e) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<bool>(static_cast<ut>(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cast given enum flags to its equvalent underlying integer value like performing <TT>static_cast<std::underlying_type_t<T>>(e)</TT>
|
||||
* @tparam TEnum Enum type for processing.
|
||||
* @param e The enum flags to be cast.
|
||||
* @return The equvalent integer value of given enum flag.
|
||||
*/
|
||||
template<typename TEnum>
|
||||
requires(std::is_enum_v<TEnum>)
|
||||
constexpr std::underlying_type_t<TEnum> integer(TEnum e) {
|
||||
using ut = std::underlying_type_t<TEnum>;
|
||||
return static_cast<ut>(e);
|
||||
}
|
||||
|
||||
} // namespace yycc::flag_enum
|
62
src/yycc/macro/class_copy_move.hpp
Normal file
62
src/yycc/macro/class_copy_move.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief Explicitly remove copy (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&) = delete; \
|
||||
CLSNAME& operator=(const CLSNAME&) = delete;
|
||||
|
||||
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) noexcept = delete; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept = delete;
|
||||
|
||||
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DELETE_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DELETE_COPY(CLSNAME) \
|
||||
YYCC_DELETE_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Explicitly set default copy (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&) = default; \
|
||||
CLSNAME& operator=(const CLSNAME&) = default;
|
||||
|
||||
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) noexcept = default; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept = default;
|
||||
|
||||
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
|
||||
#define YYCC_DEFAULT_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DEFAULT_COPY(CLSNAME) \
|
||||
YYCC_DEFAULT_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Make declaration of copy (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_COPY(CLSNAME) \
|
||||
CLSNAME(const CLSNAME&); \
|
||||
CLSNAME& operator=(const CLSNAME&);
|
||||
|
||||
/// @brief Make declaration of move (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_MOVE(CLSNAME) \
|
||||
CLSNAME(CLSNAME&&) noexcept; \
|
||||
CLSNAME& operator=(CLSNAME&&) noexcept;
|
||||
|
||||
/// @brief Make declaration of copy and move (\c constructor and \c operator\=) for given class to avoid typo.
|
||||
#define YYCC_DECL_COPY_MOVE(CLSNAME) \
|
||||
YYCC_DECL_COPY(CLSNAME) \
|
||||
YYCC_DECL_MOVE(CLSNAME)
|
||||
|
||||
/// @brief Make implementation signature of copy \c constrctor for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_COPY_CTOR(CLSNAME, RHS) \
|
||||
CLSNAME::CLSNAME(const CLSNAME& RHS)
|
||||
|
||||
/// @brief Make implementation signature of copy \c operator\= for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_COPY_OPER(CLSNAME, RHS) \
|
||||
CLSNAME& CLSNAME::operator=(const CLSNAME& RHS)
|
||||
|
||||
/// @brief Make implementation signature of move \c constrctor for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_MOVE_CTOR(CLSNAME, RHS) \
|
||||
CLSNAME::CLSNAME(CLSNAME&& RHS) noexcept
|
||||
|
||||
/// @brief Make implementation signature of move \c operator\= for given class and right operand name to avoid typo.
|
||||
#define YYCC_IMPL_MOVE_OPER(CLSNAME, RHS) \
|
||||
CLSNAME& CLSNAME::operator=(CLSNAME&& RHS) noexcept
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user