commit e5fe507b67ac4461aec1195a99eb2ba44ffeca2d
parent acf78d86b24d9223a32b26aeedd672f2b90171a2
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date: Sat, 29 Jun 2024 20:59:50 +0200
Rewrite CMake project for better integration
Diffstat:
54 files changed, 1939 insertions(+), 899 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,6 +1,13 @@
-build
-.cache
-demo/demo_menu.c
-demo/demo_menu.cpp
-demo/demo_menu.h
-demo/demo_menu.hpp
+**/.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: `stamen`
+* Target name: `stamen::stamen`
+
+Example usage:
+
+```cmake
+find_package(stamen 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
+ stamen::stamen
+)
+```
+
+### 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,99 +1,101 @@
-cmake_minimum_required(VERSION 3.25.2)
-set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+cmake_minimum_required(VERSION 3.14)
+
+include(cmake/prelude.cmake)
project(
stamen
- VERSION 1.1.0
+ VERSION 1.1.1
DESCRIPTION "Static menu generator"
+ HOMEPAGE_URL "https://git.dimitrijedobrota.com/stamen"
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(cmake/project-is-top-level.cmake)
+include(cmake/variables.cmake)
-include(GNUInstallDirs)
+find_package(poafloc 1.1 CONFIG REQUIRED)
-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()
-
-find_package(poafloc 1 CONFIG REQUIRED)
-
-add_subdirectory(src)
-add_subdirectory(demo)
-
-install(
- TARGETS
- stamen-generate
- stamen
- EXPORT
- stamenTargets
- ARCHIVE
- DESTINATION ${INSTALL_LIBDIR}
- COMPONENT lib
- RUNTIME
- DESTINATION ${INSTALL_BINDIR}
- COMPONENT bin
- LIBRARY
- DESTINATION ${INSTALL_LIBDIR}
- COMPONENT lib
- PUBLIC_HEADER
- DESTINATION ${INSTALL_INCLUDEDIR}/stamen
- COMPONENT dev
+add_library(
+ stamen_stamen
+ source/stamen.cpp
+ source/menu.cpp
+ source/c_bindings.cpp
)
-
-install(
- EXPORT stamenTargets
- NAMESPACE "stamen::"
- DESTINATION ${INSTALL_CMAKEDIR}
- COMPONENT dev
+target_link_libraries(stamen_stamen PUBLIC poafloc::poafloc)
+add_library(stamen::stamen ALIAS stamen_stamen)
+
+include(GenerateExportHeader)
+generate_export_header(
+ stamen_stamen
+ BASE_NAME stamen
+ EXPORT_FILE_NAME export/stamen/stamen_export.hpp
+ CUSTOM_CONTENT_FROM_VARIABLE pragma_suppress_c4251
)
-include(CMakePackageConfigHelpers)
+if(NOT BUILD_SHARED_LIBS)
+ target_compile_definitions(stamen_stamen PUBLIC STAMEN_STATIC_DEFINE)
+endif()
-write_basic_package_version_file(
- ${CMAKE_CURRENT_BINARY_DIR}/stamenConfigVersion.cmake
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY SameMajorVersion
+set_target_properties(
+ stamen_stamen PROPERTIES
+ CXX_VISIBILITY_PRESET hidden
+ VISIBILITY_INLINES_HIDDEN YES
+ VERSION "${PROJECT_VERSION}"
+ SOVERSION "${PROJECT_VERSION_MAJOR}"
+ EXPORT_NAME stamen
+ OUTPUT_NAME stamen
)
-configure_package_config_file(
- ${PROJECT_SOURCE_DIR}/stamenConfig.cmake.in
- ${CMAKE_CURRENT_BINARY_DIR}/stamenConfig.cmake
- INSTALL_DESTINATION ${INSTALL_CMAKEDIR}
+target_include_directories(
+ stamen_stamen ${warning_guard}
+ PUBLIC
+ "\$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
)
-install(
- FILES
- ${CMAKE_CURRENT_BINARY_DIR}/stamenConfig.cmake
- ${CMAKE_CURRENT_BINARY_DIR}/stamenConfigVersion.cmake
- DESTINATION
- ${INSTALL_CMAKEDIR}
+target_include_directories(
+ stamen_stamen SYSTEM
+ PUBLIC
+ "\$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/export>"
)
+
+target_compile_features(stamen_stamen PUBLIC cxx_std_20)
+
+# ---- Declare executable ----
+
+add_executable(stamen_exe source/generate.cpp)
+add_executable(stamen::exe ALIAS stamen_exe)
+
+target_link_libraries(stamen_exe PRIVATE stamen::stamen)
+set_property(TARGET stamen_exe PROPERTY OUTPUT_NAME stamen)
+
+target_compile_features(stamen_exe PRIVATE 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." "${stamen_DEVELOPER_MODE}")
+ if(BUILD_EXAMPLES)
+ add_subdirectory(example)
+ endif()
+endif()
+
+# ---- Developer mode ----
+
+if(NOT stamen_DEVELOPER_MODE)
+ return()
+elseif(NOT PROJECT_IS_TOP_LEVEL)
+ message(
+ AUTHOR_WARNING
+ "Developer mode is intended for developers of stamen"
+ )
+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": {
+ "stamen_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,14 @@
+# 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,170 @@
+# 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 `stamen_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 `stamen_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.
+
+#### `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
@@ -1,8 +1,7 @@
-# Stamen
+# stamen
Static menu generator written in C++20
-
## Description
This project allows for a creation of static menus, to be used in C or C++, on
@@ -20,23 +19,34 @@ The main advantage of stamen is two modes it can operate in:
2) Dynamic mode, where menus are generated on the fly (possibly in runtime) without recompilation.
-## Getting Started
-
-### Dependencies
+## Dependencies
* CMake 3.25.2 or latter
* Compiler with C++20 support
-* [Poafloc Library](https://github.com/DimitrijeDobrota/poafloc)
+* [Poafloc 1.1](https://github.com/DimitrijeDobrota/poafloc)
+
+
+## Building and installing
+
+See the [BUILDING](BUILDING.md) document.
-### Installing
+## Usage
-* Clone the repo
-* Make a build folder and cd into it
-* Run `cmake <path to cloned repo>` to set up the build scripts
-* Run `make` to build the project
-* Run `cmake --build . --target install` to install the project
+> Please reference example folder for relevant usage example.
+> Refer to example/CMakeLists.txt to see how to integrate stamen into build system
+
+There are a few things needed before you begin.
+
+* All types and functions with prefixes `stamen_` and `stamen_menu_` are also
+available in namespaces `stamen::` and `stamen::menu::` in C++ for easier use.
+* Panel and item codes must be one word. In addition they must be valid C/C++
+function names if static menu is to be build correctly.
+* Each free function must have `int name(int);` signature as prescribed by
+`stamen_callback_f`. Passed int it is intended to detonate the position of an
+item in the previous panel which is essential for dynamic menu implementation,
+but it is not required to be used like that.
### Configuration
@@ -74,29 +84,11 @@ reference to another panel or any other function (from now on referred to as
`free function`).
-### Usage
-
-> Please reference demo folder for relevant usage example.
-
-> Refer to demo/CMakeLists.txt to see how to integrate stamen into build system
+### Static menu
-There are a few things needed before you begin.
-
-* All types and functions with prefixes `stamen_` and `stamen_menu_` are also
-available in namespaces `stamen::` and `stamen::menu::` in C++ for easier use.
-* Panel and item codes must be one word. In addition they must be valid C/C++
-function names if static menu is to be build correctly.
-* Each free function must have `int name(int);` signature as prescribed by
-`stamen_callback_f`. Passed int it is intended to detonate the position of an
-item in the previous panel which is essential for dynamic menu implementation,
-but it is not required to be used like that.
-
-
-#### Static menu
+> Please refer to `stamen --help` for list of all options
-> Please refer to `stamen-generate --help` for list of all options
-
-After writing a configuration file, run `stamen-generate <config file>` which
+After writing a configuration file, run `stamen <config file>` which
will create source file and include file in the current directory with the name
as the configuration file but with extensions `.cpp` and `.hpp` respectively.
You can create files with extensions `.c` and `.h` by appending adding `--c`
@@ -120,7 +112,7 @@ Generated source file should be compiled with the rest of your code. If
You can call any function to display the menu starting from that specific pane.
-#### Custom display function
+### Custom display function
Please refer to the implementation of `stamen_builtin_display` to get a general
idea of the direction.
@@ -135,7 +127,7 @@ measure how many panels back should be backtracked after a free function
terminates, but you can use in any way you see fit.
-#### Dynamic menu
+### Dynamic menu
In dynamic mode, configuration file is read every time the program is run. In
order to invoke the menu you need to add the following snippet to your C
@@ -177,6 +169,10 @@ written in C or C++.
## Version History
+- 1.2
+ * Modernize CMake project
+ * Modernize codebase
+
- 1.1
* Separate C and C++ interfaces
* Separate dynamic mode into menu namespace
@@ -188,7 +184,14 @@ written in C or C++.
* 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) file for details
+
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}/stamenTargets.cmake")
diff --git a/cmake/install-rules.cmake b/cmake/install-rules.cmake
@@ -0,0 +1,77 @@
+if(PROJECT_IS_TOP_LEVEL)
+ set(
+ CMAKE_INSTALL_INCLUDEDIR "include/stamen-${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 stamen)
+
+install(
+ DIRECTORY
+ include/
+ "${PROJECT_BINARY_DIR}/export/"
+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+ COMPONENT stamen_Development
+)
+
+install(
+ TARGETS stamen_stamen
+ EXPORT stamenTargets
+ RUNTIME #
+ COMPONENT stamen_Runtime
+ LIBRARY #
+ COMPONENT stamen_Runtime
+ NAMELINK_COMPONENT stamen_Development
+ ARCHIVE #
+ COMPONENT stamen_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(
+ stamen_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package}"
+ CACHE STRING "CMake package config location relative to the install prefix"
+)
+set_property(CACHE stamen_INSTALL_CMAKEDIR PROPERTY TYPE PATH)
+mark_as_advanced(stamen_INSTALL_CMAKEDIR)
+
+install(
+ FILES cmake/install-config.cmake
+ DESTINATION "${stamen_INSTALL_CMAKEDIR}"
+ RENAME "${package}Config.cmake"
+ COMPONENT stamen_Development
+)
+
+install(
+ FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake"
+ DESTINATION "${stamen_INSTALL_CMAKEDIR}"
+ COMPONENT stamen_Development
+)
+
+install(
+ EXPORT stamenTargets
+ NAMESPACE stamen::
+ DESTINATION "${stamen_INSTALL_CMAKEDIR}"
+ COMPONENT stamen_Development
+)
+
+install(
+ TARGETS stamen_exe
+ RUNTIME COMPONENT stamen_Runtime
+)
+
+if(PROJECT_IS_TOP_LEVEL)
+ include(CPack)
+endif()
diff --git a/cmake/lint-targets.cmake b/cmake/lint-targets.cmake
@@ -0,0 +1,33 @@
+set(
+ FORMAT_PATTERNS
+ source/*.cpp source/*.hpp
+ include/*.hpp
+ test/*.cpp test/*.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,51 @@
+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
+)
+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 stamen
+# 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(stamen_DEVELOPER_MODE "Enable developer mode" OFF)
+ option(BUILD_SHARED_LIBS "Build shared libs." OFF)
+endif()
+
+# ---- Suppress C4251 on Windows ----
+
+# Please see include/stamen/stamen.hpp for more details
+set(pragma_suppress_c4251 "
+/* This needs to suppress only for MSVC */
+#if defined(_MSC_VER) && !defined(__ICL)
+# define STAMEN_SUPPRESS_C4251 _Pragma(\"warning(suppress:4251)\")
+#else
+# define STAMEN_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(
+ stamen_INCLUDES_WITH_SYSTEM
+ "Use SYSTEM modifier for stamen's includes, disabling warnings"
+ ON
+ )
+ mark_as_advanced(stamen_INCLUDES_WITH_SYSTEM)
+ if(stamen_INCLUDES_WITH_SYSTEM)
+ set(warning_guard SYSTEM)
+ endif()
+endif()
diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt
@@ -1,51 +0,0 @@
-set(GENERATE_OUT "${CMAKE_BINARY_DIR}/bin")
-
-configure_file(demo_menu.conf ${GENERATE_OUT}/demo_menu.conf COPYONLY)
-
-add_custom_command(
- OUTPUT ${GENERATE_OUT}/demo_menu.hpp ${GENERATE_OUT}/demo_menu.cpp
- COMMAND ${GENERATE_OUT}/stamen-generate --user -d test_display --cpp ${GENERATE_OUT}/demo_menu.conf
- DEPENDS demo_menu.conf stamen-generate
- COMMENT "Generating menu files"
-)
-
-add_executable(demo main.cpp ${GENERATE_OUT}/demo_menu.cpp)
-# target_link_libraries(demo stamen) - no need to link
-target_include_directories(demo PRIVATE ../include)
-set_target_properties(demo PROPERTIES LINKER_LANGUAGE CXX)
-target_include_directories(demo PRIVATE ${GENERATE_OUT} ${CMAKE_CURRENT_SOURCE_DIR})
-
-set_target_properties(demo PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION ${PROJECT_VERSION_MAJOR}
- RUNTIME_OUTPUT_DIRECTORY "${GENERATE_OUT}"
-)
-
-add_custom_command(
- OUTPUT ${GENERATE_OUT}/demo_menu.h ${GENERATE_OUT}/demo_menu.c
- COMMAND ${GENERATE_OUT}/stamen-generate --user --c ${GENERATE_OUT}/demo_menu.conf
- DEPENDS demo_menu.conf stamen-generate
- COMMENT "Generating cmenu files"
-)
-
-add_executable(cdemo main.c ${GENERATE_OUT}/demo_menu.c)
-target_link_libraries(cdemo stamen)
-set_target_properties(cdemo PROPERTIES LINKER_LANGUAGE C)
-target_include_directories(cdemo PRIVATE ${GENERATE_OUT} ${CMAKE_CURRENT_SOURCE_DIR})
-
-set_target_properties(cdemo PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION ${PROJECT_VERSION_MAJOR}
- RUNTIME_OUTPUT_DIRECTORY "${GENERATE_OUT}"
-)
-
-add_executable(dynamic dynamic.cpp)
-target_link_libraries(dynamic stamen)
-target_include_directories(dynamic PRIVATE ../include)
-set_target_properties(dynamic PROPERTIES LINKER_LANGUAGE CXX)
-
-set_target_properties(dynamic PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION ${PROJECT_VERSION_MAJOR}
- RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
-)
diff --git a/demo/dynamic.cpp b/demo/dynamic.cpp
@@ -1,35 +0,0 @@
-#include <iostream>
-
-#include "menu.hpp"
-
-int finish(int) { exit(1); }
-
-int operation1(int) {
- std::cout << "1" << std::endl;
- return 1;
-}
-
-int operation2(int) {
- std::cout << "2" << std::endl;
- return 1;
-}
-
-int operation3(int) {
- std::cout << "3" << std::endl;
- return 1;
-}
-
-int main() {
- // read the configuration
- stamen::menu::read("./bin/demo_menu.conf");
-
- // register free functions
- stamen::menu::insert("finish", finish);
- stamen::menu::insert("operation1", operation1);
- stamen::menu::insert("operation2", operation2);
- stamen::menu::insert("operation3", operation3);
-
- // start the menu on specific panel
- stamen::menu::dynamic("menu_main", stamen::builtin_display);
- return 0;
-}
diff --git a/demo/main.c b/demo/main.c
@@ -1,33 +0,0 @@
-#include "demo_menu.h"
-#include "stamen.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-
-int operation1(void) {
- printf("operation 1\n");
- printf("Some operation is done\n");
- return 1;
-}
-
-int operation2(void) {
- printf("operation 2\n");
- printf("Some other operation is done\n");
- return 1;
-}
-
-int operation3(void) {
- printf("operation 3\n");
- printf("Yet another operation is done\n");
- return 1;
-}
-
-int finish(void) {
- printf("finishing...\n");
- exit(0);
-}
-
-int main(void) {
- menu_main(0);
- return 0;
-}
diff --git a/demo/main.cpp b/demo/main.cpp
@@ -1,41 +0,0 @@
-#include "demo_menu.hpp"
-#include "stamen.hpp"
-
-#include <iostream>
-
-int test_display(const char *title, const stamen::item_t itemv[], int size) {
- for (auto i = 0ul; i < size; i++) {
- std::cout << i + 1 << ": " << itemv[i].prompt << '\n';
- }
- std::cout << "Auto calling option 1...\n";
- itemv[1].callback(1);
- return 0;
-}
-
-int operation1(int) {
- std::cout << "operation 1" << std::endl;
- std::cout << "Some operation is done" << std::endl;
- return 1;
-}
-
-int operation2(int) {
- std::cout << "operation 2" << std::endl;
- std::cout << "Some other operation is done" << std::endl;
- return 1;
-}
-
-int operation3(int) {
- std::cout << "operation 3" << std::endl;
- std::cout << "Yet another operation is done" << std::endl;
- return 1;
-}
-
-int finish(int) {
- std::cout << "finishing..." << std::endl;
- exit(0);
-}
-
-int main() {
- menu_main(0);
- return 0;
-}
diff --git a/demo/shared.h b/demo/shared.h
@@ -1,9 +0,0 @@
-#ifndef STAMEN_DEMO_SHARED_H
-#define STAMEN_DEMO_SHARED_H
-
-int finish(int);
-int operation1(int);
-int operation2(int);
-int operation3(int);
-
-#endif
diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt
@@ -0,0 +1,52 @@
+cmake_minimum_required(VERSION 3.14)
+
+project(stamenExamples CXX)
+
+include(../cmake/project-is-top-level.cmake)
+include(../cmake/folders.cmake)
+
+if(PROJECT_IS_TOP_LEVEL)
+ find_package(stamen REQUIRED)
+endif()
+
+configure_file(demo_menu.conf demo_menu.conf COPYONLY)
+configure_file(shared.h shared.h COPYONLY)
+
+add_custom_target(run-examples)
+
+add_custom_command(
+ OUTPUT demo_menu.hpp demo_menu.cpp
+ COMMAND stamen::exe -d test_display --cpp demo_menu.conf
+ DEPENDS demo_menu.conf stamen::exe
+ WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
+ COMMENT "Generating menu files"
+)
+
+add_custom_command(
+ OUTPUT demo_menu.h demo_menu.c
+ COMMAND stamen::exe --c demo_menu.conf
+ DEPENDS demo_menu.conf stamen::exe
+ WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
+ COMMENT "Generating cmenu files"
+)
+
+function(add_example NAME EXT)
+ add_executable("${NAME}" "${NAME}.${EXT}")
+ target_include_directories("${NAME}" PRIVATE "${PROJECT_BINARY_DIR}")
+ target_link_libraries("${NAME}" PRIVATE stamen::stamen)
+ target_compile_features("${NAME}" PRIVATE cxx_std_20)
+ add_custom_target("run_${NAME}" COMMAND "${NAME}" demo_menu.conf VERBATIM)
+ add_dependencies("run_${NAME}" "${NAME}")
+ add_dependencies(run-examples "run_${NAME}")
+
+endfunction()
+
+add_example(example_c c)
+target_sources(example_c PRIVATE "demo_menu.c")
+
+add_example(example_cpp cpp)
+target_sources(example_cpp PRIVATE "demo_menu.cpp")
+
+add_example(dynamic cpp)
+
+add_folders(Example)
diff --git a/demo/demo_menu.conf b/example/demo_menu.conf
diff --git a/example/dynamic.cpp b/example/dynamic.cpp
@@ -0,0 +1,46 @@
+#include <iostream>
+#include <span>
+
+#include "stamen/menu.hpp"
+
+int finish(size_t /* unused */)
+{
+ exit(0);
+}
+
+int operation1(size_t /* unused */)
+{
+ std::cout << "1" << std::endl;
+ return 1;
+}
+
+int operation2(size_t /* unused */)
+{
+ std::cout << "2" << std::endl;
+ return 1;
+}
+
+int operation3(size_t /* unused */)
+{
+ std::cout << "3" << std::endl;
+ return 1;
+}
+
+int main(int argc, char* argv[])
+{
+ const std::span args(argv, argv + argc);
+
+ // read the configuration
+ for (const auto& arg : args.subspan(1)) stamen::menu::read(arg);
+
+ // register free functions
+ stamen::menu::insert("finish", finish);
+ stamen::menu::insert("operation1", operation1);
+ stamen::menu::insert("operation2", operation2);
+ stamen::menu::insert("operation3", operation3);
+
+ // start the menu on specific panel
+ stamen::menu::dynamic("menu_main", stamen::builtin_display);
+
+ return 0;
+}
diff --git a/example/example_c.c b/example/example_c.c
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "demo_menu.h"
+#include "stamen/stamen.h"
+
+int operation1(size_t /* unsused */)
+{
+ printf("operation 1\n");
+ printf("Some operation is done\n");
+ return 1;
+}
+
+int operation2(size_t /* unsused */)
+{
+ printf("operation 2\n");
+ printf("Some other operation is done\n");
+ return 1;
+}
+
+int operation3(size_t /* unsused */)
+{
+ printf("operation 3\n");
+ printf("Yet another operation is done\n");
+ return 1;
+}
+
+int finish(size_t /* unsused */)
+{
+ printf("finishing...\n");
+ exit(0);
+}
+
+int main(void)
+{
+ menu_main(0);
+ return 0;
+}
diff --git a/example/example_cpp.cpp b/example/example_cpp.cpp
@@ -0,0 +1,52 @@
+#include <iostream>
+#include <span>
+
+#include "demo_menu.hpp"
+#include "stamen/stamen.hpp"
+
+int test_display(const char* title, const stamen::item_t itemv[], size_t size)
+{
+ const auto items = std::span(itemv, itemv + size);
+
+ std::cout << title << std::endl;
+ for (auto i = 0UL; i < size; i++)
+ {
+ std::cout << i + 1 << ": " << items[i].prompt << '\n';
+ }
+ std::cout << "Auto calling option 1...\n";
+ items[1].callback(1);
+ return 0;
+}
+
+int operation1(size_t /* unused */)
+{
+ std::cout << "operation 1" << std::endl;
+ std::cout << "Some operation is done" << std::endl;
+ return 1;
+}
+
+int operation2(size_t /* unused */)
+{
+ std::cout << "operation 2" << std::endl;
+ std::cout << "Some other operation is done" << std::endl;
+ return 1;
+}
+
+int operation3(size_t /* unused */)
+{
+ std::cout << "operation 3" << std::endl;
+ std::cout << "Yet another operation is done" << std::endl;
+ return 1;
+}
+
+int finish(size_t /* unused */)
+{
+ std::cout << "finishing..." << std::endl;
+ exit(0);
+}
+
+int main()
+{
+ menu_main(0);
+ return 0;
+}
diff --git a/example/shared.h b/example/shared.h
@@ -0,0 +1,11 @@
+#ifndef STAMEN_DEMO_SHARED_H
+#define STAMEN_DEMO_SHARED_H
+
+#include <stddef.h> // NOLINT
+
+int finish(size_t);
+int operation1(size_t);
+int operation2(size_t);
+int operation3(size_t);
+
+#endif
diff --git a/include/menu.h b/include/menu.h
@@ -1,20 +0,0 @@
-#ifndef STAMEN_MENU_H
-#define STAMEN_MENU_H
-
-#include "stamen.h"
-
-#ifdef __cplusplus
-extern "C" {
-namespace stamen {
-#endif
-
-void stamen_menu_read(const char *filename);
-void stamen_menu_insert(const char *code, stamen_callback_f callback);
-int stamen_menu_dynamic(const char *code, stamen_display_f display);
-
-#ifdef __cplusplus
-} // namespace stamen
-} // extern "C"
-#endif
-
-#endif
diff --git a/include/menu.hpp b/include/menu.hpp
@@ -1,77 +0,0 @@
-#ifndef STAMEN_MENU_HPP
-#define STAMEN_MENU_HPP
-
-#include "stamen.hpp"
-
-#include <cstring>
-#include <iostream>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-namespace stamen {
-namespace menu {
-
-class menu_t;
-
-extern std::unordered_map<std::string, callback_f> free_lookup;
-extern std::unordered_map<std::string, menu_t> menu_lookup;
-extern std::string display_stub_default;
-extern display_f display;
-
-void read(const char *filename);
-void insert(const char *code, callback_f callback);
-int dynamic(const char *code, display_f display);
-int display_stub(int idx);
-
-class menu_t {
- struct private_ctor_t {};
- friend void read(const char *filename);
-
- public:
- // Tag type dispatch
- menu_t(private_ctor_t, const std::string &code, const std::string &prompt)
- : menu_t(code, prompt) {}
-
- ~menu_t() noexcept {
- for (const auto [_, prompt] : items) {
- delete[] prompt;
- }
- }
-
- const std::string &getCode() const { return code; }
- const std::string &getTitle() const { return title; }
-
- const item_t *getItemv() const { return items.data(); }
- std::size_t getSize() const { return items.size(); }
-
- auto getCallback(std::size_t idx) const { return items[idx].callback; }
- const auto &getCode(std::size_t idx) const { return codes[idx].code; }
- const auto &getPrompt(std::size_t idx) const { return codes[idx].prompt; }
-
- private:
- menu_t(std::string code, std::string prompt)
- : code(std::move(code)), title(std::move(prompt)) {}
-
- menu_t(const menu_t &) = delete;
- menu_t &operator=(const menu_t &) = delete;
- menu_t(menu_t &&) = delete;
- menu_t &operator=(menu_t &&) = delete;
-
- void insert(const std::string &code, const std::string &prompt,
- callback_f callback = display_stub);
-
- struct code_t {
- std::string code;
- std::string prompt;
- };
-
- const std::string code, title;
- std::vector<code_t> codes;
- std::vector<item_t> items;
-};
-
-} // namespace menu
-} // namespace stamen
-
-#endif
diff --git a/include/stamen.h b/include/stamen.h
@@ -1,31 +0,0 @@
-#ifndef STAMEN_STAMEN_H
-#define STAMEN_STAMEN_H
-
-#ifdef __cplusplus
-extern "C" {
-namespace stamen {
-#endif
-
-typedef int (*stamen_callback_f)(int);
-
-typedef struct stamen_item_t stamen_item_t;
-struct stamen_item_t {
- stamen_callback_f callback;
- const char *prompt;
-};
-
-typedef int (*stamen_display_f)(const char *, const stamen_item_t[], int);
-
-#if !defined __cplusplus || defined WITH_C_BINDINGS
-
-int stamen_builtin_display(const char *title, const stamen_item_t itemv[],
- int size);
-
-#endif
-
-#ifdef __cplusplus
-} // namespace stamen
-} // extern "C"
-#endif
-
-#endif
diff --git a/include/stamen.hpp b/include/stamen.hpp
@@ -1,16 +0,0 @@
-#ifndef STAMEN_STAMEN_HPP
-#define STAMEN_STAMEN_HPP
-
-#include "stamen.h"
-
-namespace stamen {
-
-using callback_f = stamen_callback_f;
-using display_f = stamen_display_f;
-using item_t = stamen_item_t;
-
-int builtin_display(const char *title, const item_t itemv[], int size);
-
-} // namespace stamen
-
-#endif
diff --git a/include/stamen/menu.h b/include/stamen/menu.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "stamen/stamen.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+namespace stamen {
+#endif
+
+void stamen_menu_read(const char* filename);
+void stamen_menu_insert(const char* code, stamen_callback_f callback);
+int stamen_menu_dynamic(const char* code, stamen_display_f display);
+
+#ifdef __cplusplus
+} // namespace stamen
+} // extern "C"
+#endif
diff --git a/include/stamen/menu.hpp b/include/stamen/menu.hpp
@@ -0,0 +1,89 @@
+#pragma once
+
+#include <cstring>
+#include <iostream>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "stamen/stamen.hpp"
+
+namespace stamen::menu {
+
+class menu_t;
+
+// NOLINTBEGIN
+extern std::unordered_map<std::string, callback_f> free_lookup;
+extern std::unordered_map<std::string, menu_t> menu_lookup;
+extern std::string display_stub_default;
+extern display_f display;
+// NOLINTEND
+
+void read(const char* filename);
+void insert(const char* code, callback_f callback);
+int dynamic(const char* code, display_f disp);
+int display_stub(std::size_t idx);
+
+class menu_t
+{
+ struct private_ctor_t
+ {
+ };
+ friend void read(const char* filename);
+
+public:
+ // Tag type dispatch
+ menu_t(private_ctor_t, // NOLINT
+ const std::string& code,
+ const std::string& prompt)
+ : menu_t(code, prompt)
+ {
+ }
+
+ menu_t(const menu_t&) = delete;
+ menu_t& operator=(const menu_t&) = delete;
+ menu_t(menu_t&&) = delete;
+ menu_t& operator=(menu_t&&) = delete;
+
+ ~menu_t() noexcept
+ {
+ for (const auto [_, prompt] : m_items)
+ {
+ delete[] prompt; // NOLINT
+ }
+ }
+
+ const std::string& get_code() const { return m_code; }
+ const std::string& get_title() const { return m_title; }
+
+ const item_t* get_itemv() const { return m_items.data(); }
+ std::size_t get_size() const { return m_items.size(); }
+
+ auto get_callback(std::size_t idx) const { return m_items[idx].callback; }
+ const auto& get_code(std::size_t idx) const { return m_codes[idx].code; }
+ const auto& get_prompt(std::size_t idx) const { return m_codes[idx].prompt; }
+
+private:
+ menu_t(std::string code, std::string prompt)
+ : m_code(std::move(code))
+ , m_title(std::move(prompt))
+ {
+ }
+
+ void insert(const std::string& code,
+ const std::string& prompt,
+ callback_f callback = display_stub);
+
+ struct code_t
+ {
+ std::string code;
+ std::string prompt;
+ };
+
+ std::string m_code;
+ std::string m_title;
+ std::vector<code_t> m_codes;
+ std::vector<item_t> m_items;
+};
+
+} // namespace stamen::menu
diff --git a/include/stamen/stamen.h b/include/stamen/stamen.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "stddef.h" // NOLINT
+
+#ifdef __cplusplus
+extern "C"
+{
+namespace stamen {
+#endif
+
+typedef int (*stamen_callback_f)(size_t); // NOLINT
+
+typedef struct stamen_item_t stamen_item_t; // NOLINT
+struct stamen_item_t
+{
+ stamen_callback_f callback;
+ const char* prompt;
+};
+
+typedef int (*stamen_display_f)(const char*, // NOLINT
+ const stamen_item_t[],
+ size_t);
+
+#if !defined __cplusplus || defined WITH_C_BINDINGS
+
+int stamen_builtin_display(const char* title,
+ const stamen_item_t itemv[],
+ size_t size);
+
+#endif
+
+#ifdef __cplusplus
+} // namespace stamen
+} // extern "C"
+#endif
diff --git a/include/stamen/stamen.hpp b/include/stamen/stamen.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "stamen/stamen.h"
+
+namespace stamen {
+
+using callback_f = stamen_callback_f;
+using display_f = stamen_display_f;
+using item_t = stamen_item_t;
+
+int builtin_display(const char* title, const item_t itemv[], size_t size);
+
+} // namespace stamen
diff --git a/source/c_bindings.cpp b/source/c_bindings.cpp
@@ -0,0 +1,35 @@
+#include "stamen/menu.hpp"
+#include "stamen/stamen.hpp"
+
+extern "C"
+{
+namespace stamen {
+
+int stamen_builtin_display(const char* title,
+ const stamen_item_t itemv[],
+ size_t size)
+{
+ return builtin_display(title, itemv, size);
+}
+
+} // namespace stamen
+
+namespace stamen::menu {
+
+void stamen_menu_read(const char* filename)
+{
+ return read(filename);
+}
+
+void stamen_menu_insert(const char* code, stamen_callback_f callback)
+{
+ return insert(code, callback);
+}
+
+int stamen_menu_dynamic(const char* code, stamen_display_f disp)
+{
+ return dynamic(code, disp);
+}
+
+} // namespace stamen::menu
+}
diff --git a/source/generate.cpp b/source/generate.cpp
@@ -0,0 +1,160 @@
+#include <format>
+#include <fstream>
+#include <iostream>
+#include <string>
+
+#include "poafloc/poafloc.hpp"
+#include "stamen/menu.hpp"
+#include "stamen/stamen.hpp"
+
+struct arguments_t
+{
+ std::string config;
+ std::string display;
+ std::string header = "shared.h";
+ bool cpp = false;
+ bool user = false;
+};
+
+void generate_include(std::ostream& ost)
+{
+ ost << "#ifndef STAMEN_MENU_H\n";
+ ost << "#define STAMEN_MENU_H\n\n";
+
+ for (const auto& [code, menu] : stamen::menu::menu_lookup)
+ {
+ ost << std::format("int {}(size_t);\n", menu.get_code());
+ }
+
+ ost << "\n#endif\n";
+}
+
+void generate_source(std::ostream& ost, const arguments_t& args)
+{
+ ost << std::format("#include \"{}\"\n", args.header);
+ if (args.user)
+ {
+ if (args.cpp) ost << "#include \"stamen.hpp\"\n\n";
+ else ost << "#include \"stamen.h\"\n\n";
+ }
+ else
+ {
+ if (args.cpp) ost << "#include <stamen/stamen.hpp>\n\n";
+ else ost << "#include <stamen/stamen.h>\n\n";
+ }
+
+ ost << std::format("extern int {}(const char *title, ", args.display);
+ if (args.cpp) ost << "const stamen::item_t itemv[], size_t size);\n\n";
+ else ost << "const stamen_item_t itemv[], size_t size);\n\n";
+
+ for (const auto& [code, menu] : stamen::menu::menu_lookup)
+ {
+ ost << std::format("int {}(size_t /* unused */) {{\n", menu.get_code());
+
+ if (args.cpp) ost << "\tstatic const stamen::item_t items[] = ";
+ else ost << "\tstatic const stamen_item_t items[] = ";
+
+ ost << "{\n";
+ for (auto i = 0UL; i < menu.get_size(); i++)
+ {
+ ost << "\t\t{ " << menu.get_code(i);
+ ost << ", \"" << menu.get_prompt(i) << "\" },\n";
+ }
+ ost << "\t};\n";
+
+ ost << std::format("\treturn {}(\"{}\"", args.display, menu.get_title());
+ ost << ", items, sizeof(items) / sizeof(items[0]));\n";
+ ost << "}\n\n";
+ }
+}
+
+int parse_opt(int key, const char* arg, poafloc::Parser* parser)
+{
+ auto* arguments = static_cast<arguments_t*>(parser->input());
+ switch (key)
+ {
+ case 'd':
+ arguments->display = arg;
+ break;
+ case 'h':
+ arguments->header = arg;
+ break;
+ case 'u':
+ arguments->user = true;
+ break;
+ case 666:
+ arguments->cpp = false;
+ break;
+ case 777:
+ arguments->cpp = true;
+ break;
+ case poafloc::ARG:
+ if (!arguments->config.empty())
+ {
+ poafloc::failure(parser, 0, 0, "Too many arguments");
+ poafloc::help(parser, stderr, poafloc::STD_USAGE);
+ }
+ arguments->config = arg;
+ break;
+ case poafloc::NO_ARGS:
+ poafloc::failure(parser, 0, 0, "Missing an argument");
+ poafloc::help(parser, stderr, poafloc::STD_USAGE);
+ break;
+ case poafloc::END:
+ if (arguments->display.empty())
+ {
+ if (arguments->cpp) arguments->display = "stamen::builtin_display";
+ else arguments->display = "stamen_builtin_display";
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const poafloc::option_t options[] {
+ {nullptr, 0, nullptr, 0, "Output mode", 1},
+ {"c", 666, nullptr, 0, "Generate files for C"},
+ {"cpp", 777, nullptr, 0, "Generate files for C++"},
+ {nullptr, 0, nullptr, 0, "Output settings", 2},
+ {"display", 'd', "FUNC", 0, "Set display function to be called"},
+ {"user", 'u', nullptr, 0, "Include user stamen headers"},
+ {"header", 'h', "HDR", 0, "Header with free functions, default: shared.h"},
+ {nullptr, 0, nullptr, 0, "Informational Options", -1},
+ {nullptr},
+};
+
+static const poafloc::arg_t arg {
+ options,
+ parse_opt,
+ "config_file",
+ "",
+};
+
+int main(int argc, char* argv[])
+{
+ arguments_t args;
+
+ if (poafloc::parse(&arg, argc, argv, 0, &args) != 0)
+ {
+ std::cerr << "There was an error while parsing arguments";
+ return 1;
+ }
+
+ const auto& config = args.config;
+ stamen::menu::read(config.c_str());
+
+ const std::string::size_type pos = args.config.rfind('.');
+ const std::string ext = args.cpp ? "pp" : "";
+ const std::string base =
+ pos != std::string::npos ? config.substr(0, pos) : config;
+
+ std::ofstream include(base + ".h" + ext);
+ generate_include(include);
+
+ std::ofstream source(base + ".c" + ext);
+ generate_source(source, args);
+
+ return 0;
+}
diff --git a/source/menu.cpp b/source/menu.cpp
@@ -0,0 +1,99 @@
+#include <deque>
+#include <format>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <tuple>
+#include <utility>
+
+#include "stamen/menu.hpp"
+
+namespace stamen::menu {
+
+// NOLINTBEGIN
+std::unordered_map<std::string, menu_t> menu_lookup;
+std::unordered_map<std::string, callback_f> free_lookup;
+std::string display_stub_default;
+display_f display;
+// NOLINTEND
+
+void read(const char* filename)
+{
+ std::fstream fst(filename);
+ std::string line;
+ std::string delim;
+ std::string code;
+ std::string prompt;
+
+ auto last = menu_lookup.end();
+ while (std::getline(fst, line))
+ {
+ if (line.empty()) continue;
+
+ std::istringstream iss(line);
+ iss >> delim >> code >> std::ws;
+ std::getline(iss, prompt);
+
+ if (delim != "+") last->second.insert(code, prompt);
+ else
+ {
+ const auto [iter, succ] = menu_lookup.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(code),
+ std::forward_as_tuple(menu_t::private_ctor_t {}, code, prompt));
+ last = iter;
+ }
+ }
+}
+
+void insert(const char* code, callback_f callback)
+{
+ free_lookup.emplace(code, callback);
+}
+
+int dynamic(const char* code, display_f disp)
+{
+ menu::display_stub_default = code;
+ menu::display = disp;
+ return display_stub(0);
+}
+
+int display_stub(std::size_t idx)
+{
+ static std::deque<const menu_t*> stack;
+
+ const std::string& code =
+ !stack.empty() ? stack.back()->get_code(idx) : display_stub_default;
+
+ const auto ml_it = menu_lookup.find(code);
+ if (ml_it != menu_lookup.end())
+ {
+ const auto& m = ml_it->second; // NOLINT
+
+ stack.push_back(&m);
+ const int ret =
+ display(m.get_title().c_str(), m.get_itemv(), m.get_size());
+ stack.pop_back();
+
+ return ret;
+ }
+
+ const auto fl_it = free_lookup.find(code);
+ if (fl_it != free_lookup.end()) return fl_it->second(0);
+
+ std::cout << "Stamen: nothing to do..." << std::endl;
+ return 1;
+}
+
+void menu_t::insert(const std::string& code,
+ const std::string& prompt,
+ callback_f callback)
+{
+ char* buffer = new char[prompt.size() + 1]; // NOLINT
+ strcpy(buffer, prompt.c_str()); // NOLINT
+
+ m_items.emplace_back(callback, buffer);
+ m_codes.emplace_back(code, prompt);
+}
+
+} // namespace stamen::menu
diff --git a/source/stamen.cpp b/source/stamen.cpp
@@ -0,0 +1,61 @@
+#include <cmath>
+#include <format>
+#include <iostream>
+
+#include "stamen/stamen.hpp"
+
+#include "stamen/menu.h"
+
+namespace stamen {
+
+int builtin_display(const char* title, const item_t itemv[], size_t size)
+{
+ const auto items = std::span(itemv, size);
+ const size_t dgts = static_cast<size_t>(std::log10(size)) + 1;
+ int choice = 0;
+
+ while (true)
+ {
+ std::cout << std::format("{}:\n", title);
+ for (auto i = 0UL; i < size; i++)
+ {
+ std::cout << std::format(" {:{}}. {}\n", i, dgts, items[i].prompt);
+ }
+
+ while (true)
+ {
+ std::cout << "Choose an option: ";
+ if (std::cin >> choice && choice >= -1
+ && choice < static_cast<int>(size))
+ {
+ if (choice == -1)
+ {
+ std::cout << "Choice: back\n";
+ return 1;
+ }
+
+ const auto uchoice = static_cast<size_t>(choice);
+ std::cout << "Choice: " << items[uchoice].prompt << "\n\n";
+ const int res = items[uchoice].callback(uchoice);
+
+ if (res < 2) break;
+ return res - 1;
+ }
+
+ if (std::cin.eof())
+ {
+ std::cerr << "encountered end of input!\n";
+ return std::numeric_limits<int>::max();
+ }
+
+ std::cin.clear();
+ std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
+ std::cout << "Invalid option, please choose again!\n";
+ }
+ std::cout << std::endl;
+ }
+
+ return 1;
+}
+
+} // namespace stamen
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -1,23 +0,0 @@
-add_library(stamen STATIC stamen.cpp menu.cpp c_bindings.cpp)
-target_include_directories(stamen PRIVATE ../include)
-target_compile_definitions(stamen PRIVATE WITH_C_BINDINGS)
-set_target_properties(stamen PROPERTIES LINKER_LANGUAGE CXX)
-
-set_target_properties(stamen PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION ${PROJECT_VERSION_MAJOR}
- DEBUG_POSTFIX "d"
- PUBLIC_HEADER "include/stamen.h;include/stamen.hpp;include/menu.h;include/menu.hpp"
- MACOSX_RPATH ON
- WINDOWS_EXPORT_ALL_SYMBOLS ON
-)
-
-add_executable(stamen-generate generate.cpp)
-target_link_libraries(stamen-generate PRIVATE poafloc stamen)
-target_include_directories(stamen-generate PRIVATE ../include)
-
-set_target_properties(stamen-generate PROPERTIES
- VERSION ${PROJECT_VERSION}
- SOVERSION ${PROJECT_VERSION_MAJOR}
- RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
-)
diff --git a/src/c_bindings.cpp b/src/c_bindings.cpp
@@ -1,23 +0,0 @@
-#include "menu.hpp"
-#include "stamen.hpp"
-
-namespace stamen {
-
-int stamen_builtin_display(const char *title, const stamen_item_t itemv[], int size) {
- return builtin_display(title, itemv, size);
-}
-
-namespace menu {
-
-void stamen_menu_read(const char *filename) { return read(filename); }
-
-void stamen_menu_insert(const char *code, stamen_callback_f callback) {
- return insert(code, callback);
-}
-
-int stamen_menu_dynamic(const char *code, stamen_display_f display) {
- return dynamic(code, display);
-}
-
-} // namespace menu
-} // namespace stamen
diff --git a/src/generate.cpp b/src/generate.cpp
@@ -1,132 +0,0 @@
-#include <poafloc/poafloc.hpp>
-
-#include "menu.hpp"
-#include "stamen.hpp"
-
-#include <format>
-#include <fstream>
-#include <iostream>
-#include <string>
-
-struct arguments_t {
- std::string config;
- std::string display;
- std::string header = "shared.h";
- bool cpp = false;
- bool user = false;
-} opt;
-
-void generateIncludeHeaders(std::ostream &os) {}
-
-void generateInclude(std::ostream &os) {
- os << "#ifndef STAMEN_MENU_H\n";
- os << "#define STAMEN_MENU_H\n\n";
-
- for (const auto &[code, menu] : stamen::menu::menu_lookup) {
- os << std::format("int {}(int);\n", menu.getCode());
- }
-
- os << "\n#endif\n";
-}
-
-void generateSource(std::ostream &os) {
- os << std::format("#include \"{}\"\n", opt.header);
- if (opt.user) {
- if (opt.cpp) os << "#include \"stamen.hpp\"\n\n";
- else os << "#include \"stamen.h\"\n\n";
- } else {
- if (opt.cpp) os << "#include <stamen/stamen.hpp>\n\n";
- else os << "#include <stamen/stamen.h>\n\n";
- }
-
- os << std::format("extern int {}(const char *title, ", opt.display);
- if (opt.cpp) os << "const stamen::item_t itemv[], int size);\n\n";
- else os << "const stamen_item_t itemv[], int size);\n\n";
-
- for (const auto &[code, menu] : stamen::menu::menu_lookup) {
- os << std::format("int {}(int) {{\n", menu.getCode());
-
- if (opt.cpp) os << "\tstatic const stamen::item_t items[] = ";
- else os << "\tstatic const stamen_item_t items[] = ";
-
- os << "{\n";
- for (int i = 0; i < menu.getSize(); i++) {
- os << "\t\t{ " << menu.getCode(i);
- os << ", \"" << menu.getPrompt(i) << "\" },\n";
- }
- os << "\t};\n";
-
- os << std::format("\treturn {}(\"{}\"", opt.display, menu.getTitle());
- os << ", items, sizeof(items) / sizeof(items[0]));\n";
- os << "}\n\n";
- }
-}
-
-int parse_opt(int key, const char *arg, poafloc::Parser *parser) {
- auto arguments = (arguments_t *)parser->input();
- switch (key) {
- case 'd': arguments->display = arg; break;
- case 'h': arguments->header = arg; break;
- case 'u': arguments->user = true; break;
- case 666: arguments->cpp = false; break;
- case 777: arguments->cpp = true; break;
- case poafloc::ARG:
- if (!arguments->config.empty()) {
- poafloc::failure(parser, 0, 0, "Too many arguments");
- poafloc::help(parser, stderr, poafloc::STD_USAGE);
- }
- arguments->config = arg;
- break;
- case poafloc::NO_ARGS:
- poafloc::failure(parser, 0, 0, "Missing an argument");
- poafloc::help(parser, stderr, poafloc::STD_USAGE);
- case poafloc::END:
- if (arguments->display.empty()) {
- if (arguments->cpp) arguments->display = "stamen::builtin_display";
- else arguments->display = "stamen_builtin_display";
- }
- break;
- }
- return 0;
-}
-
-static const poafloc::option_t options[]{
- {0, 0, 0, 0, "Output mode", 1},
- {"c", 666, 0, 0, "Generate files for C"},
- {"cpp", 777, 0, 0, "Generate files for C++"},
- {0, 0, 0, 0, "Output settings", 2},
- {"display", 'd', "FUNC", 0, "Set display function to be called"},
- {"user", 'u', 0, 0, "Include user stamen headers"},
- {"header", 'h', "NAME", 0,
- "Header with free functions, default: shared.h"},
- {0, 0, 0, 0, "Informational Options", -1},
- {0},
-};
-
-static const poafloc::arg_t arg{
- options,
- parse_opt,
- "config_file",
- "",
-};
-
-int main(int argc, char *argv[]) {
- if (poafloc::parse(&arg, argc, argv, 0, &opt)) {
- std::cerr << "There was an error while parsing arguments";
- return 0;
- }
-
- const auto &config = opt.config;
- stamen::menu::read(config.c_str());
-
- std::string::size_type pos = opt.config.rfind('.');
- std::string base =
- pos != std::string::npos ? config.substr(0, pos) : config;
- std::string ext = opt.cpp ? "pp" : "";
-
- std::ofstream source(base + ".c" + ext), include(base + ".h" + ext);
- generateInclude(include);
- generateSource(source);
-
- return 0;
-}
diff --git a/src/menu.cpp b/src/menu.cpp
@@ -1,85 +0,0 @@
-#include "menu.hpp"
-
-#include <deque>
-#include <format>
-#include <fstream>
-#include <iostream>
-#include <sstream>
-#include <tuple>
-#include <utility>
-
-namespace stamen {
-namespace menu {
-
-std::unordered_map<std::string, menu_t> menu_lookup;
-std::unordered_map<std::string, callback_f> free_lookup;
-std::string display_stub_default;
-display_f display;
-
-void read(const char *filename) {
- std::string line, delim, code, prompt;
- std::fstream fs(filename);
-
- auto last = menu_lookup.end();
- while (std::getline(fs, line)) {
- if (line.empty()) continue;
-
- std::istringstream ss(line);
- ss >> delim >> code >> std::ws;
- std::getline(ss, prompt);
-
- if (delim != "+") last->second.insert(code, prompt);
- else {
- const auto [iter, succ] = menu_lookup.emplace(
- std::piecewise_construct, std::forward_as_tuple(code),
- std::forward_as_tuple(menu_t::private_ctor_t{}, code, prompt));
- last = iter;
- }
- }
-}
-
-void insert(const char *code, callback_f callback) {
- free_lookup.emplace(code, callback);
-}
-
-int dynamic(const char *code, display_f display) {
- menu::display_stub_default = code;
- menu::display = display;
- return display_stub(-1);
-}
-
-int display_stub(int idx) {
- static std::deque<const menu_t *> st;
-
- const std::string &code =
- !st.empty() ? st.back()->getCode(idx) : display_stub_default;
-
- const auto ml_it = menu_lookup.find(code);
- if (ml_it != menu_lookup.end()) {
- const auto &m = ml_it->second;
-
- st.push_back(&m);
- int ret = display(m.getTitle().c_str(), m.getItemv(), m.getSize());
- st.pop_back();
-
- return ret;
- }
-
- const auto fl_it = free_lookup.find(code);
- if (fl_it != free_lookup.end()) return fl_it->second(0);
-
- std::cout << "Stamen: nothing to do..." << std::endl;
- return 1;
-}
-
-void menu_t::insert(const std::string &code, const std::string &prompt,
- callback_f callback) {
- char *buffer = new char[prompt.size() + 1];
- strcpy(buffer, prompt.c_str());
-
- items.emplace_back(callback, buffer);
- codes.emplace_back(code, prompt);
-}
-
-} // namespace menu
-} // namespace stamen
diff --git a/src/stamen.cpp b/src/stamen.cpp
@@ -1,49 +0,0 @@
-#include "stamen.hpp"
-#include "menu.h"
-
-#include <cmath>
-#include <format>
-#include <iostream>
-
-namespace stamen {
-
-int builtin_display(const char *title, const item_t itemv[], int size) {
- const auto items = std::span(itemv, size_t(size));
- const size_t dgts = size_t(std::log10(size)) + 1;
- int choice = 0;
-
- while (true) {
- std::cout << std::format("{}:\n", title);
- for (auto i = 0ul; i < size; i++) {
- std::cout << std::format(" {:{}}. {}\n", i, dgts, items[i].prompt);
- }
-
- while (true) {
- std::cout << "Choose an option: ";
- if (std::cin >> choice && choice >= -1 && choice < (int)size) {
- if (choice == -1) {
- std::cout << "Choice: back\n";
- return 1;
- }
-
- std::cout << "Choice: " << items[choice].prompt << "\n\n";
- const int res = items[choice].callback(choice);
-
- if (res < 2) break;
- return res - 1;
- } else if (std::cin.eof()) {
- std::cerr << "encountered end of input!\n";
- return std::numeric_limits<int>::max();
- }
-
- std::cin.clear();
- std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
- std::cout << "Invalid option, please choose again!\n";
- }
- std::cout << std::endl;
- }
-
- return 1;
-}
-
-} // namespace stamen
diff --git a/stamenConfig.cmake.in b/stamenConfig.cmake.in
@@ -1,7 +0,0 @@
-@PACKAGE_INIT@
-include("${CMAKE_CURRENT_LIST_DIR}/stamenTargets.cmake")
-
-check_required_components(
- "stamen-generate"
- "stamen"
-)
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.14)
+
+project(stamenTests LANGUAGES CXX)
+
+include(../cmake/project-is-top-level.cmake)
+include(../cmake/folders.cmake)
+
+# ---- Dependencies ----
+
+if(PROJECT_IS_TOP_LEVEL)
+ find_package(stamen REQUIRED)
+ enable_testing()
+endif()
+
+# ---- Tests ----
+
+add_executable(stamen_test source/stamen_test.cpp)
+target_link_libraries(stamen_test PRIVATE stamen::stamen)
+target_compile_features(stamen_test PRIVATE cxx_std_20)
+
+add_test(NAME stamen_test COMMAND stamen_test)
+
+# ---- End-of-file commands ----
+
+add_folders(Test)
diff --git a/test/source/stamen_test.cpp b/test/source/stamen_test.cpp
@@ -0,0 +1,8 @@
+#include <string>
+
+#include "stamen/stamen.hpp"
+
+int main()
+{
+ return 0;
+}