poaflocParser Of Arguments For Lines Of Commands |
git clone git://git.dimitrijedobrota.com/poafloc.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | |
commit | 42b158159fc1188afc6b1afecf30ac0184fb0c52 |
parent | fce2a3185e54b0777e79090200f3dc6824a48d08 |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Mon, 17 Jun 2024 13:33:55 +0200 |
Rewrite CMake project for better integration
Diffstat:M | .clang-format | | | +++++++++++++++++++++++++++++++++++----------------------------------------------- |
A | .clang-tidy | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | .codespellrc | | | ++++++ |
M | .gitignore | | | +++++++++++++-- |
A | BUILDING.md | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | CMakeLists.txt | | | ++++++++++++++++++++++++++++++++++++++-------------------------------------------- |
A | CMakePresets.json | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | CODE_OF_CONDUCT.md | | | +++++ |
A | CONTRIBUTING.md | | | +++++++++++++++ |
A | HACKING.md | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | README.md | | | +++++++++++++++------------------------------ |
A | cmake/coverage.cmake | | | +++++++++++++++++++++++++++++++++ |
A | cmake/dev-mode.cmake | | | ++++++++++++++++ |
A | cmake/folders.cmake | | | +++++++++++++++++++++ |
A | cmake/install-config.cmake | | | + |
A | cmake/install-rules.cmake | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | cmake/lint-targets.cmake | | | ++++++++++++++++++++++++++++++++++ |
A | cmake/lint.cmake | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | cmake/prelude.cmake | | | ++++++++++ |
A | cmake/project-is-top-level.cmake | | | ++++++ |
A | cmake/spell-targets.cmake | | | ++++++++++++++++++++++ |
A | cmake/spell.cmake | | | +++++++++++++++++++++++++++++ |
A | cmake/variables.cmake | | | +++++++++++++++++++++++++++++++++++++++++ |
D | demo/CMakeLists.txt | | | ----------------- |
D | demo/main.c | | | --------------------------------------------------------------------------------- |
D | demo/main.cpp | | | --------------------------------------------------------------------------------- |
A | example/CMakeLists.txt | | | ++++++++++++++++++++++++++ |
A | example/example_c.c | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | example/example_cpp.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | include/poafloc.h | | | --------------------------------------------------------------------------------- |
D | include/poafloc.hpp | | | --------------------------------------------------------------------------------- |
A | include/poafloc/poafloc.h | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | include/poafloc/poafloc.hpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | poaflocConfig.cmake.in | | | ------ |
A | source/c_bindings.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++ |
A | source/help.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | source/poafloc.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | source/trie.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | src/CMakeLists.txt | | | -------------- |
D | src/c_bindings.cpp | | | ----------------------------- |
D | src/help.cpp | | | --------------------------------------------------------------------------------- |
D | src/poafloc.cpp | | | --------------------------------------------------------------------------------- |
D | src/trie.cpp | | | ----------------------------------------------------- |
A | test/CMakeLists.txt | | | +++++++++++++++++++++++++ |
A | test/source/poafloc_test.cpp | | | ++++++++ |
45 files changed, 2427 insertions(+), 1257 deletions(-)
diff --git a/.clang-format b/.clang-format
@@ -1,216 +1,178 @@
---
Language: Cpp
# BasedOnStyle: Microsoft
Language: Cpp
# BasedOnStyle: Chromium
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: true
AlignConsecutiveBitFields: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AlignOperands: DontAlign
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: true
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AllowShortLoopsOnASingleLine: false
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: false
BeforeElse: true
BeforeLambdaBody: true
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
# BreakBeforeInheritanceComma: true
BreakInheritanceList: BeforeComma
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakConstructorInitializersBeforeComma: true
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: true
BreakStringLiterals: true
ColumnLimit: 79
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
ColumnLimit: 79
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DeriveLineEnding: false
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: BinPack
BasedOnStyle: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
# Standard library headers come before anything else
- Regex: '^<[a-z_]+>'
Priority: -1
- Regex: '^<.+\.h(pp)?>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: ''
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequiresClause: true
IndentWidth: 4
IndentPPDirectives: AfterHash
IndentExternBlock: NoIndent
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertBraces: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
InsertTrailingCommas: Wrapped
JavaScriptQuotes: Double
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000
PenaltyIndentedWhitespace: 0
PointerAlignment: Right
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RequiresClausePosition: OwnLine
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
- ParseTestProto
- ParsePartialTestProto
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeParens: ControlStatementsExceptForEachMacros
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
Standard: Auto
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Never
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...
diff --git a/.clang-tidy b/.clang-tidy
@@ -0,0 +1,172 @@
---
# Enable ALL the things! Except not really
# misc-non-private-member-variables-in-classes: the options don't do anything
# modernize-use-nodiscard: too aggressive, attribute is situationally useful
Checks: "*,\
-altera-*,\
-fuchsia-*,\
-llvmlibc-*,\
-*-avoid-goto,\
-*-braces-around-statements,\
-*-c-arrays,\
-*-vararg,\
-*-array*decay,\
-bugprone-argument-comment,\
-bugprone-easily-swappable-parameters,\
-cert-dcl50-cpp,\
-concurrency-mt-unsafe,\
-cppcoreguidelines-avoid-magic-numbers,\
-fuchsia-multiple-inheritance,\
-hicpp-signed-bitwise,\
-llvm-header-guard,\
-llvm-include-order,\
-misc-no-recursion,\
-misc-non-private-member-variables-in-classes,\
-modernize-use-nodiscard,\
-modernize-use-trailing-return-type,\
-readability-function-cognitive-complexity,\
-readability-magic-numbers
"
WarningsAsErrors: ''
CheckOptions:
- key: 'bugprone-argument-comment.StrictMode'
value: 'true'
# Prefer using enum classes with 2 values for parameters instead of bools
- key: 'bugprone-argument-comment.CommentBoolLiterals'
value: 'true'
- key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts'
value: 'true'
- key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression'
value: 'true'
- key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison'
value: 'true'
- key: 'readability-simplify-boolean-expr.ChainedConditionalReturn'
value: 'true'
- key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment'
value: 'true'
- key: 'readability-uniqueptr-delete-release.PreferResetCall'
value: 'true'
- key: 'cppcoreguidelines-init-variables.MathHeader'
value: '<cmath>'
- key: 'cppcoreguidelines-narrowing-conversions.PedanticMode'
value: 'true'
- key: 'readability-else-after-return.WarnOnUnfixable'
value: 'true'
- key: 'readability-else-after-return.WarnOnConditionVariables'
value: 'true'
- key: 'readability-inconsistent-declaration-parameter-name.Strict'
value: 'true'
- key: 'readability-qualified-auto.AddConstToQualified'
value: 'true'
- key: 'readability-redundant-access-specifiers.CheckFirstDeclaration'
value: 'true'
# These seem to be the most common identifier styles
- key: 'readability-identifier-naming.AbstractClassCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantPointerParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprFunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.EnumConstantCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.FunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalConstantPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalFunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.InlineNamespaceCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalConstantPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.MacroDefinitionCase'
value: 'UPPER_CASE'
- key: 'readability-identifier-naming.MemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.MethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.NamespaceCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ParameterPackCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PointerParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PrivateMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PrivateMemberPrefix'
value: 'm_'
- key: 'readability-identifier-naming.PrivateMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ProtectedMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ProtectedMemberPrefix'
value: 'm_'
- key: 'readability-identifier-naming.ProtectedMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PublicMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PublicMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ScopedEnumConstantCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.StaticConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StructCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TemplateTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TypeAliasCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypedefCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypeTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TypeTemplateParameterIgnoredRegexp'
value: 'expr-type'
- key: 'readability-identifier-naming.UnionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ValueTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.VariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.VirtualMethodCase'
value: 'lower_case'
...
diff --git a/.codespellrc b/.codespellrc
@@ -0,0 +1,6 @@
[codespell]
builtin = clear,rare,en-GB_to_en-US,names,informal,code
check-filenames =
check-hidden =
skip = */.git,*/build,*/prefix
quiet-level = 2
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,13 @@
build
.cache
**/.DS_Store
.idea/
.vs/
.vscode/
build/
cmake-build-*/
prefix/
.clangd
CMakeLists.txt.user
CMakeUserPresets.json
compile_commands.json
env.bat
env.ps1
diff --git a/BUILDING.md b/BUILDING.md
@@ -0,0 +1,89 @@
# Building with CMake
## Build
This project doesn't require any special command-line flags to build to keep
things simple.
Here are the steps for building in release mode with a single-configuration
generator, like the Unix Makefiles one:
```sh
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
cmake --build build
```
Here are the steps for building in release mode with a multi-configuration
generator, like the Visual Studio ones:
```sh
cmake -S . -B build
cmake --build build --config Release
```
### Building with MSVC
Note that MSVC by default is not standards compliant and you need to pass some
flags to make it behave properly. See the `flags-msvc` preset in the
[CMakePresets.json](CMakePresets.json) file for the flags and with what
variable to provide them to CMake during configuration.
### Building on Apple Silicon
CMake supports building on Apple Silicon properly since 3.20.1. Make sure you
have the [latest version][1] installed.
## Install
This project doesn't require any special command-line flags to install to keep
things simple. As a prerequisite, the project has to be built with the above
commands already.
The below commands require at least CMake 3.15 to run, because that is the
version in which [Install a Project][2] was added.
Here is the command for installing the release mode artifacts with a
single-configuration generator, like the Unix Makefiles one:
```sh
cmake --install build
```
Here is the command for installing the release mode artifacts with a
multi-configuration generator, like the Visual Studio ones:
```sh
cmake --install build --config Release
```
### CMake package
This project exports a CMake package to be used with the [`find_package`][3]
command of CMake:
* Package name: `poafloc`
* Target name: `poafloc::poafloc`
Example usage:
```cmake
find_package(poafloc REQUIRED)
# Declare the imported target as a build requirement using PRIVATE, where
# project_target is a target created in the consuming project
target_link_libraries(
project_target PRIVATE
poafloc::poafloc
)
```
### Note to packagers
The `CMAKE_INSTALL_INCLUDEDIR` is set to a path other than just `include` if
the project is configured as a top level project to avoid indirectly including
other libraries when installed to a common prefix. Please review the
[install-rules.cmake](cmake/install-rules.cmake) file for the full set of
install rules.
[1]: https://cmake.org/download/
[2]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project
[3]: https://cmake.org/cmake/help/latest/command/find_package.html
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -1,100 +1,89 @@
cmake_minimum_required(VERSION 3.25.2)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
cmake_minimum_required(VERSION 3.14)
include(cmake/prelude.cmake)
project(
poafloc
VERSION 1.0.0
VERSION 1.0.1
DESCRIPTION "Parser Of Arguments For Lines Of Commands"
LANGUAGES CXX C
HOMEPAGE_URL "https://git.dimitrijedobrota.com/poafloc.git"
LANGUAGES C CXX
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
include(GNUInstallDirs)
include(cmake/project-is-top-level.cmake)
include(cmake/variables.cmake)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
# ---- Declare library ----
set(INSTALL_LIBDIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Installation directory for libraries")
set(INSTALL_BINDIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Installation directory for executables")
set(INSTALL_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Installation directory for header files")
if(WIN32 AND NOT CYGWIN)
set(DEF_INSTALL_CMAKEDIR CMake)
else()
set(DEF_INSTALL_CMAKEDIR share/cmake/${PROJECT_NAME})
endif()
set(INSTALL_CMAKEDIR ${DEF_INSTALL_CMAKEDIR} CACHE PATH "Installation directory for CMake files")
message(STATUS "Build type set to ${CMAKE_BUILD_TYPE}")
message(STATUS "Project will be installed to ${CMAKE_INSTALL_PREFIX}")
foreach(p LIB BIN INCLUDE CMAKE)
file(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${INSTALL_${p}DIR} _path)
message(STATUS "installing ${p} components to ${_path}")
unset(_path)
endforeach()
add_subdirectory(src)
add_subdirectory(demo)
install(
TARGETS
poafloc
EXPORT
poaflocTargets
ARCHIVE
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
RUNTIME
DESTINATION ${INSTALL_BINDIR}
COMPONENT bin
LIBRARY
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
PUBLIC_HEADER
DESTINATION ${INSTALL_INCLUDEDIR}/poafloc
COMPONENT dev
add_library(
poafloc_poafloc
source/poafloc.cpp
source/c_bindings.cpp
source/help.cpp
source/trie.cpp
)
add_library(poafloc::poafloc ALIAS poafloc_poafloc)
install(
EXPORT
poaflocTargets
NAMESPACE
"poafloc::"
DESTINATION
${INSTALL_CMAKEDIR}
COMPONENT
dev
include(GenerateExportHeader)
generate_export_header(
poafloc_poafloc
BASE_NAME poafloc
EXPORT_FILE_NAME export/poafloc/poafloc_export.hpp
CUSTOM_CONTENT_FROM_VARIABLE pragma_suppress_c4251
)
include(CMakePackageConfigHelpers)
if(NOT BUILD_SHARED_LIBS)
target_compile_definitions(poafloc_poafloc PUBLIC POAFLOC_STATIC_DEFINE)
endif()
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/poaflocConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
set_target_properties(
poafloc_poafloc PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}"
EXPORT_NAME poafloc
OUTPUT_NAME poafloc
)
configure_package_config_file(
${PROJECT_SOURCE_DIR}/poaflocConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/poaflocConfig.cmake
INSTALL_DESTINATION ${INSTALL_CMAKEDIR}
target_include_directories(
poafloc_poafloc ${warning_guard}
PUBLIC
"\$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/poaflocConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/poaflocConfigVersion.cmake
DESTINATION
${INSTALL_CMAKEDIR}
target_include_directories(
poafloc_poafloc SYSTEM
PUBLIC
"\$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/export>"
)
target_compile_features(poafloc_poafloc PUBLIC cxx_std_20)
# ---- Install rules ----
if(NOT CMAKE_SKIP_INSTALL_RULES)
include(cmake/install-rules.cmake)
endif()
# ---- Examples ----
if(PROJECT_IS_TOP_LEVEL)
option(BUILD_EXAMPLES "Build examples tree." "${poafloc_DEVELOPER_MODE}")
if(BUILD_EXAMPLES)
add_subdirectory(example)
endif()
endif()
# ---- Developer mode ----
if(NOT poafloc_DEVELOPER_MODE)
return()
elseif(NOT PROJECT_IS_TOP_LEVEL)
message(
AUTHOR_WARNING
"Developer mode is intended for developers of poafloc"
)
endif()
include(cmake/dev-mode.cmake)
diff --git a/CMakePresets.json b/CMakePresets.json
@@ -0,0 +1,160 @@
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "cmake-pedantic",
"hidden": true,
"warnings": {
"dev": true,
"deprecated": true,
"uninitialized": true,
"unusedCli": true,
"systemVars": false
},
"errors": {
"dev": true,
"deprecated": true
}
},
{
"name": "dev-mode",
"hidden": true,
"inherits": "cmake-pedantic",
"cacheVariables": {
"poafloc_DEVELOPER_MODE": "ON"
}
},
{
"name": "cppcheck",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr"
}
},
{
"name": "clang-tidy",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/"
}
},
{
"name": "ci-std",
"description": "This preset makes sure the project actually builds with at least the specified standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_CXX_STANDARD": "20",
"CMAKE_CXX_STANDARD_REQUIRED": "ON"
}
},
{
"name": "flags-gcc-clang",
"description": "These flags are supported by both GCC and Clang",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS=1 -fstack-protector-strong -fcf-protection=full -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast",
"CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen",
"CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now,-z,nodlopen"
}
},
{
"name": "flags-appleclang",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-fstack-protector-strong -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Werror=float-equal -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast"
}
},
{
"name": "flags-msvc",
"description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "/sdl /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w44263 /w34265 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w34640 /w24826 /w14905 /w14906 /w14928 /w45038 /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor /Zc:enumTypes /Zc:lambda /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew /EHsc",
"CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf",
"CMAKE_SHARED_LINKER_FLAGS": "/machine:x64 /guard:cf"
}
},
{
"name": "ci-linux",
"inherits": ["flags-gcc-clang", "ci-std"],
"generator": "Unix Makefiles",
"hidden": true,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "ci-darwin",
"inherits": ["flags-appleclang", "ci-std"],
"generator": "Xcode",
"hidden": true
},
{
"name": "ci-win64",
"inherits": ["flags-msvc", "ci-std"],
"generator": "Visual Studio 17 2022",
"architecture": "x64",
"hidden": true
},
{
"name": "coverage-linux",
"binaryDir": "${sourceDir}/build/coverage",
"inherits": "ci-linux",
"hidden": true,
"cacheVariables": {
"ENABLE_COVERAGE": "ON",
"CMAKE_BUILD_TYPE": "Coverage",
"CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage"
}
},
{
"name": "ci-coverage",
"inherits": ["coverage-linux", "dev-mode"],
"cacheVariables": {
"COVERAGE_HTML_COMMAND": ""
}
},
{
"name": "ci-sanitize",
"binaryDir": "${sourceDir}/build/sanitize",
"inherits": ["ci-linux", "dev-mode"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Sanitize",
"CMAKE_CXX_FLAGS_SANITIZE": "-U_FORTIFY_SOURCE -O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common"
}
},
{
"name": "ci-build",
"binaryDir": "${sourceDir}/build",
"hidden": true
},
{
"name": "ci-multi-config",
"description": "Speed up multi-config generators by generating only one configuration instead of the defaults",
"hidden": true,
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Release"
}
},
{
"name": "ci-macos",
"inherits": ["ci-build", "ci-darwin", "dev-mode", "ci-multi-config"]
},
{
"name": "ci-ubuntu",
"inherits": ["ci-build", "ci-linux", "clang-tidy", "cppcheck", "dev-mode"]
},
{
"name": "ci-windows",
"inherits": ["ci-build", "ci-win64", "dev-mode", "ci-multi-config"]
}
]
}
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
@@ -0,0 +1,5 @@
# Code of Conduct
* You will be judged by your contributions first, and your sense of humor
second.
* Nobody owes you anything.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
@@ -0,0 +1,15 @@
# Contributing
## Code of Conduct
Please see the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) document.
## Getting started
Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md)
document.
In addition to he above, if you use the presets file as instructed, then you
should NOT check it into source control, just as the CMake documentation
suggests.
diff --git a/HACKING.md b/HACKING.md
@@ -0,0 +1,174 @@
# Hacking
Here is some wisdom to help you build and test this project as a developer and
potential contributor.
If you plan to contribute, please read the [CONTRIBUTING](CONTRIBUTING.md)
guide.
## Developer mode
Build system targets that are only useful for developers of this project are
hidden if the `poafloc_DEVELOPER_MODE` option is disabled. Enabling this
option makes tests and other developer targets and options available. Not
enabling this option means that you are a consumer of this project and thus you
have no need for these targets and options.
Developer mode is always set to on in CI workflows.
### Presets
This project makes use of [presets][1] to simplify the process of configuring
the project. As a developer, you are recommended to always have the [latest
CMake version][2] installed to make use of the latest Quality-of-Life
additions.
You have a few options to pass `poafloc_DEVELOPER_MODE` to the configure
command, but this project prefers to use presets.
As a developer, you should create a `CMakeUserPresets.json` file at the root of
the project:
```json
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "dev",
"binaryDir": "${sourceDir}/build/dev",
"inherits": ["dev-mode", "ci-<os>"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "dev",
"configurePreset": "dev",
"configuration": "Debug"
}
],
"testPresets": [
{
"name": "dev",
"configurePreset": "dev",
"configuration": "Debug",
"output": {
"outputOnFailure": true
}
}
]
}
```
You should replace `<os>` in your newly created presets file with the name of
the operating system you have, which may be `win64`, `linux` or `darwin`. You
can see what these correspond to in the
[`CMakePresets.json`](CMakePresets.json) file.
`CMakeUserPresets.json` is also the perfect place in which you can put all
sorts of things that you would otherwise want to pass to the configure command
in the terminal.
> **Note**
> Some editors are pretty greedy with how they open projects with presets.
> Some just randomly pick a preset and start configuring without your consent,
> which can be confusing. Make sure that your editor configures when you
> actually want it to, for example in CLion you have to make sure only the
> `dev-dev preset` has `Enable profile` ticked in
> `File > Settings... > Build, Execution, Deployment > CMake` and in Visual
> Studio you have to set the option `Never run configure step automatically`
> in `Tools > Options > CMake` **prior to opening the project**, after which
> you can manually configure using `Project > Configure Cache`.
### Configure, build and test
If you followed the above instructions, then you can configure, build and test
the project respectively with the following commands from the project root on
any operating system with any build system:
```sh
cmake --preset=dev
cmake --build --preset=dev
ctest --preset=dev
```
If you are using a compatible editor (e.g. VSCode) or IDE (e.g. CLion, VS), you
will also be able to select the above created user presets for automatic
integration.
Please note that both the build and test commands accept a `-j` flag to specify
the number of jobs to use, which should ideally be specified to the number of
threads your CPU has. You may also want to add that to your preset using the
`jobs` property, see the [presets documentation][1] for more details.
### Developer mode targets
These are targets you may invoke using the build command from above, with an
additional `-t <target>` flag:
#### `coverage`
Available if `ENABLE_COVERAGE` is enabled. This target processes the output of
the previously run tests when built with coverage configuration. The commands
this target runs can be found in the `COVERAGE_TRACE_COMMAND` and
`COVERAGE_HTML_COMMAND` cache variables. The trace command produces an info
file by default, which can be submitted to services with CI integration. The
HTML command uses the trace command's output to generate an HTML document to
`<binary-dir>/coverage_html` by default.
#### `docs`
Available if `BUILD_MCSS_DOCS` is enabled. Builds to documentation using
Doxygen and m.css. The output will go to `<binary-dir>/docs` by default
(customizable using `DOXYGEN_OUTPUT_DIRECTORY`).
#### `format-check` and `format-fix`
These targets run the clang-format tool on the codebase to check errors and to
fix them respectively. Customization available using the `FORMAT_PATTERNS` and
`FORMAT_COMMAND` cache variables.
#### `run-examples`
Runs all the examples created by the `add_example` command.
#### `spell-check` and `spell-fix`
These targets run the codespell tool on the codebase to check errors and to fix
them respectively. Customization available using the `SPELL_COMMAND` cache
variable.
## Running tests on Windows with `BUILD_SHARED_LIBS=ON`
If you are building a shared library on Windows, you must add the path to the
DLL file to `PATH` when you want to run tests. One way you could do that is by
using PowerShell and writing a script for it, e.g. `env.ps1` at the project
root:
```powershell
$oldPrompt = (Get-Command prompt).ScriptBlock
function prompt() { "(Debug) $(& $oldPrompt)" }
$VsInstallPath = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -Property InstallationPath
$Env:Path += ";$VsInstallPath\Common7\IDE;$Pwd\build\dev\Debug"
```
Then you can source this script by running `. env.ps1`. This particular
example will only work for Debug builds.
### Passing `PATH` to editors
Make sure you launch your editor of choice from the console with the above
script sourced. Look for `(Debug)` in the prompt to confirm, then run e.g.
`code .` for VScode or `devenv .` for Visual Studio.
[1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html
[2]: https://cmake.org/download/
diff --git a/README.md b/README.md
@@ -2,7 +2,6 @@
Command-line argument parser for C and C++ written in C++20
## Description
This project is heavily inspired by GNU argp and the initial goal was to have
@@ -21,43 +20,23 @@ both languages simultaneously. There were a lot of caveats and edge cases to
be understood first, and later implemented.
## Getting Started
### Dependencies
- Linux
* CMake 3.25.2 or latter
* Compiler with C++20 support (tested: clang 16.0.5, gcc 13.2.0)
- Windows
* Not tested
## Dependencies
### Installing
* CMake 3.25.2 or latter
* Compiler with C++20 support (tested: clang 16.0.5, gcc 13.2.0)
* Clone the repo
* Make a build folder and cd into it
* Run `cmake <path to cloned repo>` to set up the build scripts [Release mode]
* Run `make` to build the project
* Run `cmake --build . --target install` to install the project
## Building and installing
### Using the library
See the [BUILDING](BUILDING.md) document.
- Cmake:
* Add `find_package(poafloc 1 CONFIG REQUIRED)` to the top level CMakeLists.txt
* Add `poafloc` to `target_link_libraries` of the desired target
- Other:
* Link against `poafloc` with `-lpoafloc`
## Usage
> Please reference example folder for relevant usage example.
### Usage
> Please reference demo folder for relevant usage example.
### Help
## Help
Refer to [GNU argp documentation](https://www.gnu.org/software/libc/manual/html_node/Argp.html)
@@ -68,9 +47,15 @@ Refer to [GNU argp documentation](https://www.gnu.org/software/libc/manual/html_
* Initial Release
## Contributing
See the [CONTRIBUTING](CONTRIBUTING.md) document.
## License
This project is licensed under the MIT License - see the LICENSE.md file for details
This project is licensed under the MIT License -
see the [LICENSE](LICENSE.md) document for details
## Acknowledgments
diff --git a/cmake/coverage.cmake b/cmake/coverage.cmake
@@ -0,0 +1,33 @@
# ---- Variables ----
# We use variables separate from what CTest uses, because those have
# customization issues
set(
COVERAGE_TRACE_COMMAND
lcov -c -q
-o "${PROJECT_BINARY_DIR}/coverage.info"
-d "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/*"
CACHE STRING
"; separated command to generate a trace for the 'coverage' target"
)
set(
COVERAGE_HTML_COMMAND
genhtml --legend -f -q
"${PROJECT_BINARY_DIR}/coverage.info"
-p "${PROJECT_SOURCE_DIR}"
-o "${PROJECT_BINARY_DIR}/coverage_html"
CACHE STRING
"; separated command to generate an HTML report for the 'coverage' target"
)
# ---- Coverage target ----
add_custom_target(
coverage
COMMAND ${COVERAGE_TRACE_COMMAND}
COMMAND ${COVERAGE_HTML_COMMAND}
COMMENT "Generating coverage report"
VERBATIM
)
diff --git a/cmake/dev-mode.cmake b/cmake/dev-mode.cmake
@@ -0,0 +1,16 @@
include(cmake/folders.cmake)
include(CTest)
if(BUILD_TESTING)
add_subdirectory(test)
endif()
option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF)
if(ENABLE_COVERAGE)
include(cmake/coverage.cmake)
endif()
include(cmake/lint-targets.cmake)
include(cmake/spell-targets.cmake)
add_folders(Project)
diff --git a/cmake/folders.cmake b/cmake/folders.cmake
@@ -0,0 +1,21 @@
set_property(GLOBAL PROPERTY USE_FOLDERS YES)
# Call this function at the end of a directory scope to assign a folder to
# targets created in that directory. Utility targets will be assigned to the
# UtilityTargets folder, otherwise to the ${name}Targets folder. If a target
# already has a folder assigned, then that target will be skipped.
function(add_folders name)
get_property(targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
foreach(target IN LISTS targets)
get_property(folder TARGET "${target}" PROPERTY FOLDER)
if(DEFINED folder)
continue()
endif()
set(folder Utility)
get_property(type TARGET "${target}" PROPERTY TYPE)
if(NOT type STREQUAL "UTILITY")
set(folder "${name}")
endif()
set_property(TARGET "${target}" PROPERTY FOLDER "${folder}Targets")
endforeach()
endfunction()
diff --git a/cmake/install-config.cmake b/cmake/install-config.cmake
@@ -0,0 +1,1 @@
include("${CMAKE_CURRENT_LIST_DIR}/poaflocTargets.cmake")
diff --git a/cmake/install-rules.cmake b/cmake/install-rules.cmake
@@ -0,0 +1,72 @@
if(PROJECT_IS_TOP_LEVEL)
set(
CMAKE_INSTALL_INCLUDEDIR "include/poafloc-${PROJECT_VERSION}"
CACHE STRING ""
)
set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH)
endif()
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
# find_package(<package>) call for consumers to find this project
set(package poafloc)
install(
DIRECTORY
include/
"${PROJECT_BINARY_DIR}/export/"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT poafloc_Development
)
install(
TARGETS poafloc_poafloc
EXPORT poaflocTargets
RUNTIME #
COMPONENT poafloc_Runtime
LIBRARY #
COMPONENT poafloc_Runtime
NAMELINK_COMPONENT poafloc_Development
ARCHIVE #
COMPONENT poafloc_Development
INCLUDES #
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
write_basic_package_version_file(
"${package}ConfigVersion.cmake"
COMPATIBILITY SameMajorVersion
)
# Allow package maintainers to freely override the path for the configs
set(
poafloc_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package}"
CACHE STRING "CMake package config location relative to the install prefix"
)
set_property(CACHE poafloc_INSTALL_CMAKEDIR PROPERTY TYPE PATH)
mark_as_advanced(poafloc_INSTALL_CMAKEDIR)
install(
FILES cmake/install-config.cmake
DESTINATION "${poafloc_INSTALL_CMAKEDIR}"
RENAME "${package}Config.cmake"
COMPONENT poafloc_Development
)
install(
FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake"
DESTINATION "${poafloc_INSTALL_CMAKEDIR}"
COMPONENT poafloc_Development
)
install(
EXPORT poaflocTargets
NAMESPACE poafloc::
DESTINATION "${poafloc_INSTALL_CMAKEDIR}"
COMPONENT poafloc_Development
)
if(PROJECT_IS_TOP_LEVEL)
include(CPack)
endif()
diff --git a/cmake/lint-targets.cmake b/cmake/lint-targets.cmake
@@ -0,0 +1,34 @@
set(
FORMAT_PATTERNS
source/*.cpp source/*.hpp
include/*.hpp
test/*.cpp test/*.hpp
example/*.cpp example/*.hpp
CACHE STRING
"; separated patterns relative to the project source dir to format"
)
set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use")
add_custom_target(
format-check
COMMAND "${CMAKE_COMMAND}"
-D "FORMAT_COMMAND=${FORMAT_COMMAND}"
-D "PATTERNS=${FORMAT_PATTERNS}"
-P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Linting the code"
VERBATIM
)
add_custom_target(
format-fix
COMMAND "${CMAKE_COMMAND}"
-D "FORMAT_COMMAND=${FORMAT_COMMAND}"
-D "PATTERNS=${FORMAT_PATTERNS}"
-D FIX=YES
-P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Fixing the code"
VERBATIM
)
diff --git a/cmake/lint.cmake b/cmake/lint.cmake
@@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.14)
macro(default name)
if(NOT DEFINED "${name}")
set("${name}" "${ARGN}")
endif()
endmacro()
default(FORMAT_COMMAND clang-format)
default(
PATTERNS
source/*.cpp source/*.hpp
include/*.hpp
test/*.cpp test/*.hpp
example/*.cpp example/*.hpp
)
default(FIX NO)
set(flag --output-replacements-xml)
set(args OUTPUT_VARIABLE output)
if(FIX)
set(flag -i)
set(args "")
endif()
file(GLOB_RECURSE files ${PATTERNS})
set(badly_formatted "")
set(output "")
string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length)
foreach(file IN LISTS files)
execute_process(
COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE result
${args}
)
if(NOT result EQUAL "0")
message(FATAL_ERROR "'${file}': formatter returned with ${result}")
endif()
if(NOT FIX AND output MATCHES "\n<replacement offset")
string(SUBSTRING "${file}" "${path_prefix_length}" -1 relative_file)
list(APPEND badly_formatted "${relative_file}")
endif()
set(output "")
endforeach()
if(NOT badly_formatted STREQUAL "")
list(JOIN badly_formatted "\n" bad_list)
message("The following files are badly formatted:\n\n${bad_list}\n")
message(FATAL_ERROR "Run again with FIX=YES to fix these files.")
endif()
diff --git a/cmake/prelude.cmake b/cmake/prelude.cmake
@@ -0,0 +1,10 @@
# ---- In-source guard ----
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(
FATAL_ERROR
"In-source builds are not supported. "
"Please read the BUILDING document before trying to build this project. "
"You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' first."
)
endif()
diff --git a/cmake/project-is-top-level.cmake b/cmake/project-is-top-level.cmake
@@ -0,0 +1,6 @@
# This variable is set by project() in CMake 3.21+
string(
COMPARE EQUAL
"${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}"
PROJECT_IS_TOP_LEVEL
)
diff --git a/cmake/spell-targets.cmake b/cmake/spell-targets.cmake
@@ -0,0 +1,22 @@
set(SPELL_COMMAND codespell CACHE STRING "Spell checker to use")
add_custom_target(
spell-check
COMMAND "${CMAKE_COMMAND}"
-D "SPELL_COMMAND=${SPELL_COMMAND}"
-P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Checking spelling"
VERBATIM
)
add_custom_target(
spell-fix
COMMAND "${CMAKE_COMMAND}"
-D "SPELL_COMMAND=${SPELL_COMMAND}"
-D FIX=YES
-P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Fixing spelling errors"
VERBATIM
)
diff --git a/cmake/spell.cmake b/cmake/spell.cmake
@@ -0,0 +1,29 @@
cmake_minimum_required(VERSION 3.14)
macro(default name)
if(NOT DEFINED "${name}")
set("${name}" "${ARGN}")
endif()
endmacro()
default(SPELL_COMMAND codespell)
default(FIX NO)
set(flag "")
if(FIX)
set(flag -w)
endif()
execute_process(
COMMAND "${SPELL_COMMAND}" ${flag}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE result
)
if(result EQUAL "65")
message(FATAL_ERROR "Run again with FIX=YES to fix these errors.")
elseif(result EQUAL "64")
message(FATAL_ERROR "Spell checker printed the usage info. Bad arguments?")
elseif(NOT result EQUAL "0")
message(FATAL_ERROR "Spell checker returned with ${result}")
endif()
diff --git a/cmake/variables.cmake b/cmake/variables.cmake
@@ -0,0 +1,41 @@
# ---- Developer mode ----
# Developer mode enables targets and code paths in the CMake scripts that are
# only relevant for the developer(s) of poafloc
# Targets necessary to build the project must be provided unconditionally, so
# consumers can trivially build and package the project
if(PROJECT_IS_TOP_LEVEL)
option(poafloc_DEVELOPER_MODE "Enable developer mode" OFF)
option(BUILD_SHARED_LIBS "Build shared libs." OFF)
endif()
# ---- Suppress C4251 on Windows ----
# Please see include/poafloc/poafloc.hpp for more details
set(pragma_suppress_c4251 "
/* This needs to suppress only for MSVC */
#if defined(_MSC_VER) && !defined(__ICL)
# define POAFLOC_SUPPRESS_C4251 _Pragma(\"warning(suppress:4251)\")
#else
# define POAFLOC_SUPPRESS_C4251
#endif
")
# ---- Warning guard ----
# target_include_directories with the SYSTEM modifier will request the compiler
# to omit warnings from the provided paths, if the compiler supports that
# This is to provide a user experience similar to find_package when
# add_subdirectory or FetchContent is used to consume this project
set(warning_guard "")
if(NOT PROJECT_IS_TOP_LEVEL)
option(
poafloc_INCLUDES_WITH_SYSTEM
"Use SYSTEM modifier for poafloc's includes, disabling warnings"
ON
)
mark_as_advanced(poafloc_INCLUDES_WITH_SYSTEM)
if(poafloc_INCLUDES_WITH_SYSTEM)
set(warning_guard SYSTEM)
endif()
endif()
diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
@@ -1,17 +0,0 @@
add_executable(demo main.cpp)
target_link_libraries(demo PRIVATE poafloc)
target_include_directories(demo PRIVATE ../include)
set_target_properties(demo PROPERTIES LANGUAGE CXX)
set_target_properties(demo PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
add_executable(cdemo main.c)
target_link_libraries(cdemo PRIVATE poafloc)
target_include_directories(cdemo PRIVATE ../include)
set_target_properties(cdemo PROPERTIES LANGUAGE C)
set_target_properties(cdemo PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
diff --git a/demo/main.c b/demo/main.c
@@ -1,84 +0,0 @@
#include "poafloc.h"
#include <stdio.h>
void error(const char *message) { fprintf(stderr, "%s\n", message); }
typedef struct {
const char *output_file;
const char *input_file;
int debug;
int hex;
int relocatable;
} arguments_t;
int parse_opt(int key, const char *arg, poafloc_parser_t *parser) {
arguments_t *arguments = (arguments_t *)poafloc_parser_input(parser);
switch (key) {
case 777: arguments->debug = 1; break;
case 'h':
if (arguments->relocatable) error("cannot mix -hex and -relocatable");
arguments->hex = 1;
break;
case 'r':
if (arguments->hex) error("cannot mix -hex and -relocatable");
arguments->relocatable = 1;
break;
case 'o': arguments->output_file = arg ? arg : "stdout"; break;
case 'i': arguments->input_file = arg; break;
// case Parser::Key::ARG: arguments->args.push_back(arg); break;
case POAFLOC_KEY_ERROR: fprintf(stderr, "handled error\n");
case POAFLOC_KEY_INIT:
arguments->input_file = "stdin";
arguments->output_file = "stdout";
}
return 0;
}
// clang-format off
static const poafloc_option_t options[] = {
{ 0, 'R', 0, 0, "random 0-group option"},
{ 0, 0, 0, 0, "Program mode", 1},
{"relocatable", 'r', 0, 0, "Output in relocatable format"},
{ "hex", 'h', 0, 0, "Output in hex format"},
{"hexadecimal", 0, 0, POAFLOC_OPTION_ALIAS | POAFLOC_OPTION_HIDDEN},
{ 0, 0, 0, 0, "For developers", 4},
{ "debug", 777, 0, 0, "Enable debugging mode"},
{ 0, 0, 0, 0, "Input/output", 3},
{ "output", 'o', "file", POAFLOC_OPTION_ARG_OPTIONAL, "Output file, default stdout"},
{ 0, 'i', "file", 0, "Input file"},
{ 0, 0, 0, 0, "Informational Options", -1},
{0},
};
static const poafloc_arg_t argp = {
options, parse_opt, "doc string\nother usage",
"First half of the message\vsecond half of the message"
};
// clang-format on
int main(int argc, char *argv[]) {
arguments_t arguments = {0};
if (poafloc_parse(&argp, argc, argv, 0, &arguments)) {
error("There was an error while parsing arguments");
return 1;
}
printf("Command line options:\n");
printf("\t input: %s\n", arguments.input_file);
printf("\t output: %s\n", arguments.output_file);
printf("\t hex: %d\n", arguments.hex);
printf("\t debug: %d\n", arguments.debug);
printf("\t relocatable: %d\n", arguments.relocatable);
// std::cout << "\t args: ";
// for (const auto &arg : arguments.args)
// std::cout << arg << " ";
// std::cout << std::endl;
return 0;
}
diff --git a/demo/main.cpp b/demo/main.cpp
@@ -1,87 +0,0 @@
#include "poafloc.hpp"
#include <cstdint>
#include <iostream>
#include <vector>
using namespace poafloc;
void error(const std::string &message) { std::cerr << message << std::endl; }
struct arguments_t {
const char *output_file = "stdout";
const char *input_file = "stdin";
bool debug = 0;
bool hex = 0;
bool relocatable = 0;
std::vector<const char *> args;
};
int parse_opt(int key, const char *arg, Parser *parser) {
auto arguments = (arguments_t *)parser->input();
switch (key) {
case 777: arguments->debug = true; break;
case 'h':
if (arguments->relocatable) error("cannot mix -hex and -relocatable");
arguments->hex = true;
break;
case 'r':
if (arguments->hex) error("cannot mix -hex and -relocatable");
arguments->relocatable = true;
break;
case 'o': arguments->output_file = arg ? arg : "stdout"; break;
case 'i': arguments->input_file = arg; break;
case Key::ARG: arguments->args.push_back(arg); break;
case Key::ERROR: help(parser, stderr, STD_ERR);
}
return 0;
}
// clang-format off
static const option_t options[] = {
{ 0, 'R', 0, 0, "random 0-group option"},
{ 0, 0, 0, 0, "Program mode", 1},
{"relocatable", 'r', 0, 0, "Output in relocatable format"},
{ "hex", 'h', 0, 0, "Output in hex format"},
{"hexadecimal", 0, 0, ALIAS | HIDDEN},
{ 0, 0, 0, 0, "For developers", 4},
{ "debug", 777, 0, 0, "Enable debugging mode"},
{ 0, 0, 0, 0, "Input/output", 3},
{ "output", 'o', "file", ARG_OPTIONAL, "Output file, default stdout"},
{ 0, 'i', "file", 0, "Input file"},
{ 0, 0, 0, 0, "Informational Options", -1},
{0},
};
static const arg_t argp = {
options, parse_opt, "doc string\nother usage",
"First half of the message\vsecond half of the message"
};
// clang-format on
int main(int argc, char *argv[]) {
arguments_t arguments;
if (parse(&argp, argc, argv, 0, &arguments)) {
error("There was an error while parsing arguments");
return 1;
}
std::cout << "Command line options: " << std::endl;
std::cout << "\t input: " << arguments.input_file << std::endl;
std::cout << "\t output: " << arguments.output_file << std::endl;
std::cout << "\t hex: " << arguments.hex << std::endl;
std::cout << "\t debug: " << arguments.debug << std::endl;
std::cout << "\t relocatable: " << arguments.relocatable << std::endl;
std::cout << "\t args: ";
for (const auto &arg : arguments.args)
std::cout << arg << " ";
std::cout << std::endl;
return 0;
}
diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt
@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.14)
project(poaflocExamples CXX)
include(../cmake/project-is-top-level.cmake)
include(../cmake/folders.cmake)
if(PROJECT_IS_TOP_LEVEL)
find_package(poafloc REQUIRED)
endif()
add_custom_target(run-examples)
function(add_example NAME EXT)
add_executable("${NAME}" "${NAME}.${EXT}")
target_link_libraries("${NAME}" PRIVATE poafloc::poafloc)
target_compile_features("${NAME}" PRIVATE cxx_std_20)
add_custom_target("run_${NAME}" COMMAND "${NAME}" VERBATIM)
add_dependencies("run_${NAME}" "${NAME}")
add_dependencies(run-examples "run_${NAME}")
endfunction()
add_example(example_c c)
add_example(example_cpp cpp)
add_folders(Example)
diff --git a/example/example_c.c b/example/example_c.c
@@ -0,0 +1,101 @@
#include <poafloc/poafloc.h>
#include <stdio.h>
void error(const char* message)
{
(void)fprintf(stderr, "%s\n", message);
}
typedef struct
{
const char* output_file;
const char* input_file;
int debug;
int hex;
int relocatable;
} arguments_t;
int parse_opt(int key, const char* arg, poafloc_parser_t* parser)
{
arguments_t* arguments = (arguments_t*)poafloc_parser_input(parser);
switch (key)
{
case 777:
arguments->debug = 1;
break;
case 'h':
if (arguments->relocatable) error("cannot mix -hex and -relocatable");
arguments->hex = 1;
break;
case 'r':
if (arguments->hex) error("cannot mix -hex and -relocatable");
arguments->relocatable = 1;
break;
case 'o':
arguments->output_file = arg ? arg : "stdout";
break;
case 'i':
arguments->input_file = arg;
break;
// case Parser::Key::ARG: arguments->args.push_back(arg); break;
case POAFLOC_KEY_ERROR:
(void)fprintf(stderr, "handled error\n");
break;
case POAFLOC_KEY_INIT:
arguments->input_file = "stdin";
arguments->output_file = "stdout";
default:
break;
}
return 0;
}
// clang-format off
static const poafloc_option_t options[] = {
{ 0, 'R', 0, 0, "random 0-group option"},
{ 0, 0, 0, 0, "Program mode", 1},
{"relocatable", 'r', 0, 0, "Output in relocatable format"},
{ "hex", 'h', 0, 0, "Output in hex format"},
{"hexadecimal", 0, 0, POAFLOC_OPTION_ALIAS | POAFLOC_OPTION_HIDDEN},
{ 0, 0, 0, 0, "For developers", 4},
{ "debug", 777, 0, 0, "Enable debugging mode"},
{ 0, 0, 0, 0, "Input/output", 3},
{ "output", 'o', "file", POAFLOC_OPTION_ARG_OPTIONAL, "Output file, default stdout"},
{ 0, 'i', "file", 0, "Input file"},
{ 0, 0, 0, 0, "Informational Options", -1},
{0},
};
static const poafloc_arg_t argp = {
options, parse_opt, "doc string\nother usage",
"First half of the message\vsecond half of the message"
};
// clang-format on
int main(int argc, char* argv[])
{
arguments_t arguments = {0};
if (poafloc_parse(&argp, argc, argv, 0, &arguments))
{
error("There was an error while parsing arguments");
return 1;
}
printf("Command line options:\n");
printf("\t input: %s\n", arguments.input_file);
printf("\t output: %s\n", arguments.output_file);
printf("\t hex: %d\n", arguments.hex);
printf("\t debug: %d\n", arguments.debug);
printf("\t relocatable: %d\n", arguments.relocatable);
// std::cout << "\t args: ";
// for (const auto &arg : arguments.args)
// std::cout << arg << " ";
// std::cout << std::endl;
return 0;
}
diff --git a/example/example_cpp.cpp b/example/example_cpp.cpp
@@ -0,0 +1,107 @@
#include <cstdint>
#include <iostream>
#include <vector>
#include <poafloc/poafloc.hpp>
using namespace poafloc; // NOLINT
void error(const std::string& message)
{
std::cerr << message << std::endl;
}
struct arguments_t
{
const char* output_file = "stdout";
const char* input_file = "stdin";
bool debug = false;
bool hex = false;
bool relocatable = false;
std::vector<const char*> args;
};
int parse_opt(int key, const char* arg, Parser* parser)
{
auto* arguments = static_cast<arguments_t*>(parser->input());
switch (key)
{
case 777:
arguments->debug = true;
break;
case 'h':
if (arguments->relocatable) error("cannot mix -hex and -relocatable");
arguments->hex = true;
break;
case 'r':
if (arguments->hex) error("cannot mix -hex and -relocatable");
arguments->relocatable = true;
break;
case 'o':
arguments->output_file = arg != nullptr ? arg : "stdout";
break;
case 'i':
arguments->input_file = arg;
break;
case Key::ARG:
arguments->args.push_back(arg);
break;
case Key::ERROR:
help(parser, stderr, STD_ERR);
break;
default:
break;
}
return 0;
}
// clang-format off
static const option_t options[] = {
{ nullptr, 'R', nullptr, 0, "random 0-group option"},
{ nullptr, 0, nullptr, 0, "Program mode", 1},
{"relocatable", 'r', nullptr, 0, "Output in relocatable format"},
{ "hex", 'h', nullptr, 0, "Output in hex format"},
{"hexadecimal", 0, nullptr, ALIAS | HIDDEN},
{ nullptr, 0, nullptr, 0, "For developers", 4},
{ "debug", 777, nullptr, 0, "Enable debugging mode"},
{ nullptr, 0, nullptr, 0, "Input/output", 3},
{ "output", 'o', "file", ARG_OPTIONAL, "Output file, default stdout"},
{ nullptr, 'i', "file", 0, "Input file"},
{ nullptr, 0, nullptr, 0, "Informational Options", -1},
{},
};
static const arg_t argp = {
options, parse_opt, "doc string\nother usage",
"First half of the message\vsecond half of the message"
};
// clang-format on
int main(int argc, char* argv[])
{
arguments_t arguments;
if (parse(&argp, argc, argv, 0, &arguments) != 0)
{
error("There was an error while parsing arguments");
return 1;
}
std::cout << "Command line options: " << std::endl;
std::cout << "\t input: " << arguments.input_file << std::endl;
std::cout << "\t output: " << arguments.output_file << std::endl;
std::cout << "\t hex: " << arguments.hex << std::endl;
std::cout << "\t debug: " << arguments.debug << std::endl;
std::cout << "\t relocatable: " << arguments.relocatable << std::endl;
std::cout << "\t args: ";
for (const auto& arg : arguments.args) std::cout << arg << " ";
std::cout << std::endl;
return 0;
}
diff --git a/include/poafloc.h b/include/poafloc.h
@@ -1,121 +0,0 @@
#ifndef POAFLOC_POAFLOC_H
#define POAFLOC_POAFLOC_H
#ifdef __cplusplus
#include <cstdio>
#define MANGLE_ENUM(enumn, name) name
#define ENUM_OPTION Option
#define ENUM_KEY Key
#define ENUM_HELP Help
#define ENUM_PARSE Parse
extern "C" {
namespace poafloc {
struct Parser;
typedef Parser poafloc_parser_t;
#else
#include <stdio.h>
#define MANGLE_ENUM(enumn, name) POAFLOC_##enumn##_##name
#define ENUM_OPTION poafloc_option_e
#define ENUM_KEY poafloc_key_e
#define ENUM_HELP poafloc_help_e
#define ENUM_PARSE poafloc_parse_e
struct __Parser;
typedef struct __Parser poafloc_parser_t;
#endif
typedef struct {
char const *name;
int key;
char const *arg;
int flags;
char const *message;
int group;
} poafloc_option_t;
typedef int (*poafloc_parse_f)(int key, const char *arg, poafloc_parser_t *parser);
typedef struct {
poafloc_option_t const *options;
poafloc_parse_f parse;
char const *doc;
char const *message;
} poafloc_arg_t;
enum ENUM_OPTION {
MANGLE_ENUM(OPTION, ARG_OPTIONAL) = 0x1,
MANGLE_ENUM(OPTION, HIDDEN) = 0x2,
MANGLE_ENUM(OPTION, ALIAS) = 0x4,
};
enum ENUM_KEY {
MANGLE_ENUM(KEY, ARG) = 0,
MANGLE_ENUM(KEY, END) = 0x1000001,
MANGLE_ENUM(KEY, NO_ARGS) = 0x1000002,
MANGLE_ENUM(KEY, INIT) = 0x1000003,
MANGLE_ENUM(KEY, SUCCESS) = 0x1000004,
MANGLE_ENUM(KEY, ERROR) = 0x1000005,
};
enum ENUM_HELP {
MANGLE_ENUM(HELP, SHORT_USAGE) = 0x1,
MANGLE_ENUM(HELP, USAGE) = 0x2,
MANGLE_ENUM(HELP, SEE) = 0x4,
MANGLE_ENUM(HELP, LONG) = 0x8,
MANGLE_ENUM(HELP, EXIT_ERR) = 0x10,
MANGLE_ENUM(HELP, EXIT_OK) = 0x20,
MANGLE_ENUM(HELP, STD_ERR) = MANGLE_ENUM(HELP, SEE) |
MANGLE_ENUM(HELP, EXIT_ERR),
MANGLE_ENUM(HELP, STD_HELP) = MANGLE_ENUM(HELP, LONG) |
MANGLE_ENUM(HELP, EXIT_OK),
MANGLE_ENUM(HELP, STD_USAGE) = MANGLE_ENUM(HELP, USAGE) |
MANGLE_ENUM(HELP, EXIT_ERR),
};
enum ENUM_PARSE {
MANGLE_ENUM(PARSE, NO_ERRS) = 0x1,
MANGLE_ENUM(PARSE, NO_HELP) = 0x2,
MANGLE_ENUM(PARSE, NO_EXIT) = 0x4,
MANGLE_ENUM(PARSE, SILENT) = 0x8,
MANGLE_ENUM(PARSE, IN_ORDER) = 0x10,
};
#if !defined __cplusplus || defined WITH_C_BINDINGS
void *poafloc_parser_input(poafloc_parser_t *parser);
void poafloc_usage(poafloc_parser_t *parser);
void poafloc_help(const poafloc_parser_t *state, FILE *stream, unsigned flags);
int poafloc_parse(const poafloc_arg_t *argp, int argc, char *argv[], unsigned flags,
void *input);
void poafloc_failure(const poafloc_parser_t *parser, int status, int errnum,
const char *fmt, ...);
#endif
#undef MANGLE_ENUM
#undef ENUM_OPTION
#undef ENUM_KEY
#ifdef __cplusplus
} // namespace poafloc
} // extern "C"
#endif
#endif
diff --git a/include/poafloc.hpp b/include/poafloc.hpp
@@ -1,111 +0,0 @@
#ifndef POAFLOC_POAFLOC_HPP
#define POAFLOC_POAFLOC_HPP
#include "poafloc.h"
#include <cstdarg>
#include <string>
#include <unordered_map>
#include <vector>
namespace poafloc {
using option_t = poafloc_option_t;
using arg_t = poafloc_arg_t;
int parse(const arg_t *argp, int argc, char *argv[], unsigned flags,
void *input) noexcept;
void usage(const Parser *parser);
void help(const Parser *parser, FILE *stream, unsigned flags);
void failure(const Parser *parser, int status, int errnum, const char *fmt,
va_list args);
void failure(const Parser *parser, int status, int errnum, const char *fmt,
...);
class Parser {
public:
void *input() const { return m_input; }
const char *name() const { return m_name; }
unsigned flags() const { return m_flags; }
private:
friend int parse(const arg_t *, int, char **, unsigned, void *) noexcept;
friend void help(const Parser *parser, FILE *stream, unsigned flags);
Parser(const arg_t *argp, unsigned flags, void *input);
Parser(const Parser &) = delete;
Parser(Parser &&) = delete;
Parser &operator=(const Parser &) = delete;
Parser &operator=(Parser &&) = delete;
~Parser() noexcept = default;
int parse(int argc, char *argv[]);
int handle_unknown(bool shrt, const char *argv);
int handle_missing(bool shrt, const char *argv);
int handle_excess(const char *argv);
void print_other_usages(FILE *stream) const;
void help(FILE *stream) const;
void usage(FILE *stream) const;
void see(FILE *stream) const;
static const char *basename(const char *name);
struct help_entry_t {
help_entry_t(const char *arg, const char *message, int group,
bool opt = false)
: arg(arg), message(message), group(group), opt(opt) {}
void push(char sh) { opt_short.push_back(sh); }
void push(const char *lg) { opt_long.push_back(lg); }
bool operator<(const help_entry_t &rhs) const;
const char *arg;
const char *message;
int group;
bool opt;
std::vector<const char *> opt_long;
std::vector<char> opt_short;
};
class trie_t {
public:
trie_t() = default;
trie_t(const trie_t &) = delete;
trie_t(trie_t &&) = delete;
trie_t &operator=(const trie_t &) = delete;
trie_t &operator=(trie_t &&) = delete;
~trie_t() noexcept;
bool insert(const char *option, int key);
int get(const char *option) const;
private:
static bool is_valid(const char *option);
trie_t *children[26] = {0};
int count = 0;
int key = 0;
bool terminal = false;
};
const arg_t *argp;
unsigned m_flags;
void *m_input;
const char *m_name;
std::unordered_map<int, const option_t *> options;
std::vector<help_entry_t> help_entries;
trie_t trie;
};
} // namespace poafloc
#endif
diff --git a/include/poafloc/poafloc.h b/include/poafloc/poafloc.h
@@ -0,0 +1,149 @@
#ifndef POAFLOC_POAFLOC_H
#define POAFLOC_POAFLOC_H
#ifdef __cplusplus
# include <cstdio>
# include "poafloc/poafloc_export.hpp"
// NOLINTNEXTLINE
# define MANGLE_ENUM(enumn, name) name
# define ENUM_OPTION Option
# define ENUM_KEY Key
# define ENUM_HELP Help
# define ENUM_PARSE Parse
# define INIT_0 = 0
# define INIT_NULLPTR = nullptr
extern "C"
{
namespace poafloc {
#else
# include <stdio.h>
# define MANGLE_ENUM(enumn, name) POAFLOC_##enumn##_##name
# define ENUM_OPTION poafloc_option_e
# define ENUM_KEY poafloc_key_e
# define ENUM_HELP poafloc_help_e
# define ENUM_PARSE poafloc_parse_e
# define INIT_0
# define INIT_NULLPTR
#endif
struct Parser;
typedef struct Parser poafloc_parser_t; // NOLINT
typedef struct // NOLINT
POAFLOC_EXPORT
{
char const* name INIT_NULLPTR;
int key INIT_0;
char const* arg INIT_NULLPTR;
int flags INIT_0;
char const* message INIT_NULLPTR;
int group INIT_0;
} poafloc_option_t;
// NOLINTNEXTLINE
typedef int (*poafloc_parse_f)(int key,
const char* arg,
poafloc_parser_t* parser);
// NOLINTNEXTLINE
typedef struct
{
poafloc_option_t const* options INIT_NULLPTR;
poafloc_parse_f parse INIT_NULLPTR;
char const* doc INIT_NULLPTR;
char const* message INIT_NULLPTR;
} poafloc_arg_t;
enum ENUM_OPTION
{
MANGLE_ENUM(OPTION, ARG_OPTIONAL) = 0x1,
MANGLE_ENUM(OPTION, HIDDEN) = 0x2,
MANGLE_ENUM(OPTION, ALIAS) = 0x4,
};
enum ENUM_KEY
{
MANGLE_ENUM(KEY, ARG) = 0,
MANGLE_ENUM(KEY, END) = 0x1000001,
MANGLE_ENUM(KEY, NO_ARGS) = 0x1000002,
MANGLE_ENUM(KEY, INIT) = 0x1000003,
MANGLE_ENUM(KEY, SUCCESS) = 0x1000004,
MANGLE_ENUM(KEY, ERROR) = 0x1000005,
};
enum ENUM_HELP
{
MANGLE_ENUM(HELP, SHORT_USAGE) = 0x1,
MANGLE_ENUM(HELP, USAGE) = 0x2,
MANGLE_ENUM(HELP, SEE) = 0x4,
MANGLE_ENUM(HELP, LONG) = 0x8,
MANGLE_ENUM(HELP, EXIT_ERR) = 0x10,
MANGLE_ENUM(HELP, EXIT_OK) = 0x20,
MANGLE_ENUM(HELP, STD_ERR) = MANGLE_ENUM(HELP, SEE)
| MANGLE_ENUM(HELP, EXIT_ERR),
MANGLE_ENUM(HELP, STD_HELP) = MANGLE_ENUM(HELP, LONG)
| MANGLE_ENUM(HELP, EXIT_OK),
MANGLE_ENUM(HELP, STD_USAGE) = MANGLE_ENUM(HELP, USAGE)
| MANGLE_ENUM(HELP, EXIT_ERR),
};
enum ENUM_PARSE
{
MANGLE_ENUM(PARSE, NO_ERRS) = 0x1,
MANGLE_ENUM(PARSE, NO_HELP) = 0x2,
MANGLE_ENUM(PARSE, NO_EXIT) = 0x4,
MANGLE_ENUM(PARSE, SILENT) = 0x8,
MANGLE_ENUM(PARSE, IN_ORDER) = 0x10,
};
#if !defined __cplusplus || defined WITH_C_BINDINGS
void poafloc_usage(poafloc_parser_t* parser);
void poafloc_help(const poafloc_parser_t* state, FILE* stream, unsigned flags);
int poafloc_parse(const poafloc_arg_t* argp,
int argc,
char* argv[],
unsigned flags,
void* input);
void* poafloc_parser_input(poafloc_parser_t* parser);
void poafloc_failure(const poafloc_parser_t* parser,
int status,
int errnum,
const char* fmt,
...);
#endif
#undef MANGLE_ENUM
#undef ENUM_OPTION
#undef ENUM_KEY
#undef INIT_0
#undef INIT_NULLPTR
#ifdef __cplusplus
} // namespace poafloc
} // extern "C"
#endif
#endif
diff --git a/include/poafloc/poafloc.hpp b/include/poafloc/poafloc.hpp
@@ -0,0 +1,134 @@
#ifndef POAFLOC_POAFLOC_HPP
#define POAFLOC_POAFLOC_HPP
#include <array>
#include <cstdarg>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "poafloc.h"
namespace poafloc {
using option_t = poafloc_option_t;
using arg_t = poafloc_arg_t;
int parse(const arg_t* argp,
int argc,
char* argv[],
unsigned flags,
void* input) noexcept;
void usage(const Parser* parser);
void help(const Parser* parser, FILE* stream, unsigned flags);
void failure(const Parser* parser,
int status,
int errnum,
const char* fmt,
va_list args);
void failure(
const Parser* parser, int status, int errnum, const char* fmt, ...);
struct Parser
{
Parser(const Parser&) = delete;
Parser(Parser&&) = delete;
Parser& operator=(const Parser&) = delete;
Parser& operator=(Parser&&) = delete;
void* input() const { return m_input; }
unsigned flags() const { return m_flags; }
const std::string& name() const { return m_name; }
private:
friend void help(const Parser* parser, FILE* stream, unsigned flags);
friend int parse(const arg_t* argp,
int argc,
char** argv,
unsigned flags,
void* input) noexcept;
Parser(const arg_t* argp, unsigned flags, void* input);
~Parser() noexcept = default;
int parse(std::size_t argc, char* argv[]);
int handle_unknown(bool shrt, const char* argv);
int handle_missing(bool shrt, const char* argv);
int handle_excess(const char* argv);
void print_other_usages(FILE* stream) const;
void help(FILE* stream) const;
void usage(FILE* stream) const;
void see(FILE* stream) const;
static std::string basename(const std::string& name);
struct help_entry_t
{
help_entry_t(const char* arg,
const char* message,
int group,
bool opt = false)
: m_arg(arg)
, m_message(message)
, m_group(group)
, m_opt(opt)
{
}
void push(char shrt) { m_opt_short.push_back(shrt); }
void push(const char* lng) { m_opt_long.push_back(lng); }
bool operator<(const help_entry_t& rhs) const;
const char* m_arg;
const char* m_message;
int m_group;
bool m_opt;
std::vector<const char*> m_opt_long;
std::vector<char> m_opt_short;
};
class trie_t
{
public:
trie_t() = default;
~trie_t() noexcept = default;
trie_t(const trie_t&) = delete;
trie_t(trie_t&&) = delete;
trie_t& operator=(const trie_t&) = delete;
trie_t& operator=(trie_t&&) = delete;
bool insert(const std::string& option, int key);
int get(const std::string& option) const;
private:
static bool is_valid(const std::string& option);
std::array<std::unique_ptr<trie_t>, 26> m_children;
int m_count = 0;
int m_key = 0;
bool m_terminal = false;
};
const arg_t* m_argp;
unsigned m_flags;
void* m_input;
std::string m_name;
std::unordered_map<int, const option_t*> m_options;
std::vector<help_entry_t> m_help_entries;
trie_t m_trie;
};
} // namespace poafloc
#endif
diff --git a/poaflocConfig.cmake.in b/poaflocConfig.cmake.in
@@ -1,6 +0,0 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/poaflocTargets.cmake")
check_required_components(
"poafloc"
)
diff --git a/source/c_bindings.cpp b/source/c_bindings.cpp
@@ -0,0 +1,47 @@
#include <cstdarg>
#include "poafloc/poafloc.h"
#include "poafloc/poafloc.hpp"
extern "C"
{
namespace poafloc {
int poafloc_parse(const poafloc_arg_t* argp,
int argc,
char* argv[],
unsigned flags,
void* input)
{
return parse(argp, argc, argv, flags, input);
}
void* poafloc_parser_input(poafloc_parser_t* parser)
{
return parser->input();
}
void poafloc_usage(poafloc_parser_t* parser)
{
return usage(parser);
}
void poafloc_help(const poafloc_parser_t* parser, FILE* stream, unsigned flags)
{
help(parser, stream, flags);
}
void poafloc_failure(const poafloc_parser_t* parser,
int status,
int errnum,
const char* fmt,
...)
{
std::va_list args;
va_start(args, fmt);
failure(parser, status, errnum, fmt, args);
va_end(args);
}
} // namespace poafloc
}
diff --git a/source/help.cpp b/source/help.cpp
@@ -0,0 +1,222 @@
#include <cstring>
#include <format>
#include <sstream>
#include "poafloc/poafloc.hpp"
namespace poafloc {
bool Parser::help_entry_t::operator<(const help_entry_t& rhs) const
{
if (m_group != rhs.m_group)
{
if ((m_group != 0) && (rhs.m_group != 0))
{
if (m_group < 0 && rhs.m_group < 0) return m_group < rhs.m_group;
if (m_group < 0 || rhs.m_group < 0) return rhs.m_group < 0;
return m_group < rhs.m_group;
}
return m_group == 0;
}
const char ch1 = !m_opt_long.empty() ? m_opt_long.front()[0]
: !m_opt_short.empty() ? m_opt_short.front()
: '0';
const char ch22 = !rhs.m_opt_long.empty() ? rhs.m_opt_long.front()[0]
: !rhs.m_opt_short.empty() ? rhs.m_opt_short.front()
: '0';
if (ch1 != ch22)
{
return ch1 < ch22;
}
if (!m_opt_long.empty() || !rhs.m_opt_long.empty())
{
return !m_opt_long.empty();
}
return std::strcmp(m_opt_long.front(), rhs.m_opt_long.front()) < 0;
}
void Parser::print_other_usages(FILE* stream) const
{
if (m_argp->doc != nullptr)
{
std::istringstream iss(m_argp->doc);
std::string str;
std::getline(iss, str, '\n');
std::ignore = std::fprintf(stream, " %s", str.c_str());
while (std::getline(iss, str, '\n'))
{
std::ignore = std::fprintf(
stream, "\n or: %s [OPTIONS...] %s", m_name.c_str(), str.c_str());
}
}
}
void Parser::help(FILE* stream) const
{
std::string msg1;
std::string msg2;
if (m_argp->message != nullptr)
{
std::istringstream iss(m_argp->message);
std::getline(iss, msg1, '\v');
std::getline(iss, msg2, '\v');
}
std::ignore = std::fprintf(stream, "Usage: %s [OPTIONS...]", m_name.c_str());
print_other_usages(stream);
if (!msg1.empty()) std::ignore = std::fprintf(stream, "\n%s", msg1.c_str());
std::ignore = std::fprintf(stream, "\n\n");
bool first = true;
for (const auto& entry : m_help_entries)
{
bool prev = false;
if (entry.m_opt_short.empty() && entry.m_opt_long.empty())
{
if (!first) std::ignore = std::putc('\n', stream);
if (entry.m_message != nullptr)
std::ignore = std::fprintf(stream, " %s:\n", entry.m_message);
continue;
}
first = false;
std::string message = " ";
for (const char shrt : entry.m_opt_short)
{
if (!prev) prev = true;
else message += ", ";
message += std::format("-{}", shrt);
if ((entry.m_arg == nullptr) || !entry.m_opt_long.empty()) continue;
if (entry.m_opt) message += std::format("[{}]", entry.m_arg);
else message += std::format(" {}", entry.m_arg);
}
if (!prev) message += " ";
for (const auto* const lng : entry.m_opt_long)
{
if (!prev) prev = true;
else message += ", ";
message += std::format("--{}", lng);
if (entry.m_arg == nullptr) continue;
if (entry.m_opt) message += std::format("[={}]", entry.m_arg);
else message += std::format("={}", entry.m_arg);
}
static const int limit = 30;
if (size(message) < limit)
message += std::string(limit - size(message), ' ');
std::ignore = std::fprintf(stream, "%s", message.c_str());
if (entry.m_message != nullptr)
{
std::istringstream iss(entry.m_message);
std::size_t count = 0;
std::string str;
std::ignore = std::fprintf(stream, " ");
while (iss >> str)
{
count += size(str);
if (count > limit)
{
std::ignore = std::fprintf(stream, "\n%*c", limit + 5, ' ');
count = size(str);
}
std::ignore = std::fprintf(stream, "%s ", str.c_str());
}
}
std::ignore = std::putc('\n', stream);
}
if (!msg2.empty())
std::ignore = std::fprintf(stream, "\n%s\n", msg2.c_str());
}
void Parser::usage(FILE* stream) const
{
static const std::size_t limit = 60;
static std::size_t count = 0;
const auto print = [&stream](const std::string& message)
{
if (count + size(message) > limit)
count = static_cast<std::size_t>(std::fprintf(stream, "\n "));
std::ignore = std::fprintf(stream, "%s", message.c_str());
count += size(message);
};
std::string message = std::format("Usage: {}", m_name);
message += " [-";
for (const auto& entry : m_help_entries)
{
if (entry.m_arg != nullptr) continue;
for (const char shrt : entry.m_opt_short) message += shrt;
}
message += "]";
std::ignore = std::fprintf(stream, "%s", message.c_str());
count = size(message);
for (const auto& entry : m_help_entries)
{
if (entry.m_arg == nullptr) continue;
for (const char shrt : entry.m_opt_short)
{
if (entry.m_opt) print(std::format(" [-{}[{}]]", shrt, entry.m_arg));
else print(std::format(" [-{} {}]", shrt, entry.m_arg));
}
}
for (const auto& entry : m_help_entries)
{
for (const char* name : entry.m_opt_long)
{
if (entry.m_arg == nullptr)
{
print(std::format(" [--{}]", name));
continue;
}
if (entry.m_opt) print(std::format(" [--{}[={}]]", name, entry.m_arg));
else print(std::format(" [--{}={}]", name, entry.m_arg));
}
}
print_other_usages(stream);
std::ignore = std::putc('\n', stream);
}
void Parser::see(FILE* stream) const
{
std::ignore =
std::fprintf(stream,
"Try '%s --help' or '%s --usage' for more information\n",
m_name.c_str(),
m_name.c_str());
}
} // namespace poafloc
diff --git a/source/poafloc.cpp b/source/poafloc.cpp
@@ -0,0 +1,379 @@
#include <algorithm>
#include <cstring>
#include <format>
#include <iostream>
#include <sstream>
#include "poafloc/poafloc.hpp"
namespace poafloc {
int parse(const arg_t* argp,
int argc,
char* argv[],
unsigned flags,
void* input) noexcept
{
Parser parser(argp, flags, input);
return parser.parse(static_cast<std::size_t>(argc), argv);
}
void usage(const Parser* parser)
{
help(parser, stderr, Help::STD_USAGE);
}
void help(const Parser* parser, FILE* stream, unsigned flags)
{
if ((parser == nullptr) || (stream == nullptr)) return;
if ((flags & LONG) != 0U) parser->help(stream);
else if ((flags & USAGE) != 0U) parser->usage(stream);
else if ((flags & SEE) != 0U) parser->see(stream);
if ((parser->flags() & NO_EXIT) != 0U) return;
if ((flags & EXIT_ERR) != 0U) exit(2);
if ((flags & EXIT_OK) != 0U) exit(0);
}
void failure(const Parser* parser,
int status,
int errnum,
const char* fmt,
std::va_list args)
{
(void)errnum;
std::cerr << parser->name() << ": ";
std::ignore = std::vfprintf(stderr, fmt, args); // NOLINT
std::cerr << '\n';
if (status != 0) exit(status);
}
void failure(
const Parser* parser, int status, int errnum, const char* fmt, ...)
{
std::va_list args;
va_start(args, fmt);
failure(parser, status, errnum, fmt, args);
va_end(args);
}
Parser::Parser(const arg_t* argp, unsigned flags, void* input)
: m_argp(argp)
, m_flags(flags)
, m_input(input)
{
int group = 0;
int key_last = 0;
bool hidden = false;
for (int i = 0; true; i++)
{
const auto& opt = argp->options[i]; // NOLINT
if ((opt.name == nullptr) && (opt.key == 0) && (opt.message == nullptr))
break;
if ((opt.name == nullptr) && (opt.key == 0))
{
group = opt.group != 0U ? opt.group : group + 1;
m_help_entries.emplace_back(nullptr, opt.message, group);
continue;
}
if (opt.key == 0)
{
// non alias without a key, silently ignoring
if ((opt.flags & ALIAS) == 0) continue;
// nothing to alias, silently ignoring
if (key_last == 0) continue;
// option not valid, silently ignoring
if (!m_trie.insert(opt.name, key_last)) continue;
if (hidden) continue;
if ((opt.flags & Option::HIDDEN) != 0U) continue;
m_help_entries.back().push(opt.name);
}
else
{
// duplicate key, silently ignoring
if (!m_options.contains(opt.key)) continue;
if (opt.name != nullptr) m_trie.insert(opt.name, opt.key);
m_options[key_last = opt.key] = &opt;
bool const arg_opt = (opt.flags & Option::ARG_OPTIONAL) != 0U;
if ((opt.flags & ALIAS) == 0U)
{
hidden = (opt.flags & Option::HIDDEN) != 0;
if (hidden) continue;
m_help_entries.emplace_back(opt.arg, opt.message, group, arg_opt);
if (opt.name != nullptr) m_help_entries.back().push(opt.name);
if (std::isprint(opt.key) != 0U)
{
m_help_entries.back().push(static_cast<char>(opt.key & 0xFF));
}
}
else
{
// nothing to alias, silently ignoring
if (key_last == 0) continue;
if (hidden) continue;
if ((opt.flags & Option::HIDDEN) != 0U) continue;
if (opt.name != nullptr) m_help_entries.back().push(opt.name);
if (std::isprint(opt.key) != 0U)
{
m_help_entries.back().push(static_cast<char>(opt.key & 0xFF));
}
}
}
}
if ((m_flags & NO_HELP) == 0U)
{
m_help_entries.emplace_back(nullptr, "Give this help list", -1);
m_help_entries.back().push("help");
m_help_entries.back().push('?');
m_help_entries.emplace_back(nullptr, "Give a short usage message", -1);
m_help_entries.back().push("usage");
}
std::sort(begin(m_help_entries), end(m_help_entries));
}
int Parser::parse(std::size_t argc, char* argv[])
{
const std::span args(argv, argv + argc);
std::vector<const char*> args_free;
std::size_t idx = 0;
int arg_cnt = 0;
int err_code = 0;
const bool is_help = (m_flags & NO_HELP) == 0U;
const bool is_error = (m_flags & NO_ERRS) == 0U;
m_name = basename(args[0]);
m_argp->parse(Key::INIT, nullptr, this);
for (idx = 1; idx < argc; idx++)
{
if (args[idx][0] != '-')
{
if ((m_flags & IN_ORDER) != 0U) m_argp->parse(Key::ARG, args[idx], this);
else args_free.push_back(args[idx]);
arg_cnt++;
continue;
}
// stop parsing options, rest are normal arguments
if (std::strcmp(args[idx], "--") == 0) break;
if (args[idx][1] != '-')
{ // short option
const std::string opt = args[idx] + 1;
// loop over ganged options
for (std::size_t j = 0; opt[j] != 0; j++)
{
const char key = opt[j];
if (is_help && key == '?')
{
if (is_error) ::poafloc::help(this, stderr, STD_HELP);
continue;
}
if (!m_options.contains(key))
{
err_code = handle_unknown(false, args[idx]);
goto error;
}
const auto* option = m_options[key];
bool const is_opt = (option->flags & ARG_OPTIONAL) != 0;
if (option->arg == nullptr)
{
m_argp->parse(key, nullptr, this);
}
if (opt[j + 1] != 0)
{
m_argp->parse(key, opt.substr(j + 1).c_str(), this);
break;
}
if (is_opt) m_argp->parse(key, nullptr, this);
else if (idx + 1 != argc)
{
m_argp->parse(key, args[++idx], this);
break;
}
else
{
err_code = handle_missing(true, args[idx]);
goto error;
}
}
}
else
{ // long option
const std::string tmp = args[idx] + 2;
const auto pos = tmp.find_first_of('=');
const auto opt = tmp.substr(0, pos);
const auto arg = tmp.substr(pos + 1);
if (is_help && opt == "help")
{
if (pos != std::string::npos)
{
err_code = handle_excess(args[idx]);
goto error;
}
if (!is_error) continue;
::poafloc::help(this, stderr, STD_HELP);
}
if (is_help && opt == "usage")
{
if (pos != std::string::npos)
{
err_code = handle_excess(args[idx]);
goto error;
}
if (!is_error) continue;
::poafloc::help(this, stderr, STD_USAGE);
}
const int key = m_trie.get(opt);
if (key == 0)
{
err_code = handle_unknown(false, args[idx]);
goto error;
}
const auto* option = m_options[key];
if (pos != std::string::npos && option->arg == nullptr)
{
err_code = handle_excess(args[idx]);
goto error;
}
const bool is_opt = (option->flags & ARG_OPTIONAL) != 0;
if (option->arg == nullptr)
{
m_argp->parse(key, nullptr, this);
continue;
}
if (pos != std::string::npos)
{
m_argp->parse(key, arg.c_str(), this);
continue;
}
if (is_opt)
{
m_argp->parse(key, nullptr, this);
continue;
}
if (idx + 1 != argc)
{
m_argp->parse(key, args[++idx], this);
continue;
}
err_code = handle_missing(false, args[idx]);
goto error;
}
}
// parse previous arguments if IN_ORDER is not set
for (const auto* const arg : args_free) m_argp->parse(Key::ARG, arg, this);
// parse rest argv as normal arguments
for (idx = idx + 1; idx < argc; idx++)
{
m_argp->parse(Key::ARG, args[idx], this);
arg_cnt++;
}
if (arg_cnt == 0) m_argp->parse(Key::NO_ARGS, nullptr, this);
m_argp->parse(Key::END, nullptr, this);
m_argp->parse(Key::SUCCESS, nullptr, this);
return 0;
error:
return err_code;
}
int Parser::handle_unknown(bool shrt, const char* argv)
{
if ((m_flags & NO_ERRS) != 0U)
return m_argp->parse(Key::ERROR, nullptr, this);
static const char* const unknown_fmt[2] = {
"unrem_argpized option '-%s'\n",
"invalid option -- '%s'\n",
};
failure(this, 1, 0, unknown_fmt[shrt], argv + 1); // NOLINT
see(stderr);
if ((m_flags & NO_EXIT) != 0U) return 1;
exit(1);
}
int Parser::handle_missing(bool shrt, const char* argv)
{
if ((m_flags & NO_ERRS) != 0U)
return m_argp->parse(Key::ERROR, nullptr, this);
static const char* const missing_fmt[2] = {
"option '-%s' requires an argument\n",
"option requires an argument -- '%s'\n",
};
failure(this, 2, 0, missing_fmt[shrt], argv + 1); // NOLINT
see(stderr);
if ((m_flags & NO_EXIT) != 0U) return 2;
exit(2);
}
int Parser::handle_excess(const char* argv)
{
if ((m_flags & NO_ERRS) != 0U)
{
return m_argp->parse(Key::ERROR, nullptr, this);
}
failure(this, 3, 0, "option '%s' doesn't allow an argument\n", argv);
see(stderr);
if ((m_flags & NO_EXIT) != 0U) return 3;
exit(3);
}
std::string Parser::basename(const std::string& name)
{
return name.substr(name.find_first_of('/') + 1);
}
} // namespace poafloc
diff --git a/source/trie.cpp b/source/trie.cpp
@@ -0,0 +1,65 @@
#include <algorithm>
#include <cstdint>
#include "poafloc/poafloc.hpp"
namespace poafloc {
bool Parser::trie_t::insert(const std::string& option, int key)
{
trie_t* crnt = this;
if (!is_valid(option))
{
return false;
}
for (const char chr : option)
{
if (!crnt->m_terminal) crnt->m_key = key;
crnt->m_count++;
const size_t idx = static_cast<unsigned>(chr) - 'a';
if (!crnt->m_children.at(idx))
crnt->m_children.at(idx) = std::make_unique<trie_t>();
crnt = crnt->m_children.at(idx).get();
}
crnt->m_terminal = true;
crnt->m_key = key;
return true;
}
int Parser::trie_t::get(const std::string& option) const
{
const trie_t* crnt = this;
if (!is_valid(option))
{
return 0;
}
for (const char chr : option)
{
const size_t idx = static_cast<unsigned>(chr) - 'a';
if (!crnt->m_children.at(idx)) return 0;
crnt = crnt->m_children.at(idx).get();
}
if (!crnt->m_terminal && crnt->m_count > 1)
{
return 0;
}
return crnt->m_key;
}
bool Parser::trie_t::is_valid(const std::string& option)
{
return std::all_of(
begin(option), end(option), [](char chr) { return std::islower(chr); });
}
} // namespace poafloc
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -1,14 +0,0 @@
add_library(poafloc STATIC poafloc.cpp help.cpp trie.cpp c_bindings.cpp)
target_include_directories(poafloc PRIVATE ../include)
target_compile_definitions(poafloc PRIVATE WITH_C_BINDINGS)
set_target_properties(poafloc PROPERTIES LINKER_LANGUAGE CXX)
set_target_properties(poafloc PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
OUTPUT_NAME "poafloc"
DEBUG_POSTFIX "d"
PUBLIC_HEADER "include/poafloc.hpp;include/poafloc.h"
MACOSX_RPATH ON
WINDOWS_EXPORT_ALL_SYMBOLS ON
)
diff --git a/src/c_bindings.cpp b/src/c_bindings.cpp
@@ -1,29 +0,0 @@
#include "poafloc.h"
#include "poafloc.hpp"
#include <cstdarg>
namespace poafloc {
int poafloc_parse(const poafloc_arg_t *argp, int argc, char *argv[], unsigned flags,
void *input) {
return parse(argp, argc, argv, flags, input);
}
void *poafloc_parser_input(poafloc_parser_t *parser) { return parser->input(); }
void poafloc_usage(poafloc_parser_t *parser) { return usage(parser); }
void poafloc_help(const poafloc_parser_t *parser, FILE *stream, unsigned flags) {
help(parser, stream, flags);
}
void poafloc_failure(const poafloc_parser_t *parser, int status, int errnum,
const char *fmt, ...) {
std::va_list args;
va_start(args, fmt);
failure(parser, status, errnum, fmt, args);
va_end(args);
}
} // namespace args
diff --git a/src/help.cpp b/src/help.cpp
@@ -1,189 +0,0 @@
#include "poafloc.hpp"
#include <cstring>
#include <format>
#include <sstream>
namespace poafloc {
bool Parser::help_entry_t::operator<(const help_entry_t &rhs) const {
if (group != rhs.group) {
if (group && rhs.group) {
if (group < 0 && rhs.group < 0) return group < rhs.group;
if (group < 0 || rhs.group < 0) return rhs.group < 0;
return group < rhs.group;
}
return !group;
}
const char l1 = !opt_long.empty() ? opt_long.front()[0]
: !opt_short.empty() ? opt_short.front()
: '0';
const char l2 = !rhs.opt_long.empty() ? rhs.opt_long.front()[0]
: !rhs.opt_short.empty() ? rhs.opt_short.front()
: '0';
if (l1 != l2) return l1 < l2;
if (!opt_long.empty() || !rhs.opt_long.empty()) return !opt_long.empty();
return std::strcmp(opt_long.front(), rhs.opt_long.front()) < 0;
}
void Parser::print_other_usages(FILE *stream) const {
if (argp->doc) {
std::istringstream iss(argp->doc);
std::string s;
std::getline(iss, s, '\n');
std::fprintf(stream, " %s", s.c_str());
while (std::getline(iss, s, '\n')) {
std::fprintf(stream, "\n or: %s [OPTIONS...] %s", m_name,
s.c_str());
}
}
}
void Parser::help(FILE *stream) const {
std::string m1, m2;
if (argp->message) {
std::istringstream iss(argp->message);
std::getline(iss, m1, '\v');
std::getline(iss, m2, '\v');
}
std::fprintf(stream, "Usage: %s [OPTIONS...]", m_name);
print_other_usages(stream);
if (!m1.empty()) std::fprintf(stream, "\n%s", m1.c_str());
std::fprintf(stream, "\n\n");
bool first = true;
for (const auto &entry : help_entries) {
bool prev = false;
if (entry.opt_short.empty() && entry.opt_long.empty()) {
if (!first) std::putc('\n', stream);
if (entry.message) std::fprintf(stream, " %s:\n", entry.message);
continue;
}
first = false;
std::string message = " ";
for (const char c : entry.opt_short) {
if (!prev) prev = true;
else message += ", ";
message += std::format("-{}", c);
if (!entry.arg || !entry.opt_long.empty()) continue;
if (entry.opt) message += std::format("[{}]", entry.arg);
else message += std::format(" {}", entry.arg);
}
if (!prev) message += " ";
for (const auto l : entry.opt_long) {
if (!prev) prev = true;
else message += ", ";
message += std::format("--{}", l);
if (!entry.arg) continue;
if (entry.opt) message += std::format("[={}]", entry.arg);
else message += std::format("={}", entry.arg);
}
static const int limit = 30;
if (size(message) < limit) {
message += std::string(limit - size(message), ' ');
}
std::fprintf(stream, "%s", message.c_str());
if (entry.message) {
std::istringstream iss(entry.message);
std::size_t count = 0;
std::string s;
std::fprintf(stream, " ");
while (iss >> s) {
count += size(s);
if (count > limit) {
std::fprintf(stream, "\n%*c", limit + 5, ' ');
count = size(s);
}
std::fprintf(stream, "%s ", s.c_str());
}
}
std::putc('\n', stream);
}
if (!m2.empty()) std::fprintf(stream, "\n%s\n", m2.c_str());
}
void Parser::usage(FILE *stream) const {
static const std::size_t limit = 60;
static std::size_t count = 0;
static const auto print = [&stream](const std::string &message) {
if (count + size(message) > limit) {
std::fprintf(stream, "\n ");
count = 6;
}
std::fprintf(stream, "%s", message.c_str());
count += size(message);
};
std::string message = std::format("Usage: {}", m_name);
message += " [-";
for (const auto &entry : help_entries) {
if (entry.arg) continue;
for (const char c : entry.opt_short) {
message += c;
}
}
message += "]";
std::fprintf(stream, "%s", message.c_str());
count = size(message);
for (const auto &entry : help_entries) {
if (!entry.arg) continue;
for (const char c : entry.opt_short) {
if (entry.opt) print(std::format(" [-{}[{}]]", c, entry.arg));
else print(std::format(" [-{} {}]", c, entry.arg));
}
}
for (const auto &entry : help_entries) {
for (const char *name : entry.opt_long) {
if (!entry.arg) {
print(std::format(" [--{}]", name));
continue;
}
if (entry.opt) {
print(std::format(" [--{}[={}]]", name, entry.arg));
} else {
print(std::format(" [--{}={}]", name, entry.arg));
}
}
}
print_other_usages(stream);
std::putc('\n', stream);
}
void Parser::see(FILE *stream) const {
std::fprintf(stream,
"Try '%s --help' or '%s --usage' for more information\n",
m_name, m_name);
}
} // namespace args
diff --git a/src/poafloc.cpp b/src/poafloc.cpp
@@ -1,287 +0,0 @@
#include "poafloc.hpp"
#include <algorithm>
#include <cstring>
#include <format>
#include <iostream>
#include <sstream>
namespace poafloc {
int parse(const arg_t *argp, int argc, char *argv[], unsigned flags,
void *input) noexcept {
Parser parser(argp, flags, input);
return parser.parse(argc, argv);
}
void usage(const Parser *parser) { help(parser, stderr, Help::STD_USAGE); }
void help(const Parser *parser, FILE *stream, unsigned flags) {
if (!parser || !stream) return;
if (flags & LONG) parser->help(stream);
else if (flags & USAGE) parser->usage(stream);
else if (flags & SEE) parser->see(stream);
if (parser->flags() & NO_EXIT) return;
if (flags & EXIT_ERR) exit(2);
if (flags & EXIT_OK) exit(0);
}
void failure(const Parser *parser, int status, int errnum, const char *fmt,
std::va_list args) {
(void)errnum;
std::fprintf(stderr, "%s: ", parser->name());
std::vfprintf(stderr, fmt, args);
std::putc('\n', stderr);
if (status) exit(status);
}
void failure(const Parser *parser, int status, int errnum, const char *fmt,
...) {
std::va_list args;
va_start(args, fmt);
failure(parser, status, errnum, fmt, args);
va_end(args);
}
Parser::Parser(const arg_t *argp, unsigned flags, void *input)
: argp(argp), m_flags(flags), m_input(input) {
int group = 0, key_last = 0;
bool hidden = false;
for (int i = 0; true; i++) {
const auto &opt = argp->options[i];
if (!opt.name && !opt.key && !opt.message) break;
if (!opt.name && !opt.key) {
group = opt.group ? opt.group : group + 1;
help_entries.emplace_back(nullptr, opt.message, group);
continue;
}
if (!opt.key) {
// non alias without a key, silently ignoring
if (!(opt.flags & ALIAS)) continue;
// nothing to alias, silently ignoring
if (!key_last) continue;
// option not valid, silently ignoring
if (!trie.insert(opt.name, key_last)) continue;
if (hidden) continue;
if (opt.flags & Option::HIDDEN) continue;
help_entries.back().push(opt.name);
} else {
// duplicate key, silently ignoring
if (options.count(opt.key)) continue;
if (opt.name) trie.insert(opt.name, opt.key);
options[key_last = opt.key] = &opt;
bool arg_opt = opt.flags & Option::ARG_OPTIONAL;
if (!(opt.flags & ALIAS)) {
if ((hidden = opt.flags & Option::HIDDEN)) continue;
help_entries.emplace_back(opt.arg, opt.message, group,
arg_opt);
if (opt.name) help_entries.back().push(opt.name);
if (std::isprint(opt.key)) help_entries.back().push(opt.key);
} else {
// nothing to alias, silently ignoring
if (!key_last) continue;
if (hidden) continue;
if (opt.flags & Option::HIDDEN) continue;
if (opt.name) help_entries.back().push(opt.name);
if (std::isprint(opt.key)) help_entries.back().push(opt.key);
}
}
}
if (!(m_flags & NO_HELP)) {
help_entries.emplace_back(nullptr, "Give this help list", -1);
help_entries.back().push("help");
help_entries.back().push('?');
help_entries.emplace_back(nullptr, "Give a short usage message", -1);
help_entries.back().push("usage");
}
std::sort(begin(help_entries), end(help_entries));
}
int Parser::parse(int argc, char *argv[]) {
std::vector<const char *> args;
int arg_cnt = 0, err_code = 0, i;
const bool is_help = !(m_flags & NO_HELP);
const bool is_error = !(m_flags & NO_ERRS);
m_name = basename(argv[0]);
argp->parse(Key::INIT, 0, this);
for (i = 1; i < argc; i++) {
if (argv[i][0] != '-') {
if (m_flags & IN_ORDER) argp->parse(Key::ARG, argv[i], this);
else args.push_back(argv[i]);
arg_cnt++;
continue;
}
// stop parsing options, rest are normal arguments
if (!std::strcmp(argv[i], "--")) break;
if (argv[i][1] != '-') { // short option
const char *opt = argv[i] + 1;
// loop over ganged options
for (int j = 0; opt[j]; j++) {
const char key = opt[j];
if (is_help && key == '?') {
if (is_error) ::poafloc::help(this, stderr, STD_HELP);
continue;
}
if (!options.count(key)) {
err_code = handle_unknown(1, argv[i]);
goto error;
}
const auto *option = options[key];
bool is_opt = option->flags & ARG_OPTIONAL;
if (!option->arg) argp->parse(key, nullptr, this);
if (opt[j + 1] != 0) {
argp->parse(key, opt + j + 1, this);
break;
} else if (is_opt) argp->parse(key, nullptr, this);
else if (i + 1 != argc) {
argp->parse(key, argv[++i], this);
break;
} else {
err_code = handle_missing(1, argv[i]);
goto error;
}
}
} else { // long option
const char *opt = argv[i] + 2;
const auto is_eq = std::strchr(opt, '=');
std::string opt_s = !is_eq ? opt : std::string(opt, is_eq - opt);
if (is_help && opt_s == "help") {
if (is_eq) {
err_code = handle_excess(argv[i]);
goto error;
}
if (is_error) ::poafloc::help(this, stderr, STD_HELP);
continue;
}
if (is_help && opt_s == "usage") {
if (is_eq) {
err_code = handle_excess(argv[i]);
goto error;
}
if (is_error) ::poafloc::help(this, stderr, STD_USAGE);
continue;
}
const int key = trie.get(opt_s.data());
if (!key) {
err_code = handle_unknown(0, argv[i]);
goto error;
}
const auto *option = options[key];
if (!option->arg && is_eq) {
err_code = handle_excess(argv[i]);
goto error;
}
bool is_opt = option->flags & ARG_OPTIONAL;
if (!option->arg) argp->parse(key, nullptr, this);
else if (is_eq) argp->parse(key, is_eq + 1, this);
else if (is_opt) argp->parse(key, nullptr, this);
else if (i + 1 != argc) argp->parse(key, argv[++i], this);
else {
err_code = handle_missing(0, argv[i]);
goto error;
}
}
}
// parse previous arguments if IN_ORDER is not set
for (const auto arg : args) {
argp->parse(Key::ARG, arg, this);
}
// parse rest argv as normal arguments
for (i = i + 1; i < argc; i++) {
argp->parse(Key::ARG, argv[i], this);
arg_cnt++;
}
if (!arg_cnt) argp->parse(Key::NO_ARGS, 0, this);
argp->parse(Key::END, 0, this);
argp->parse(Key::SUCCESS, 0, this);
return 0;
error:
return err_code;
}
int Parser::handle_unknown(bool shrt, const char *argv) {
if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this);
static const char *const unknown_fmt[2] = {
"unrecognized option '-%s'\n",
"invalid option -- '%s'\n",
};
failure(this, 1, 0, unknown_fmt[shrt], argv + 1);
see(stderr);
if (m_flags & NO_EXIT) return 1;
exit(1);
}
int Parser::handle_missing(bool shrt, const char *argv) {
if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this);
static const char *const missing_fmt[2] = {
"option '-%s' requires an argument\n",
"option requires an argument -- '%s'\n",
};
failure(this, 2, 0, missing_fmt[shrt], argv + 1);
see(stderr);
if (m_flags & NO_EXIT) return 2;
exit(2);
}
int Parser::handle_excess(const char *argv) {
if (m_flags & NO_ERRS) return argp->parse(Key::ERROR, 0, this);
failure(this, 3, 0, "option '%s' doesn't allow an argument\n", argv);
see(stderr);
if (m_flags & NO_EXIT) return 3;
exit(3);
}
const char *Parser::basename(const char *name) {
const char *name_sh = std::strrchr(name, '/');
return name_sh ? name_sh + 1 : name;
}
} // namespace args
diff --git a/src/trie.cpp b/src/trie.cpp
@@ -1,53 +0,0 @@
#include "poafloc.hpp"
#include <cstdint>
namespace poafloc {
Parser::trie_t::~trie_t() noexcept {
for (uint8_t i = 0; i < 26; i++) {
delete children[i];
}
}
bool Parser::trie_t::insert(const char *option, int key) {
trie_t *crnt = this;
if (!is_valid(option)) return false;
for (; *option; option++) {
if (!crnt->terminal) crnt->key = key;
crnt->count++;
const uint8_t idx = *option - 'a';
if (!crnt->children[idx]) crnt->children[idx] = new trie_t();
crnt = crnt->children[idx];
}
crnt->terminal = true;
crnt->key = key;
return true;
}
int Parser::trie_t::get(const char *option) const {
const trie_t *crnt = this;
if (!is_valid(option)) return 0;
for (; *option; option++) {
const uint8_t idx = *option - 'a';
if (!crnt->children[idx]) return 0;
crnt = crnt->children[idx];
}
if (!crnt->terminal && crnt->count > 1) return 0;
return crnt->key;
}
bool Parser::trie_t::is_valid(const char *option) {
for (; *option; option++) {
if (!std::islower(*option)) return false;
}
return true;
}
} // namespace args
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.14)
project(poaflocTests LANGUAGES CXX)
include(../cmake/project-is-top-level.cmake)
include(../cmake/folders.cmake)
# ---- Dependencies ----
if(PROJECT_IS_TOP_LEVEL)
find_package(poafloc REQUIRED)
enable_testing()
endif()
# ---- Tests ----
add_executable(poafloc_test source/poafloc_test.cpp)
target_link_libraries(poafloc_test PRIVATE poafloc::poafloc)
target_compile_features(poafloc_test PRIVATE cxx_std_20)
add_test(NAME poafloc_test COMMAND poafloc_test)
# ---- End-of-file commands ----
add_folders(Test)
diff --git a/test/source/poafloc_test.cpp b/test/source/poafloc_test.cpp
@@ -0,0 +1,8 @@
#include <string>
#include "poafloc/poafloc.hpp"
int main()
{
return 0;
}