stamen

Stamen - static menu generator
git clone git://git.dimitrijedobrota.com/stamen.git
Log | Files | Refs | README | LICENSE

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:
M.clang-format | 252++++++++++++++++++++++++++++++++++---------------------------------------------
A.clang-tidy | 172+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.codespellrc | 6++++++
M.gitignore | 19+++++++++++++------
ABUILDING.md | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MCMakeLists.txt | 162++++++++++++++++++++++++++++++++++++++++---------------------------------------
ACMakePresets.json | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACODE_OF_CONDUCT.md | 5+++++
ACONTRIBUTING.md | 14++++++++++++++
AHACKING.md | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MREADME.md | 75+++++++++++++++++++++++++++++++++++++++------------------------------------
Acmake/coverage.cmake | 33+++++++++++++++++++++++++++++++++
Acmake/dev-mode.cmake | 16++++++++++++++++
Acmake/folders.cmake | 21+++++++++++++++++++++
Acmake/install-config.cmake | 1+
Acmake/install-rules.cmake | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmake/lint-targets.cmake | 33+++++++++++++++++++++++++++++++++
Acmake/lint.cmake | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Acmake/prelude.cmake | 10++++++++++
Acmake/project-is-top-level.cmake | 6++++++
Acmake/spell-targets.cmake | 22++++++++++++++++++++++
Acmake/spell.cmake | 29+++++++++++++++++++++++++++++
Acmake/variables.cmake | 41+++++++++++++++++++++++++++++++++++++++++
Ddemo/CMakeLists.txt | 51---------------------------------------------------
Ddemo/dynamic.cpp | 35-----------------------------------
Ddemo/main.c | 33---------------------------------
Ddemo/main.cpp | 41-----------------------------------------
Ddemo/shared.h | 9---------
Aexample/CMakeLists.txt | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Rdemo/demo_menu.conf -> example/demo_menu.conf | 0
Aexample/dynamic.cpp | 46++++++++++++++++++++++++++++++++++++++++++++++
Aexample/example_c.c | 38++++++++++++++++++++++++++++++++++++++
Aexample/example_cpp.cpp | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexample/shared.h | 11+++++++++++
Dinclude/menu.h | 20--------------------
Dinclude/menu.hpp | 77-----------------------------------------------------------------------------
Dinclude/stamen.h | 31-------------------------------
Dinclude/stamen.hpp | 16----------------
Ainclude/stamen/menu.h | 18++++++++++++++++++
Ainclude/stamen/menu.hpp | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/stamen/stamen.h | 35+++++++++++++++++++++++++++++++++++
Ainclude/stamen/stamen.hpp | 13+++++++++++++
Asource/c_bindings.cpp | 35+++++++++++++++++++++++++++++++++++
Asource/generate.cpp | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/menu.cpp | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/stamen.cpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/CMakeLists.txt | 23-----------------------
Dsrc/c_bindings.cpp | 23-----------------------
Dsrc/generate.cpp | 132-------------------------------------------------------------------------------
Dsrc/menu.cpp | 85-------------------------------------------------------------------------------
Dsrc/stamen.cpp | 49-------------------------------------------------
DstamenConfig.cmake.in | 7-------
Atest/CMakeLists.txt | 25+++++++++++++++++++++++++
Atest/source/stamen_test.cpp | 8++++++++
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; +}