Add template overrides: canon templates and templates export
Every generated source file, README.md and .gitignore now comes from a template that a file of the same name can replace: - <project>/.canon/templates/ for that project, which wins over - ~/.config/canon/templates/ ($XDG_CONFIG_HOME respected) for all projects; canon new reads only these canon templates lists each template, where it comes from and the placeholders it can use. canon templates export [<file>...] [--user] copies built-in texts as a starting point and never overwrites. Overrides are checked strictly: an unknown file name or a placeholder the template does not provide is an error naming the file; hidden files are ignored. Generators take a template_set, so they stay pure. Also: - executor: a symlink to a directory counts as a directory (macOS /var, linked config directories) - plan: add_directory stops at the filesystem root - project: find_project_root, shared by load_project and templates - tests: a templates scenario; scenarios run canon with their own empty XDG_CONFIG_HOME so personal templates cannot affect results Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# canon project manifest: what canon declared for this project.
|
||||
# The managed block in CMakeLists.txt is rendered from this file.
|
||||
|
||||
[project]
|
||||
name = "libhello"
|
||||
kind = "lib"
|
||||
stem = "hello"
|
||||
spec = "P1204R0"
|
||||
|
||||
[build]
|
||||
backend = "cmake"
|
||||
cmake-minimum = "3.20"
|
||||
cxx-standard = 23
|
||||
|
||||
[[targets]]
|
||||
name = "libhello"
|
||||
type = "library"
|
||||
sources = ["hello/hello.cpp", "hello/details/core.cpp"]
|
||||
|
||||
[[modules]]
|
||||
path = "hello/hello"
|
||||
target = "libhello"
|
||||
unit-test = true
|
||||
|
||||
[[modules]]
|
||||
path = "hello/details/core"
|
||||
target = "libhello"
|
||||
unit-test = true
|
||||
|
||||
[[tests]]
|
||||
name = "basics"
|
||||
kind = "driver"
|
||||
path = "tests/basics"
|
||||
target = "libhello"
|
||||
@@ -0,0 +1,6 @@
|
||||
// Part of {{namespace}}.
|
||||
#pragma once
|
||||
|
||||
namespace {{namespace}} {
|
||||
|
||||
} // namespace {{namespace}}
|
||||
@@ -0,0 +1 @@
|
||||
/build/
|
||||
@@ -0,0 +1,40 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(libhello VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
option(BUILD_TESTING "Build libhello's tests" ON)
|
||||
if(BUILD_TESTING)
|
||||
enable_testing()
|
||||
endif()
|
||||
|
||||
# Add your own CMake outside the managed block below.
|
||||
|
||||
# >>> canon:managed >>>
|
||||
# Rendered from .canon.toml; canon rewrites everything between these markers.
|
||||
|
||||
add_library(libhello
|
||||
hello/hello.cpp
|
||||
hello/details/core.cpp
|
||||
)
|
||||
set_target_properties(libhello PROPERTIES PREFIX "")
|
||||
target_include_directories(libhello PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(libhello PUBLIC cxx_std_23)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(hello.hello.test hello/hello.test.cpp)
|
||||
target_link_libraries(hello.hello.test PRIVATE libhello)
|
||||
add_test(NAME hello/hello.test COMMAND hello.hello.test)
|
||||
|
||||
add_executable(hello.details.core.test hello/details/core.test.cpp)
|
||||
target_link_libraries(hello.details.core.test PRIVATE libhello)
|
||||
add_test(NAME hello/details/core.test COMMAND hello.details.core.test)
|
||||
|
||||
add_executable(tests.basics tests/basics/driver.cpp)
|
||||
target_link_libraries(tests.basics PRIVATE libhello)
|
||||
add_test(NAME basics COMMAND tests.basics)
|
||||
endif()
|
||||
|
||||
# <<< canon:managed <<<
|
||||
@@ -0,0 +1,12 @@
|
||||
# libhello
|
||||
|
||||
Created by canon. The layout follows [P1204R0](https://wg21.link/p1204r0)
|
||||
(Canonical Project Structure).
|
||||
|
||||
## Build and test
|
||||
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
#include <hello/details/core.hpp>
|
||||
|
||||
namespace hello::details {
|
||||
|
||||
} // namespace hello::details
|
||||
@@ -0,0 +1,6 @@
|
||||
// Part of hello::details.
|
||||
#pragma once
|
||||
|
||||
namespace hello::details {
|
||||
|
||||
} // namespace hello::details
|
||||
@@ -0,0 +1,6 @@
|
||||
// Unit tests for <hello/details/core.hpp>. Return non-zero from main() to fail.
|
||||
|
||||
#include <hello/details/core.hpp>
|
||||
|
||||
int main() {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <hello/hello.hpp>
|
||||
|
||||
#include <format>
|
||||
|
||||
namespace hello {
|
||||
|
||||
std::string greeting(std::string_view name) {
|
||||
return std::format("Hello, {}!", name);
|
||||
}
|
||||
|
||||
} // namespace hello
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace hello {
|
||||
|
||||
// Returns a greeting for name, e.g. "Hello, World!".
|
||||
[[nodiscard]] std::string greeting(std::string_view name);
|
||||
|
||||
} // namespace hello
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <hello/hello.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <print>
|
||||
|
||||
int main() {
|
||||
const auto actual = hello::greeting("World");
|
||||
if (actual != "Hello, World!") {
|
||||
std::println(stderr, "greeting(\"World\") returned \"{}\"", actual);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Functional test: uses libhello only through its public headers, as a consumer would.
|
||||
|
||||
#include <hello/hello.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <print>
|
||||
|
||||
int main() {
|
||||
const auto greeting = hello::greeting("libhello");
|
||||
std::println("{}", greeting);
|
||||
return greeting.starts_with("Hello") ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
+38
-3
@@ -1,7 +1,10 @@
|
||||
# canon command sequences shared by the golden and end-to-end tests. Each
|
||||
# scenario builds one project inside <work_dir>/<scenario>/. Requires CANON.
|
||||
#
|
||||
# canon runs with XDG_CONFIG_HOME set to <work_dir>/<scenario>-config, so user
|
||||
# templates in your own ~/.config never leak into the results.
|
||||
|
||||
set(CANON_SCENARIOS new-lib new-exe add-lib add-exe multi-target exe-tests)
|
||||
set(CANON_SCENARIOS new-lib new-exe add-lib add-exe multi-target exe-tests templates)
|
||||
|
||||
# Scenarios run canon from other directories, so a relative path would break.
|
||||
get_filename_component(CANON "${CANON}" ABSOLUTE)
|
||||
@@ -9,7 +12,7 @@ get_filename_component(CANON "${CANON}" ABSOLUTE)
|
||||
# Runs canon in working_dir and fails the script if it fails.
|
||||
function(canon_run working_dir)
|
||||
execute_process(
|
||||
COMMAND ${CANON} ${ARGN}
|
||||
COMMAND ${CMAKE_COMMAND} -E env XDG_CONFIG_HOME=${config_home} ${CANON} ${ARGN}
|
||||
WORKING_DIRECTORY ${working_dir}
|
||||
RESULT_VARIABLE status
|
||||
OUTPUT_VARIABLE output
|
||||
@@ -23,7 +26,7 @@ endfunction()
|
||||
# Runs canon in working_dir and fails the script if it succeeds.
|
||||
function(canon_run_fails working_dir)
|
||||
execute_process(
|
||||
COMMAND ${CANON} ${ARGN}
|
||||
COMMAND ${CMAKE_COMMAND} -E env XDG_CONFIG_HOME=${config_home} ${CANON} ${ARGN}
|
||||
WORKING_DIRECTORY ${working_dir}
|
||||
RESULT_VARIABLE status
|
||||
OUTPUT_QUIET ERROR_QUIET)
|
||||
@@ -38,6 +41,9 @@ function(canon_scenario scenario work_dir out_var)
|
||||
set(dir ${work_dir}/${scenario})
|
||||
file(REMOVE_RECURSE ${dir})
|
||||
file(MAKE_DIRECTORY ${dir})
|
||||
set(config_home ${work_dir}/${scenario}-config) # seen by canon_run
|
||||
file(REMOVE_RECURSE ${config_home})
|
||||
file(MAKE_DIRECTORY ${config_home})
|
||||
|
||||
if(scenario STREQUAL "new-lib")
|
||||
canon_run(${dir} new lib libhello)
|
||||
@@ -85,6 +91,35 @@ function(canon_scenario scenario work_dir out_var)
|
||||
canon_run(${project} add test spaces --arg "two words" --expect "Hello, two words!")
|
||||
canon_run_fails(${project} add test greets-paul)
|
||||
|
||||
elseif(scenario STREQUAL "templates")
|
||||
# User templates apply to every new project: a license line on library and module headers.
|
||||
canon_run(${dir} templates export library.hpp module.hpp --user)
|
||||
foreach(template library.hpp module.hpp)
|
||||
set(file ${config_home}/canon/templates/${template})
|
||||
file(READ ${file} text)
|
||||
file(WRITE ${file} "// SPDX-License-Identifier: MIT\n${text}")
|
||||
endforeach()
|
||||
canon_run(${dir} new lib libhello)
|
||||
set(project ${dir}/libhello)
|
||||
|
||||
# A project template wins over a user template.
|
||||
canon_run(${project} templates export module.hpp --dry-run) # must write nothing
|
||||
canon_run(${project} templates export module.hpp)
|
||||
file(READ ${project}/.canon/templates/module.hpp text)
|
||||
file(WRITE ${project}/.canon/templates/module.hpp "// Part of {{namespace}}.\n${text}")
|
||||
canon_run(${project} add module details/core)
|
||||
canon_run(${project} templates)
|
||||
canon_run_fails(${project} templates export module.hpp) # never overwrites
|
||||
|
||||
# Broken templates are errors, not silently skipped.
|
||||
file(WRITE ${project}/.canon/templates/module.cpp "{{nmae}}\n")
|
||||
canon_run_fails(${project} add module broken)
|
||||
canon_run_fails(${project} templates)
|
||||
file(REMOVE ${project}/.canon/templates/module.cpp)
|
||||
file(WRITE ${config_home}/canon/templates/modul.hpp "")
|
||||
canon_run_fails(${project} add module broken)
|
||||
file(REMOVE ${config_home}/canon/templates/modul.hpp)
|
||||
|
||||
else()
|
||||
message(FATAL_ERROR "unknown scenario '${scenario}'")
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user