commit 597755cfc6f067e3eee598175b6d88763c9993c1
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date: Sat, 22 Jun 2024 14:43:18 +0200
Initial commit
Diffstat:
28 files changed, 1311 insertions(+), 0 deletions(-)
diff --git a/.clang-format b/.clang-format
@@ -0,0 +1,178 @@
+---
+Language: Cpp
+# BasedOnStyle: Chromium
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveMacros: false
+AlignConsecutiveAssignments: false
+AlignConsecutiveBitFields: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: DontAlign
+AlignOperands: DontAlign
+AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortEnumsOnASingleLine: false
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: true
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: true
+ AfterControlStatement: MultiLine
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: false
+ AfterStruct: true
+ AfterUnion: true
+ AfterExternBlock: true
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: true
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Custom
+# BreakBeforeInheritanceComma: true
+BreakInheritanceList: BeforeComma
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: true
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: true
+BreakStringLiterals: true
+ColumnLimit: 79
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: false
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Regroup
+IncludeCategories:
+ # 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: ''
+IndentCaseLabels: true
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: AfterHash
+IndentExternBlock: NoIndent
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+InsertTrailingCommas: Wrapped
+JavaScriptQuotes: Double
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Never
+ObjCBlockIndentWidth: 2
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+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: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: true
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatementsExceptForEachMacros
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+Standard: Auto
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 8
+UseCRLF: false
+UseTab: Never
+WhitespaceSensitiveMacros:
+ - STRINGIZE
+ - PP_STRINGIZE
+ - BOOST_PP_STRINGIZE
+...
diff --git a/.clang-tidy b/.clang-tidy
@@ -0,0 +1,165 @@
+---
+# 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-*,\
+ -*-braces-around-statements,\
+ -bugprone-argument-comment,\
+ -bugprone-easily-swappable-parameters,\
+ -cppcoreguidelines-avoid-magic-numbers,\
+ -hicpp-signed-bitwise,\
+ -llvm-header-guard,\
+ -llvm-include-order,\
+ -modernize-use-nodiscard,\
+ -modernize-use-trailing-return-type,\
+ -readability-function-cognitive-complexity,\
+ -readability-magic-numbers,\
+ fuchsia-multiple-inheritance,\
+ -misc-no-recursion,\
+ -misc-non-private-member-variables-in-classes"
+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: 'lower_case'
+ - key: 'readability-identifier-naming.EnumConstantCase'
+ value: 'lower_case'
+ - 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: 'lower_case'
+ - 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
@@ -0,0 +1,13 @@
+**/.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: `hemplate`
+* Target name: `hemplate::hemplate`
+
+Example usage:
+
+```cmake
+find_package(hemplate 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
+ hemplate::hemplate
+)
+```
+
+### 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
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 3.14)
+
+include(cmake/prelude.cmake)
+
+project(
+ hemplate
+ VERSION 0.1.0
+ DESCRIPTION "Simple HTML template engine"
+ HOMEPAGE_URL "https://git.dimitrijedobrota.com/hemplate.git"
+ LANGUAGES CXX
+)
+
+include(cmake/project-is-top-level.cmake)
+include(cmake/variables.cmake)
+
+# ---- Declare library ----
+
+add_library(
+ hemplate_hemplate
+ source/hemplate.cpp
+)
+add_library(hemplate::hemplate ALIAS hemplate_hemplate)
+
+include(GenerateExportHeader)
+generate_export_header(
+ hemplate_hemplate
+ BASE_NAME hemplate
+ EXPORT_FILE_NAME export/hemplate/hemplate_export.hpp
+ CUSTOM_CONTENT_FROM_VARIABLE pragma_suppress_c4251
+)
+
+if(NOT BUILD_SHARED_LIBS)
+ target_compile_definitions(hemplate_hemplate PUBLIC HEMPLATE_STATIC_DEFINE)
+endif()
+
+set_target_properties(
+ hemplate_hemplate PROPERTIES
+ CXX_VISIBILITY_PRESET hidden
+ VISIBILITY_INLINES_HIDDEN YES
+ VERSION "${PROJECT_VERSION}"
+ SOVERSION "${PROJECT_VERSION_MAJOR}"
+ EXPORT_NAME hemplate
+ OUTPUT_NAME hemplate
+)
+
+target_include_directories(
+ hemplate_hemplate ${warning_guard}
+ PUBLIC
+ "\$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
+)
+
+target_include_directories(
+ hemplate_hemplate SYSTEM
+ PUBLIC
+ "\$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/export>"
+)
+
+target_compile_features(hemplate_hemplate PUBLIC cxx_std_20)
+
+# ---- Install rules ----
+
+if(NOT CMAKE_SKIP_INSTALL_RULES)
+ include(cmake/install-rules.cmake)
+endif()
+
+# ---- Developer mode ----
+
+if(NOT hemplate_DEVELOPER_MODE)
+ return()
+elseif(NOT PROJECT_IS_TOP_LEVEL)
+ message(
+ AUTHOR_WARNING
+ "Developer mode is intended for developers of hemplate"
+ )
+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": {
+ "hemplate_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 `hemplate_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 `hemplate_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/LICENSE.md b/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Dimitrije Dobrota
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
@@ -0,0 +1,15 @@
+# hemplate
+
+This is the hemplate project.
+
+# Building and installing
+
+See the [BUILDING](BUILDING.md) document.
+
+# Contributing
+
+See the [CONTRIBUTING](CONTRIBUTING.md) document.
+
+# Licensing
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) document 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}/hemplateTargets.cmake")
diff --git a/cmake/install-rules.cmake b/cmake/install-rules.cmake
@@ -0,0 +1,72 @@
+if(PROJECT_IS_TOP_LEVEL)
+ set(
+ CMAKE_INSTALL_INCLUDEDIR "include/hemplate-${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 hemplate)
+
+install(
+ DIRECTORY
+ include/
+ "${PROJECT_BINARY_DIR}/export/"
+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+ COMPONENT hemplate_Development
+)
+
+install(
+ TARGETS hemplate_hemplate
+ EXPORT hemplateTargets
+ RUNTIME #
+ COMPONENT hemplate_Runtime
+ LIBRARY #
+ COMPONENT hemplate_Runtime
+ NAMELINK_COMPONENT hemplate_Development
+ ARCHIVE #
+ COMPONENT hemplate_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(
+ hemplate_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package}"
+ CACHE STRING "CMake package config location relative to the install prefix"
+)
+set_property(CACHE hemplate_INSTALL_CMAKEDIR PROPERTY TYPE PATH)
+mark_as_advanced(hemplate_INSTALL_CMAKEDIR)
+
+install(
+ FILES cmake/install-config.cmake
+ DESTINATION "${hemplate_INSTALL_CMAKEDIR}"
+ RENAME "${package}Config.cmake"
+ COMPONENT hemplate_Development
+)
+
+install(
+ FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake"
+ DESTINATION "${hemplate_INSTALL_CMAKEDIR}"
+ COMPONENT hemplate_Development
+)
+
+install(
+ EXPORT hemplateTargets
+ NAMESPACE hemplate::
+ DESTINATION "${hemplate_INSTALL_CMAKEDIR}"
+ COMPONENT hemplate_Development
+)
+
+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 hemplate
+# 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(hemplate_DEVELOPER_MODE "Enable developer mode" OFF)
+ option(BUILD_SHARED_LIBS "Build shared libs." OFF)
+endif()
+
+# ---- Suppress C4251 on Windows ----
+
+# Please see include/hemplate/hemplate.hpp for more details
+set(pragma_suppress_c4251 "
+/* This needs to suppress only for MSVC */
+#if defined(_MSC_VER) && !defined(__ICL)
+# define HEMPLATE_SUPPRESS_C4251 _Pragma(\"warning(suppress:4251)\")
+#else
+# define HEMPLATE_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(
+ hemplate_INCLUDES_WITH_SYSTEM
+ "Use SYSTEM modifier for hemplate's includes, disabling warnings"
+ ON
+ )
+ mark_as_advanced(hemplate_INCLUDES_WITH_SYSTEM)
+ if(hemplate_INCLUDES_WITH_SYSTEM)
+ set(warning_guard SYSTEM)
+ endif()
+endif()
diff --git a/include/hemplate/hemplate.hpp b/include/hemplate/hemplate.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <string>
+
+#include "hemplate/hemplate_export.hpp"
+
+class HEMPLATE_EXPORT exported_class
+{
+public:
+ exported_class();
+
+ char const* name() const;
+
+private:
+ HEMPLATE_SUPPRESS_C4251
+ std::string m_name;
+};
diff --git a/source/hemplate.cpp b/source/hemplate.cpp
@@ -0,0 +1,13 @@
+#include <string>
+
+#include "hemplate/hemplate.hpp"
+
+exported_class::exported_class()
+ : m_name {"hemplate"}
+{
+}
+
+char const* exported_class::name() const
+{
+ return m_name.c_str();
+}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.14)
+
+project(hemplateTests LANGUAGES CXX)
+
+include(../cmake/project-is-top-level.cmake)
+include(../cmake/folders.cmake)
+
+# ---- Dependencies ----
+
+if(PROJECT_IS_TOP_LEVEL)
+ find_package(hemplate REQUIRED)
+ enable_testing()
+endif()
+
+# ---- Tests ----
+
+add_executable(hemplate_test source/hemplate_test.cpp)
+target_link_libraries(hemplate_test PRIVATE hemplate::hemplate)
+target_compile_features(hemplate_test PRIVATE cxx_std_20)
+
+add_test(NAME hemplate_test COMMAND hemplate_test)
+
+# ---- End-of-file commands ----
+
+add_folders(Test)
diff --git a/test/source/hemplate_test.cpp b/test/source/hemplate_test.cpp
@@ -0,0 +1,8 @@
+#include <string>
+
+#include "hemplate/hemplate.hpp"
+
+int main()
+{
+ return 0;
+}