hemplate

Simple XML template engine
git clone git://git.dimitrijedobrota.com/hemplate.git
Log | Files | Refs | README | LICENSE

commit 597755cfc6f067e3eee598175b6d88763c9993c1
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Sat, 22 Jun 2024 14:43:18 +0200

Initial commit

Diffstat:
A.clang-format | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.clang-tidy | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.codespellrc | 6++++++
A.gitignore | 13+++++++++++++
ABUILDING.md | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACMakeLists.txt | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACMakePresets.json | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACODE_OF_CONDUCT.md | 5+++++
ACONTRIBUTING.md | 14++++++++++++++
AHACKING.md | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE.md | 21+++++++++++++++++++++
AREADME.md | 15+++++++++++++++
Acmake/coverage.cmake | 33+++++++++++++++++++++++++++++++++
Acmake/dev-mode.cmake | 16++++++++++++++++
Acmake/folders.cmake | 21+++++++++++++++++++++
Acmake/install-config.cmake | 1+
Acmake/install-rules.cmake | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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+++++++++++++++++++++++++++++++++++++++++
Ainclude/hemplate/hemplate.hpp | 17+++++++++++++++++
Asource/hemplate.cpp | 13+++++++++++++
Atest/CMakeLists.txt | 25+++++++++++++++++++++++++
Atest/source/hemplate_test.cpp | 8++++++++
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; +}