1
0

89 Commits

Author SHA1 Message Date
82c3ed5b32 fix: fix compile error in gcc.
- remove __attribute__(format) checker for UTF8 char type printf becuase it forcely require the type of format string is const char*, rather than const char8_t*, and I can not use any switches to remove this.
- delete useless macro and header in clap manual.
2025-09-28 22:38:09 +08:00
d6be8a11ac fix: use more clear format spec in format test.
- this is designed to avoid different behavior between msvc and gcc.
	- gcc output char in default.
	- msvc output it as integer in default.
2025-09-28 22:31:08 +08:00
31c624797f revert: revert the delete of std::stacktrace in rust panic.
- i revert previous changes because currently code have not been compiled in clang.
2025-09-28 22:02:54 +08:00
6ecf6935d8 fix: use new format to replace all printf as much as possible.
- ironpad is a very conservative module so I don't want to bring any changes for it except bug fix.
2025-09-28 21:57:18 +08:00
d6b1d7fd46 fix: redirect all std::format to my personal format 2025-09-28 21:44:44 +08:00
8d7eff2a15 fix: fix all old usage of UTF8 in std::ostream.
- use patch stream instead of all old use of UTF8 in std::ostream (reinterpret way).
2025-09-28 21:37:05 +08:00
bd5032cee7 feat: change join data provider.
- use Rust iterator style for join data provider for better understanding.
2025-09-28 17:01:48 +08:00
cc1ce5bb04 feat: finish string strip (maybe)
- add Trie Tree for string strip op.
- finish string strip but it may still buggy (though i have tested it)
2025-09-28 16:41:40 +08:00
190beeed58 feat: add code point splittor for utf8 string.
- this feature is added for strip function in string op.
2025-09-26 21:43:12 +08:00
99146ddd55 feat: add lazy split in string op namespace. 2025-09-26 14:43:13 +08:00
ce3d5b9556 feat: finish basic of manual in clap 2025-09-25 15:52:28 +08:00
c8d763bdcf feat: add utf8 format
- move utf8 stream and format patch from string to patch namespace.
- add ordinay format alias and utf8 format in our format patch.
- add char8_t and char inter-cast in string reinterpret namespace.
- fix bug of utf8 formatter.
- add test for utf8 format.
2025-09-25 15:29:55 +08:00
a61955bb09 feat: finish summary and application for clap.
- finish summary and application for clap.
- add patch for utf8 string in std::format.
2025-09-24 16:20:21 +08:00
776adb0c96 feat: finish option and variable in clap 2025-09-23 16:13:52 +08:00
c85830902b fix: change the behavior of printf in string op.
- add compiler hint for checking the arguments of printf.
- change the return value of printf. from std::expected to normal value. use C++ exception to indicate error.
	* the error of printf usually caused by programmer. so it can be found when testing program.
	* so i use std::logic_error to indicate this and programmer should fix this before releasing program.
- change the use of encoding convertion. for those cases that convertion must be safe, we unwrap it directly.
2025-09-22 22:21:25 +08:00
45e4031b5c refactor: finish ironpad migration
- finish ironpad migration but no test because it is not easy to test.
2025-09-18 16:04:41 +08:00
ccd0219ead refactor: migrate console helper.
- migrate csharp style console helper.
- i just do a simple migration and mark it as deprecated. it should works like 1.x version.
2025-08-22 23:28:17 +08:00
4bfba6f243 feat: add windows-spec console patch 2025-08-22 21:51:32 +08:00
9e994dd4f0 refactor: bring utf8 patch for std::ostream back. 2025-08-22 21:28:29 +08:00
d6034f8cb0 fix: fix minor issue of testbench
- fix testbench minor issue.
- delete migrated source code files.
2025-08-22 21:09:57 +08:00
0694d923f3 test: finish testbench for tabulate 2025-08-21 14:32:51 +08:00
580b096cb3 fix: fix bug for windows dialog 2025-08-21 11:00:33 +08:00
f9365481b9 fix: fix all build issue of dialog namespace but not test. 2025-08-21 10:26:28 +08:00
15aade052f refactor: expand ugly statement from while statement header to body. 2025-08-20 19:32:44 +08:00
050bed400d doc: update doc 2025-08-20 14:29:41 +08:00
244e39c4d1 revert: remove stacktrace feature for rust panic.
- remove stacktrace feature for rust panic function due to not all STL are ready for this.
- add more os type in CMake file.
- add lost header in fopen.
2025-08-19 21:47:21 +08:00
d52630ac5c feat: add tabulate but no test. 2025-08-19 20:53:51 +08:00
a76f10722d fix: fix todos waiting termcolor. 2025-08-19 13:58:05 +08:00
8a72e6a655 feat: add termcolor in carton.
- add termcolor and its testbench
- add integer() in flag_enum and update its testbench according to the requirements in termcolor.
2025-08-19 13:50:51 +08:00
dfc0c127c5 test: add testbench for macro namespace 2025-08-15 16:55:39 +08:00
2f11ba6023 feat: add new package wcwidth
- add wcwidth in carton.
- order clang-format do not format some generated content.
2025-08-15 16:42:28 +08:00
00c8f09907 doc: start to refactor doc 2025-08-14 21:33:17 +08:00
734cd01da8 feat: add Rust env namespace 2025-08-14 20:17:02 +08:00
bdeaea294f refactor: migrate windows specific content.
- move com, dialog and winfct function into new place.
- add testbench for com and winfct.
- dialog now still not working.
2025-08-13 15:29:47 +08:00
ff8c7d04cc fix: fix utf convertion issue under MSVC 2025-08-13 10:49:35 +08:00
0ab470367c fix: fix safe num overflow ops issue under MSVC 2025-08-13 09:51:40 +08:00
c4d441f5fa fix: fix pycodec MSVC error 2025-08-13 09:36:21 +08:00
f8a696b4e8 fix: fix MSVC __VA_OPT__ error 2025-08-13 09:24:19 +08:00
f65eff6edf fix: replace find with contains in constraint builder
- remove useless old files.
2025-08-13 08:56:09 +08:00
8fcfa180b4 fix: fix unfinished u8 fopen.
- finish unfinished utf8 version fopen.
- add testbench for it.
2025-08-13 08:49:18 +08:00
e23a1346eb test: add testbench for pycodec 2025-08-12 19:47:24 +08:00
2576523dbb fix: use new way to manage iconv token.
- use new way to manage iconv token, instead of std::unique_ptr which cause template error (no viable sizeof) in pycodec.
- fix the wrong name in pycodec.
- remove useless code.
2025-08-12 19:40:23 +08:00
9ce52e8d4b feat: finish pycodec but has compile issue 2025-08-12 17:34:03 +08:00
7785773196 refactor: use new layout for YYCC 2025-08-12 16:32:59 +08:00
cfbc3c68e0 doc: add Doxygen comment 2025-08-12 16:15:00 +08:00
8dbe32cb8e fix: fix fatal error in encoding/iconv.
- fix the size of input data error (forget mul with the size of unit)
- fix the wrong code name for UTF16 and UTF32.
- fix wrong assign for `outbytesleft`.
2025-08-12 16:05:11 +08:00
664763afbb test: add testbench for stl, windows and iconv encoding 2025-08-12 13:05:35 +08:00
a34bab07c1 refactor: rename encoding/stlcvt to encoding/stl 2025-08-12 09:45:44 +08:00
51d288ac4b test: add testbench for num/safe_cast 2025-08-11 22:19:02 +08:00
20a9ef4166 fix: fix comment for new added files.
- translate zh-CN comment into en-US.
- change some comment into Doxygen style.
- add lost Doxygen comment.
- enrich the testbench for ceil_div.
- add lost metaprogramming functions for some files in macro namespace.
2025-08-11 21:57:42 +08:00
17540072d3 fix: change the find order in PyCodec.
- now PyCodec will try to use Iconv first.
- re-claim the meaning of YYCC_FEAT_ICONV macro.
2025-08-05 14:04:20 +08:00
fcac886f07 refactor: migrate rust-like ops.
- migrate rust-like ops.
- migrate testbench for them but not finished.
2025-08-05 13:53:59 +08:00
27baf2a080 refactor: refactor old IOHelper.
- move pointer left padding macro into single header file.
- move utf8 fopen into single header but not finished.
- add testbench for pointer left padding macro.
- add system pointer size detector according to new migrated features requested.
2025-08-05 10:54:15 +08:00
b9f81c16a0 refactor: refactor enum helper as flag enum.
- refactor enum helper.
- add testbench for it.
2025-08-04 22:31:37 +08:00
54134b342e test: add testbench for macro/versiom_cmp 2025-08-04 21:15:49 +08:00
ce2b411b0b refactor: continue refactor to make the project can be built 2025-07-31 22:25:14 +08:00
5372af79f8 refactor: finish iconv refactor 2025-07-25 11:06:22 +08:00
b79df0c65e refactor: continue refactor project from C++17 to 23 2025-07-25 10:49:07 +08:00
4f0b3d19d1 refactor: update C++ from 17 to 23 2025-07-25 09:35:26 +08:00
f014e54604 feat: update pycodec.
- rename encoding::utf to encoding::stlcvt.
- use uv to manage script and add pycodec generator script.
- update script in modern python.
- fix added pycodec generator.
2025-07-23 16:07:49 +08:00
821a592f02 feat: add various detector.
- add endian and compiler detector, and modify os detector.
- now we use CMake to add detector-used macro, instead of using some C++ features to detect them.
- change Windows environment detection according to the change of os detector.
2025-07-23 10:18:01 +08:00
6043609709 feat: finish windows encoding 2025-07-22 21:52:09 +08:00
53e8a77f47 feat: finish iconv module 2025-07-22 14:15:53 +08:00
6d44c7605b refactor: update encoding namespace
- add all essential functions prototypes in iconv encoding.
- add lost UTF convertion for windows encoding.
2025-07-21 20:36:26 +08:00
c2f6e29c36 feat: finish iconv kernel 2025-07-18 15:57:33 +08:00
c102964703 refactor: write iconv.
- write iconv encoding (not finished).
- rename united_codec to pycodec.
2025-07-15 16:17:59 +08:00
3605151caf refactor: finish Windows encoding namespace.
- finish Windows encoding namespace.
- add std::expected polyfill for help.
2025-07-14 15:06:33 +08:00
fa52d7416f refactor: condense the shared test data of parse and stringify. 2025-07-14 09:43:23 +08:00
e42a3b6e58 refactor: move YYCC_U8 from reinterpret.hpp to string.hpp
- move YYCC_U8 def.
- create shared template in testbench.
2025-07-14 09:13:47 +08:00
cec6091996 refactor: add utf convertion namespace 2025-07-02 10:36:33 +08:00
6e884d865d test: add testbench for patch (starts ends with, and contains) 2025-07-01 14:04:02 +08:00
58ec960e9c refactor: move std patch into correct position 2025-07-01 11:00:09 +08:00
732a560a65 refactor: finish constraint builder and its testbench 2025-06-30 09:33:46 +08:00
3030a67ca3 refactor: re-place files into correct position according to namespace hierarchy 2025-06-30 08:45:18 +08:00
e166dc41ac refactor: finish rust parse and add testbench for it. 2025-06-26 10:27:33 +08:00
a6382d6a22 refactor: add document for some namespaces 2025-06-25 10:40:05 +08:00
adc99274f4 refactor: add testbench for parse and stringify 2025-06-24 11:29:01 +08:00
3abd0969c0 refactor: add Rust infrastructure: Option, Result and panic 2025-06-23 16:22:55 +08:00
28ff7008a8 add parse and stringify 2025-06-22 19:53:49 +08:00
ab8d74efe6 test: add testbench for string module 2025-06-22 17:14:49 +08:00
df3b602110 refactor: start to refactor project 2025-06-20 23:38:34 +08:00
bec36b4b3c fix: fix install path in CMake script.
- replace some CMake variables to our custom variables in install path, however it does nothing because they are equal in default.
2024-12-23 09:30:39 +08:00
0b7e58c8e8 feat: use CMake to generate library version info.
- use CMake to produce YYCC version header when configuring.
2024-11-03 18:52:02 +08:00
831fa130bc feat: improve EnumHelper
- allow multiple enum flags in EnumHelper::Has to check whether given flags contains specified flags.
- update version in CMake script.
2024-11-03 18:06:36 +08:00
7adac00035 doc: fix document about recent changes
- fix build commandline introduction in documentation.
- update build script template.
- only install document in any Release-like build type.
- now testbench will be installed in any Release-like build type, not only Release.
2024-11-03 17:29:34 +08:00
0cd9582757 refactor: refactor project layout
- move header files into an individual directory to prevent the possibility that file name is conflict in Linux include directory.
- update build script generator. use jinja2 template engine to get better view, rather than python code written by hand.
- add version number and version comparation macros in core library.
2024-11-03 14:51:18 +08:00
2206825223 doc: add document for the change of loading function of ConfigManager. 2024-11-02 17:31:19 +08:00
21f7e7f786 feat: add new helper for scoped enum type.
- Add EnumHelper for bitwise operation of scoped enum type (copied from libcmo21)
- Enrich the return value of ConfigManager load function to present more infomation of loading.
- update testbench for new added feature and modification.
- add document for new added feature.
2024-11-02 17:10:55 +08:00
50dd086b53 fix: fix linux build issue 2024-08-27 17:35:57 +08:00
176 changed files with 13941 additions and 4993 deletions

317
.clang-format Normal file
View 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
View File

@ -0,0 +1,3 @@
[*.{cpp,hpp}]
indent_style = space
indent_size = 4

14
.gitignore vendored
View File

@ -1,11 +1,17 @@
# -------------------- Output -------------------- ## ===== Personal =====
# Ignore build resources
out/ out/
build/
install/
# Ignore CMake generated stuff
src/yycc/version.hpp
CMakeSettings.json CMakeSettings.json
# -------------------- VSCode -------------------- ## ===== VSCode =====
.vscode/ .vscode/
# -------------------- CMake -------------------- ## ===== CMake =====
CMakeLists.txt.user CMakeLists.txt.user
CMakeCache.txt CMakeCache.txt
CMakeFiles CMakeFiles
@ -18,7 +24,7 @@ compile_commands.json
CTestTestfile.cmake CTestTestfile.cmake
_deps _deps
# -------------------- Visual Studio -------------------- ## ===== Visual Studio =====
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
## ##

View File

@ -1,13 +1,18 @@
cmake_minimum_required(VERSION 3.23) cmake_minimum_required(VERSION 3.23)
project(YYCC project(YYCC
VERSION 1.2.0 VERSION 2.0.0
LANGUAGES CXX LANGUAGES CXX
) )
# Setup C++ standard
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Provide options # Provide options
option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF) option(YYCC_BUILD_TESTBENCH "Build testbench of YYCCommonplace." OFF)
option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF) option(YYCC_BUILD_DOC "Build document of YYCCommonplace." OFF)
option(YYCC_DEBUG_UE_FILTER "YYCC developer used switch for testing Windows unhandled exception filter. Should not set to ON!!!" OFF) option(YYCC_ENFORCE_ICONV "Enforce iconv support for this library (e.g. in MSYS2 environment)." OFF)
# Setup install path from CMake provided install path for convenient use. # Setup install path from CMake provided install path for convenient use.
include(GNUInstallDirs) include(GNUInstallDirs)
@ -20,6 +25,18 @@ set(YYCC_INSTALL_BIN_PATH ${CMAKE_INSTALL_BINDIR} CACHE PATH
set(YYCC_INSTALL_DOC_PATH ${CMAKE_INSTALL_DOCDIR} CACHE PATH set(YYCC_INSTALL_DOC_PATH ${CMAKE_INSTALL_DOCDIR} CACHE PATH
"Non-arch doc install path relative to CMAKE_INSTALL_PREFIX unless set to an absolute path.") "Non-arch doc install path relative to CMAKE_INSTALL_PREFIX unless set to an absolute path.")
# 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 # Import 3 build targets
add_subdirectory(src) add_subdirectory(src)
if (YYCC_BUILD_TESTBENCH) if (YYCC_BUILD_TESTBENCH)
@ -46,7 +63,7 @@ write_basic_package_version_file(
configure_package_config_file( configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/cmake/YYCCommonplaceConfig.cmake.in ${CMAKE_CURRENT_LIST_DIR}/cmake/YYCCommonplaceConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfig.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/YYCCommonplace INSTALL_DESTINATION ${YYCC_INSTALL_LIB_PATH}/cmake/YYCCommonplace
) )
# Copy package files to install destination # Copy package files to install destination
install( install(
@ -54,6 +71,6 @@ FILES
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfigVersion.cmake" "${CMAKE_CURRENT_BINARY_DIR}/YYCCommonplaceConfigVersion.cmake"
DESTINATION DESTINATION
${CMAKE_INSTALL_LIBDIR}/cmake/YYCCommonplace ${YYCC_INSTALL_LIB_PATH}/cmake/YYCCommonplace
) )

106
COMPILE.md Normal file
View 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.

View File

@ -4,14 +4,9 @@ YYC Commonplace, or YYCCommonplace (abbr. YYCC) is a static library specifically
## Usage ## Usage
For more usage about this library, please build documentation of this project via Doxygen and read it. 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.
And I also highly recommend that you read documentation first before writing with this library.
However, the documentation need CMake to build and you may don't know how to use CMake in this project. So as the alternative, you also can browse the raw Doxygen documentation file: `doc/src/intro.dox` for how to build this project (including documentation) first.
## Build ## Build
This project require at least CMake 3.23 to build. We suggest that you only use stable version (tagged commit). The latest commit may still work in progress and not stable. See [Compile Manual](./COMPILE.md).
See documentation for how to build this project.

View File

@ -14,5 +14,6 @@ add_custom_target (YYCCDocumentation
# Install built documentation # Install built documentation
install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html install (DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html
CONFIGURATIONS Release RelWithDebInfo MinSizeRel
DESTINATION ${YYCC_INSTALL_DOC_PATH} DESTINATION ${YYCC_INSTALL_DOC_PATH}
) )

View File

@ -86,12 +86,66 @@ so it must be initialized after initializing all settings.
When initializing core manager, you need assign config file path first. When initializing core manager, you need assign config file path first.
Then you need specify a version number. Then you need specify a version number.
Version number will be used when reading config file. Version number is important.
If the version of config file is higher than your given number, It will be used when reading config file and only can be increased if needed (version can not downgrade).
core manager will assume you are trying to read a config file created by a higher version program.
Core manager will reject reading and use default value for all settings.
Otherwise, core manager will try to read config file and do proper migration if possible.
The last argument is an initializer list which contain the \b pointer to all settings this manager managed. 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.
*/ */
} }

35
doc/src/enum_helper.dox Normal file
View 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.
*/
}

View File

@ -29,6 +29,9 @@
\li \subpage intro \li \subpage intro
\li \subpage premise_and_principle
<!--
\li \subpage library_macros \li \subpage library_macros
\li \subpage library_encoding \li \subpage library_encoding
@ -45,19 +48,25 @@
\li \subpage std_patch \li \subpage std_patch
\li \subpage enum_helper
-->
<B>Advanced Features</B> <B>Advanced Features</B>
<!--
\li \subpage constraints \li \subpage constraints
\li \subpage config_manager \li \subpage config_manager
\li \subpage arg_parser \li \subpage arg_parser
-->
</TD> </TD>
<TD ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT" VALIGN="TOP">
<B>Windows Specific Features</B> <B>Windows Specific Features</B>
<!--
\li \subpage win_import \li \subpage win_import
\li \subpage com_helper \li \subpage com_helper
@ -67,6 +76,7 @@
\li \subpage win_fct_helper \li \subpage win_fct_helper
\li \subpage exception_helper \li \subpage exception_helper
-->
</TD> </TD>
</TR> </TR>

View File

@ -6,7 +6,7 @@ YYCCommonplace, or YYC Commonplace (abbr. YYCC),
is a static library providing various useful C++ functions is a static library providing various useful C++ functions
when programming with standard library or Windows environment. when programming with standard library or Windows environment.
During the development of a few projects, At the beginning, during the development of a few projects,
I gradually understand how Windows make the compromise with the code written by its old developers, 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. 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. So I create this static library for all of my C++ project.
@ -15,6 +15,16 @@ I can use a clear and easy way to manage these codes.
I can easily fix issues found in project using this library by updating a single project, 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 rather than fixing these duplicated code in each project one by one
because all of them share the same implementations. because all of them share the same implementations.
This is the origin of the 1.x version of YYCC.
After a few years ago, I start to write in Rust and more complicated codes.
I was allured by Rust and hope all these feature Rust holded can be adapted into C++,
so I start to refactor this library in modern way.
However, the compatibility with low C++ standard version is now become the shortcoming of this library.
I was forced to considering the compatibility with C++ 17 and it cause a huge work.
So, after think twice, I decide to drop the support of C++ 17, which the higest C++ standard I can used for Virtools project.
And increase the standard to C++ 23 directly to have better experience.
That's the origin of the 2.x version of YYCC.
This project mainly is served for my personal use. This project mainly is served for my personal use.
But I would be honored if you would like to use this in your project. But I would be honored if you would like to use this in your project.
@ -46,17 +56,18 @@ Thus I can have a similar Linux C++ programming experience on Windows.
The eccentric decision of standard commission also is the reason why I create this library. The eccentric decision of standard commission also is the reason why I create this library.
\li C++ standard commission loves to bring one feature with N concepts and P assistant classes. \li C++ standard commission prefer to provide one function with very fundamental classes and functions
and programmer need to write too much code to achieve a simple work.
\li C++ standard commission seems doesn't want to bring any features the programmer urgent needed. \li C++ standard commission 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 C++ standard commission loves delete programmer loved convenient functions and classes.
\li etc... \li etc...
There is not a proper way to \e format a string in C++ until C++ 20 (\c std::format). 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. String related functions, such as split, lower, higher, replace, now still not be included in standard library.
Programmer loved, easy to used UTF8 procession functions and classes was deprecate now and will be removed in future. Programmer loved, easy to used encoding convertion functions and classes are deprecate now and will be removed in future.
That's why I create this library. That's why I create this library.
I bring these function in this library. I bring these functions in this library.
Not industrial level, but easy to use and have enough performance in my project. Not industrial level, but easy to use and have enough performance in my project.
\subsection intro__why__boost Boost Issues \subsection intro__why__boost Boost Issues
@ -65,7 +76,7 @@ Bosst is a powerful C++ library. But the shortcoming is overt. It's tooooo big.
This drawback will be more obvious considering the bad dependency mechanism of C++. 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. 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. It order you download the whole Boost library and extract it in your hard disk.
Cost more than half hours, 5+ GB disk space and the life time of your disk. Cost more than half hour of your life, 5+ GB disk space and the life time of your disk.
The functions belonging to Boost is industrial level. The functions belonging to Boost is industrial level.
But what I want is not industrial level functions. But what I want is not industrial level functions.
@ -74,6 +85,7 @@ I don't need extreme performance. I just want my code works.
So I create this library, bring some Boost functions with ordinary but not bad implementation. So I create this library, bring some Boost functions with ordinary but not bad implementation.
<!--
\section intro__usage Library Usage \section intro__usage Library Usage
Before using this library, I suggest you read this manual fully to have a full overview of this library. Before using this library, I suggest you read this manual fully to have a full overview of this library.
@ -116,11 +128,14 @@ Due to the different defination of UTF8 char type,
C++ 20 program can not use this library built by C++ 17 environment. 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. 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. The lowest and defult version of C++ standard is 17.
\li \c -d, \c --no-doc: Specify this if you don't want to build documentation. \li \c -d, \c --build-doc: Specify this if you want to build documentation.
End user usually needs documentation, End user usually needs documentation,
however if you are the developer of this library, you may need this switch. 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. Because documentation take too much disk space and cost a bunch of time for building and copying.
In default, generator will produce script which build documentation automatically. 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>. 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>. and you will also find your MSVC distribution in directory <TT>bin/<I>cpp_ver</I>/msvc_install</TT>.
@ -184,5 +199,5 @@ YYCC CMake build script contains a special option called \c YYCC_DEBUG_UE_FILTER
If you set it to true, it will add a public macro \c YYCC_DEBUG_UE_FILTER to YYCC project. 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. 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. So in common use, user should not enable this option.
-->
*/ */

View File

@ -6,6 +6,48 @@ namespace YYCC {
In this page we will introduce the macros defined by this library In this page we will introduce the macros defined by this library
which can not be grouped in other topic. 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 \section library_macros__platform_checker Platform Checker
In many cross platform applications, In many cross platform applications,
@ -31,7 +73,7 @@ Assume \c blabla() function is Windows specific.
We have following example code: We have following example code:
\code \code
#if YYCC_OS == YYCC_OS_WINDOWS #if defined(YYCC_OS_WINDOWS)
blabla(); blabla();
#endif #endif
\endcode \endcode

View 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.
*/

View File

@ -71,7 +71,7 @@ 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. 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. Then, you can pass the created #JoinDataProvider object to #Join function.
And specify decilmer at the same time. And specify delimiter at the same time.
Then you can get the final joined string. Then you can get the final joined string.
There is an example: There is an example:
@ -88,7 +88,7 @@ auto joined_string = YYCC::StringHelper::Join(
++iter; ++iter;
return true; return true;
}, },
decilmer delimiter
); );
\endcode \endcode
@ -105,7 +105,7 @@ Otherwise this overload will throw template error.
std::vector<yycc_u8string> data { std::vector<yycc_u8string> data {
YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("") YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")
}; };
auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), decilmer); auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), delimiter);
\endcode \endcode
\section string_helper__lower_upper Lower Upper \section string_helper__lower_upper Lower Upper
@ -134,14 +134,14 @@ std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view&, const yycc_
\endcode \endcode
All these overloads take a string view as the first argument representing the string need to be split. 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 decilmer for splitting. 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 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 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, 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. 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. 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 decilmer is empty, 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. 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. There is no way that these methods return an empty list, except the code is buggy.

View File

@ -11,7 +11,7 @@ Due to legacy reason, Windows defines various things which are not compatible wi
YYCC has a way to solve the issue introduced above. YYCC has a way to solve the issue introduced above.
\code \code
#if YYCC_OS == YYCC_OS_WINDOWS #if defined(YYCC_OS_WINDOWS)
#include <WinImportPrefix.hpp> #include <WinImportPrefix.hpp>
#include <Windows.h> #include <Windows.h>
#include "other_header_depend_on_windows.h" #include "other_header_depend_on_windows.h"

216
script/.gitignore vendored
View File

@ -1,2 +1,216 @@
# -------------------- Output -------------------- ## ===== Myself =====
# Exclude VSCode
.vscode/
# Exclude generated files
win_build.bat 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__/

106
script/gen_build_script.py Normal file
View File

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

View File

@ -1,170 +0,0 @@
import argparse
import os
import io
import re
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: io.TextIOWrapper, 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_argument(arg):
if not arg or re.search(r'(["\s])', arg):
arg = '"' + arg.replace('"', r'\"') + '"'
return escape_for_cmd_exe(arg)
def escape_for_cmd_exe(arg):
meta_re = re.compile(r'([()%!^"<>&|])')
return meta_re.sub('^\1', arg)
class ScriptSettings:
m_CppVersion: str
m_NoDoc: bool
def __init__(self, cpp_ver: str, no_doc: bool):
self.m_CppVersion = cpp_ver
self.m_NoDoc = no_doc
def script_head(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# change directory to root folder
write_line(f, ':: Navigate to project root directory')
root_dir: str = os.path.dirname(os.path.dirname(__file__))
write_line(f, f'CD /d {escape_argument(root_dir)}')
# create build directory and enter
write_line(f, ':: Create build directory and enter it')
write_line(f, 'MKDIR bin')
write_line(f, 'CD bin')
cpp_dir: str = f'cpp{s.m_CppVersion}'
write_line(f, f'MKDIR {cpp_dir}')
write_line(f, f'CD {cpp_dir}')
# blank line
write_line(f, '')
def script_tail(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# leave build directory and report success
write_line(f, ':: Leave build directory and report')
write_line(f, 'CD ..\\..')
write_line(f, 'ECHO Windows CMake Build Done')
def create_directory(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# create build directory
write_line(f, ':: Create internal build directory')
write_line(f, 'MKDIR Win32')
write_line(f, 'MKDIR x64')
write_line(f, 'MKDIR documentation')
# create install directory
write_line(f, ':: Create internal install directory')
write_line(f, 'MKDIR install')
write_line(f, 'CD install')
write_line(f, 'MKDIR Win32_Debug')
write_line(f, 'MKDIR Win32_Release')
write_line(f, 'MKDIR x64_Debug')
write_line(f, 'MKDIR x64_Release')
write_line(f, 'CD ..')
# create msvc install directory
write_line(f, ':: Create internal MSVC specific install directory')
write_line(f, 'MKDIR msvc_install')
write_line(f, 'CD msvc_install')
write_line(f, 'MKDIR bin')
write_line(f, 'MKDIR include')
write_line(f, 'MKDIR lib')
write_line(f, 'MKDIR share')
write_line(f, 'CD bin')
write_line(f, 'MKDIR Win32')
write_line(f, 'MKDIR x64')
write_line(f, 'CD ..')
write_line(f, 'CD lib')
write_line(f, 'MKDIR Win32\\Debug')
write_line(f, 'MKDIR Win32\\Release')
write_line(f, 'MKDIR x64\\Debug')
write_line(f, 'MKDIR x64\\Release')
write_line(f, 'CD ..')
write_line(f, 'CD ..')
# blank line
write_line(f, '')
def cmake_build(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# build for Win32
write_line(f, ':: Build for Win32')
write_line(f, 'CD Win32')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_TESTBENCH=ON ../../..')
write_line(f, 'cmake --build . --config Debug')
write_line(f, 'cmake --install . --prefix=../install/Win32_Debug --config Debug')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --install . --prefix=../install/Win32_Release --config Release')
write_line(f, 'CD ..')
# build for x64
write_line(f, ':: Build for x64')
write_line(f, 'CD x64')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_TESTBENCH=ON ../../..')
write_line(f, 'cmake --build . --config Debug')
write_line(f, 'cmake --install . --prefix=../install/x64_Debug --config Debug')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --install . --prefix=../install/x64_Release --config Release')
write_line(f, 'CD ..')
# build for documentation
if not s.m_NoDoc:
write_line(f, ':: Build for documentation')
write_line(f, 'CD documentation')
write_line(f, f'cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_CXX_STANDARD={s.m_CppVersion} -DYYCC_BUILD_DOC=ON ../../..')
write_line(f, 'cmake --build . --config Release')
write_line(f, 'cmake --build . --target YYCCDocumentation')
write_line(f, 'cmake --install . --prefix=../install/x64_Release --config Release')
write_line(f, 'CD ..')
# blank line
write_line(f, '')
def msvc_build(f: io.TextIOWrapper, s: ScriptSettings) -> None:
# copy include from x64_Release build
write_line(f, ':: Copy header files')
write_line(f, 'XCOPY install\\x64_Release\\include msvc_install\\include\\ /E /Y')
# copy binary testbench
write_line(f, ':: Copy binary files')
write_line(f, 'COPY install\\Win32_Release\\bin\\YYCCTestbench.exe msvc_install\\bin\\Win32\\YYCCTestbench.exe /Y')
write_line(f, 'COPY install\\x64_Release\\bin\\YYCCTestbench.exe msvc_install\\bin\\x64\\YYCCTestbench.exe /Y')
# copy static library
write_line(f, ':: Copy library files')
write_line(f, 'COPY install\\Win32_Debug\\lib\\YYCCommonplace.lib msvc_install\\lib\\Win32\\Debug\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\Win32_Release\\lib\\YYCCommonplace.lib msvc_install\\lib\\Win32\\Release\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\x64_Debug\\lib\\YYCCommonplace.lib msvc_install\\lib\\x64\\Debug\\YYCCommonplace.lib /Y')
write_line(f, 'COPY install\\x64_Release\\lib\\YYCCommonplace.lib msvc_install\\lib\\x64\\Release\\YYCCommonplace.lib /Y')
# Copy document from x64_Release build
if not s.m_NoDoc:
write_line(f, ':: Copy documentation files')
write_line(f, 'XCOPY install\\x64_Release\\share msvc_install\\share\\ /E /Y')
# blank line
write_line(f, '')
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', '--no-doc',
action='store_true', dest='no_doc',
help='Build YYCC without documentation.'
)
args = parser.parse_args()
# build settings
settings = ScriptSettings(args.cpp, args.no_doc)
# write result
filepath = os.path.join(os.path.dirname(__file__), 'win_build.bat')
with open(filepath, 'w') as f:
write_line(f, '@ECHO OFF')
script_head(f, settings)
create_directory(f, settings)
cmake_build(f, settings)
msvc_build(f, settings)
script_tail(f, settings)

View File

@ -1,32 +0,0 @@
#!/bin/bash
README_PATH=$(pwd)/README.md
if [ ! -f "$README_PATH" ]; then
echo "Error: You must run this script at the root folder of this project!"
exit
fi
# Create main binary directory
mkdir bin
cd bin
# Create build directory
mkdir build
# Create install directory
mkdir install
cd install
mkdir Debug
mkdir Release
cd ..
# Build current system debug and release version
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ../.. --fresh
cmake --build .
cmake --install . --prefix ../install/Debug
cmake -DCMAKE_BUILD_TYPE=Release -DYYCC_BUILD_TESTBENCH=ON ../.. --fresh
cmake --build .
cmake --install . --prefix ../install/Release
cd ..
# Exit to original path
cd ..
echo "Linux CMake Build Done"

View File

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

2
script/pycodec/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Exclude result
*.cpp

7
script/pycodec/README.md Normal file
View 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.

View 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)

View 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 -%}
};

View 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
1 ascii 437 ASCII 646 us-ascii
2 big5 950 BIG5 big5-tw csbig5
3 big5hkscs BIG5-HKSCS big5-hkscs hkscs
4 cp037 037 IBM037 IBM039
5 cp273 273 IBM273 csIBM273
6 cp424 EBCDIC-CP-HE IBM424
7 cp437 437 437 IBM437
8 cp500 500 EBCDIC-CP-BE EBCDIC-CP-CH IBM500
9 cp720 720
10 cp737 737
11 cp775 775 IBM775
12 cp850 850 CP850 850 IBM850
13 cp852 852 852 IBM852
14 cp855 855 855 IBM855
15 cp856
16 cp857 857 857 IBM857
17 cp858 858 858 IBM858
18 cp860 860 860 IBM860
19 cp861 861 861 CP-IS IBM861
20 cp862 862 CP862 862 IBM862
21 cp863 863 863 IBM863
22 cp864 864 IBM864
23 cp865 865 865 IBM865
24 cp866 866 CP866 866 IBM866
25 cp869 869 869 CP-GR IBM869
26 cp874 874 CP874
27 cp875 875
28 cp932 932 CP932 932 ms932 mskanji ms-kanji windows-31j
29 cp949 949 CP949 949 ms949 uhc
30 cp950 950 CP950 950 ms950
31 cp1006
32 cp1026 1026 ibm1026
33 cp1125 1125 ibm1125 cp866u ruscii
34 cp1140 1140 ibm1140
35 cp1250 1250 CP1250 windows-1250
36 cp1251 1251 CP1251 windows-1251
37 cp1252 1252 CP1252 windows-1252
38 cp1253 1253 CP1253 windows-1253
39 cp1254 1254 CP1254 windows-1254
40 cp1255 1255 CP1255 windows-1255
41 cp1256 1256 CP1256 windows-1256
42 cp1257 1257 CP1257 windows-1257
43 cp1258 1258 CP1258 windows-1258
44 euc_jp 20932 EUC-JP eucjp ujis u-jis
45 euc_jis_2004 jisx0213 eucjis2004
46 euc_jisx0213 eucjisx0213
47 euc_kr 51949 EUC-KR euckr korean ksc5601 ks_c-5601 ks_c-5601-1987 ksx1001 ks_x-1001
48 gb2312 936 CP936 chinese csiso58gb231280 euc-cn euccn eucgb2312-cn gb2312-1980 gb2312-80 iso-ir-58
49 gbk 936 GBK 936 cp936 ms936
50 gb18030 54936 GB18030 gb18030-2000
51 hz 52936 HZ hzgb hz-gb hz-gb-2312
52 iso2022_jp 50220 ISO-2022-JP csiso2022jp iso2022jp iso-2022-jp
53 iso2022_jp_1 ISO-2022-JP-1 iso2022jp-1 iso-2022-jp-1
54 iso2022_jp_2 ISO-2022-JP-2 iso2022jp-2 iso-2022-jp-2
55 iso2022_jp_2004 iso2022jp-2004 iso-2022-jp-2004
56 iso2022_jp_3 iso2022jp-3 iso-2022-jp-3
57 iso2022_jp_ext iso2022jp-ext iso-2022-jp-ext
58 iso2022_kr 50225 ISO-2022-KR csiso2022kr iso2022kr iso-2022-kr
59 latin_1 28591 ISO-8859-1 iso-8859-1 iso8859-1 8859 cp819 latin latin1 L1
60 iso8859_2 28592 ISO-8859-2 iso-8859-2 latin2 L2
61 iso8859_3 28593 ISO-8859-3 iso-8859-3 latin3 L3
62 iso8859_4 28594 ISO-8859-4 iso-8859-4 latin4 L4
63 iso8859_5 28595 ISO-8859-5 iso-8859-5 cyrillic
64 iso8859_6 28596 ISO-8859-6 iso-8859-6 arabic
65 iso8859_7 28597 ISO-8859-7 iso-8859-7 greek greek8
66 iso8859_8 28598 ISO-8859-8 iso-8859-8 hebrew
67 iso8859_9 28599 ISO-8859-9 iso-8859-9 latin5 L5
68 iso8859_10 ISO-8859-10 iso-8859-10 latin6 L6
69 iso8859_11 ISO-8859-11 iso-8859-11 thai
70 iso8859_13 28603 ISO-8859-13 iso-8859-13 latin7 L7
71 iso8859_14 ISO-8859-14 iso-8859-14 latin8 L8
72 iso8859_15 28605 ISO-8859-15 iso-8859-15 latin9 L9
73 iso8859_16 ISO-8859-16 iso-8859-16 latin10 L10
74 johab 1361 JOHAB cp1361 ms1361
75 koi8_r
76 koi8_t KOI8-T
77 koi8_u
78 kz1048 kz_1048 strk1048_2002 rk1048
79 mac_cyrillic 10007 MacCyrillic maccyrillic
80 mac_greek 10006 MacGreek macgreek
81 mac_iceland 10079 MacIceland maciceland
82 mac_latin2 maclatin2 maccentraleurope mac_centeuro
83 mac_roman MacRoman macroman macintosh
84 mac_turkish 10081 MacTurkish macturkish
85 ptcp154 PT154 csptcp154 pt154 cp154 cyrillic-asian
86 shift_jis 932 SHIFT_JIS csshiftjis shiftjis sjis s_jis
87 shift_jis_2004 shiftjis2004 sjis_2004 sjis2004
88 shift_jisx0213 shiftjisx0213 sjisx0213 s_jisx0213
89 utf_32 UTF-32 U32 utf32
90 utf_32_be UTF-32BE UTF-32BE
91 utf_32_le UTF-32LE UTF-32LE
92 utf_16 UTF16 U16 utf16
93 utf_16_be UTF-16BE UTF-16BE
94 utf_16_le UTF-16LE UTF-16LE
95 utf_7 65000 UTF-7 U7 unicode-1-1-utf-7
96 utf_8 65001 UTF-8 U8 UTF utf8 utf-8 cp65001
97 utf_8_sig

7
script/pyproject.toml Normal file
View 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
View 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" }]

View File

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

View File

@ -1,57 +0,0 @@
@ECHO OFF
:: Check environment
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 main binary directory
MKDIR bin
CD bin
:: Create build folder
MKDIR Win32
MKDIR x64
MKDIR documentation
:: Create install folder
MKDIR install
CD install
MKDIR Win32_Debug
MKDIR Win32_Release
MKDIR x64_Debug
MKDIR x64_Release
CD ..
:: 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/Win32_Debug --config Debug
cmake --build . --config Release
cmake --install . --prefix=../install/Win32_Release --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/x64_Debug --config Debug
cmake --build . --config Release
cmake --install . --prefix=../install/x64_Release --config Release
CD ..
:: Build for documentation
IF NOT "%1"=="NODOC" (
CD documentation
cmake -G "Visual Studio 16 2019" -A x64 -DYYCC_BUILD_DOC=ON ../..
cmake --build . --config Release
cmake --build . --target YYCCDocumentation
cmake --install . --prefix=../install/x64_Release --config Release
CD ..
)
:: Exit to original path
CD ..
ECHO Windows CMake Build Done

View File

@ -1,52 +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
)
:: Enter main binary directory
CD bin
:: Create MSVC binary directory
MKDIR msvc_install
CD msvc_install
:: Create direcotries tree
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 ..
:: Exit MSVC binary directory
CD ..
:: Copy result
:: Copy include from x64_Release build
XCOPY install\x64_Release\include msvc_install\include\ /E /Y
:: Copy document from x64_Release build
IF NOT "%1"=="NODOC" (
XCOPY install\x64_Release\share msvc_install\share\ /E /Y
)
:: Copy binary testbench
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 static library
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
:: Exit to original path
CD ..
ECHO Windows MSVC Build Done

View File

@ -1,48 +1,100 @@
# Configure version file
configure_file(
${CMAKE_CURRENT_LIST_DIR}/yycc/version.hpp.in
${CMAKE_CURRENT_LIST_DIR}/yycc/version.hpp
@ONLY
)
# Create static library # Create static library
add_library(YYCCommonplace STATIC "") add_library(YYCCommonplace STATIC "")
# Setup static library sources # Setup static library sources
target_sources(YYCCommonplace target_sources(YYCCommonplace
PRIVATE PRIVATE
# Sources # Sources
COMHelper.cpp yycc/string/reinterpret.cpp
ArgParser.cpp yycc/string/op.cpp
ConfigManager.cpp yycc/patch/fopen.cpp
ConsoleHelper.cpp yycc/patch/stream.cpp
DialogHelper.cpp yycc/rust/panic.cpp
EncodingHelper.cpp yycc/rust/env.cpp
ExceptionHelper.cpp yycc/windows/com.cpp
StdPatch.cpp yycc/windows/dialog.cpp
IOHelper.cpp yycc/windows/winfct.cpp
StringHelper.cpp yycc/windows/console.cpp
WinFctHelper.cpp yycc/encoding/stl.cpp
# Natvis (only for MSVC) yycc/encoding/windows.cpp
$<$<CXX_COMPILER_ID:MSVC>:YYCC.natvis> yycc/encoding/iconv.cpp
yycc/carton/pycodec.cpp
yycc/carton/termcolor.cpp
yycc/carton/wcwidth.cpp
yycc/carton/tabulate.cpp
yycc/carton/ironpad.cpp
yycc/carton/csconsole.cpp
yycc/carton/clap/option.cpp
yycc/carton/clap/variable.cpp
yycc/carton/clap/summary.cpp
yycc/carton/clap/application.cpp
yycc/carton/clap/manual.cpp
) )
target_sources(YYCCommonplace target_sources(YYCCommonplace
PUBLIC PUBLIC
FILE_SET HEADERS FILE_SET HEADERS
FILES FILES
# Headers # Headers
# Common headers yycc.hpp
Constraints.hpp yycc/version.hpp
COMHelper.hpp yycc/macro/version_cmp.hpp
ArgParser.hpp yycc/macro/os_detector.hpp
ConfigManager.hpp yycc/macro/stl_detector.hpp
ConsoleHelper.hpp yycc/macro/endian_detector.hpp
DialogHelper.hpp yycc/macro/compiler_detector.hpp
EncodingHelper.hpp yycc/macro/ptr_size_detector.hpp
ExceptionHelper.hpp yycc/macro/class_copy_move.hpp
StdPatch.hpp yycc/macro/printf_checker.hpp
IOHelper.hpp yycc/flag_enum.hpp
ParserHelper.hpp yycc/string/reinterpret.hpp
StringHelper.hpp yycc/string/op.hpp
WinFctHelper.hpp yycc/patch/ptr_pad.hpp
# Windows including guard pair yycc/patch/fopen.hpp
WinImportPrefix.hpp yycc/patch/stream.hpp
WinImportSuffix.hpp yycc/patch/format.hpp
# Misc yycc/num/parse.hpp
YYCCInternal.hpp yycc/num/stringify.hpp
YYCCommonplace.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/windows/console.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
yycc/carton/ironpad.hpp
yycc/carton/csconsole.hpp
yycc/carton/clap.hpp
yycc/carton/clap/types.hpp
yycc/carton/clap/option.hpp
yycc/carton/clap/variable.hpp
yycc/carton/clap/summary.hpp
yycc/carton/clap/application.hpp
yycc/carton/clap/manual.hpp
) )
# Setup header infomations # Setup header infomations
target_include_directories(YYCCommonplace target_include_directories(YYCCommonplace
@ -50,30 +102,70 @@ PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>" "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" "$<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 # Link with DbgHelp.lib on Windows
target_link_libraries(YYCCommonplace target_link_libraries(YYCCommonplace
PRIVATE PRIVATE
$<$<BOOL:${WIN32}>:DbgHelp.lib> $<$<BOOL:${WIN32}>:DbgHelp.lib>
) )
# Setup C++ standard
target_compile_features(YYCCommonplace PUBLIC cxx_std_17)
set_target_properties(YYCCommonplace PROPERTIES CXX_EXTENSION OFF)
# Setup macros # Setup macros
target_compile_definitions(YYCCommonplace target_compile_definitions(YYCCommonplace
# Debug macro should populate to child projects
PUBLIC PUBLIC
$<$<BOOL:${YYCC_DEBUG_UE_FILTER}>:YYCC_DEBUG_UE_FILTER> # Iconv environment macro
# Unicode charset for private using $<$<BOOL:${Iconv_FOUND}>:YYCC_FEAT_ICONV>
PRIVATE # 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>
$<$<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>
) )
target_compile_options(YYCCommonplace target_compile_options(YYCCommonplace
# Order build as UTF-8 in MSVC PUBLIC
PRIVATE # Order build as UTF-8 in MSVC
$<$<CXX_COMPILER_ID:MSVC>:/utf-8> $<$<CXX_COMPILER_ID:MSVC>:/utf-8>
# Order preprocessor conformance mode (fix __VA_OPT__ error in MSVC)
$<$<CXX_COMPILER_ID:MSVC>:/Zc:preprocessor>
# Resolve MSVC __cplusplus macro value error.
$<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>
) )
# Fix GCC std::stacktrace link error
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
target_link_libraries(YYCCommonplace PRIVATE stdc++exp)
else ()
target_link_libraries(YYCCommonplace PRIVATE stdc++_libbacktrace)
endif ()
endif ()
# Install binary and headers # Install binary and headers
install(TARGETS YYCCommonplace install(TARGETS YYCCommonplace
EXPORT YYCCommonplaceTargets EXPORT YYCCommonplaceTargets

View File

@ -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

View File

@ -1,71 +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 Windows COM related types and checker.
* @details
* This namespace is Windows specific.
* In other platforms, this whole namespace will be unavailable.
*
* See also \ref com_helper.
*/
namespace YYCC::COMHelper {
/// @brief C++ standard deleter for every COM interfaces inheriting IUnknown.
class ComPtrDeleter {
public:
ComPtrDeleter() {}
void operator() (IUnknown* com_ptr) {
if (com_ptr != nullptr) {
com_ptr->Release();
}
}
};
/// @brief Smart unique pointer of \c IFileDialog
using SmartIFileDialog = std::unique_ptr<IFileDialog, ComPtrDeleter>;
/// @brief Smart unique pointer of \c IFileOpenDialog
using SmartIFileOpenDialog = std::unique_ptr<IFileOpenDialog, ComPtrDeleter>;
/// @brief Smart unique pointer of \c IShellItem
using SmartIShellItem = std::unique_ptr<IShellItem, ComPtrDeleter>;
/// @brief Smart unique pointer of \c IShellItemArray
using SmartIShellItemArray = std::unique_ptr<IShellItemArray, ComPtrDeleter>;
/// @brief Smart unique pointer of \c IShellFolder
using SmartIShellFolder = std::unique_ptr<IShellFolder, ComPtrDeleter>;
/// @brief C++ standard deleter for almost raw pointer used in COM which need to be free by CoTaskMemFree()
class CoTaskMemDeleter {
public:
CoTaskMemDeleter() {}
void operator() (void* com_ptr) {
if (com_ptr != nullptr) {
CoTaskMemFree(com_ptr);
}
}
};
/// @brief Smart unique pointer of COM created \c WCHAR sequence.
using SmartLPWSTR = std::unique_ptr<std::remove_pointer_t<LPWSTR>, CoTaskMemDeleter>;
/**
* @brief Check whether COM environment has been initialized.
* @return True if it is, otherwise false.
* @remarks
* This function will call corresponding function of COM Guard.
* Do not remove this function and you must preserve at least one reference to this function in final program.
* Some compiler will try to drop COM Guard in final program if no reference to it and it will cause the initialization of COM environment failed.
* This is the reason why I order you do the things said above.
*/
bool IsInitialized();
}
#endif

View File

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

View File

@ -1,163 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <cstdio>
#include <string>
/**
* @brief The helper providing universal C\# style console function and other console related stuff
* @details
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
*/
namespace YYCC::ConsoleHelper {
/// @brief The head of ASCII escape code of black color.
#define YYCC_COLORHDR_BLACK "\033[30m"
/// @brief The head of ASCII escape code of red color.
#define YYCC_COLORHDR_RED "\033[31m"
/// @brief The head of ASCII escape code of green color.
#define YYCC_COLORHDR_GREEN "\033[32m"
/// @brief The head of ASCII escape code of yellow color.
#define YYCC_COLORHDR_YELLOW "\033[33m"
/// @brief The head of ASCII escape code of blue color.
#define YYCC_COLORHDR_BLUE "\033[34m"
/// @brief The head of ASCII escape code of magenta color.
#define YYCC_COLORHDR_MAGENTA "\033[35m"
/// @brief The head of ASCII escape code of cyan color.
#define YYCC_COLORHDR_CYAN "\033[36m"
/// @brief The head of ASCII escape code of white color.
#define YYCC_COLORHDR_WHITE "\033[37m"
/// @brief The head of ASCII escape code of light black color.
#define YYCC_COLORHDR_LIGHT_BLACK "\033[90m"
/// @brief The head of ASCII escape code of light red color.
#define YYCC_COLORHDR_LIGHT_RED "\033[91m"
/// @brief The head of ASCII escape code of light green color.
#define YYCC_COLORHDR_LIGHT_GREEN "\033[92m"
/// @brief The head of ASCII escape code of light yellow color.
#define YYCC_COLORHDR_LIGHT_YELLOW "\033[93m"
/// @brief The head of ASCII escape code of light blue color.
#define YYCC_COLORHDR_LIGHT_BLUE "\033[94m"
/// @brief The head of ASCII escape code of light magenta color.
#define YYCC_COLORHDR_LIGHT_MAGENTA "\033[95m"
/// @brief The head of ASCII escape code of light cyan color.
#define YYCC_COLORHDR_LIGHT_CYAN "\033[96m"
/// @brief The head of ASCII escape code of light white color.
#define YYCC_COLORHDR_LIGHT_WHITE "\033[97m"
/// @brief The tail of ASCII escape code of every color.
#define YYCC_COLORTAIL "\033[0m"
/// @brief The ASCII escape code pair of black color.
#define YYCC_COLOR_BLACK(T) "\033[30m" T "\033[0m"
/// @brief The ASCII escape code pair of red color.
#define YYCC_COLOR_RED(T) "\033[31m" T "\033[0m"
/// @brief The ASCII escape code pair of green color.
#define YYCC_COLOR_GREEN(T) "\033[32m" T "\033[0m"
/// @brief The ASCII escape code pair of yellow color.
#define YYCC_COLOR_YELLOW(T) "\033[33m" T "\033[0m"
/// @brief The ASCII escape code pair of blue color.
#define YYCC_COLOR_BLUE(T) "\033[34m" T "\033[0m"
/// @brief The ASCII escape code pair of magenta color.
#define YYCC_COLOR_MAGENTA(T) "\033[35m" T "\033[0m"
/// @brief The ASCII escape code pair of cyan color.
#define YYCC_COLOR_CYAN(T) "\033[36m" T "\033[0m"
/// @brief The ASCII escape code pair of white color.
#define YYCC_COLOR_WHITE(T) "\033[37m" T "\033[0m"
/// @brief The ASCII escape code pair of light black color.
#define YYCC_COLOR_LIGHT_BLACK(T) "\033[90m" T "\033[0m"
/// @brief The ASCII escape code pair of light red color.
#define YYCC_COLOR_LIGHT_RED(T) "\033[91m" T "\033[0m"
/// @brief The ASCII escape code pair of light green color.
#define YYCC_COLOR_LIGHT_GREEN(T) "\033[92m" T "\033[0m"
/// @brief The ASCII escape code pair of light yellow color.
#define YYCC_COLOR_LIGHT_YELLOW(T) "\033[93m" T "\033[0m"
/// @brief The ASCII escape code pair of light blue color.
#define YYCC_COLOR_LIGHT_BLUE(T) "\033[94m" T "\033[0m"
/// @brief The ASCII escape code pair of light magenta color.
#define YYCC_COLOR_LIGHT_MAGENTA(T) "\033[95m" T "\033[0m"
/// @brief The ASCII escape code pair of light cyan color.
#define YYCC_COLOR_LIGHT_CYAN(T) "\033[96m" T "\033[0m"
/// @brief The ASCII escape code pair of light white color.
#define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m"
/**
* @brief Enable console color support for Windows.
* @details This actually is enable virtual console feature for \c stdout and \c stderr.
* @return True if success, otherwise false.
* @remarks
* This function only works on Windows and do nothing on other platforms such as Linux,
* because we assume all terminals existing on other platform support color feature as default.
*/
bool EnableColorfulConsole();
/**
* @brief Reads the next line of UTF8 characters from the standard input stream.
* @return
* The next line of UTF8 characters from the input stream.
* Empty string if user just press Enter key or function failed.
*/
yycc_u8string ReadLine();
/**
* @brief
* Writes the text representation of the specified object
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void Format(const yycc_char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void FormatLine(const yycc_char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void Write(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void WriteLine(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the text representation of the specified object
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void ErrFormat(const yycc_char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ErrWrite(const yycc_char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ErrWriteLine(const yycc_char8_t* u8_strl);
}

View File

@ -1,85 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <functional>
#include <stdexcept>
#include <set>
#include <initializer_list>
/**
* @brief The namespace containing constraint declaration
* and functions generating common used constraint.
*/
namespace YYCC::Constraints {
/**
* @brief The constraint applied to settings to limit its stored value.
* @tparam _Ty The data type this constraint need to be processed with.
*/
template<typename _Ty>
struct Constraint {
/// @brief Return true if value is legal, otherwise false.
using CheckFct_t = std::function<bool(const _Ty&)>;
/// @brief The function pointer used for checking whether given value is valid.
CheckFct_t m_CheckFct;
/**
* @brief Check whether this constraint is valid for using.
* @return
* True if this constraint is valid, otherwise false.
* If this function return false, it means that there is no contraint.
* And you should not use this constraint provided any function pointer.
*/
bool IsValid() const {
return m_CheckFct != nullptr;
}
};
/**
* @brief Get constraint for arithmetic or enum values by minimum and maximum value range.
* @tparam _Ty An arithmetic or enum type (except bool) of underlying stored value.
* @param[in] min_value The minimum value of range (inclusive).
* @param[in] max_value The maximum value of range (inclusive).
* @return The generated constraint instance which can be directly applied.
*/
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
Constraint<_Ty> GetMinMaxRangeConstraint(_Ty min_value, _Ty max_value) {
if (min_value > max_value)
throw std::invalid_argument("invalid min max value for NumberRangeConstraint");
return Constraint<_Ty> {
[min_value, max_value](const _Ty& val) -> bool { return (val <= max_value) && (val >= min_value); }
/*[min_value, max_value](const _Ty& val) -> _Ty { return std::clamp(val, min_value, max_value); }*/
};
}
/**
* @brief Get constraint for enum values by enumerating all possible values.
* @tparam _Ty An enum type (except bool) of underlying stored value.
* @param[in] il An initializer list storing all possible values.
* @return The generated constraint instance which can be directly applied.
*/
template<typename _Ty, std::enable_if_t<std::is_enum_v<_Ty>, int> = 0>
Constraint<_Ty> GetEnumEnumerationConstraint(const std::initializer_list<_Ty>& il) {
std::set<_Ty> data(il);
return Constraint<_Ty> {
[data](const _Ty& val) -> bool { return data.find(val) != data.end(); }
};
}
/**
* @brief Get constraint for string values by enumerating all possible values.
* @param[in] il An initializer list storing all possible values.
* @return The generated constraint instance which can be directly applied.
* @remarks
* Caller must make sure that the string view passed in initializer list is valid until this Constraint life time gone.
* Becasue this generator will not copy your given string view into string.
*/
inline Constraint<yycc_u8string> GetStringEnumerationConstraint(const std::initializer_list<yycc_u8string_view>& il) {
std::set<yycc_u8string_view> data(il);
return Constraint<yycc_u8string> {
[data](const yycc_u8string& val) -> bool { return data.find(yycc_u8string_view(val)) != data.end(); }
};
}
}

View File

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

View File

@ -1,312 +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"
/**
* @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

View File

@ -1,409 +0,0 @@
#include "EncodingHelper.hpp"
#include <locale>
namespace YYCC::EncodingHelper {
#pragma region UTF8 Ordinary Convertion
const yycc_char8_t* ToUTF8(const char* src) {
return reinterpret_cast<const yycc_char8_t*>(src);
}
yycc_char8_t* ToUTF8(char* src) {
return reinterpret_cast<yycc_char8_t*>(src);
}
yycc_u8string ToUTF8(const std::string_view& src) {
return yycc_u8string(reinterpret_cast<const yycc_char8_t*>(src.data()), src.size());
}
yycc_u8string_view ToUTF8View(const std::string_view& src) {
return yycc_u8string_view(reinterpret_cast<const yycc_char8_t*>(src.data()), src.size());
}
const char* ToOrdinary(const yycc_char8_t* src) {
return reinterpret_cast<const char*>(src);
}
char* ToOrdinary(yycc_char8_t* src) {
return reinterpret_cast<char*>(src);
}
std::string ToOrdinary(const yycc_u8string_view& src) {
return std::string(reinterpret_cast<const char*>(src.data()), src.size());
}
std::string_view ToOrdinaryView(const yycc_u8string_view& src) {
return std::string_view(reinterpret_cast<const char*>(src.data()), src.size());
}
#pragma endregion
/* Define some assistant macros for easy writing. */
#define CONVFCT_TYPE2(fct_name, src_char_type, dst_char_type, ...) if (src == nullptr) return false; \
std::basic_string_view<src_char_type> cache(src); \
return fct_name(cache, dst, ##__VA_ARGS__);
#define CONVFCT_TYPE3(fct_name, src_char_type, dst_char_type, ...) std::basic_string<dst_char_type> ret; \
if (!fct_name(src, ret, ##__VA_ARGS__)) ret.clear(); \
return ret;
#define CONVFCT_TYPE4(fct_name, src_char_type, dst_char_type, ...) std::basic_string<dst_char_type> ret; \
if (!fct_name(src, ret, ##__VA_ARGS__)) ret.clear(); \
return ret;
#if YYCC_OS == YYCC_OS_WINDOWS
#pragma region WcharToChar
bool WcharToChar(const std::wstring_view& src, std::string& dst, UINT code_page) {
// if src is empty, direct output
if (src.empty()) {
dst.clear();
return true;
}
// 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 false;
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 false;
// 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 false;
return true;
}
bool WcharToChar(const wchar_t* src, std::string& dst, UINT code_page) {
CONVFCT_TYPE2(WcharToChar, wchar_t, char, code_page);
}
std::string WcharToChar(const std::wstring_view& src, UINT code_page) {
CONVFCT_TYPE3(WcharToChar, wchar_t, char, code_page);
}
std::string WcharToChar(const wchar_t* src, UINT code_page) {
CONVFCT_TYPE4(WcharToChar, wchar_t, char, code_page);
}
#pragma endregion
#pragma region CharToWchar
bool CharToWchar(const std::string_view& src, std::wstring& dst, UINT code_page) {
// if src is empty, direct output
if (src.empty()) {
dst.clear();
return true;
}
// 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 false;
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 false;
// 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 false;
return true;
}
bool CharToWchar(const char* src, std::wstring& dst, UINT code_page) {
CONVFCT_TYPE2(CharToWchar, char, wchar_t, code_page);
}
std::wstring CharToWchar(const std::string_view& src, UINT code_page) {
CONVFCT_TYPE3(CharToWchar, char, wchar_t, code_page);
}
std::wstring CharToWchar(const char* src, UINT code_page) {
CONVFCT_TYPE4(CharToWchar, char, wchar_t, code_page);
}
#pragma endregion
#pragma region CharToChar
bool CharToChar(const std::string_view& src, std::string& dst, UINT src_code_page, UINT dst_code_page) {
std::wstring intermediary;
if (!CharToWchar(src, intermediary, src_code_page)) return false;
if (!WcharToChar(intermediary, dst, dst_code_page)) return false;
return true;
}
bool CharToChar(const char* src, std::string& dst, UINT src_code_page, UINT dst_code_page) {
CONVFCT_TYPE2(CharToChar, char, char, src_code_page, dst_code_page);
}
std::string CharToChar(const std::string_view& src, UINT src_code_page, UINT dst_code_page) {
CONVFCT_TYPE3(CharToChar, char, char, src_code_page, dst_code_page);
}
std::string CharToChar(const char* src, UINT src_code_page, UINT dst_code_page) {
CONVFCT_TYPE4(CharToChar, char, char, src_code_page, dst_code_page);
}
#pragma endregion
#pragma region WcharToUTF8
bool WcharToUTF8(const std::wstring_view& src, yycc_u8string& dst) {
std::string adapted_dst;
bool ret = WcharToChar(src, adapted_dst, CP_UTF8);
if (ret) dst = ToUTF8(adapted_dst);
return ret;
}
bool WcharToUTF8(const wchar_t* src, yycc_u8string& dst) {
CONVFCT_TYPE2(WcharToUTF8, wchar_t, yycc_char8_t);
}
yycc_u8string WcharToUTF8(const std::wstring_view& src) {
CONVFCT_TYPE3(WcharToUTF8, wchar_t, yycc_char8_t);
}
yycc_u8string WcharToUTF8(const wchar_t* src) {
CONVFCT_TYPE4(WcharToUTF8, wchar_t, yycc_char8_t);
}
#pragma endregion
#pragma region UTF8ToWchar
bool UTF8ToWchar(const yycc_u8string_view& src, std::wstring& dst) {
std::string_view adapted_src(ToOrdinaryView(src));
return CharToWchar(adapted_src, dst, CP_UTF8);
}
bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst) {
CONVFCT_TYPE2(UTF8ToWchar, yycc_char8_t, wchar_t);
}
std::wstring UTF8ToWchar(const yycc_u8string_view& src) {
CONVFCT_TYPE3(UTF8ToWchar, yycc_char8_t, wchar_t);
}
std::wstring UTF8ToWchar(const yycc_char8_t* src) {
CONVFCT_TYPE4(UTF8ToWchar, yycc_char8_t, wchar_t);
}
#pragma endregion
#pragma region CharToUTF8
bool CharToUTF8(const std::string_view& src, yycc_u8string& dst, UINT code_page) {
std::string adapted_dst;
bool ret = CharToChar(src, adapted_dst, code_page, CP_UTF8);
if (ret) dst = ToUTF8(adapted_dst);
return ret;
}
bool CharToUTF8(const char* src, yycc_u8string& dst, UINT code_page) {
CONVFCT_TYPE2(CharToUTF8, char, yycc_char8_t, code_page);
}
yycc_u8string CharToUTF8(const std::string_view& src, UINT code_page) {
CONVFCT_TYPE3(CharToUTF8, char, yycc_char8_t, code_page);
}
yycc_u8string CharToUTF8(const char* src, UINT code_page) {
CONVFCT_TYPE4(CharToUTF8, char, yycc_char8_t, code_page);
}
#pragma endregion
#pragma region UTF8ToChar
bool UTF8ToChar(const yycc_u8string_view& src, std::string& dst, UINT code_page) {
std::string_view adapted_src(ToOrdinaryView(src));
return CharToChar(adapted_src, dst, CP_UTF8, code_page);
}
bool UTF8ToChar(const yycc_char8_t* src, std::string& dst, UINT code_page) {
CONVFCT_TYPE2(UTF8ToChar, yycc_char8_t, char, code_page);
}
std::string UTF8ToChar(const yycc_u8string_view& src, UINT code_page) {
CONVFCT_TYPE3(UTF8ToChar, yycc_char8_t, char, code_page);
}
std::string UTF8ToChar(const yycc_char8_t* src, UINT code_page) {
CONVFCT_TYPE4(UTF8ToChar, yycc_char8_t, char, code_page);
}
#pragma endregion
#endif
#pragma region UTF8 UTF16 UTF32 Help Funcs
/*
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.
It is also independend from we defined \c yycc_char8_t.
So it is essential define a type which can correctly trigger specific specializations of \c std::codecv in there.
*/
#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 yycc_u8string_view& src, std::basic_string<_TChar>& dst) {
// Reference:
// https://zh.cppreference.com/w/cpp/locale/codecvt/in
// if src is empty, return directly
if (src.empty()) {
dst.clear();
return true;
}
// 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{};
dst.resize(src.size());
const CodecvtUTF8Char_t* intern_from = reinterpret_cast<const CodecvtUTF8Char_t*>(src.data()),
*intern_from_end = reinterpret_cast<const CodecvtUTF8Char_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_t<_TChar>::ok)
return false;
// resize result and return
dst.resize(extern_to_next - dst.data());
return true;
}
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 std::basic_string_view<_TChar>& src, yycc_u8string& dst) {
// Reference:
// https://zh.cppreference.com/w/cpp/locale/codecvt/out
// if src is empty, return directly
if (src.empty()) {
dst.clear();
return true;
}
// 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{};
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;
CodecvtUTF8Char_t* extern_to = reinterpret_cast<CodecvtUTF8Char_t*>(dst.data()),
*extern_to_end = reinterpret_cast<CodecvtUTF8Char_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_t<_TChar>::ok)
return false;
// resize result and retuen
dst.resize(extern_to_next - reinterpret_cast<CodecvtUTF8Char_t*>(dst.data()));
return true;
}
#pragma endregion
#pragma region UTF8ToUTF16
bool UTF8ToUTF16(const yycc_u8string_view& src, std::u16string& dst) {
return UTF8ToUTFOther<char16_t>(src, dst);
}
bool UTF8ToUTF16(const yycc_char8_t* src, std::u16string& dst) {
CONVFCT_TYPE2(UTF8ToUTF16, yycc_char8_t, char16_t);
}
std::u16string UTF8ToUTF16(const yycc_u8string_view& src) {
CONVFCT_TYPE3(UTF8ToUTF16, yycc_char8_t, char16_t);
}
std::u16string UTF8ToUTF16(const yycc_char8_t* src) {
CONVFCT_TYPE4(UTF8ToUTF16, yycc_char8_t, char16_t);
}
#pragma endregion
#pragma region UTF16ToUTF8
bool UTF16ToUTF8(const std::u16string_view& src, yycc_u8string& dst) {
return UTFOtherToUTF8<char16_t>(src, dst);
}
bool UTF16ToUTF8(const char16_t* src, yycc_u8string& dst) {
CONVFCT_TYPE2(UTF16ToUTF8, char16_t, yycc_char8_t);
}
yycc_u8string UTF16ToUTF8(const std::u16string_view& src) {
CONVFCT_TYPE3(UTF16ToUTF8, char16_t, yycc_char8_t);
}
yycc_u8string UTF16ToUTF8(const char16_t* src) {
CONVFCT_TYPE4(UTF16ToUTF8, char16_t, yycc_char8_t);
}
#pragma endregion
#pragma region UTF8ToUTF32
bool UTF8ToUTF32(const yycc_u8string_view& src, std::u32string& dst) {
return UTF8ToUTFOther<char32_t>(src, dst);
}
bool UTF8ToUTF32(const yycc_char8_t* src, std::u32string& dst) {
CONVFCT_TYPE2(UTF8ToUTF32, yycc_char8_t, char32_t);
}
std::u32string UTF8ToUTF32(const yycc_u8string_view& src) {
CONVFCT_TYPE3(UTF8ToUTF32, yycc_char8_t, char32_t);
}
std::u32string UTF8ToUTF32(const yycc_char8_t* src) {
CONVFCT_TYPE4(UTF8ToUTF32, yycc_char8_t, char32_t);
}
#pragma endregion
#pragma region UTF32ToUTF8
bool UTF32ToUTF8(const std::u32string_view& src, yycc_u8string& dst) {
return UTFOtherToUTF8<char32_t>(src, dst);
}
bool UTF32ToUTF8(const char32_t* src, yycc_u8string& dst) {
CONVFCT_TYPE2(UTF32ToUTF8, char32_t, yycc_char8_t);
}
yycc_u8string UTF32ToUTF8(const std::u32string_view& src) {
CONVFCT_TYPE3(UTF32ToUTF8, char32_t, yycc_char8_t);
}
yycc_u8string UTF32ToUTF8(const char32_t* src) {
CONVFCT_TYPE4(UTF32ToUTF8, char32_t, yycc_char8_t);
}
#pragma endregion
#undef CONVFCT_TYPE2
#undef CONVFCT_TYPE3
#undef CONVFCT_TYPE4
}

View File

@ -1,95 +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 helper for all encoding stuff.
* @details
* For more infomations about how to use the functions provided by this namespace,
* please see \ref library_encoding and \ref encoding_helper.
*/
namespace YYCC::EncodingHelper {
#define _YYCC_U8(strl) u8 ## strl ///< The assistant macro for YYCC_U8.
#define YYCC_U8(strl) (reinterpret_cast<const ::YYCC::yycc_char8_t*>(_YYCC_U8(strl))) ///< The macro for creating UTF8 string literal. See \ref library_encoding.
#define YYCC_U8_CHAR(chr) (static_cast<YYCC::yycc_char8_t>(chr)) ///< The macro for casting ordinary char type into YYCC UTF8 char type.
const yycc_char8_t* ToUTF8(const char* src);
yycc_char8_t* ToUTF8(char* src);
yycc_u8string ToUTF8(const std::string_view& src);
yycc_u8string_view ToUTF8View(const std::string_view& src);
const char* ToOrdinary(const yycc_char8_t* src);
char* ToOrdinary(yycc_char8_t* src);
std::string ToOrdinary(const yycc_u8string_view& src);
std::string_view ToOrdinaryView(const yycc_u8string_view& src);
#if YYCC_OS == YYCC_OS_WINDOWS
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);
bool CharToWchar(const std::string_view& src, std::wstring& dst, UINT code_page);
bool CharToWchar(const char* src, std::wstring& dst, UINT code_page);
std::wstring CharToWchar(const std::string_view& src, UINT code_page);
std::wstring CharToWchar(const char* src, UINT code_page);
bool CharToChar(const std::string_view& src, std::string& dst, UINT src_code_page, UINT dst_code_page);
bool CharToChar(const char* src, std::string& dst, UINT src_code_page, UINT dst_code_page);
std::string CharToChar(const std::string_view& src, UINT src_code_page, UINT dst_code_page);
std::string CharToChar(const char* src, UINT src_code_page, UINT dst_code_page);
bool WcharToUTF8(const std::wstring_view& src, yycc_u8string& dst);
bool WcharToUTF8(const wchar_t* src, yycc_u8string& dst);
yycc_u8string WcharToUTF8(const std::wstring_view& src);
yycc_u8string WcharToUTF8(const wchar_t* src);
bool UTF8ToWchar(const yycc_u8string_view& src, std::wstring& dst);
bool UTF8ToWchar(const yycc_char8_t* src, std::wstring& dst);
std::wstring UTF8ToWchar(const yycc_u8string_view& src);
std::wstring UTF8ToWchar(const yycc_char8_t* src);
bool CharToUTF8(const std::string_view& src, yycc_u8string& dst, UINT code_page);
bool CharToUTF8(const char* src, yycc_u8string& dst, UINT code_page);
yycc_u8string CharToUTF8(const std::string_view& src, UINT code_page);
yycc_u8string CharToUTF8(const char* src, UINT code_page);
bool UTF8ToChar(const yycc_u8string_view& src, std::string& dst, UINT code_page);
bool UTF8ToChar(const yycc_char8_t* src, std::string& dst, UINT code_page);
std::string UTF8ToChar(const yycc_u8string_view& src, UINT code_page);
std::string UTF8ToChar(const yycc_char8_t* src, UINT code_page);
#endif
bool UTF8ToUTF16(const yycc_u8string_view& src, std::u16string& dst);
bool UTF8ToUTF16(const yycc_char8_t* src, std::u16string& dst);
std::u16string UTF8ToUTF16(const yycc_u8string_view& src);
std::u16string UTF8ToUTF16(const yycc_char8_t* src);
bool UTF16ToUTF8(const std::u16string_view& src, yycc_u8string& dst);
bool UTF16ToUTF8(const char16_t* src, yycc_u8string& dst);
yycc_u8string UTF16ToUTF8(const std::u16string_view& src);
yycc_u8string UTF16ToUTF8(const char16_t* src);
bool UTF8ToUTF32(const yycc_u8string_view& src, std::u32string& dst);
bool UTF8ToUTF32(const yycc_char8_t* src, std::u32string& dst);
std::u32string UTF8ToUTF32(const yycc_u8string_view& src);
std::u32string UTF8ToUTF32(const yycc_char8_t* src);
bool UTF32ToUTF8(const std::u32string_view& src, yycc_u8string& dst);
bool UTF32ToUTF8(const char32_t* src, yycc_u8string& dst);
yycc_u8string UTF32ToUTF8(const std::u32string_view& src);
yycc_u8string UTF32ToUTF8(const char32_t* src);
}

View File

@ -1,563 +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 "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

View File

@ -1,64 +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.
* 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

View File

@ -1,37 +0,0 @@
#include "IOHelper.hpp"
#include "EncodingHelper.hpp"
#include <cstdio>
#include <iostream>
#include <string>
#include <stdexcept>
#include <memory>
#if YYCC_OS == YYCC_OS_WINDOWS
#include "WinImportPrefix.hpp"
#include <Windows.h>
#include "WinImportSuffix.hpp"
#endif
namespace YYCC::IOHelper {
std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode) {
#if 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(EncodingHelper::ToOrdinary(u8_filepath), EncodingHelper::ToOrdinary(u8_mode));
#endif
}
}

View File

@ -1,55 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <cstdio>
#include <filesystem>
/**
* @brief Some IO related stuff
* @details
* See also \ref io_helper.
*/
namespace YYCC::IOHelper {
#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 C++ standard deleter for std::FILE*
class StdFileDeleter {
public:
StdFileDeleter() {}
void operator() (std::FILE* ptr) {
if (ptr != nullptr) {
std::fclose(ptr);
}
}
};
/// @brief Smart unique pointer of \c std::FILE*
using SmartStdFile = std::unique_ptr<std::FILE, StdFileDeleter>;
/**
* @brief The UTF8 version of \c std::fopen.
* @param[in] u8_filepath The UTF8 encoded path to the file to be opened.
* @param[in] u8_mode UTF8 encoded mode string of the file to be opened.
* @remarks
* This function is suit for Windows because std::fopen do not support UTF8 on Windows.
* On other platforms, this function will delegate request directly to std::fopen.
* @return \c FILE* of the file to be opened, or nullptr if failed.
*/
std::FILE* UTF8FOpen(const yycc_char8_t* u8_filepath, const yycc_char8_t* u8_mode);
}

View File

@ -1,221 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"
#include <string>
#include <cinttypes>
#include <type_traits>
#include <stdexcept>
#include <charconv>
#include <array>
/**
* @brief The helper involving convertion between arithmetic types (integral, floating point and bool) and string
* @details
* See also \ref parser_helper.
*/
namespace YYCC::ParserHelper {
// Reference: https://zh.cppreference.com/w/cpp/utility/from_chars
/**
* @brief Try parsing given string to floating point types.
* @tparam _Ty The type derived from floating point type.
* @param[in] strl The string need to be parsed.
* @param[out] num
* The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed.
* @param[in] fmt The floating point format used when try parsing.
* @return True if success, otherwise false.
*/
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num, std::chars_format fmt = std::chars_format::general) {
auto [ptr, ec] = std::from_chars(
EncodingHelper::ToOrdinary(strl.data()),
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
num, fmt
);
if (ec == std::errc()) {
// check whether the full string is matched
return ptr == EncodingHelper::ToOrdinary(strl.data() + 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.");
}
}
/**
* @brief Try parsing given string to integral types.
* @tparam _Ty The type derived from integral type except bool type.
* @param[in] strl The string need to be parsed.
* @param[out] num
* The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed.
* @param[in] base Integer base to use: a value between 2 and 36 (inclusive).
* @return True if success, otherwise false.
*/
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num, int base = 10) {
auto [ptr, ec] = std::from_chars(
EncodingHelper::ToOrdinary(strl.data()),
EncodingHelper::ToOrdinary(strl.data() + strl.size()),
num, base
);
if (ec == std::errc()) {
// check whether the full string is matched
return ptr == EncodingHelper::ToOrdinary(strl.data() + 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.");
}
}
/**
* @brief Try parsing given string to bool types.
* @tparam _Ty The type derived from bool type.
* @param[in] strl The string need to be parsed ("true" or "false", case insensitive).
* @param[out] num
* The variable receiving result.
* There is no guarantee that the content is not modified when parsing failed.
* @return True if success, otherwise false.
*/
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
bool TryParse(const yycc_u8string_view& strl, _Ty& num) {
// get lower case
yycc_u8string lower_case(strl);
YYCC::StringHelper::Lower(lower_case);
// compare result
if (strl == YYCC_U8("true")) num = true;
else if (strl == YYCC_U8("false")) num = false;
else return false;
return true;
}
/**
* @brief Parse given string to floating point types.
* @tparam _Ty The type derived from floating point type.
* @param[in] strl The string need to be parsed.
* @param[in] fmt The floating point format used when try parsing.
* @return
* The parsing result.
* There is no guarantee about the content of this return value when parsing failed.
* It may be any possible value but usually is its default value.
*/
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
_Ty Parse(const yycc_u8string_view& strl, std::chars_format fmt = std::chars_format::general) {
_Ty ret;
TryParse(strl, ret, fmt);
return ret;
}
/**
* @brief Parse given string to integral type types.
* @tparam _Ty The type derived from integral type except bool type.
* @param[in] strl The string need to be parsed.
* @param[in] base Integer base to use: a value between 2 and 36 (inclusive).
* @return
* The parsing result.
* There is no guarantee about the content of this return value when parsing failed.
* It may be any possible value but usually is its default value.
*/
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
_Ty Parse(const yycc_u8string_view& strl, int base = 10) {
_Ty ret;
TryParse(strl, ret, base);
return ret;
}
/**
* @brief Parse given string to bool types.
* @tparam _Ty The type derived from bool type.
* @param[in] strl The string need to be parsed ("true" or "false", case insensitive).
* @return
* The parsing result.
* There is no guarantee about the content of this return value when parsing failed.
* It may be any possible value but usually is its default value.
*/
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
_Ty Parse(const yycc_u8string_view& strl) {
_Ty ret;
TryParse(strl, ret);
return ret;
}
// Reference: https://en.cppreference.com/w/cpp/utility/to_chars
/**
* @brief Return the string representation of given floating point value.
* @tparam _Ty The type derived from floating point type.
* @param[in] num The value need to get string representation.
* @param[in] fmt The floating point format used when getting string representation.
* @param[in] precision The floating point precision used when getting string representation.
* @return The string representation of given value.
*/
template<typename _Ty, std::enable_if_t<std::is_floating_point_v<_Ty>, int> = 0>
yycc_u8string ToString(_Ty num, std::chars_format fmt = std::chars_format::general, int precision = 6) {
// default precision = 6 is gotten from: https://en.cppreference.com/w/c/io/fprintf
std::array<yycc_char8_t, 64> buffer;
auto [ptr, ec] = std::to_chars(
EncodingHelper::ToOrdinary(buffer.data()),
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
num, fmt, precision
);
if (ec == std::errc()) {
return yycc_u8string(buffer.data(), EncodingHelper::ToUTF8(ptr) - buffer.data());
} else if (ec == std::errc::value_too_large) {
// too short buffer
// this should not happened
throw std::out_of_range("ToString() buffer is not sufficient.");
} else {
// unreachable
throw std::runtime_error("unreachable code.");
}
}
/**
* @brief Return the string representation of given integral value.
* @tparam _Ty The type derived from integral type except bool type.
* @param[in] num The value need to get string representation.
* @param[in] base Integer base used when getting string representation: a value between 2 and 36 (inclusive).
* @return The string representation of given value.
*/
template<typename _Ty, std::enable_if_t<std::is_integral_v<_Ty> && !std::is_same_v<_Ty, bool>, int> = 0>
yycc_u8string ToString(_Ty num, int base = 10) {
std::array<yycc_char8_t, 64> buffer;
auto [ptr, ec] = std::to_chars(
EncodingHelper::ToOrdinary(buffer.data()),
EncodingHelper::ToOrdinary(buffer.data() + buffer.size()),
num, base
);
if (ec == std::errc()) {
return yycc_u8string(buffer.data(), EncodingHelper::ToUTF8(ptr) - buffer.data());
} else if (ec == std::errc::value_too_large) {
// too short buffer
// this should not happened
throw std::out_of_range("ToString() buffer is not sufficient.");
} else {
// unreachable
throw std::runtime_error("unreachable code.");
}
}
/**
* @brief Return the string representation of given bool value.
* @tparam _Ty The type derived from bool type.
* @param[in] num The value need to get string representation.
* @return The string representation of given value ("true" or "false").
*/
template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, bool>, int> = 0>
yycc_u8string ToString(_Ty num) {
if (num) return yycc_u8string(YYCC_U8("true"));
else return yycc_u8string(YYCC_U8("false"));
}
}

View File

@ -1,41 +0,0 @@
#include "StdPatch.hpp"
#include "EncodingHelper.hpp"
#include <string>
#include <stdexcept>
namespace YYCC::StdPatch {
std::filesystem::path ToStdPath(const yycc_u8string_view& 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.");
// return path with wchar_t ctor
return std::filesystem::path(wpath);
#else
std::string cache = YYCC::EncodingHelper::ToOrdinary(u8_path);
return std::filesystem::path(cache.c_str());
#endif
}
yycc_u8string ToUTF8Path(const std::filesystem::path& path) {
#if YYCC_OS == YYCC_OS_WINDOWS
// get and convert to utf8
yycc_u8string 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 EncodingHelper::ToUTF8(path.string());
#endif
}
}

View File

@ -1,218 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <filesystem>
#include <string>
#include <string_view>
/**
* @brief \c Standard library related patches for UTF8 compatibility and the limitation of C++ standard version.
* @details
* See also \ref std_patch.
*/
namespace YYCC::StdPatch {
/**
* @brief Constructs \c std::filesystem::path from UTF8 path.
* @param[in] u8_path UTF8 path string for building.
* @return \c std::filesystem::path instance.
* @exception std::invalid_argument Fail to parse given UTF8 string (maybe invalid?).
*/
std::filesystem::path ToStdPath(const yycc_u8string_view& u8_path);
/**
* @brief Returns the UTF8 representation of given \c std::filesystem::path.
* @param[in] path The \c std::filesystem::path instance converting to UTF8 path.
* @return The UTF8 representation of given \c std::filesystem::path.
* @exception std::invalid_argument Fail to convert to UTF8 string.
*/
yycc_u8string ToUTF8Path(const std::filesystem::path& path);
#pragma region StartsWith EndsWith
// Reference:
// https://en.cppreference.com/w/cpp/string/basic_string_view/starts_with
// https://en.cppreference.com/w/cpp/string/basic_string_view/ends_with
// https://en.cppreference.com/w/cpp/string/basic_string/starts_with
// https://en.cppreference.com/w/cpp/string/basic_string/ends_with
#pragma region String View
/**
* @brief Checks if the string view begins with the given prefix
* @param[in] that The string view to find.
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
return std::basic_string_view<CharT, Traits>(that.data(), std::min(that.size(), sv.size())) == sv;
}
/**
* @brief Checks if the string view begins with the given prefix
* @param[in] that The string view to find.
* @param[in] ch A single character.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, CharT ch) noexcept {
return !that.empty() && Traits::eq(that.front(), ch);
}
/**
* @brief Checks if the string view begins with the given prefix
* @param[in] that The string view to find.
* @param[in] s A null-terminated character string.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string_view<CharT, Traits>& that, const CharT* s) noexcept {
return StartsWith(that, std::basic_string_view(s));
}
/**
* @brief Checks if the string view ends with the given suffix
* @param[in] that The string view to find.
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
return that.size() >= sv.size() && that.compare(that.size() - sv.size(), std::basic_string_view<CharT, Traits>::npos, sv) == 0;
}
/**
* @brief Checks if the string view ends with the given suffix
* @param[in] that The string view to find.
* @param[in] ch A single character.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, CharT ch) noexcept {
return !that.empty() && Traits::eq(that.back(), ch);
}
/**
* @brief Checks if the string view ends with the given suffix
* @param[in] that The string view to find.
* @param[in] s A null-terminated character string.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string_view<CharT, Traits>& that, const CharT* s) noexcept {
return EndsWith(that, std::basic_string_view(s));
}
#pragma endregion
#pragma region String
/**
* @brief Checks if the string begins with the given prefix
* @param[in] that The string to find.
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), sv);
}
/**
* @brief Checks if the string begins with the given prefix
* @param[in] that The string to find.
* @param[in] ch A single character.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string<CharT, Traits>& that, CharT ch) noexcept {
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), ch);
}
/**
* @brief Checks if the string begins with the given prefix
* @param[in] that The string to find.
* @param[in] s A null-terminated character string.
* @return True if the string view begins with the provided prefix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool StartsWith(const std::basic_string<CharT, Traits>& that, const CharT* s) noexcept {
return StartsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), s);
}
/**
* @brief Checks if the string ends with the given suffix
* @param[in] that The string to find.
* @param[in] sv A string view which may be a result of implicit conversion from \c std::basic_string.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string<CharT, Traits>& that, std::basic_string_view<CharT, Traits> sv) noexcept {
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), sv);
}
/**
* @brief Checks if the string ends with the given suffix
* @param[in] that The string to find.
* @param[in] ch A single character.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string<CharT, Traits>& that, CharT ch) noexcept {
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), ch);
}
/**
* @brief Checks if the string ends with the given suffix
* @param[in] that The string to find.
* @param[in] s A null-terminated character string.
* @return True if the string view ends with the provided suffix, false otherwise.
*/
template<class CharT, class Traits = std::char_traits<CharT>>
bool EndsWith(const std::basic_string<CharT, Traits>& that, const CharT* s) noexcept {
return EndsWith(std::basic_string_view<CharT, Traits>(that.data(), that.size()), s);
}
#pragma endregion
#pragma endregion
#pragma region Contain
/**
* @brief Checks if there is an element with key equivalent to key in the container.
* @details
* The polyfill to \c Contains function of unordered and ordered associative container.
* Because this function only present after C++ 20.
* This function will use our custom polyfill if the version of C++ standard you are using lower than C++ 20.
* Otherwise it will fallback to vanilla standard library function.
* @tparam _TContainer
* The type of container. This container must have \c find() and \c end() member functions.
* @tparam _TKey
* The type of key of container.
* If the container is a set, this type is the type of item in set.
* If the container is a map, this type is the key type of map.
* @param[in] container The reference to container to find.
* @param[in] key Key value of the element to search for
* @return True if there is such an element, otherwise false.
* @remarks
* This template function do not have constraint check.
* If container type has \c find() and \c end() member functions, this template function will be created without any error.
* However, this function should be used for standard library associative container according to its original purpose.
* It means that the type of container usually and should be one of following types:
* \li \c std::set
* \li \c std::multiset
* \li \c std::map
* \li \c std::multimap
* \li \c std::unordered_set
* \li \c std::unordered_multiset
* \li \c std::unordered_map
* \li \c std::unordered_multimap
*/
template<class _TContainer, class _TKey>
bool Contains(const _TContainer& container, const _TKey& key) {
// __cplusplus macro need special compiler switch enabled when compiling.
// So we use _MSVC_LANG check it instead.
#if __cplusplus >= 202002L || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L)
return container.contains(key);
#else
return container.find(key) != container.end();
#endif
}
#pragma endregion
}

View File

@ -1,223 +0,0 @@
#include "StringHelper.hpp"
#include "EncodingHelper.hpp"
#include <algorithm>
namespace YYCC::StringHelper {
#pragma region Printf VPrintf
bool Printf(yycc_u8string& strl, const yycc_char8_t* format, ...) {
va_list argptr;
va_start(argptr, format);
bool ret = VPrintf(strl, format, argptr);
va_end(argptr);
return ret;
}
bool VPrintf(yycc_u8string& strl, const yycc_char8_t* 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,
EncodingHelper::ToOrdinary(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(
EncodingHelper::ToOrdinary(strl.data()),
strl.size() + 1,
EncodingHelper::ToOrdinary(format),
args2
);
va_end(args2);
if (write_result < 0 || write_result > count) {
// invalid write result in vsnprintf.
return false;
}
return true;
}
yycc_u8string Printf(const yycc_char8_t* format, ...) {
yycc_u8string ret;
va_list argptr;
va_start(argptr, format);
VPrintf(ret, format, argptr);
va_end(argptr);
return ret;
}
yycc_u8string VPrintf(const yycc_char8_t* format, va_list argptr) {
yycc_u8string ret;
va_list argcpy;
va_copy(argcpy, argptr);
VPrintf(ret, format, argcpy);
va_end(argcpy);
return ret;
}
#pragma endregion
#pragma region Replace
void Replace(yycc_u8string& strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl) {
// Reference: https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
// check requirements
// from string should not be empty
yycc_u8string from_strl(_from_strl);
yycc_u8string 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)) != yycc_u8string::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'
}
}
yycc_u8string Replace(const yycc_u8string_view& _strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl) {
// prepare result
yycc_u8string strl(_strl);
Replace(strl, _from_strl, _to_strl);
// return value
return strl;
}
#pragma endregion
#pragma region Join
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer) {
yycc_u8string ret;
bool is_first = true;
yycc_u8string_view element;
// fetch element
while (fct_data(element)) {
// insert decilmer
if (is_first) is_first = false;
else {
// append decilmer.
ret.append(decilmer);
}
// insert element if it is not empty
if (!element.empty())
ret.append(element);
}
return ret;
}
#pragma endregion
#pragma region Upper Lower
template<bool bIsToLower>
static void GeneralStringLowerUpper(yycc_u8string& 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);
}
);
}
void Lower(yycc_u8string& strl) {
GeneralStringLowerUpper<true>(strl);
}
yycc_u8string Lower(const yycc_u8string_view& strl) {
yycc_u8string ret(strl);
Lower(ret);
return ret;
}
void Upper(yycc_u8string& strl) {
GeneralStringLowerUpper<false>(strl);
}
yycc_u8string Upper(const yycc_u8string_view& strl) {
// same as Lower, just replace char transform function.
yycc_u8string ret(strl);
Upper(ret);
return ret;
}
#pragma endregion
#pragma region Split
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
// call split view
auto view_result = SplitView(strl, _decilmer);
// copy string view result to string
std::vector<yycc_u8string> elems;
elems.reserve(view_result.size());
for (const auto& strl_view : view_result) {
elems.emplace_back(yycc_u8string(strl_view));
}
// return copied result
return elems;
}
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) {
// Reference:
// https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c
// prepare return value
std::vector<yycc_u8string_view> elems;
// if string need to be splitted is empty, return original string (empty string).
// if decilmer is empty, return original string.
yycc_u8string decilmer(_decilmer);
if (strl.empty() || decilmer.empty()) {
elems.emplace_back(strl);
return elems;
}
// start spliting
std::size_t previous = 0, current;
while ((current = strl.find(decilmer.c_str(), previous)) != yycc_u8string::npos) {
elems.emplace_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.emplace_back(strl.substr(previous));
}
return elems;
}
#pragma endregion
}

View File

@ -1,159 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include <string>
#include <cstdarg>
#include <functional>
#include <vector>
/**
* @brief The helper containing string operations
* @details
* See also \ref string_helper.
*/
namespace YYCC::StringHelper {
/**
* @brief Perform a string formatting operation.
* @param[out] strl
* The string container receiving the result.
* There is no guarantee that the content is not modified when function failed.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return True if success, otherwise false.
*/
bool Printf(yycc_u8string& strl, const yycc_char8_t* format, ...);
/**
* @brief Perform a string formatting operation.
* @param[out] strl
* The string container receiving the result.
* There is no guarantee that the content is not modified when function failed.
* @param[in] format The format string.
* @param[in] argptr Argument list of format string.
* @return True if success, otherwise false.
*/
bool VPrintf(yycc_u8string& strl, const yycc_char8_t* format, va_list argptr);
/**
* @brief Perform a string formatting operation.
* @param[in] format The format string.
* @param[in] ... Argument list of format string.
* @return The formatting result. Empty string if error happened.
*/
yycc_u8string Printf(const yycc_char8_t* format, ...);
/**
* @brief Perform a string formatting operation.
* @param[in] format The format string.
* @param[in] argptr Argument list of format string.
* @return The formatting result. Empty string if error happened.
*/
yycc_u8string VPrintf(const yycc_char8_t* format, va_list argptr);
/**
* @brief Modify given string with all occurrences of substring \e old replaced by \e new.
* @param[in,out] strl The string for replacing
* @param[in] _from_strl The \e old string.
* @param[in] _to_strl The \e new string.
*/
void Replace(yycc_u8string& strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl);
/**
* @brief Return a copy with all occurrences of substring \e old replaced by \e new.
* @param[in] _strl The string for replacing
* @param[in] _from_strl The \e old string.
* @param[in] _to_strl The \e new string.
* @return The result of replacement.
*/
yycc_u8string Replace(const yycc_u8string_view& _strl, const yycc_u8string_view& _from_strl, const yycc_u8string_view& _to_strl);
/**
* @brief The data provider of general join function.
* @details
* For programmer using lambda to implement this function pointer:
* \li During calling, implementation should assign the reference of string view passed in argument
* to the string which need to be joined.
* \li Function return true to continue joining. otherwise return false to stop joining.
* The argument content assigned in the calling returning false is not included in join process.
*/
using JoinDataProvider = std::function<bool(yycc_u8string_view&)>;
/**
* @brief Universal join function.
* @details
* This function use function pointer as a general data provider interface,
* so this function suit for all types container.
* You can use this universal join function for any custom container by
* using C++ lambda syntax to create a code block adapted to this function pointer.
* @param[in] fct_data The function pointer in JoinDataProvider type prividing the data to be joined.
* @param[in] decilmer The decilmer used for joining.
* @return The result string of joining.
*/
yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer);
/**
* @brief Specialized join function for standard library container.
* @tparam InputIt
* Must meet the requirements of LegacyInputIterator.
* It also can be dereferenced and then implicitly converted to yycc_u8string_view.
* @param[in] first The beginning of the range of elements to join.
* @param[in] last The terminal of the range of elements to join (exclusive).
* @param[in] decilmer The decilmer used for joining.
* @return The result string of joining.
*/
template<class InputIt>
yycc_u8string Join(InputIt first, InputIt last, const yycc_u8string_view& decilmer) {
return Join([&first, &last](yycc_u8string_view& view) -> bool {
// if we reach tail, return false to stop join process
if (first == last) return false;
// otherwise fetch data, inc iterator and return.
view = *first;
++first;
return true;
}, decilmer);
}
/**
* @brief Convert given string to lowercase.
* @param[in,out] strl The string to be lowercase.
*/
void Lower(yycc_u8string& strl);
/**
* @brief Return a copy of the string converted to lowercase.
* @param[in] strl The string to be lowercase.
* @return The copy of the string converted to lowercase.
*/
yycc_u8string Lower(const yycc_u8string_view& strl);
/**
* @brief Convert given string to uppercase.
* @param[in,out] strl The string to be uppercase.
*/
void Upper(yycc_u8string& strl);
/**
* @brief Return a copy of the string converted to uppercase.
* @param[in] strl The string to be uppercase.
* @return The copy of the string converted to uppercase.
*/
yycc_u8string Upper(const yycc_u8string_view& strl);
/**
* @brief Split given string with specified decilmer.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @return
* The split result.
* \par
* If given string or decilmer are empty,
* the result container will only contain 1 entry which is equal to given string.
*/
std::vector<yycc_u8string> Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
/**
* @brief Split given string with specified decilmer as string view.
* @param[in] strl The string need to be splitting.
* @param[in] _decilmer The decilmer for splitting.
* @return
* The split result with string view format.
* This will not produce any copy of original string.
* \par
* If given string or decilmer are empty,
* the result container will only contain 1 entry which is equal to given string.
* @see Split(const yycc_u8string_view&, const yycc_char8_t*)
*/
std::vector<yycc_u8string_view> SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer);
}

View File

@ -1,115 +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(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

View File

@ -1,106 +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 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

View File

@ -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

View File

@ -1,23 +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
#undef CopyFile
#undef MoveFile
#undef DeleteFile
#endif

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) Microsoft Corporation, yyc12345.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<!-- Following XML are copied from Visual Studio embedded Natvis files. -->
<!-- <Microsoft Visual Studio Install Directory>\Common7\Packages\Debugger\Visualizers\stl.natvis -->
<!-- VC 2013 -->
<Type Name="std::basic_string&lt;YYCC::yycc_char8_t,*&gt;" Priority="MediumLow">
<AlternativeType Name="std::basic_string&lt;char8_t,*&gt;" />
<AlternativeType Name="std::basic_string&lt;unsigned char,*&gt;" />
<DisplayString Condition="_Myres &lt; _BUF_SIZE">{_Bx._Buf,s8}</DisplayString>
<DisplayString Condition="_Myres &gt;= _BUF_SIZE">{_Bx._Ptr,s8}</DisplayString>
<StringView Condition="_Myres &lt; _BUF_SIZE">_Bx._Buf,s8</StringView>
<StringView Condition="_Myres &gt;= _BUF_SIZE">_Bx._Ptr,s8</StringView>
<Expand>
<Item Name="[size]" ExcludeView="simple">_Mysize</Item>
<Item Name="[capacity]" ExcludeView="simple">_Myres</Item>
<ArrayItems>
<Size>_Mysize</Size>
<ValuePointer Condition="_Myres &lt; _BUF_SIZE">_Bx._Buf</ValuePointer>
<ValuePointer Condition="_Myres &gt;= _BUF_SIZE">_Bx._Ptr</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<!-- VC 2015+ ABI basic_string -->
<Type Name="std::basic_string&lt;YYCC::yycc_char8_t,*&gt;">
<AlternativeType Name="std::basic_string&lt;char8_t,*&gt;" />
<AlternativeType Name="std::basic_string&lt;unsigned char,*&gt;" />
<Intrinsic Name="size" Expression="_Mypair._Myval2._Mysize" />
<Intrinsic Name="capacity" Expression="_Mypair._Myval2._Myres" />
<!-- _BUF_SIZE = 16 / sizeof(char) &lt; 1 ? 1 : 16 / sizeof(char) == 16 -->
<Intrinsic Name="bufSize" Expression="16" />
<Intrinsic Name="isShortString" Expression="capacity() &lt; bufSize()" />
<Intrinsic Name="isLongString" Expression="capacity() &gt;= bufSize()" />
<DisplayString Condition="isShortString()">{_Mypair._Myval2._Bx._Buf,s8}</DisplayString>
<DisplayString Condition="isLongString()">{_Mypair._Myval2._Bx._Ptr,s8}</DisplayString>
<StringView Condition="isShortString()">_Mypair._Myval2._Bx._Buf,s8</StringView>
<StringView Condition="isLongString()">_Mypair._Myval2._Bx._Ptr,s8</StringView>
<Expand>
<Item Name="[size]" ExcludeView="simple">size()</Item>
<Item Name="[capacity]" ExcludeView="simple">capacity()</Item>
<Item Name="[allocator]" ExcludeView="simple">_Mypair</Item>
<ArrayItems>
<Size>_Mypair._Myval2._Mysize</Size>
<ValuePointer Condition="isShortString()">_Mypair._Myval2._Bx._Buf</ValuePointer>
<ValuePointer Condition="isLongString()">_Mypair._Myval2._Bx._Ptr</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="std::basic_string_view&lt;YYCC::yycc_char8_t,*&gt;">
<AlternativeType Name="std::basic_string_view&lt;char8_t,*&gt;" />
<AlternativeType Name="std::basic_string_view&lt;unsigned char,*&gt;" />
<Intrinsic Name="size" Expression="_Mysize" />
<Intrinsic Name="data" Expression="_Mydata" />
<DisplayString>{_Mydata,[_Mysize],s8}</DisplayString>
<StringView>_Mydata,[_Mysize],s8</StringView>
<Expand>
<Item Name="[size]" ExcludeView="simple">size()</Item>
<ArrayItems>
<Size>size()</Size>
<ValuePointer>data()</ValuePointer>
</ArrayItems>
</Expand>
</Type>
</AutoVisualizer>

View File

@ -1,116 +0,0 @@
#pragma once
#pragma region Operating System Identifier Macros
// Define operating system macros
#define YYCC_OS_WINDOWS 2
#define YYCC_OS_LINUX 3
// Check current operating system.
#if defined(_WIN32)
#define YYCC_OS YYCC_OS_WINDOWS
#else
#define YYCC_OS YYCC_OS_LINUX
#endif
#pragma endregion
#pragma region Windows Shitty Behavior Disable Macros
// If we are in Windows,
// we need add 2 macros to disable Windows shitty warnings and errors of
// depracted functions and not secure functions.
#if 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
#pragma endregion
#pragma region YYCC UTF8 Types
// Define the UTF8 char type we used.
// And do a polyfill if no embedded char8_t type.
#include <string>
#include <string_view>
/**
* @brief Library core namespace
* @details Almost library functions are located in this namespace.
*/
namespace YYCC {
#if defined(__cpp_char8_t)
using yycc_char8_t = char8_t;
using yycc_u8string = std::u8string;
using yycc_u8string_view = std::u8string_view;
#else
using yycc_char8_t = unsigned char;
using yycc_u8string = std::basic_string<yycc_char8_t>;
using yycc_u8string_view = std::basic_string_view<yycc_char8_t>;
#endif
}
/**
\typedef YYCC::yycc_char8_t
\brief YYCC UTF8 char type.
\details
This char type is an alias to \c std::char8_t if your current C++ standard support it.
Otherwise it is defined as <TT>unsigned char</TT> as C++ 20 stdandard does.
*/
/**
\typedef YYCC::yycc_u8string
\brief YYCC UTF8 string container type.
\details
This type is defined as \c std::basic_string<yycc_char8_t>.
It is equal to \c std::u8string if your current C++ standard support it.
*/
/**
\typedef YYCC::yycc_u8string_view
\brief YYCC UTF8 string view type.
\details
This type is defined as \c std::basic_string_view<yycc_char8_t>.
It is equal to \c std::u8string_view if your current C++ standard support it.
*/
#pragma endregion
#pragma region Batch Class Move / Copy Function Macros
/// @brief Explicitly remove copy (\c constructor and \c operator\=) for given class.
#define YYCC_DEL_CLS_COPY(CLSNAME) \
CLSNAME(const CLSNAME&) = delete; \
CLSNAME& operator=(const CLSNAME&) = delete;
/// @brief Explicitly remove move (\c constructor and \c operator\=) for given class.
#define YYCC_DEL_CLS_MOVE(CLSNAME) \
CLSNAME(CLSNAME&&) = delete; \
CLSNAME& operator=(CLSNAME&&) = delete;
/// @brief Explicitly remove (copy and move) (\c constructor and \c operator\=) for given class.
#define YYCC_DEL_CLS_COPY_MOVE(CLSNAME) \
YYCC_DEL_CLS_COPY(CLSNAME) \
YYCC_DEL_CLS_MOVE(CLSNAME)
/// @brief Explicitly set default copy (\c constructor and \c operator\=) for given class.
#define YYCC_DEF_CLS_COPY(CLSNAME) \
CLSNAME(const CLSNAME&) = default; \
CLSNAME& operator=(const CLSNAME&) = default;
/// @brief Explicitly set default move (\c constructor and \c operator\=) for given class.
#define YYCC_DEF_CLS_MOVE(CLSNAME) \
CLSNAME(CLSNAME&&) = default; \
CLSNAME& operator=(CLSNAME&&) = default;
/// @brief Explicitly set default (copy and move) (\c constructor and \c operator\=) for given class.
#define YYCC_DEF_CLS_COPY_MOVE(CLSNAME) \
YYCC_DEF_CLS_COPY(CLSNAME) \
YYCC_DEF_CLS_MOVE(CLSNAME)
#pragma endregion

View File

@ -3,7 +3,7 @@
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "ConsoleHelper.hpp" #include "ConsoleHelper.hpp"
#if YYCC_OS == YYCC_OS_WINDOWS #if defined(YYCC_OS_WINDOWS)
#include "WinImportPrefix.hpp" #include "WinImportPrefix.hpp"
#include <Windows.h> #include <Windows.h>
#include <shellapi.h> #include <shellapi.h>
@ -24,7 +24,7 @@ namespace YYCC::ArgParser {
return ArgumentList(std::move(args)); return ArgumentList(std::move(args));
} }
#if YYCC_OS == YYCC_OS_WINDOWS #if defined(YYCC_OS_WINDOWS)
ArgumentList ArgumentList::CreateFromWin32() { ArgumentList ArgumentList::CreateFromWin32() {
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw // Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw

View File

@ -4,6 +4,7 @@
#include "Constraints.hpp" #include "Constraints.hpp"
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "ParserHelper.hpp" #include "ParserHelper.hpp"
#include <cstring>
#include <functional> #include <functional>
#include <vector> #include <vector>
#include <map> #include <map>
@ -36,7 +37,7 @@ namespace YYCC::ArgParser {
* and should not be seen as a part of arguments. * and should not be seen as a part of arguments.
*/ */
static ArgumentList CreateFromStd(int argc, char* argv[]); static ArgumentList CreateFromStd(int argc, char* argv[]);
#if YYCC_OS == YYCC_OS_WINDOWS #if defined(YYCC_OS_WINDOWS)
/** /**
* @brief Create argument list from Win32 function. * @brief Create argument list from Win32 function.
* @details * @details

View File

@ -2,6 +2,7 @@
#include "EncodingHelper.hpp" #include "EncodingHelper.hpp"
#include "IOHelper.hpp" #include "IOHelper.hpp"
#include "EnumHelper.hpp"
#include <stdexcept> #include <stdexcept>
namespace YYCC::ConfigManager { namespace YYCC::ConfigManager {
@ -44,7 +45,10 @@ namespace YYCC::ConfigManager {
} }
} }
bool CoreManager::Load() { ConfigLoadResult CoreManager::Load() {
// prepare result variables
ConfigLoadResult ret = ConfigLoadResult::OK;
// reset all settings first // reset all settings first
Reset(); Reset();
@ -53,20 +57,27 @@ namespace YYCC::ConfigManager {
if (fs.get() == nullptr) { if (fs.get() == nullptr) {
// if we fail to get, it means that we do not have corresponding cfg file. // if we fail to get, it means that we do not have corresponding cfg file.
// all settings should be reset to default value. // all settings should be reset to default value.
return true; ret = ConfigLoadResult::Created;
return ret;
} }
// fetch version info // fetch version info
uint64_t version_info; uint64_t version_info;
if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) if (std::fread(&version_info, 1u, sizeof(version_info), fs.get()) != sizeof(version_info)) {
return false; ret = ConfigLoadResult::Created;
return ret;
}
// check version // check version
// if read version is greater than we expected, // if read version is greater than we expected,
// it means that this cfg file is created by the program higer than this. // it means that this cfg file is created by the program higer than this.
// we should not read anything from it. // we should not read anything from it.
// however, for compaitibility reason, we allow read old cfg data. // however, for compaitibility reason, we allow read old cfg data.
if (version_info > m_VersionIdentifier) if (version_info > m_VersionIdentifier) {
return true; ret = ConfigLoadResult::ForwardNew;
return ret;
} else if (version_info < m_VersionIdentifier) {
EnumHelper::Add(ret, ConfigLoadResult::Migrated);
}
// fetch setting item from file // fetch setting item from file
yycc_u8string name_cache; yycc_u8string name_cache;
@ -77,37 +88,50 @@ namespace YYCC::ConfigManager {
if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) { if (std::fread(&name_length, 1u, sizeof(name_length), fs.get()) != sizeof(name_length)) {
// we also check whether reach EOF at there. // we also check whether reach EOF at there.
if (std::feof(fs.get())) break; if (std::feof(fs.get())) break;
else return false; else {
EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
} }
// fetch name body // fetch name body
name_cache.resize(name_length); name_cache.resize(name_length);
if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) if (std::fread(name_cache.data(), 1u, name_length, fs.get()) != name_length) {
return false; EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get setting data length // get setting data length
size_t data_length; size_t data_length;
if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) if (std::fread(&data_length, 1u, sizeof(data_length), fs.get()) != sizeof(data_length)) {
return false; EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// get matched setting first // get matched setting first
const auto& found = m_Settings.find(name_cache); const auto& found = m_Settings.find(name_cache);
if (found != m_Settings.end()) { if (found != m_Settings.end()) {
// found. read data for it // found. read data for it
found->second->ResizeData(data_length); found->second->ResizeData(data_length);
if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) if (std::fread(found->second->GetDataPtr(), 1u, data_length, fs.get()) != data_length) {
return false; EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
// call user defined load function // call user defined load function
// if fail to parse, reset to default value // if fail to parse, reset to default value
if (!found->second->UserLoad()) if (!found->second->UserLoad()) {
EnumHelper::Add(ret, ConfigLoadResult::ItemError);
found->second->UserReset(); found->second->UserReset();
}
} else { } else {
// fail to find. skip this unknown setting // fail to find. skip this unknown setting
if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) if (fseek(fs.get(), static_cast<long>(data_length), SEEK_CUR) != 0) {
return false; EnumHelper::Add(ret, ConfigLoadResult::BrokenFile);
return ret;
}
} }
} }
return true; return ret;
} }
bool CoreManager::Save() { bool CoreManager::Save() {

View File

@ -18,6 +18,19 @@
*/ */
namespace YYCC::ConfigManager { 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. /// @brief The base class of every setting.
/// @details Programmer can inherit this class and implement essential functions to create custom setting. /// @details Programmer can inherit this class and implement essential functions to create custom setting.
class AbstractSetting { class AbstractSetting {
@ -96,8 +109,8 @@ namespace YYCC::ConfigManager {
public: public:
/// @brief Load settings from file. /// @brief Load settings from file.
/// @details Before loading, all settings will be reset to default value first. /// @details Before loading, all settings will be reset to default value first.
/// @return True if success, otherwise false. /// @return What happend when loading config. This function always success.
bool Load(); ConfigLoadResult Load();
/// @brief Save settings to file. /// @brief Save settings to file.
/// @return True if success, otherwise false. /// @return True if success, otherwise false.
bool Save(); bool Save();

View File

@ -1,17 +0,0 @@
#pragma once
#include "YYCCInternal.hpp"
#include "EncodingHelper.hpp"
#include "StringHelper.hpp"
#include "ConsoleHelper.hpp"
#include "COMHelper.hpp"
#include "DialogHelper.hpp"
#include "ParserHelper.hpp"
#include "IOHelper.hpp"
#include "WinFctHelper.hpp"
#include "StdPatch.hpp"
#include "ExceptionHelper.hpp"
#include "ConfigManager.hpp"
#include "ArgParser.hpp"

26
src/yycc.hpp Normal file
View 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 {}

View File

5
src/yycc/carton/clap.hpp Normal file
View File

@ -0,0 +1,5 @@
#pragma once
namespace yycc::carton::clap {
}

View File

@ -0,0 +1,28 @@
#include "application.hpp"
#define CLAP ::yycc::carton::clap
namespace yycc::carton::clap::application {
using Summary = CLAP::summary::Summary;
using OptionCollection = CLAP::option::OptionCollection;
using VariableCollection = CLAP::variable::VariableCollection;
Application::Application(Summary &&summary, OptionCollection &&options, VariableCollection &&variables) :
summary(std::move(summary)), options(std::move(options)), variables(std::move(variables)) {}
Application::~Application() {}
const Summary &Application::get_summary() const {
return this->summary;
}
const OptionCollection &Application::get_options() const {
return this->options;
}
const VariableCollection &Application::get_variables() const {
return this->variables;
}
} // namespace yycc::carton::clap::application

View File

@ -0,0 +1,32 @@
#pragma once
#include "summary.hpp"
#include "option.hpp"
#include "variable.hpp"
#include "../../macro/class_copy_move.hpp"
#define NS_YYCC_CLAP ::yycc::carton::clap
namespace yycc::carton::clap::application {
class Application {
public:
Application(NS_YYCC_CLAP::summary::Summary&& summary,
NS_YYCC_CLAP::option::OptionCollection&& options,
NS_YYCC_CLAP::variable::VariableCollection&& variables);
~Application();
YYCC_DEFAULT_COPY_MOVE(Application);
public:
const NS_YYCC_CLAP::summary::Summary& get_summary() const;
const NS_YYCC_CLAP::option::OptionCollection& get_options() const;
const NS_YYCC_CLAP::variable::VariableCollection& get_variables() const;
private:
NS_YYCC_CLAP::summary::Summary summary;
NS_YYCC_CLAP::option::OptionCollection options;
NS_YYCC_CLAP::variable::VariableCollection variables;
};
}
#undef NS_YYCC_CLAP

View File

@ -0,0 +1,92 @@
#include "manual.hpp"
#include "../termcolor.hpp"
#include "../../patch/stream.hpp"
#include "../../patch/format.hpp"
#define CLAP ::yycc::carton::clap
#define TABULATE ::yycc::carton::tabulate
#define TERMCOLOR ::yycc::carton::termcolor
#define FORMAT ::yycc::patch::format
using namespace ::yycc::patch::stream;
namespace yycc::carton::clap::manual {
#pragma region Manual Translation
ManualTr::ManualTr() :
author_and_version(u8"Invented by {0}. Version {1}."), usage_title(u8"Usage:"), usage_body(u8"{0} <options> ..."),
avail_opt(u8"Available options:"), avail_var(u8"Available environment variables:") {}
ManualTr::~ManualTr() {}
#pragma endregion
#pragma region Manual
using Application = CLAP::application::Application;
using Tabulate = TABULATE::Tabulate;
static constexpr char8_t INDENT[] = u8" ";
Manual::Manual(const Application &app, ManualTr &&trctx) : trctx(std::move(trctx)), app(app), opt_printer(3), var_printer(2) {
this->setup_table();
this->fill_opt_table();
this->fill_var_table();
}
Manual::~Manual() {}
void Manual::setup_table() {
this->opt_printer.show_header(false);
this->opt_printer.show_bar(false);
this->opt_printer.set_prefix(INDENT);
this->var_printer.show_header(false);
this->var_printer.show_bar(false);
this->var_printer.set_prefix(INDENT);
}
void Manual::fill_opt_table() {
const auto &options = app.get_options();
for (const auto &reg_opt : options.all_options()) {
const auto &opt = reg_opt.get_option();
}
}
void Manual::fill_var_table() {}
void Manual::print_version(std::ostream &dst) const {
const auto &summary = this->app.get_summary();
TERMCOLOR::cprintln(summary.get_name(), TERMCOLOR::Color::Yellow, TERMCOLOR::Color::Default, TERMCOLOR::Attribute::Default, dst);
dst << summary.get_name() << std::endl;
dst << FORMAT::format(trctx.author_and_version, summary.get_author(), summary.get_version()) << std::endl;
dst << summary.get_description() << std::endl;
dst << std::endl;
}
void Manual::print_help(std::ostream &dst) const {
this->print_version();
TERMCOLOR::cprintln(trctx.usage_title, TERMCOLOR::Color::Yellow, TERMCOLOR::Color::Default, TERMCOLOR::Attribute::Default, dst);
dst << INDENT << FORMAT::format(trctx.usage_body, app.get_summary().get_bin_name()) << std::endl;
const auto &variables = app.get_variables();
if (!variables.empty()) {
TERMCOLOR::cprintln(trctx.avail_var, TERMCOLOR::Color::Yellow, TERMCOLOR::Color::Default, TERMCOLOR::Attribute::Default, dst);
this->var_printer.print(dst);
dst << std::endl;
}
const auto &options = app.get_options();
if (!options.empty()) {
TERMCOLOR::cprintln(trctx.avail_opt, TERMCOLOR::Color::Yellow, TERMCOLOR::Color::Default, TERMCOLOR::Attribute::Default, dst);
this->opt_printer.print(dst);
dst << std::endl;
}
}
#pragma endregion
} // namespace yycc::carton::clap::manual

View File

@ -0,0 +1,49 @@
#pragma once
#include "application.hpp"
#include "../../macro/class_copy_move.hpp"
#include "../tabulate.hpp"
#include <iostream>
#define NS_YYCC_CLAP ::yycc::carton::clap
#define NS_YYCC_TABULATE ::yycc::carton::tabulate
namespace yycc::carton::clap::manual {
struct ManualTr {
public:
ManualTr();
~ManualTr();
YYCC_DEFAULT_COPY_MOVE(ManualTr);
public:
std::u8string author_and_version;
std::u8string usage_title, usage_body;
std::u8string avail_opt, avail_var;
};
class Manual {
public:
Manual(const NS_YYCC_CLAP::application::Application& app, ManualTr&& trctx = ManualTr());
~Manual();
YYCC_DEFAULT_COPY_MOVE(Manual);
private:
void setup_table();
void fill_opt_table();
void fill_var_table();
public:
void print_version(std::ostream& dst = std::cout) const;
void print_help(std::ostream& dst = std::cout) const;
private:
ManualTr trctx;
const NS_YYCC_CLAP::application::Application app;
NS_YYCC_TABULATE::Tabulate opt_printer;
NS_YYCC_TABULATE::Tabulate var_printer;
};
} // namespace yycc::carton::clap::manual
#undef NS_YYCC_TABULATE
#undef NS_YYCC_CLAP

View File

@ -0,0 +1,178 @@
#include "option.hpp"
#include "../../patch/format.hpp"
#include <stdexcept>
#define TYPES ::yycc::carton::clap::types
#define FORMAT ::yycc::patch::format
namespace yycc::carton::clap::option {
#pragma region Option
Option::Option(std::optional<std::u8string_view> short_name,
std::optional<std::u8string_view> long_name,
std::optional<std::u8string_view> value_hint,
const std::u8string& description) :
short_name(short_name), long_name(long_name), value_hint(value_hint), description(description) {
if (!short_name.has_value() && !long_name.has_value()) {
throw std::logic_error("must have at least one name, short or long name");
}
if (short_name.has_value()) {
const auto& short_name_value = short_name.value();
if (!legal_short_name(short_name_value)) {
throw std::logic_error(FORMAT::format("invalid short name {}", short_name_value));
}
}
if (long_name.has_value()) {
const auto& long_name_value = long_name.value();
if (!legal_long_name(long_name_value)) {
throw std::logic_error(FORMAT::format("invalid long name {}", long_name_value));
}
}
}
Option::~Option() {}
std::optional<std::u8string_view> Option::get_short_name() const {
return this->short_name;
}
std::optional<std::u8string_view> Option::get_long_name() const {
return this->long_name;
}
std::optional<std::u8string_view> Option::get_value_hint() const {
return this->value_hint;
}
std::u8string_view Option::get_description() const {
return this->description;
}
std::u8string Option::to_showcase_name() const {
if (short_name.has_value()) {
if (long_name.has_value()) {
return FORMAT::format(u8"{}{} {}{}", TYPES::DASH, short_name.value(), TYPES::DOUBLE_DASH, long_name.value());
} else {
return FORMAT::format(u8"{}{}", TYPES::DASH, short_name.value());
}
} else {
if (long_name.has_value()) {
return FORMAT::format(u8"{}{}", TYPES::DOUBLE_DASH, long_name.value());
} else {
throw std::runtime_error("both long name and short name are empty");
}
}
}
std::u8string Option::to_showcase_value() const {
if (value_hint.has_value()) {
return FORMAT::format(u8"<{}>", value_hint.value());
} else {
return {};
}
}
bool Option::legal_short_name(const std::u8string_view& name) {
if (name.empty()) return false;
if (name.starts_with(TYPES::DASH)) return false;
return true;
}
bool Option::legal_long_name(const std::u8string_view& name) {
if (name.empty()) return false;
return true;
}
#pragma endregion
#pragma region Registered Option
RegisteredOption::RegisteredOption(TYPES::Token token, Option&& option) : token(token), option(std::move(option)) {}
RegisteredOption::~RegisteredOption() {}
TYPES::Token RegisteredOption::get_token() const {
return this->token;
}
const Option& RegisteredOption::get_option() const {
return this->option;
}
#pragma endregion
#pragma region Option Collection
OptionCollection::OptionCollection() : short_names(), long_names(), options() {}
OptionCollection::~OptionCollection() {}
TYPES::Token OptionCollection::add_option(Option&& opt) {
auto token = this->options.size();
const auto& short_name = opt.get_short_name();
if (short_name.has_value()) {
std::u8string short_name_value(short_name.value());
if (this->long_names.contains(short_name_value)) {
throw std::logic_error(
FORMAT::format("short name {} is duplicated with same long name", short_name_value));
}
auto [_, ok] = this->short_names.try_emplace(short_name_value, token);
if (!ok) {
throw std::logic_error(FORMAT::format("duplicate short name {}", short_name_value));
}
}
const auto& long_name = opt.get_long_name();
if (long_name.has_value()) {
std::u8string long_name_value(long_name.value());
if (this->short_names.contains(long_name_value)) {
throw std::logic_error(
FORMAT::format("long name {} is duplicated with same short name", long_name_value));
}
auto [_, ok] = this->long_names.try_emplace(long_name_value, token);
if (!ok) {
throw std::logic_error(FORMAT::format("duplicate long name {}", long_name_value));
}
}
this->options.emplace_back(RegisteredOption(token, std::move(opt)));
return token;
}
std::optional<TYPES::Token> OptionCollection::find_long_name(const std::u8string_view& long_name) const {
auto finder = this->long_names.find(std::u8string(long_name));
if (finder == this->long_names.end()) return std::nullopt;
else return finder->second;
}
std::optional<TYPES::Token> OptionCollection::find_short_name(const std::u8string_view& short_name) const {
auto finder = this->short_names.find(std::u8string(short_name));
if (finder == this->short_names.end()) return std::nullopt;
else return finder->second;
}
bool OptionCollection::has_option(TYPES::Token token) const {
return token < this->options.size();
}
const Option& OptionCollection::get_option(TYPES::Token token) const {
return this->options.at(token).get_option();
}
const std::vector<RegisteredOption>& OptionCollection::all_options() const {
return this->options;
}
size_t OptionCollection::length() const {
return this->options.size();
}
bool OptionCollection::empty() const {
return this->options.empty();
}
#pragma endregion
} // namespace yycc::carton::clap::option

View File

@ -0,0 +1,81 @@
#pragma once
#include "types.hpp"
#include "../../macro/class_copy_move.hpp"
#include <optional>
#include <string>
#include <vector>
#include <map>
#define NS_YYCC_CLAP_TYPES ::yycc::carton::clap::types
namespace yycc::carton::clap::option {
class Option {
public:
Option(std::optional<std::u8string_view> short_name,
std::optional<std::u8string_view> long_name,
std::optional<std::u8string_view> value_hint,
const std::u8string& description);
~Option();
YYCC_DEFAULT_COPY_MOVE(Option)
public:
std::optional<std::u8string_view> get_short_name() const;
std::optional<std::u8string_view> get_long_name() const;
std::optional<std::u8string_view> get_value_hint() const;
std::u8string_view get_description() const;
std::u8string to_showcase_name() const;
std::u8string to_showcase_value() const;
private:
static bool legal_short_name(const std::u8string_view& name);
static bool legal_long_name(const std::u8string_view& name);
private:
std::optional<std::u8string> short_name;
std::optional<std::u8string> long_name;
std::optional<std::u8string> value_hint;
std::u8string description;
};
class RegisteredOption {
public:
RegisteredOption(NS_YYCC_CLAP_TYPES::Token token, Option&& option);
~RegisteredOption();
YYCC_DEFAULT_COPY_MOVE(RegisteredOption)
public:
NS_YYCC_CLAP_TYPES::Token get_token() const;
const Option& get_option() const;
private:
NS_YYCC_CLAP_TYPES::Token token;
Option option;
};
class OptionCollection {
public:
OptionCollection();
~OptionCollection();
YYCC_DEFAULT_COPY_MOVE(OptionCollection)
public:
NS_YYCC_CLAP_TYPES::Token add_option(Option&& opt);
std::optional<NS_YYCC_CLAP_TYPES::Token> find_long_name(const std::u8string_view& long_name) const;
std::optional<NS_YYCC_CLAP_TYPES::Token> find_short_name(const std::u8string_view& short_name) const;
bool has_option(NS_YYCC_CLAP_TYPES::Token token) const;
const Option& get_option(NS_YYCC_CLAP_TYPES::Token token) const;
const std::vector<RegisteredOption>& all_options() const;
size_t length() const;
bool empty() const;
private:
std::map<std::u8string, NS_YYCC_CLAP_TYPES::Token> short_names;
std::map<std::u8string, NS_YYCC_CLAP_TYPES::Token> long_names;
std::vector<RegisteredOption> options;
};
} // namespace yycc::carton::clap::option
#undef NS_YYCC_CLAP_TYPES

View File

@ -0,0 +1,34 @@
#include "summary.hpp"
namespace yycc::carton::clap::summary {
Summary::Summary(const std::u8string_view &name,
const std::u8string_view &bin_name,
const std::u8string_view &author,
const std::u8string_view &version,
const std::u8string_view &description) :
name(name), bin_name(bin_name), author(author), version(version), description(description) {}
Summary::~Summary() {}
std::u8string_view Summary::get_name() const {
return this->name;
}
std::u8string_view Summary::get_bin_name() const {
return this->bin_name;
}
std::u8string_view Summary::get_author() const {
return this->author;
}
std::u8string_view Summary::get_version() const {
return this->version;
}
std::u8string_view Summary::get_description() const {
return this->description;
}
} // namespace yycc::carton::clap::summary

View File

@ -0,0 +1,29 @@
#pragma once
#include "../../macro/class_copy_move.hpp"
#include <string>
#include <string_view>
namespace yycc::carton::clap::summary {
class Summary {
public:
Summary(const std::u8string_view& name,
const std::u8string_view& bin_name,
const std::u8string_view& author,
const std::u8string_view& version,
const std::u8string_view& description);
~Summary();
YYCC_DEFAULT_COPY_MOVE(Summary)
public:
std::u8string_view get_name() const;
std::u8string_view get_bin_name() const;
std::u8string_view get_author() const;
std::u8string_view get_version() const;
std::u8string_view get_description() const;
private:
std::u8string name, bin_name, author, version, description;
};
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <expected>
#include <string_view>
namespace yycc::carton::clap::types {
/// @brief All possible error kind occurs in this module.
enum class ClapError {
};
/// @brief The result type used in this module.
template<typename T>
using ClapResult = std::expected<T, ClapError>;
/// @brief The dash prefix used for short name of option.
inline constexpr std::u8string_view DASH = u8"-";
/// @brief The double dash prefix used by long name of option.
inline constexpr std::u8string_view DOUBLE_DASH = u8"--";
/**
* @brief An unique token type.
* @details
* When outside code registering an option or variable,
* there must be a token returned by manager.
* When outside code want to visit this registered item again,
* they should provide this token returned when registering.
*
* Its value actually is the index of its stored vector.
* So this type is an alias to vector size type.
*/
using Token = size_t;
} // namespace yycc::carton::clap::types

View File

@ -0,0 +1,93 @@
#include "variable.hpp"
#include "../../patch/format.hpp"
#include <stdexcept>
#define TYPES ::yycc::carton::clap::types
#define FORMAT ::yycc::patch::format
namespace yycc::carton::clap::variable {
#pragma region Variable
Variable::Variable(const std::u8string_view &name, const std::u8string_view &description) : name(name), description(description) {
if (name.empty()) {
throw std::logic_error("the name of variable should not be empty");
}
}
Variable::~Variable() {}
std::u8string_view Variable::get_name() const {
return this->name;
}
std::u8string_view Variable::get_description() const {
return this->description;
}
#pragma endregion
#pragma region Registered Variable
RegisteredVariable::RegisteredVariable(TYPES::Token token, Variable &&variable) : token(token), variable(std::move(variable)) {}
RegisteredVariable::~RegisteredVariable() {}
TYPES::Token RegisteredVariable::get_token() const {
return this->token;
}
const Variable &RegisteredVariable::get_variable() const {
return this->variable;
}
#pragma endregion
#pragma region Variable Collection
VariableCollection::VariableCollection() : names(), variables() {}
VariableCollection::~VariableCollection() {}
TYPES::Token VariableCollection::add_variable(Variable &&var) {
auto token = this->variables.size();
std::u8string name(var.get_name());
auto [_, ok] = this->names.try_emplace(name, token);
if (!ok) {
throw std::logic_error(FORMAT::format("duplicated variable name {}", name));
}
this->variables.emplace_back(RegisteredVariable(token, std::move(var)));
return token;
}
std::optional<TYPES::Token> VariableCollection::find_name(const std::u8string_view &name) const {
auto finder = this->names.find(std::u8string(name));
if (finder == this->names.end()) return std::nullopt;
else return finder->second;
}
bool VariableCollection::has_variable(TYPES::Token token) const {
return token < this->variables.size();
}
const Variable &VariableCollection::get_variable(TYPES::Token token) const {
return this->variables.at(token).get_variable();
}
const std::vector<RegisteredVariable> &VariableCollection::all_variables() const {
return this->variables;
}
size_t VariableCollection::length() const {
return this->variables.size();
}
bool VariableCollection::empty() const {
return this->variables.empty();
}
#pragma endregion
} // namespace yycc::carton::clap::variable

View File

@ -0,0 +1,65 @@
#pragma once
#include "types.hpp"
#include "../../macro/class_copy_move.hpp"
#include <optional>
#include <string>
#include <vector>
#include <map>
#define NS_YYCC_CLAP_TYPES ::yycc::carton::clap::types
namespace yycc::carton::clap::variable {
class Variable {
public:
Variable(const std::u8string_view& name, const std::u8string_view& description);
~Variable();
YYCC_DEFAULT_COPY_MOVE(Variable)
public:
std::u8string_view get_name() const;
std::u8string_view get_description() const;
private:
std::u8string name;
std::u8string description;
};
class RegisteredVariable {
public:
RegisteredVariable(NS_YYCC_CLAP_TYPES::Token token, Variable&& variable);
~RegisteredVariable();
YYCC_DEFAULT_COPY_MOVE(RegisteredVariable)
public:
NS_YYCC_CLAP_TYPES::Token get_token() const;
const Variable& get_variable() const;
private:
NS_YYCC_CLAP_TYPES::Token token;
Variable variable;
};
class VariableCollection {
public:
VariableCollection();
~VariableCollection();
YYCC_DEFAULT_COPY_MOVE(VariableCollection)
public:
NS_YYCC_CLAP_TYPES::Token add_variable(Variable&& var);
std::optional<NS_YYCC_CLAP_TYPES::Token> find_name(const std::u8string_view& name) const;
bool has_variable(NS_YYCC_CLAP_TYPES::Token token) const;
const Variable& get_variable(NS_YYCC_CLAP_TYPES::Token token) const;
const std::vector<RegisteredVariable>& all_variables() const;
size_t length() const;
bool empty() const;
private:
std::map<std::u8string, NS_YYCC_CLAP_TYPES::Token> names;
std::vector<RegisteredVariable> variables;
};
} // namespace yycc::carton::clap::variable
#undef NS_YYCC_CLAP_TYPES

View File

@ -0,0 +1,242 @@
#include "csconsole.hpp"
#include "../macro/os_detector.hpp"
#include "../string/op.hpp"
#include "../string/reinterpret.hpp"
#include "../encoding/windows.hpp"
#include <iostream>
#include <cstdarg>
#include <cstdio>
#if defined(YYCC_OS_WINDOWS)
#include "../windows/import_guard_head.hpp"
#include <Windows.h>
#include "../windows/import_guard_tail.hpp"
#endif
#define OP ::yycc::string::op
#define REINTERPRET ::yycc::string::reinterpret
#define ENC ::yycc::encoding::windows
namespace yycc::carton::csconsole {
#pragma region Windows Console Specific Functions
#if defined(YYCC_OS_WINDOWS)
template<bool BIsConsole>
static std::u8string win_console_read(HANDLE hStdIn) {
using TChar = std::conditional_t<BIsConsole, wchar_t, char>;
// Prepare an internal buffer because the read data may not be fully used.
// For example, we may read x\ny in a single calling but after processing \n, this function will return
// so y will temporarily stored in this internal buffer for next using.
// Thus this function is not thread safe.
static std::basic_string<TChar> internal_buffer;
// create return value buffer
std::basic_string<TChar> return_buffer;
// Prepare some variables
DWORD dwReadNumberOfChars;
TChar szReadChars[64];
size_t eol_pos;
// try fetching EOL
while (true) {
// if internal buffer is empty,
// try fetching it.
if (internal_buffer.empty()) {
// console and non-console use different method to read.
if constexpr (BIsConsole) {
// console handle, use ReadConsoleW.
// read from console, the read data is wchar based
if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(TChar), &dwReadNumberOfChars, NULL)) break;
} else {
// anything else, use ReadFile instead.
// the read data is utf8 based
if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL)) break;
}
// send to internal buffer
if (dwReadNumberOfChars == 0) break;
internal_buffer.append(szReadChars, dwReadNumberOfChars);
}
// try finding EOL in internal buffer
if constexpr (std::is_same_v<TChar, char>) eol_pos = internal_buffer.find_first_of('\n');
else eol_pos = internal_buffer.find_first_of(L'\n');
// check finding result
if (eol_pos == std::wstring::npos) {
// the whole string do not include EOL, fully appended to return value
return_buffer += internal_buffer;
internal_buffer.clear();
// need more data, continue while
} else {
// split result
// push into result and remain some in internal buffer.
return_buffer.append(internal_buffer, 0u, eol_pos);
internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place.
// break while mean success finding
break;
}
}
// post-process for return value
std::u8string real_return_buffer;
if constexpr (BIsConsole) {
// console mode need convert wchar to utf8
auto rv = ENC::to_utf8(return_buffer);
if (rv.has_value()) real_return_buffer = std::move(rv.value());
} else {
// non-console just copt the result
real_return_buffer = REINTERPRET::as_utf8(return_buffer);
}
// every mode need delete \r words
OP::replace(real_return_buffer, u8"\r", u8"");
// return value
return real_return_buffer;
}
static void win_console_write(const std::u8string_view& strl, bool to_stderr) {
// Prepare some Win32 variables
// fetch stdout handle first
HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
DWORD dwConsoleMode;
DWORD dwWrittenNumberOfChars;
// if stdout was redirected, this handle may point to a file handle or anything else,
// WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
// console handle, use WriteConsoleW.
// convert utf8 string to wide char first
std::wstring wstrl = ENC::to_wchar(strl).value();
size_t wstrl_size = wstrl.size();
// write string with size check
if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(wstrl_size), &dwWrittenNumberOfChars, NULL);
}
} else {
// anything else, use WriteFile instead.
// WriteFile do not need extra convertion, because it is direct writing.
// check whether string length is overflow
size_t strl_size = strl.size() * sizeof(std::u8string_view::value_type);
// write string with size check
if (strl_size <= std::numeric_limits<DWORD>::max()) {
WriteFile(hStdOut, strl.data(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
}
}
}
#endif
#pragma endregion
#pragma region Read Functions
std::u8string read_line() {
#if defined(YYCC_OS_WINDOWS)
// get stdin mode
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
// use different method to get according to whether stdin is redirected
DWORD dwConsoleMode;
if (GetConsoleMode(hStdIn, &dwConsoleMode)) {
return win_console_read<true>(hStdIn);
} else {
return win_console_read<false>(hStdIn);
}
#else
// in linux, directly use C++ function to fetch.
std::string cmd;
if (std::getline(std::cin, cmd).fail()) cmd.clear();
return REINTERPRET::as_utf8(cmd);
#endif
}
#pragma endregion
#pragma region Write Functions
template<bool BNeedFmt, bool BIsErr, bool BHasEOL>
static void raw_write(const char8_t* u8_fmt, va_list argptr) {
// Buiild string need to be written first
// If no format string or plain string for writing, return.
if (u8_fmt == nullptr) return;
// Build or simply copy string
std::u8string strl;
if constexpr (BNeedFmt) {
// treat as format string
va_list argcpy;
va_copy(argcpy, argptr);
strl = OP::vprintf(u8_fmt, argcpy);
va_end(argcpy);
} else {
// treat as plain string
strl = u8_fmt;
}
// Checkout whether add EOL
if constexpr (BHasEOL) {
strl += u8'\n';
}
#if defined(YYCC_OS_WINDOWS)
// call Windows specific writer
win_console_write(strl, BIsErr);
#else
// in linux, directly use C function to write.
std::fputs(REINTERPRET::as_ordinary(strl.c_str()), BIsErr ? stderr : stdout);
#endif
}
void format(const char8_t* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
raw_write<true, false, false>(u8_fmt, argptr);
va_end(argptr);
}
void format_line(const char8_t* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
raw_write<true, false, true>(u8_fmt, argptr);
va_end(argptr);
}
void write(const char8_t* u8_strl) {
va_list empty{};
raw_write<false, false, false>(u8_strl, empty);
}
void write_line(const char8_t* u8_strl) {
va_list empty{};
raw_write<false, false, true>(u8_strl, empty);
}
void eformat(const char8_t* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
raw_write<true, true, false>(u8_fmt, argptr);
va_end(argptr);
}
void eformat_line(const char8_t* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
raw_write<true, true, true>(u8_fmt, argptr);
va_end(argptr);
}
void ewrite(const char8_t* u8_strl) {
va_list empty{};
raw_write<false, true, false>(u8_strl, empty);
}
void ewrite_line(const char8_t* u8_strl) {
va_list empty{};
raw_write<false, true, true>(u8_strl, empty);
}
#pragma endregion
}

View File

@ -0,0 +1,111 @@
#pragma once
#include <string>
/**
* @brief The helper providing universal C\# style console function and other console related stuff
* @details
* The origin of this namespace is coming from the requirement of UTF8 console in Windows.
* There are 3 ways to make Windows console enable UTF8 mode.
*
* First one is calling \c SetConsoleCP and \c SetConsoleOutputCP.
* The side effect of this is \c std::cin and \c std::cout is broken,
* however there is a patch for this issue.
*
* Second one is calling \c _set_mode with \c _O_U8TEXT or \c _O_U16TEXT to enable Unicode mode for Windows console.
* This also have side effect which is stronger than first one.
* All "puts" family functions (ASCII-based output functions) will throw assertion exception.
* You only can use "putws" family functions (wide-char-based output functions).
* However these functions can not be used without calling \c _set_mode in Windows design.
*
* There still is another method, using \c WriteConsoleW directly visiting console.
* This function family can output correct string without calling any extra functions!
* This method is what we adopted and finally become this namespace.
*
* Reference:
* \li https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows
* \li https://stackoverflow.com/questions/69830460/reading-utf-8-input
*
* For how to utilize this functions provided by this namespace, please view \ref console_helper.
*
* @warning
* All functions provided by this namespace are too aggressive.
* Once you use it, you should not use any other input output functions.
*
* @deprecated
* This namespace provided functions are too aggressive and can not cover all use scenario.
* So I start to give this up when migrating this namespace during developing YYCC 2.x version.
* I just do a simple type fix and rename when migrating this namespace to make it "just works".
* There is no suggestion for using this namespace. And there is no update for this namespace.
* Programmer should treat Windows UTF8 issue on their own.
*/
namespace yycc::carton::csconsole {
/**
* @brief Reads the next line of UTF8 characters from the standard input stream.
* @return
* The next line of UTF8 characters from the input stream.
* Empty string if user just press Enter key or function failed.
*/
std::u8string read_line();
/**
* @brief
* Writes the text representation of the specified object
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void format(const char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard output stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void format_line(const char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void write(const char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard output stream.
* @param[in] u8_strl The value to write.
*/
void write_line(const char8_t* u8_strl);
/**
* @brief
* Writes the text representation of the specified object
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void eformat(const char8_t* u8_fmt, ...);
/**
* @brief
* Writes the text representation of the specified object,
* followed by the current line terminator,
* to the standard error stream using the specified format information.
* @param[in] u8_fmt The format string.
* @param[in] ... The arguments of format string.
*/
void eformat_line(const char8_t* u8_fmt, ...);
/**
* @brief Writes the specified string value to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ewrite(const char8_t* u8_strl);
/**
* @brief
* Writes the specified string value, followed by the current line terminator,
* to the standard error stream.
* @param[in] u8_strl The value to write.
*/
void ewrite_line(const char8_t* u8_strl);
}

620
src/yycc/carton/ironpad.cpp Normal file
View File

@ -0,0 +1,620 @@
#include "ironpad.hpp"
#include "../macro/os_detector.hpp"
#include "../macro/stl_detector.hpp"
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
#include "../windows/winfct.hpp"
#include "../string/op.hpp"
#include "../string/reinterpret.hpp"
#include "../encoding/windows.hpp"
#include "../patch/ptr_pad.hpp"
#include "../patch/fopen.hpp"
#include "../macro/class_copy_move.hpp"
#include <filesystem>
#include <cstdarg>
#include <cstdio>
#include <cinttypes>
#include <mutex>
#include "../windows/import_guard_head.hpp"
#include <Windows.h>
#include <DbgHelp.h>
#include "../windows/import_guard_tail.hpp"
#define OP ::yycc::string::op
#define ENC ::yycc::encoding::windows
#define REINTERPRET ::yycc::string::reinterpret
#define WINFCT ::yycc::windows::winfct
#define FOPEN ::yycc::patch::fopen
using namespace std::literals::string_view_literals;
namespace yycc::carton::ironpad {
#pragma region Singleton Guard
static LONG WINAPI unhandled_exception_handler(LPEXCEPTION_POINTERS);
/**
* @brief The "singleton" guard class for unhandled excepetion handler.
* @details
* The class making sure that there is only one unhandled exception handler was run in the same process,
* when unhandled exception occurs, and prevent any futher possible recursive calling (exception in exception handler).
*/
class SingletonGuard {
public:
SingletonGuard() :
m_CoreMutex(), m_IsRegistered(false), m_IsProcessing(false), m_PrevProcHandler(nullptr), m_UserCallback(nullptr),
m_SingletonMutex(NULL) {}
~SingletonGuard() { shutdown(); }
YYCC_DELETE_COPY_MOVE(SingletonGuard)
public:
/**
* @brief Try to register unhandled exception handler.
* @details There is no bad outcome when calling this function multiple times.
* @return True if success, otherwise false.
*/
bool startup(ExceptionCallback callback) {
std::lock_guard<std::mutex> locker(m_CoreMutex);
// if we have registered, return
if (m_IsRegistered) return false;
// check singleton
// build mutex string first
auto mutex_name = OP::printf(u8"Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}", GetCurrentProcessId());
auto wmutex_name = ENC::to_wchar(mutex_name).value();
// create mutex
m_SingletonMutex = CreateMutexW(NULL, FALSE, wmutex_name.c_str());
DWORD errcode = GetLastError();
// check whether be created
if (m_SingletonMutex == NULL) return false;
if (errcode == ERROR_ALREADY_EXISTS) {
CloseHandle(m_SingletonMutex);
m_SingletonMutex = NULL;
return false;
}
// okey, we can register it.
// backup old handler
m_PrevProcHandler = SetUnhandledExceptionFilter(unhandled_exception_handler);
// set user callback
m_UserCallback = callback;
// mark registered
m_IsRegistered = true;
return true;
}
/**
* @brief Try to unregister unhandled exception handler.
* @details There is no bad outcome when calling this function multiple times.
* @return True if success, otherwise false.
*/
bool shutdown() {
std::lock_guard<std::mutex> locker(m_CoreMutex);
// if we are not registered, skip
if (!m_IsRegistered) return false;
// 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;
return true;
}
public:
/**
* @brief Check whether handler is registered.
* @return True if it is, otherwise false.
*/
bool is_registered() 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 is_processing() 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 get_prev_proc_handler() 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 get_user_callback() 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 start_processing() {
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 start_processing() return true.
*/
void stop_processing() {
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 SingletonGuard g_SingletonGuard;
#pragma endregion
#pragma region Exception Dumper
class ExceptionDumper {
public:
ExceptionDumper() {}
~ExceptionDumper() {}
YYCC_DELETE_COPY_MOVE(ExceptionDumper)
public:
CallbackInfo execute(LPEXCEPTION_POINTERS info) {
// fetch file path first
auto log_path = build_file_path(FileKind::LogFile);
auto coredump_path = build_file_path(FileKind::CoredumpFile);
// and report their status
if (log_path.has_value()) {
log_format_line(nullptr, u8"Crash Log: %s", log_path.value().c_str());
} else {
log_write_line(nullptr, u8"Crash occurs, but we can not create crash log!");
}
if (coredump_path.has_value()) {
log_format_line(nullptr, u8"Crash Coredump: %s", coredump_path.value().c_str());
} else {
log_write_line(nullptr, u8"Crash occurs, but we can not create coredump!");
}
// Write crash log
{
// open file stream if possible
FOPEN::SmartStdFile fs(nullptr);
if (log_path.has_value()) {
fs.reset(FOPEN::fopen(log_path.value().c_str(), u8"wb"));
}
// output basic infos
// record exception type first
PEXCEPTION_RECORD rec = info->ExceptionRecord;
log_format_line(fs.get(),
u8"Unhandled exception occured at 0x%" PRIXPTR_LPAD PRIXPTR ": %s (%" PRIu32 ").",
rec->ExceptionAddress,
get_code_message(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 char8_t* op = rec->ExceptionInformation[0] == 0 ? u8"read"
: rec->ExceptionInformation[0] == 1 ? u8"written"
: u8"executed";
log_format_line(fs.get(),
u8"The data at memory address 0x%" PRIXPTR_LPAD PRIxPTR " could not be %s.",
rec->ExceptionInformation[1],
op);
}
}
// output stacktrace
do_backtrace(fs.get(), info->ContextRecord, 1024);
}
// Write coredump
if (coredump_path.has_value()) {
do_coredump(coredump_path.value(), info);
}
// return path for user callback
return CallbackInfo{.log_path = std::move(log_path), .coredump_path = std::move(coredump_path)};
}
private:
/**
* @brief All kind of dumped files.
*/
enum class FileKind { LogFile, CoredumpFile };
/**
* @brief Build path to file stored on disk including exception data with given kind.
* @param[in] kind The kind of file to be built.
* @return The built path or nothing if error occurs.
*/
std::optional<std::u8string> build_file_path(FileKind kind) {
// build 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
std::u8string u8_process_name;
{
// get full path of process
auto u8_process_path = WINFCT::get_module_file_name(NULL);
if (!u8_process_path.has_value()) return std::nullopt;
// extract file name from full path by std::filesystem::path
std::filesystem::path process_path(u8_process_path.value());
u8_process_name = process_path.filename().u8string();
}
// then get process id
DWORD process_id = GetCurrentProcessId();
// conbine them as a file name prefix
auto u8_filename_prefix = OP::printf(u8"%s.%" PRIu32, u8_process_name.c_str(), process_id);
// then get file name for log and minidump
std::u8string u8_filename;
switch (kind) {
case FileKind::LogFile:
u8_filename = u8_filename_prefix + u8".log";
break;
case FileKind::CoredumpFile:
u8_filename = u8_filename_prefix + u8".dmp";
break;
default:
u8_filename = u8_filename_prefix;
break;
}
// fetch crash report path
// get local appdata folder
auto u8_localappdata_path = WINFCT::get_known_path(WINFCT::KnownDirectory::LocalAppData);
if (!u8_localappdata_path.has_value()) return std::nullopt;
// convert to std::filesystem::path
std::filesystem::path crashreport_path(u8_localappdata_path.value());
// slash into crash report folder
crashreport_path /= u8"IronPad";
// use create function to make sure it is existing
std::filesystem::create_directories(crashreport_path);
// build log path and coredump path
// build std::filesystem::path first
std::filesystem::path file_path = crashreport_path / u8_filename;
// output to result
return file_path.u8string();
}
/**
* @brief Do stack trace for given exception.
* @details
* This function will do stack trace with given maximum depth as much as possible
* and output trace info into given file stream and \c stderr.
*/
void do_backtrace(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
log_write_line(fs, 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) {
log_write_line(fs, u8"..."); // indicate there are some frames not listed
break;
}
// get module name
std::u8string module_name(u8"<unknown module>");
DWORD64 module_base;
if (module_base = SymGetModuleBase64(process, frame.AddrPC.Offset)) {
auto rv = WINFCT::get_module_file_name((HINSTANCE) module_base);
if (rv.has_value()) module_name = rv.value();
}
// get source file and line
const char8_t* source_file = 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 = REINTERPRET::as_utf8(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.
log_format_line(fs,
u8"0x%" PRIXPTR_LPAD PRIX64 "[%s+0x%" PRIXPTR_LPAD 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);
}
/**
* @brief Do coredump for given exception.
* @details This function will write coredump of given exception into given file path.
*/
void do_coredump(const std::u8string_view& u8_filename, LPEXCEPTION_POINTERS info) {
// convert file encoding.
// it must be okey.
auto filename = ENC::to_wchar(u8_filename).value();
// 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);
}
}
private:
/**
* @brief Get human-readable exception string from given exception code.
* @param[in] code Exception code
* @return The string view to corresponding exception explanation string.
*/
const std::u8string_view get_code_message(DWORD code) {
switch (code) {
case EXCEPTION_ACCESS_VIOLATION:
return u8"access violation"sv;
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
return u8"array index out of bound"sv;
case EXCEPTION_BREAKPOINT:
return u8"breakpoint reached"sv;
case EXCEPTION_DATATYPE_MISALIGNMENT:
return u8"misaligned data access"sv;
case EXCEPTION_FLT_DENORMAL_OPERAND:
return u8"operand had denormal value"sv;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
return u8"floating-point division by zero"sv;
case EXCEPTION_FLT_INEXACT_RESULT:
return u8"no decimal fraction representation for value"sv;
case EXCEPTION_FLT_INVALID_OPERATION:
return u8"invalid floating-point operation"sv;
case EXCEPTION_FLT_OVERFLOW:
return u8"floating-point overflow"sv;
case EXCEPTION_FLT_STACK_CHECK:
return u8"floating-point stack corruption"sv;
case EXCEPTION_FLT_UNDERFLOW:
return u8"floating-point underflow"sv;
case EXCEPTION_ILLEGAL_INSTRUCTION:
return u8"illegal instruction"sv;
case EXCEPTION_IN_PAGE_ERROR:
return u8"inaccessible page"sv;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
return u8"integer division by zero"sv;
case EXCEPTION_INT_OVERFLOW:
return u8"integer overflow"sv;
case EXCEPTION_INVALID_DISPOSITION:
return u8"documentation says this should never happen"sv;
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
return u8"can't continue after a noncontinuable exception"sv;
case EXCEPTION_PRIV_INSTRUCTION:
return u8"attempted to execute a privileged instruction"sv;
case EXCEPTION_SINGLE_STEP:
return u8"one instruction has been executed"sv;
case EXCEPTION_STACK_OVERFLOW:
return u8"stack overflow"sv;
default:
return u8"unknown exception"sv;
}
}
/**
* @brief Write error message
* @details
* This function will write given string into given file stream and \c 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.
*/
void log_write_line(std::FILE* fs, const char8_t* strl) {
// write to file
if (fs != nullptr) {
std::fputs(REINTERPRET::as_ordinary(strl), fs);
std::fputs("\n", fs);
}
// write to stderr
std::fputs(REINTERPRET::as_ordinary(strl), stderr);
std::fputs("\n", stderr);
}
/**
* @brief Format error message.
* @details
* This function will format message first.
* And write them into given file stream and \c 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.
*/
void log_format_line(std::FILE* fs, const char8_t* fmt, ...) {
// write to file and console
va_list arg;
va_start(arg, fmt);
log_write_line(fs, OP::vprintf(fmt, arg).c_str());
va_end(arg);
}
};
#pragma endregion
#pragma region Core Implementation
static LONG WINAPI unhandled_exception_handler(LPEXCEPTION_POINTERS info) {
// try to start process current unhandled exception
// to prevent any possible recursive calling.
if (!g_SingletonGuard.start_processing()) {
// process exception
ExceptionDumper dumper;
auto pathinfo = dumper.execute(info);
// call user callback
ExceptionCallback user_callback = g_SingletonGuard.get_user_callback();
if (user_callback != nullptr) user_callback(pathinfo);
// stop process
g_SingletonGuard.stop_processing();
}
// if backup proc can be run, run it
// otherwise directly return.
auto prev_proc = g_SingletonGuard.get_prev_proc_handler();
if (prev_proc != nullptr) {
return prev_proc(info);
} else {
return EXCEPTION_CONTINUE_SEARCH;
}
}
#pragma endregion
} // namespace yycc::carton::ironpad
#endif
#pragma region Exposed Function
namespace yycc::carton::ironpad {
bool startup(ExceptionCallback callback) {
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
return g_SingletonGuard.startup(callback);
#else
// Do nothing
return false;
#endif
}
void shutdown() {
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
g_SingletonGuard.shutdown();
#else
// Do nothing
#endif
}
} // namespace yycc::carton::ironpad
#pragma endregion

View File

@ -0,0 +1,83 @@
#pragma once
#include <string>
#include <string_view>
#include <optional>
/**
* @brief Windows specific unhandled exception handler.
* @details
* This namespace is currently works on Windows.
* On other platforms, this namespace provided functions do nothing.
* For how to utilize this namespace, please see \ref exception_helper.
*
* This feature is originate from my created Virtools plugin.
* Because its user frequently trigger some weird behaviors but I have no idea about the detail of them.
* So I create this feature. So that I can order user upload error log and coredump to help my debugging.
* The original implementation of this feature is copied from chirs241097's open source project whose name I forgotten.
*
* After that, I split it from my plugin and let it become an independent library call IronPad,
* and use it in libcmo21 and etc.
* After few months, I created first version of YYCC and I move it into it and rename it as Exception Helper.
*
* Now we entering the second major version of YYCC, I decide restore its original name and put it with other homebrew features.
*/
namespace yycc::carton::ironpad {
/**
* @brief The path info passed into user callback.
* @details
* For all pathes stored in this struct, if it is \c std::nullopt,
* it means that handler fail to create this, otherwise it must be created.
*/
struct CallbackInfo {
std::optional<std::u8string> log_path; ///< The path to crash log file.
std::optional<std::u8string> coredump_path; ///< The path to coredump file.
};
/**
* @brief The callback function prototype which will be called when unhandled exception happened.
* @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.
* The timing of calling this callback is the end of writing all essential files and before exiting handler.
* In other words, all passed pathes are valid for visiting if they are not \c std::nullopt.
*
* 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 in GUI application.
*/
using ExceptionCallback = void (*)(const CallbackInfo& info);
/**
* @brief Register unhandled exception handler
* @details
* This function will set an internal function as unhandled exception handler.
*
* When unhandled exception raised,
* That internal function will output error stacktrace in standard output,
* and generate log file and dump file in \c \%LOCALAPPDATA\%/IronPad 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.
* @return True when success, otherwise false.
*/
bool startup(ExceptionCallback callback = nullptr);
/**
* @brief Unregister unhandled exception handler
* @details
* The reverse operation of startup().
*
* This function and startup() should always be used as a pair.
* You must call this function to release reources if you have called startup().
*
* This function usually is called at the end of program.
*
* It is safe that call this function multiple times, or call this function when startup() return false.
* It means that there is no compulsory check for the return value of startup() if you don't care it.
*/
void shutdown();
} // namespace yycc::carton::ironpad

591
src/yycc/carton/pycodec.cpp Normal file
View 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
View 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

View File

@ -0,0 +1,189 @@
#include "tabulate.hpp"
#include "wcwidth.hpp"
#include "../num/safe_op.hpp"
#include "../patch/stream.hpp"
#include <stdexcept>
#include <ranges>
#define WCWIDTH ::yycc::carton::wcwidth
#define SAFEOP ::yycc::num::safe_op
using namespace yycc::patch::stream;
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
// Show header
if (this->header_display) {
dst << 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 << item.get_text() << spaces_view.substr(0, diff) << " ";
}
dst << std::endl;
}
// Show bar
if (this->bar_display) {
dst << 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 << this->bar.get_text() << spaces_view.substr(0, diff) << " ";
}
dst << std::endl;
}
// Show data
for (const auto& row : this->rows) {
dst << 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 << item.get_text() << spaces_view.substr(0, diff) << " ";
}
dst << std::endl;
}
}
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

View 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;
};
}

View File

@ -0,0 +1,234 @@
#include "termcolor.hpp"
#include "../flag_enum.hpp"
#include "../patch/stream.hpp"
#include <stdexcept>
#include <bit>
#define FLAG_ENUM ::yycc::flag_enum
using namespace std::literals::string_view_literals;
using namespace yycc::patch::stream;
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 << 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

View 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

475
src/yycc/carton/wcwidth.cpp Normal file
View File

@ -0,0 +1,475 @@
#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 and call underlying function
return wcswidth(ENC::to_utf32(rhs).value());
}
} // namespace yycc::carton::wcwidth

View File

@ -0,0 +1,46 @@
#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 {
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
View 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

View 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
View 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
View 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
View 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
View 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);
}

View 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

View 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
View 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

View 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

View File

@ -0,0 +1,30 @@
#pragma once
#if (defined(YYCC_CC_MSVC) + defined(YYCC_CC_GCC) + defined(YYCC_CC_CLANG)) != 1
#error "Current compiler is not supported!"
#endif
namespace yycc::macro::compiler {
/// @brief The kind of compiler.
enum class CompilerKind {
Msvc, ///< MSVC
Gcc, ///< GCC
Clang, ///< Clang
};
/**
* @brief Fetch the compiler type.
* @return The kind of compiler.
*/
inline constexpr CompilerKind get_compiler() {
#if defined(YYCC_CC_MSVC)
return CompilerKind::Msvc;
#elif defined(YYCC_CC_GCC)
return CompilerKind::Gcc;
#else
return CompilerKind::Clang;
#endif
}
} // namespace yycc::macro::compiler

View File

@ -0,0 +1,28 @@
#pragma once
// Check endian
#if (defined(YYCC_ENDIAN_LITTLE) + defined(YYCC_ENDIAN_BIG)) != 1
#error "Current system endian (byte order) is not supported!"
#endif
namespace yycc::macro::endian {
/// @brief The endian kind of OS.
enum class EndianKind {
Little, ///< Little endian.
Big, ///< Big endian.
};
/**
* @brief Fetch the endian of OS.
* @return The endian of OS.
*/
inline constexpr EndianKind get_endian() {
#if defined(YYCC_ENDIAN_LITTLE)
return EndianKind::Little;
#else
return EndianKind::Big;
#endif
}
} // namespace yycc::macro::endian

View File

@ -0,0 +1,31 @@
#pragma once
// Check OS macro
#if (defined(YYCC_OS_WINDOWS) + defined(YYCC_OS_LINUX) + defined(YYCC_OS_MACOS)) != 1
#error "Current operating system is not supported!"
#endif
namespace yycc::macro::os {
/// @brief The operating system kind.
enum class OsKind {
Windows, ///< Microsoft Windows
Linux, ///< GNU/Linux
MacOs, ///< Apple macOS
};
/**
* @brief Fetch the operating system
* @return The kind of operating system.
*/
inline constexpr OsKind get_os() {
#if defined(YYCC_OS_WINDOWS)
return OsKind::Windows;
#elif defined(YYCC_OS_LINUX)
return OsKind::Linux;
#else
return OsKind::MacOs;
#endif
}
} // namespace yycc::macro::os

View File

@ -0,0 +1,28 @@
#pragma once
#include "compiler_detector.hpp"
#include "stl_detector.hpp"
// YYC MARK:
// This code is copied from Qt project.
#if defined(YYCC_CC_GCC)
// GCC has its special attribute
#define YYCC_PRINTF_CHECK_ATTR(A, B) __attribute__((format(gnu_printf, (A), (B))))
#elif defined(YYCC_CC_CLANG)
// Clang use its own attribute
#define YYCC_PRINTF_CHECK_ATTR(A, B) __attribute__((format(printf, (A), (B))))
#else
// Other CC do not support this (like MSVC), skip it
#define YYCC_PRINTF_CHECK_ATTR(A, B)
#endif
#if defined(YYCC_STL_MSSTL)
// On Microsoft STL, we can use some mechanisms to check it.
#include "../windows/import_guard_head.hpp"
#include <sal.h>
#include "../windows/import_guard_tail.hpp"
#define YYCC_PRINTF_CHECK_FMTSTR _Printf_format_string_
#else
// Other STL do not have this.
#define YYCC_PRINTF_CHECK_FMTSTR
#endif

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