commit 42b158159fc1188afc6b1afecf30ac0184fb0c52
parent fce2a3185e54b0777e79090200f3dc6824a48d08
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date: Mon, 17 Jun 2024 15:33:55 +0200
Rewrite CMake project for better integration
Diffstat:
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 @@
+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;
+}