diff --git a/.gitattributes b/.gitattributes
index 793dce7d..9a7df7dd 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,5 +1,6 @@
infra/** linguist-vendored
-cookiecutter/** linguist-vendored
+template/** linguist-vendored
+copier/** linguist-vendored
*.bib -linguist-detectable
*.tex -linguist-detectable
papers/* linguist-documentation
diff --git a/.github/workflows/catch2_exemplar_test.yml b/.github/workflows/catch2_exemplar_test.yml
index 60ee2576..bb0b73d2 100644
--- a/.github/workflows/catch2_exemplar_test.yml
+++ b/.github/workflows/catch2_exemplar_test.yml
@@ -17,12 +17,12 @@ jobs:
uses: actions/checkout@v6
- name: Test catch2 exemplar
run: |
- cd cookiecutter
- source ./check_cookiecutter.sh
- cookiecutter_venv_path=$(mktemp --directory --dry-run)
- setup_venv "$cookiecutter_venv_path"
- stamp "$PWD" "./catch2_exemplar" "catch2" "true"
- cd catch2_exemplar/exemplar
+ cd copier
+ source ./check_copier.sh
+ copier_venv_path=$(mktemp --directory --dry-run)
+ setup_venv "$copier_venv_path"
+ stamp "$repo_root" "./catch2_exemplar" "catch2" "true"
+ cd catch2_exemplar
cmake -B build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake -DCMAKE_CXX_STANDARD=20 -DCMAKE_INSTALL_PREFIX=$PWD/dist
cmake --build build
ctest --test-dir build
diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml
index 6e6615a6..592f0ec8 100644
--- a/.github/workflows/ci_tests.yml
+++ b/.github/workflows/ci_tests.yml
@@ -19,6 +19,45 @@ jobs:
beman-submodule-check:
uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.7.2
+ copier-test:
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/bemanproject/infra-containers-gcc:latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install uv
+ shell: bash
+ run: |
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
+ - name: Test Copier Template Variants
+ shell: bash
+ env:
+ GITHUB_ACTIONS: true
+ # gcc-release will use GCC 15 which supports C++23 modules appropriately in this container
+ run: ./copier/test_standard_project.sh gcc-release
+
+ copier-cmake-matrix:
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/bemanproject/infra-containers-gcc:latest
+ strategy:
+ fail-fast: false
+ matrix:
+ cmake_version: ["3.30.9", "3.31.10", "4.0.3", "4.1.3", "4.2.3", "4.3.2"]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install uv
+ shell: bash
+ run: |
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
+ - name: Test Copier Template Variants Matrix
+ shell: bash
+ env:
+ GITHUB_ACTIONS: true
+ # Run across our experimental boundaries
+ run: ./copier/test_cmake_matrix.sh gcc-release ${{ matrix.cmake_version }}
preset-test:
uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.7.2
with:
diff --git a/.github/workflows/cookiecutter_test.yml b/.github/workflows/copier_test.yml
similarity index 52%
rename from .github/workflows/cookiecutter_test.yml
rename to .github/workflows/copier_test.yml
index 04cdd05f..78b087a2 100644
--- a/.github/workflows/cookiecutter_test.yml
+++ b/.github/workflows/copier_test.yml
@@ -1,19 +1,23 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-name: Cookiecutter Test
+name: Copier Test
on:
push:
branches:
- main
pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
jobs:
- cookiecutter-test:
+ copier-test:
runs-on: ubuntu-latest
- name: "Check cookiecutter for consistency"
+ name: "Check copier for consistency"
steps:
- name: Checkout
uses: actions/checkout@v6
- - name: beman cookiecutter consistency check
+ - name: beman copier consistency check
run: |
- ./cookiecutter/check_cookiecutter.sh
+ ./copier/check_copier.sh
diff --git a/.github/workflows/todo_exemplar_test.yml b/.github/workflows/todo_exemplar_test.yml
index e7b4c33b..38bf7aa7 100644
--- a/.github/workflows/todo_exemplar_test.yml
+++ b/.github/workflows/todo_exemplar_test.yml
@@ -17,12 +17,12 @@ jobs:
uses: actions/checkout@v6
- name: Test static exemplar
run: |
- cd cookiecutter
- source ./check_cookiecutter.sh
- cookiecutter_venv_path=$(mktemp --directory --dry-run)
- setup_venv "$cookiecutter_venv_path"
- stamp "$PWD" "./static_exemplar" "gtest" "false"
- cd static_exemplar/exemplar
+ cd copier
+ source ./check_copier.sh
+ copier_venv_path=$(mktemp --directory --dry-run)
+ setup_venv "$copier_venv_path"
+ stamp "$repo_root" "./static_exemplar" "gtest" "false"
+ cd static_exemplar
grep -r 'identity' . && exit 1
find . -name '*identity*' | grep . && exit 1
cmake -B build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake -DCMAKE_CXX_STANDARD=20 -DCMAKE_INSTALL_PREFIX=$PWD/dist
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b3dc48c2..eb7313b7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -40,4 +40,4 @@ repos:
hooks:
- id: codespell
-exclude: 'cookiecutter/|infra/|port/'
+exclude: 'template/|copier/|infra/|port/'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 42e2f3d8..9b9a85ae 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -106,6 +106,47 @@ ctest --test-dir build
The file `./lockfile.json` configures the list of dependencies and versions that will be
acquired by FetchContent.
+## Developing The Copier Template
+
+This repository doubles as both the exemplar library and the Copier template used to stamp
+new Beman libraries.
+
+The template workflow is organized as follows:
+
+* `copier.yml` defines the Copier questions, defaults, validation rules, and post-copy
+ tasks.
+* `template/` contains the files rendered into generated projects.
+* `stamp.sh` is the user-facing wrapper for stamping a forked exemplar repository into a
+ new project.
+* `./copier/check_copier.sh` validates that exemplar regenerates cleanly and that a
+ non-exemplar project does not leak exemplar-specific names like `identity`.
+
+When testing local template changes, do not render directly from the Git checkout. The
+Copier helper scripts intentionally render from a temporary snapshot with `.git`, `build`,
+and `.venv` excluded so that validation uses the current worktree contents.
+
+Run the template checks with:
+
+```shell
+./copier/check_copier.sh
+```
+
+If you need to smoke-test generated projects manually, start from the same validated flow:
+
+```shell
+tmpdir=$(mktemp -d)
+python3 -m venv "$tmpdir/venv"
+"$tmpdir/venv/bin/python3" -m pip install copier
+src=$(mktemp -d)
+rsync -a --exclude .git --exclude build --exclude .venv ./ "$src/"
+"$tmpdir/venv/bin/copier" copy --trust --defaults \
+ -d template_src_path=https://github.com/bemanproject/exemplar.git \
+ -d template_commit="$(git rev-parse HEAD)" \
+ "$src" "$tmpdir/project"
+```
+
+Then configure and build the generated project as usual.
+
## Project-specific configure arguments
Project-specific options are prefixed with `BEMAN_EXEMPLAR`.
diff --git a/README.md b/README.md
index 0399d208..b5fbe989 100644
--- a/README.md
+++ b/README.md
@@ -1,54 +1,163 @@
# How to Use This Template
-To create a new Beman library, first click the "Use this template" dropdown in the
-top-right and select "Create a new repository":
+This repository uses [Copier](https://copier.readthedocs.io/) as its templating engine to generate, manage, and update Beman library boilerplate. The template inputs are defined in `copier.yml`, and the rendered project files live under `template/`.
-
-
- |
+> [!NOTE]
+> ?? **`stamp.sh` is deprecated.** If you are used to the legacy workflow of forking this repository and running `./stamp.sh`, please transition to the natively supported `copier` workflow below. The script remains for legacy CI compatibility but will not receive future updates.
-This will create a new repository that's an exact copy of exemplar. The next step is to
-customize it for your use case.
+## ? Quick Start: Create a New Library
-To do so, execute the bash script `stamp.sh`. This script will prompt for parameters like
-the new library's name, paper number, and description. Then it will replace your exemplar
-copy with a stamped-out template containing these parameters and create a corresponding
-git commit and branch:
+If you are already familiar with the Beman project lifecycle, you can generate a new library immediately using `uvx` (via the [uv package manager](https://docs.astral.sh/uv/)):
+```bash
+uvx --from copier copier copy "git+[https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git)" my-new-library
+cd my-new-library
+git init
+git add .
+git commit -m "Initial commit from Beman Exemplar template"
+uvx pre-commit install
```
-$ ./stamp.sh
- [1/6] project_name (my_project_name): example_library
- [2/6] maintainer (your_github_username): your_username
- [3/6] minimum_cpp_build_version (20):
- [4/6] paper (PnnnnRr): P9999R9
- [5/6] description (Short project description.):
- [6/6] Select unit_test_library
- 1 - gtest
- 2 - catch2
- Choose from [1/2] (1):
-Switched to a new branch 'stamp'
-Successfully stamped out exemplar template to the new branch 'stamp'.
-Try 'git push origin stamp' to push the branch upstream,
-then create a pull request.
+
+---
+
+## ?? The Beman Project Lifecycle
+
+To create a new library and get it officially adopted into the Beman project, you will generate the project locally, push it to your personal GitHub for active development, and eventually transfer it to the `bemanproject` organization.
+
+> **Why this specific workflow?**
+> Developing on your personal account first gives you a sandbox to experiment and clean up your Git history. Transferring the repository (rather than creating it directly in the org) ensures the `bemanproject` organization ultimately holds the canonical, unbroken Git history, which prevents downstream forks from breaking later.
+
+### Step 1: Push to Your Personal GitHub
+After running the Quick Start commands above, push the fresh template to your personal account to begin development.
+
+**Option A: Using the GitHub CLI (`gh`)**
+```bash
+gh repo create my-new-library --public --source=. --remote=origin --push
```
-From there, you can simply fill in all the remaining parts of the repository that are
-labeled 'todo'.
+**Option B: Using the GitHub Browser Interface**
+1. [Create a new repository](https://github.com/new). Name it exactly what you named your local folder. Leave it completely empty.
+2. Link and push your local code:
+ ```bash
+ git remote add origin [https://github.com/](https://github.com/)/my-new-library.git
+ git branch -M main
+ git push -u origin main
+ ```
-What follow is an example of a Beman library README.
+### Step 2: Incubation & Implementation
+Before transferring the project to the Beman organization, take the time to build out the actual proposal.
+* Expand beyond the default placeholder headers (e.g., `my-new-library.hpp`).
+* Stub out the necessary files for your library's domain.
+* Write out the skeleton of the library's design and documentation.
+* *Optional:* Once you have a working sandbox, feel free to squash or clean up your Git history using an interactive rebase (`git rebase -i`) before handing it over.
-# beman.exemplar: A Beman Library Exemplar
+### Step 3: Transfer to the Beman Project
+Once the repository has enough substance to be reviewed or collaborated on, hand it over to the organization to establish it as the canonical source.
+
+1. On your GitHub repository page, go to **Settings** > **General**.
+2. Scroll to the bottom to the **Danger Zone** and click **Transfer ownership**.
+3. Type `bemanproject` as the new owner and confirm.
+
+### Step 4: Fork It Back for Ongoing Development
+Now that `bemanproject/my-new-library` is the official upstream repository, standard open-source contribution rules apply.
+
+1. Navigate to the new URL: `https://github.com/bemanproject/my-new-library`.
+2. Click **Fork** in the top right corner to create a fork in your personal namespace.
+3. Update your local repository's remotes so you can pull from upstream and push to your new fork:
+ ```bash
+ # Rename your current remote (the org repo) to 'upstream'
+ git remote rename origin upstream
+
+ # Add your new personal fork as 'origin'
+ git remote add origin [https://github.com/](https://github.com/)/my-new-library.git
+ ```
+
+---
+
+## ?? Reconfiguring or Renaming Your Project
+
+One of the biggest advantages of using `copier` is that it actively maintains your project's boilerplate. If you start out with a placeholder name and later decide to rename your library (or change your minimum C++ version), you can instruct Copier to update the structural boilerplate using the `--data` flag.
-
+For example, to rename your project to `range_discombobulator`:
+
+```bash
+uvx --from copier copier update --trust --skip-answered --data project_name=range_discombobulator
+```
+
+**What this does:**
+* `--data project_name=...`: Overrides your previous answer for the project name.
+* `--skip-answered`: Skips the interactive prompt for all the questions you aren't changing.
+
+Copier will calculate the difference and automatically update the template-provided filesrenaming CMake configurations, GitHub Actions matrices, standard folder paths, and core macros.
+
+?? **Important Caveat:** Copier only manages the files it originally generated. If you have added custom files (like `detail/internal.hpp`), or manually typed the old project name into new C++ namespaces or include guards, you will need to update those manually. Always review the changes with `git diff`, and use your IDE or `grep` to hunt down any lingering references to the old name before committing!
+
+## Rebasing An Older Exemplar Clone Onto Copier
+
+If your repository predates the Copier migration and was created as a plain GitHub template copy, bootstrap a fresh stamped baseline at the original exemplar split point, then rebase your project commits onto that baseline.
+
+Start from a clean worktree and make sure you can identify the branch you want to migrate; the examples below assume `main`.
+
+```shell
+git remote add exemplar [https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git)
+git fetch exemplar
+base=$(git merge-base main exemplar/main)
+git branch pre-copier-backup main
+git switch --detach "$base"
+
+tmpdir=$(mktemp -d)
+python3 -m venv "$tmpdir/venv"
+"$tmpdir/venv/bin/python3" -m pip install copier pre-commit
+"$tmpdir/venv/bin/copier" copy --trust --overwrite \
+ -d project_name=your_project_name \
+ -d maintainer=your_github_username \
+ -d minimum_cpp_build_version=20 \
+ -d paper=PnnnnRr \
+ -d description="Short project description." \
+ -d unit_test_library=gtest \
+ -d generating_exemplar=false \
+ -d owner=bemanproject \
+ [https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git) \
+ .
+"$tmpdir/venv/bin/pre-commit" run --all-files || true
+git add .
+git switch -c stamp
+git commit -m "Bootstrap Copier template"
+
+git switch main
+git rebase --rebase-merges --onto stamp "$base" main
+```
+
+After the rebase, commit the generated `.copier-answers.yml`. That file is what enables future template updates with:
+
+```shell
+uvx --from copier copier update --trust
+```
+
+If you want to track a fork of exemplar rather than `bemanproject/exemplar`, edit `_src_path` in `.copier-answers.yml` before your first `copier update`.
+
+## Template Maintenance
+
+If you are changing the template itself rather than developing a library, use the Copier workflow directly:
+
+* Edit `copier.yml` for template questions, defaults, validators, and post-copy tasks.
+* Edit `template/` for files that should be rendered into stamped projects.
+* Run `./copier/check_copier.sh` to verify exemplar self-regeneration and non-exemplar templating.
+* Create a new project locally using the Quick Start commands when you want to use the template for real work.
+
+The consistency check renders from a `.git`-free temporary snapshot of the repository. That keeps local validation aligned with the current worktree contents, rather than only the last committed Git state.
+
+---
+
+What follows is an example of a Beman library README.
+
+# beman.exemplar: A Beman Library Exemplar
-
   [](https://coveralls.io/github/bemanproject/exemplar?branch=main)  [](https://godbolt.org/z/4qEPK87va)
`beman.exemplar` is a minimal C++ library conforming to [The Beman Standard](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md).
This can be used as a template for those intending to write Beman libraries.
-It may also find use as a minimal and modern C++ project structure.
+It may also find use as a minimal and modern C++ project structure.
**Implements**: `std::identity` proposed in [Standard Library Concepts (P0898R3)](https://wg21.link/P0898R3).
diff --git a/cookiecutter/check_cookiecutter.sh b/cookiecutter/check_cookiecutter.sh
deleted file mode 100755
index 394cca7e..00000000
--- a/cookiecutter/check_cookiecutter.sh
+++ /dev/null
@@ -1,101 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-#!/usr/bin/env bash
-
-set -euo pipefail
-
-declare script_dir=$(realpath $(dirname "$BASH_SOURCE"))
-
-function stamp() {
- local cookiecutter_dir="$1" ; shift
- local output_dir="$1" ; shift
- local unit_test_library="$1" ; shift
- local generating_exemplar="$1" ; shift
- python3 \
- -m cookiecutter \
- --no-input \
- --output-dir "$output_dir" \
- "$cookiecutter_dir" \
- project_name="exemplar" \
- minimum_cpp_build_version="17" \
- paper="P0898R3" \
- description="A Beman Library Exemplar" \
- unit_test_library="$unit_test_library" \
- _generating_exemplar="$generating_exemplar" \
- _ci_tests_cron="30 15 * * 6" \
- _pre_commit_update_cron="0 16 * * 0"
-}
-
-function check_consistency() {
- local out_dir_path
- out_dir_path=$(mktemp --directory --dry-run)
- cd /tmp
- stamp "$script_dir" "$out_dir_path" "gtest" "true"
- cp "$script_dir"/../.github/workflows/cookiecutter_test.yml "$out_dir_path"/exemplar/.github/workflows
- cp "$script_dir"/../.github/workflows/catch2_exemplar_test.yml "$out_dir_path"/exemplar/.github/workflows
- cp "$script_dir"/../.github/workflows/todo_exemplar_test.yml "$out_dir_path"/exemplar/.github/workflows
- mkdir "$out_dir_path"/exemplar/images
- cp "$script_dir"/../images/use-this-template.png "$out_dir_path"/exemplar/images/use-this-template.png
- cp "$script_dir"/../stamp.sh "$out_dir_path"/exemplar/stamp.sh
- local diff_path
- diff_path=$(mktemp)
- diff -r "$script_dir/.." "$out_dir_path/exemplar" \
- | grep -v -e 'cookiecutter$' -e '.git$' > "$diff_path" || true
- rm -rf "$out_dir_path"
- if [[ $(wc -l "$diff_path" | cut -d' ' -f1) -gt 0 ]] ; then
- echo "Discrepancy between exemplar and cookiecutter:" >&2
- cat "$diff_path"
- rm "$diff_path"
- exit 1
- fi
- rm "$diff_path"
-}
-
-function check_templating() {
- local out_dir_path
- out_dir_path=$(mktemp --directory --dry-run)
- cd /tmp
- python3 \
- -m cookiecutter \
- --no-input \
- --output-dir "$out_dir_path" \
- "$script_dir" \
- project_name="RLZrmX9NfS" \
- minimum_cpp_build_version="17" \
- paper="P0898R3" \
- description="A Beman Library RLZrmX9NfS" \
- _generating_exemplar="false" \
- _ci_tests_cron="30 15 * * 6" \
- _pre_commit_update_cron="0 16 * * 0"
- rm -rf "$out_dir_path/RLZrmX9NfS/infra"
- local grep_path
- grep_path=$(mktemp)
- grep \
- --dereference-recursive --context=5 --color=always \
- -e "exemplar" -e "identity" "$out_dir_path/RLZrmX9NfS" > "$grep_path" || true
- rm -rf "$out_dir_path"
- if [[ $(wc -l "$grep_path" | cut -d' ' -f1) -gt 0 ]] ; then
- echo "Untemplated \"exemplar\" or \"identity\" in cookiecutter:" >&2
- cat "$grep_path"
- rm "$grep_path"
- exit 1
- fi
- rm "$grep_path"
-}
-
-function setup_venv() {
- local path="$1" ; shift
- python3 -m venv "$cookiecutter_venv_path"
- source "$cookiecutter_venv_path/bin/activate"
- python3 -m pip install cookiecutter >& /dev/null
-}
-
-function main() {
- local cookiecutter_venv_path
- cookiecutter_venv_path=$(mktemp --directory --dry-run)
- setup_venv "$cookiecutter_venv_path"
- check_consistency
- check_templating
- rm -rf "$cookiecutter_venv_path"
-}
-
-[[ "${BASH_SOURCE[0]}" != "${0}" ]] || main "$@"
diff --git a/cookiecutter/cookiecutter.json b/cookiecutter/cookiecutter.json
deleted file mode 100644
index bb406fe8..00000000
--- a/cookiecutter/cookiecutter.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "project_name": "my_project_name",
- "maintainer": "your_github_username",
- "minimum_cpp_build_version": "20",
- "paper": "PnnnnRr",
- "description": "Short project description.",
- "unit_test_library": ["gtest", "catch2"],
- "_generating_exemplar": false,
- "_owner": "bemanproject",
- "_ci_tests_cron": "",
- "_pre_commit_update_cron": "",
- "_copy_without_render": [
- "infra"
- ],
- "_jinja2_env_vars": {"trim_blocks": true}
-}
diff --git a/cookiecutter/hooks/post_gen_project.py b/cookiecutter/hooks/post_gen_project.py
deleted file mode 100755
index 9965c4c3..00000000
--- a/cookiecutter/hooks/post_gen_project.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-import shutil
-import subprocess
-from pathlib import Path
-import os
-
-project_name = "{{ cookiecutter.project_name }}"
-generating_exemplar = "{{ cookiecutter._generating_exemplar }}" == "True"
-
-if not generating_exemplar:
- os.rename("include/beman/" + project_name + "/identity.hpp", "include/beman/" + project_name + "/todo.hpp")
- os.rename("examples/identity_direct_usage.cpp", "examples/todo.cpp")
- os.remove("examples/identity_as_default_projection.cpp")
- os.rename("tests/beman/" + project_name + "/identity.test.cpp", "tests/beman/" + project_name + "/todo.test.cpp")
-
- # Record the exemplar commit this project was stamped from.
- result = subprocess.run(
- ["git", "ls-remote", "https://github.com/bemanproject/exemplar.git", "HEAD"],
- capture_output=True,
- text=True,
- check=True,
- )
- sha = result.stdout.split()[0]
- Path(".exemplar_version").write_text(sha + "\n")
diff --git a/cookiecutter/{{cookiecutter.project_name}}/CMakeLists.txt b/cookiecutter/{{cookiecutter.project_name}}/CMakeLists.txt
deleted file mode 100644
index 80181421..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/CMakeLists.txt
+++ /dev/null
@@ -1,94 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-cmake_minimum_required(VERSION 3.30...4.3)
-
-include(infra/cmake/enable-experimental-import-std.cmake)
-
-project(
- beman.{{cookiecutter.project_name}}
- DESCRIPTION "{{cookiecutter.description}}"
- LANGUAGES CXX
-{% if cookiecutter._generating_exemplar %}
- VERSION 2.4.0
-{% else %}
- VERSION 0.1.0
-{% endif %}
-)
-
-# [CMAKE.SKIP_TESTS]
-option(
- BEMAN_{{cookiecutter.project_name.upper()}}_BUILD_TESTS
- "Enable building tests and test infrastructure. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }."
- ${PROJECT_IS_TOP_LEVEL}
-)
-
-# [CMAKE.SKIP_EXAMPLES]
-option(
- BEMAN_{{cookiecutter.project_name.upper()}}_BUILD_EXAMPLES
- "Enable building examples. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }."
- ${PROJECT_IS_TOP_LEVEL}
-)
-
-option(BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES "Provide beman.{{cookiecutter.project_name}} as a C++ module" OFF)
-
-if(BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES)
- set(CMAKE_CXX_SCAN_FOR_MODULES ON)
-endif()
-
-configure_file(
- "${PROJECT_SOURCE_DIR}/include/beman/{{cookiecutter.project_name}}/config_generated.hpp.in"
- "${PROJECT_BINARY_DIR}/include/beman/{{cookiecutter.project_name}}/config_generated.hpp"
- @ONLY
-)
-
-# for find of beman_install_library and configure_build_telemetry
-include(infra/cmake/beman-install-library.cmake)
-include(infra/cmake/BuildTelemetryConfig.cmake)
-
-if(BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES)
- add_library(beman.{{cookiecutter.project_name}} STATIC)
-else()
- add_library(beman.{{cookiecutter.project_name}} INTERFACE)
-endif()
-add_library(beman::{{cookiecutter.project_name}} ALIAS beman.{{cookiecutter.project_name}})
-
-if(BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES)
- target_sources(
- beman.{{cookiecutter.project_name}}
- PUBLIC
- FILE_SET CXX_MODULES
- FILE_SET HEADERS
- BASE_DIRS
- "${CMAKE_CURRENT_SOURCE_DIR}/include"
- "${CMAKE_CURRENT_BINARY_DIR}/include"
- )
- set_target_properties(beman.{{cookiecutter.project_name}} PROPERTIES CXX_MODULE_STD ON)
- target_compile_features(beman.{{cookiecutter.project_name}} PUBLIC cxx_std_23)
-else()
- target_sources(
- beman.{{cookiecutter.project_name}}
- PUBLIC
- FILE_SET HEADERS
- BASE_DIRS
- "${CMAKE_CURRENT_SOURCE_DIR}/include"
- "${CMAKE_CURRENT_BINARY_DIR}/include"
- )
- set_target_properties(
- beman.{{cookiecutter.project_name}}
- PROPERTIES VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL}
- )
-endif()
-
-add_subdirectory(include/beman/{{cookiecutter.project_name}})
-
-beman_install_library(beman.{{cookiecutter.project_name}} TARGETS beman.{{cookiecutter.project_name}})
-configure_build_telemetry()
-
-if(BEMAN_{{cookiecutter.project_name.upper()}}_BUILD_TESTS)
- enable_testing()
- add_subdirectory(tests/beman/{{cookiecutter.project_name}})
-endif()
-
-if(BEMAN_{{cookiecutter.project_name.upper()}}_BUILD_EXAMPLES)
- add_subdirectory(examples)
-endif()
diff --git a/cookiecutter/{{cookiecutter.project_name}}/README.md b/cookiecutter/{{cookiecutter.project_name}}/README.md
deleted file mode 100644
index e3161121..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/README.md
+++ /dev/null
@@ -1,267 +0,0 @@
-{% if cookiecutter._generating_exemplar %}
-# How to Use This Template
-
-To create a new Beman library, first click the "Use this template" dropdown in the
-top-right and select "Create a new repository":
-
-
-
- |
-
-This will create a new repository that's an exact copy of exemplar. The next step is to
-customize it for your use case.
-
-To do so, execute the bash script `stamp.sh`. This script will prompt for parameters like
-the new library's name, paper number, and description. Then it will replace your exemplar
-copy with a stamped-out template containing these parameters and create a corresponding
-git commit and branch:
-
-```
-$ ./stamp.sh
- [1/6] project_name (my_project_name): example_library
- [2/6] maintainer (your_github_username): your_username
- [3/6] minimum_cpp_build_version (20):
- [4/6] paper (PnnnnRr): P9999R9
- [5/6] description (Short project description.):
- [6/6] Select unit_test_library
- 1 - gtest
- 2 - catch2
- Choose from [1/2] (1):
-Switched to a new branch 'stamp'
-Successfully stamped out exemplar template to the new branch 'stamp'.
-Try 'git push origin stamp' to push the branch upstream,
-then create a pull request.
-```
-
-From there, you can simply fill in all the remaining parts of the repository that are
-labeled 'todo'.
-
-What follow is an example of a Beman library README.
-
-{% endif %}
-# beman.{{cookiecutter.project_name}}: {{cookiecutter.description}}
-
-
-
-
-   [](https://coveralls.io/github/{{cookiecutter._owner}}/{{cookiecutter.project_name}}?branch=main) {% if cookiecutter._generating_exemplar %} [](https://godbolt.org/z/4qEPK87va){% endif %}
-
-
-{% if cookiecutter._generating_exemplar %}
-`beman.exemplar` is a minimal C++ library conforming to [The Beman Standard](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md).
-This can be used as a template for those intending to write Beman libraries.
-It may also find use as a minimal and modern C++ project structure.
-{% else %}
-`beman.{{cookiecutter.project_name}}` is (... TODO: description).
-{% endif %}
-
-{% if cookiecutter._generating_exemplar %}
-**Implements**: `std::identity` proposed in [Standard Library Concepts ({{cookiecutter.paper}})](https://wg21.link/{{cookiecutter.paper}}).
-{% else %}
-**Implements**: `std::todo` proposed in [TODO ({{cookiecutter.paper}})](https://wg21.link/{{cookiecutter.paper}}).
-{% endif %}
-
-**Status**: [Under development and not yet ready for production use.](https://github.com/bemanproject/beman/blob/main/docs/beman_library_maturity_model.md#under-development-and-not-yet-ready-for-production-use)
-
-## License
-
-`beman.{{cookiecutter.project_name}}` is licensed under the Apache License v2.0 with LLVM Exceptions.
-
-## Usage
-
-{% if cookiecutter._generating_exemplar %}
-`std::identity` is a function object type whose `operator()` returns its argument unchanged.
-`std::identity` serves as the default projection in constrained algorithms.
-Its direct usage is usually not needed.
-
-### Usage: default projection in constrained algorithms
-
-The following code snippet illustrates how we can achieve a default projection using `beman::{{cookiecutter.project_name}}::identity`:
-
-```cpp
-#include
-
-namespace exe = beman::{{cookiecutter.project_name}};
-
-// Class with a pair of values.
-struct Pair
-{
- int n;
- std::string s;
-
- // Output the pair in the form {n, s}.
- // Used by the range-printer if no custom projection is provided (default: identity projection).
- friend std::ostream &operator<<(std::ostream &os, const Pair &p)
- {
- return os << "Pair" << '{' << p.n << ", " << p.s << '}';
- }
-};
-
-// A range-printer that can print projected (modified) elements of a range.
-// All the elements of the range are printed in the form {element1, element2, ...}.
-// e.g., pairs with identity: Pair{1, one}, Pair{2, two}, Pair{3, three}
-// e.g., pairs with custom projection: {1:one, 2:two, 3:three}
-template
-void print(const std::string_view rem, R &&range, Projection projection = exe::identity>)
-{
- std::cout << rem << '{';
- std::ranges::for_each(
- range,
- [O = 0](const auto &o) mutable
- { std::cout << (O++ ? ", " : "") << o; },
- projection);
- std::cout << "}\n";
-};
-
-int main()
-{
- // A vector of pairs to print.
- const std::vector pairs = {
- {1, "one"},
- {2, "two"},
- {3, "three"},
- };
-
- // Print the pairs using the default projection.
- print("\tpairs with beman: ", pairs);
-
- return 0;
-}
-
-```
-
-{% else %}
-TODO
-
-{% endif %}
-Full runnable examples can be found in [`examples/`](examples/).
-
-## Dependencies
-
-### Build Environment
-
-This project requires at least the following to build:
-
-* A C++ compiler that conforms to the C++{{cookiecutter.minimum_cpp_build_version}} standard or greater
-* CMake 3.30 or later
-* (Test Only) GoogleTest
-
-You can disable building tests by setting CMake option `BEMAN_{{cookiecutter.project_name.upper()}}_BUILD_TESTS` to
-`OFF` when configuring the project.
-
-### Supported Platforms
-
-| Compiler | Version | C++ Standards | Standard Library |
-|------------|---------|---------------|-------------------|
-| GCC | 16-13 | C++26-C++17 | libstdc++ |
-| GCC | 12-11 | C++23-C++17 | libstdc++ |
-| Clang | 22-19 | C++26-C++17 | libstdc++, libc++ |
-| Clang | 18 | C++26-C++17 | libc++ |
-| Clang | 18 | C++23-C++17 | libstdc++ |
-| Clang | 17 | C++26-C++17 | libc++ |
-| Clang | 17 | C++20, C++17 | libstdc++ |
-| AppleClang | latest | C++26-C++17 | libc++ |
-| MSVC | latest | C++23 | MSVC STL |
-
-## Development
-
-See the [Contributing Guidelines](CONTRIBUTING.md).
-
-## Integrate beman.{{cookiecutter.project_name}} into your project
-
-### Build
-
-You can build {{cookiecutter.project_name}} using a CMake workflow preset:
-
-```bash
-cmake --workflow --preset gcc-release
-```
-
-To list available workflow presets, you can invoke:
-
-```bash
-cmake --list-presets=workflow
-```
-
-For details on building beman.{{cookiecutter.project_name}} without using a CMake preset, refer to the
-[Contributing Guidelines](CONTRIBUTING.md).
-
-### Installation
-
-#### Vcpkg
-
-The preferred way to install {{cookiecutter.project_name}} is via vcpkg. To do so, after installing vcpkg
-itself, you need to add support for the Beman project's [vcpkg
-registry](https://github.com/bemanproject/vcpkg-registry) by configuring a
-`vcpkg-configuration.json` file (which {{cookiecutter.project_name}} [provides](vcpkg-configuration.json)).
-
-Then, simply run `vcpkg install beman-{{cookiecutter.project_name.replace("_", "-")}}`.
-
-#### Manual
-
-To install beman.{{cookiecutter.project_name}} globally after building with the `gcc-release` preset, you can
-run:
-
-```bash
-sudo cmake --install build/gcc-release
-```
-
-Alternatively, to install to a prefix, for example `/opt/beman`, you can run:
-
-```bash
-sudo cmake --install build/gcc-release --prefix /opt/beman
-```
-
-This will generate the following directory structure:
-
-```txt
-/opt/beman
-├── include
-│ └── beman
-│ └── {{cookiecutter.project_name}}
-│ ├── {{cookiecutter.project_name}}.hpp
-│ └── ...
-└── lib
- └── cmake
- └── beman.{{cookiecutter.project_name}}
- ├── beman.{{cookiecutter.project_name}}-config-version.cmake
- ├── beman.{{cookiecutter.project_name}}-config.cmake
- └── beman.{{cookiecutter.project_name}}-targets.cmake
-```
-
-### CMake Configuration
-
-If you installed beman.{{cookiecutter.project_name}} to a prefix, you can specify that prefix to your CMake
-project using `CMAKE_PREFIX_PATH`; for example, `-DCMAKE_PREFIX_PATH=/opt/beman`.
-
-You need to bring in the `beman.{{cookiecutter.project_name}}` package to define the `beman::{{cookiecutter.project_name}}` CMake
-target:
-
-```cmake
-find_package(beman.{{cookiecutter.project_name}} REQUIRED)
-```
-
-You will then need to add `beman::{{cookiecutter.project_name}}` to the link libraries of any libraries or
-executables that include `beman.{{cookiecutter.project_name}}` headers.
-
-```cmake
-target_link_libraries(yourlib PUBLIC beman::{{cookiecutter.project_name}})
-```
-
-### Using beman.{{cookiecutter.project_name}}
-
-To use `beman.{{cookiecutter.project_name}}` in your C++ project,
-include an appropriate `beman.{{cookiecutter.project_name}}` header from your source code.
-
-```c++
-#include
-```
-
-> [!NOTE]
->
-> `beman.{{cookiecutter.project_name}}` headers are to be included with the `beman/{{cookiecutter.project_name}}/` prefix.
-> Altering include search paths to spell the include target another way (e.g.
-> `#include <{{cookiecutter.project_name}}.hpp>`) is unsupported.
diff --git a/cookiecutter/{{cookiecutter.project_name}}/examples/identity_direct_usage.cpp b/cookiecutter/{{cookiecutter.project_name}}/examples/identity_direct_usage.cpp
deleted file mode 100644
index 0284d36d..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/examples/identity_direct_usage.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-{% set identity = "identity" if cookiecutter._generating_exemplar else "todo" %}
-
-#include
-#include
-
-{% if cookiecutter._generating_exemplar %}
-#if BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES()
-import std;
-#else
- #include
-#endif
-
-namespace exe = beman::{{cookiecutter.project_name}};
-
-int main() {
- std::cout << exe::identity()(2024) << '\n';
- return 0;
-}
-{% else %}
-int main() {
- // TODO
-}
-{% endif %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/CMakeLists.txt b/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/CMakeLists.txt
deleted file mode 100644
index 352eb052..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/CMakeLists.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-{% set identity = "identity" if cookiecutter._generating_exemplar else "todo" %}
-
-if(BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES)
- target_sources(
- beman.{{cookiecutter.project_name}}
- PUBLIC
- FILE_SET CXX_MODULES FILES {{cookiecutter.project_name}}.cppm
- FILE_SET HEADERS
- FILES
- config.hpp
- {{cookiecutter.project_name}}.hpp
- {{identity}}.hpp
- "${PROJECT_BINARY_DIR}/include/beman/{{cookiecutter.project_name}}/config_generated.hpp"
- )
-else()
- target_sources(
- beman.{{cookiecutter.project_name}}
- PUBLIC
- FILE_SET HEADERS
- FILES
- config.hpp
- {{cookiecutter.project_name}}.hpp
- {{identity}}.hpp
- "${PROJECT_BINARY_DIR}/include/beman/{{cookiecutter.project_name}}/config_generated.hpp"
- )
-endif()
diff --git a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/config.hpp b/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/config.hpp
deleted file mode 100644
index 7b86494a..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/config.hpp
+++ /dev/null
@@ -1,12 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#ifndef BEMAN_{{cookiecutter.project_name.upper()}}_CONFIG_HPP
-#define BEMAN_{{cookiecutter.project_name.upper()}}_CONFIG_HPP
-
-#if !defined(__has_include) || __has_include()
- #include
-#else
- #define BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES() 0
-#endif
-
-#endif
diff --git a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/config_generated.hpp.in b/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/config_generated.hpp.in
deleted file mode 100644
index 90941a1f..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/config_generated.hpp.in
+++ /dev/null
@@ -1,8 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
-#ifndef BEMAN_{{cookiecutter.project_name.upper()}}_CONFIG_GENERATED_HPP
-#define BEMAN_{{cookiecutter.project_name.upper()}}_CONFIG_GENERATED_HPP
-
-#cmakedefine01 BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES()
-
-#endif
diff --git a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}.cppm b/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}.cppm
deleted file mode 100644
index 96b15487..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}.cppm
+++ /dev/null
@@ -1,11 +0,0 @@
-export module beman.{{cookiecutter.project_name}};
-
-import std;
-
-#define BEMAN_{{cookiecutter.project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT
-export {
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Winclude-angled-in-module-purview"
-#include
-#pragma clang diagnostic pop
-}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}.hpp b/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}.hpp
deleted file mode 100644
index 7df9cb80..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}.hpp
+++ /dev/null
@@ -1,20 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-{% set identity = "identity" if cookiecutter._generating_exemplar else "todo" %}
-
-#ifndef BEMAN_{{cookiecutter.project_name.upper()}}_{{cookiecutter.project_name.upper()}}_HPP
-#define BEMAN_{{cookiecutter.project_name.upper()}}_{{cookiecutter.project_name.upper()}}_HPP
-
-#include
-
-#if BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES() && !defined(BEMAN_{{cookiecutter.project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT)
-
-import beman.{{cookiecutter.project_name}};
-
-#else
-
- #include
-
-#endif // BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES() &&
- // !defined(BEMAN_{{cookiecutter.project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT)
-
-#endif // BEMAN_{{cookiecutter.project_name.upper()}}_{{cookiecutter.project_name.upper()}}_HPP
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/.beman_submodule b/cookiecutter/{{cookiecutter.project_name}}/infra/.beman_submodule
deleted file mode 100644
index 7367e0e2..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/infra/.beman_submodule
+++ /dev/null
@@ -1,3 +0,0 @@
-[beman_submodule]
-remote=https://github.com/bemanproject/infra.git
-commit_hash=eb7b7c3688bd8f26ab0c145e3147df9a8f2ec8ce
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/.github/CODEOWNERS b/cookiecutter/{{cookiecutter.project_name}}/infra/.github/CODEOWNERS
deleted file mode 100644
index 4ff90a43..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/infra/.github/CODEOWNERS
+++ /dev/null
@@ -1 +0,0 @@
-* @ednolan @neatudarius @rishyak @wusatosi @JeffGarland
diff --git a/cookiecutter/{{cookiecutter.project_name}}/tests/beman/{{cookiecutter.project_name}}/CMakeLists.txt b/cookiecutter/{{cookiecutter.project_name}}/tests/beman/{{cookiecutter.project_name}}/CMakeLists.txt
deleted file mode 100644
index 4450cd8d..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/tests/beman/{{cookiecutter.project_name}}/CMakeLists.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-{% set identity = "identity" if cookiecutter._generating_exemplar else "todo" %}
-
-{% if cookiecutter.unit_test_library == "gtest" %}
-find_package(GTest REQUIRED)
-{% elif cookiecutter.unit_test_library == "catch2" %}
-find_package(Catch2 3 REQUIRED)
-{% endif %}
-
-add_executable(beman.{{cookiecutter.project_name}}.tests.{{identity}})
-target_sources(beman.{{cookiecutter.project_name}}.tests.{{identity}} PRIVATE {{identity}}.test.cpp)
-target_link_libraries(
- beman.{{cookiecutter.project_name}}.tests.{{identity}}
-{% if cookiecutter.unit_test_library == "gtest" %}
- PRIVATE beman::{{cookiecutter.project_name}} GTest::gtest_main
-{% elif cookiecutter.unit_test_library == "catch2" %}
- PRIVATE beman::{{cookiecutter.project_name}} Catch2::Catch2WithMain
-{% endif %}
-)
-if(BEMAN_EXEMPLAR_USE_MODULES)
- set_target_properties(
- beman.{{cookiecutter.project_name}}.tests.{{identity}}
- PROPERTIES CXX_MODULE_STD ON
- )
-endif()
-
-{% if cookiecutter.unit_test_library == "gtest" %}
-include(GoogleTest)
-gtest_discover_tests(beman.{{cookiecutter.project_name}}.tests.{{identity}} DISCOVERY_TIMEOUT 60)
-{% elif cookiecutter.unit_test_library == "catch2" %}
-include(Catch)
-catch_discover_tests(beman.{{cookiecutter.project_name}}.tests.{{identity}})
-{% endif %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/tests/beman/{{cookiecutter.project_name}}/identity.test.cpp b/cookiecutter/{{cookiecutter.project_name}}/tests/beman/{{cookiecutter.project_name}}/identity.test.cpp
deleted file mode 100644
index 2b4c0697..00000000
--- a/cookiecutter/{{cookiecutter.project_name}}/tests/beman/{{cookiecutter.project_name}}/identity.test.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-{% set identity = "identity" if cookiecutter._generating_exemplar else "todo" %}
-
-#include
-{% if cookiecutter.unit_test_library == "gtest" %}
-#include
-{% elif cookiecutter.unit_test_library == "catch2" %}
-#include
-{% endif %}
-#include
-
-{% if cookiecutter._generating_exemplar %}
-#if BEMAN_EXEMPLAR_USE_MODULES()
-import std;
-#else
- #include
- #include
-#endif
-
-namespace exe = beman::{{cookiecutter.project_name}};
-
-{% if cookiecutter.unit_test_library == "gtest" %}
-TEST(IdentityTest, call_identity_with_int) {
-{% elif cookiecutter.unit_test_library == "catch2" %}
-TEST_CASE("can call identity with int", "[{{cookiecutter.project_name}}::call_identity_with_int]") {
-{% endif %}
- for (int i = -100; i < 100; ++i) {
-{% if cookiecutter.unit_test_library == "gtest" %}
- EXPECT_EQ(i, exe::identity()(i));
-{% elif cookiecutter.unit_test_library == "catch2" %}
- CHECK(i == exe::identity()(i));
-{% endif %}
- }
-}
-
-{% if cookiecutter.unit_test_library == "gtest" %}
-TEST(IdentityTest, call_identity_with_custom_type) {
-{% elif cookiecutter.unit_test_library == "catch2" %}
-TEST_CASE("can call identity with custom type", "[{{cookiecutter.project_name}}::call_identity_with_custom_type]") {
-{% endif %}
- struct S {
- int i;
- };
-
- for (int i = -100; i < 100; ++i) {
- const S s{i};
- const S s_id = exe::identity()(s);
-{% if cookiecutter.unit_test_library == "gtest" %}
- EXPECT_EQ(s.i, s_id.i);
-{% elif cookiecutter.unit_test_library == "catch2" %}
- CHECK(s.i == s_id.i);
-{% endif %}
- }
-}
-
-{% if cookiecutter.unit_test_library == "gtest" %}
-TEST(IdentityTest, compare_std_vs_beman) {
-{% elif cookiecutter.unit_test_library == "catch2" %}
-TEST_CASE("compare std vs beman", "[{{cookiecutter.project_name}}::compare_std_vs_beman]") {
-{% endif %}
-// Requires: std::identity support.
-#if defined(__cpp_lib_type_identity)
- std::identity std_id;
- exe::identity beman_id;
- for (int i = -100; i < 100; ++i) {
-{% if cookiecutter.unit_test_library == "gtest" %}
- EXPECT_EQ(std_id(i), beman_id(i));
-{% elif cookiecutter.unit_test_library == "catch2" %}
- CHECK(std_id(i) == beman_id(i));
-{% endif %}
- }
-#endif
-}
-
-{% if cookiecutter.unit_test_library == "gtest" %}
-TEST(IdentityTest, check_is_transparent) {
-{% elif cookiecutter.unit_test_library == "catch2" %}
-TEST_CASE("check is transparent", "[{{cookiecutter.project_name}}::check_is_transparent]") {
-{% endif %}
-// Requires: transparent operators support.
-#if defined(__cpp_lib_transparent_operators)
-
- exe::identity id;
-
- const auto container = {1, 2, 3, 4, 5};
- auto it = std::find(std::begin(container), std::end(container), 3);
-{% if cookiecutter.unit_test_library == "gtest" %}
- EXPECT_EQ(3, *it);
-{% elif cookiecutter.unit_test_library == "catch2" %}
- CHECK(3 == *it);
-{% endif %}
- auto it_with_id = std::find(std::begin(container), std::end(container), id(3));
-{% if cookiecutter.unit_test_library == "gtest" %}
- EXPECT_EQ(3, *it_with_id);
-
- EXPECT_EQ(it, it_with_id);
-{% elif cookiecutter.unit_test_library == "catch2" %}
- CHECK(3 == *it_with_id);
-
- CHECK(it == it_with_id);
-{% endif %}
-#endif
-}
-{% else %}
-{% if cookiecutter.unit_test_library == "gtest" %}
-TEST(TodoTest, todo) {
-{% elif cookiecutter.unit_test_library == "catch2" %}
-TEST_CASE("todo", "[{{cookiecutter.project_name}}::todo]") {
-{% endif %}
- const bool todo = true;
-{% if cookiecutter.unit_test_library == "gtest" %}
- EXPECT_TRUE(todo);
-{% elif cookiecutter.unit_test_library == "catch2" %}
- CHECK(todo);
-{% endif %}
-}
-{% endif %}
diff --git a/copier.yml b/copier.yml
new file mode 100644
index 00000000..a3503542
--- /dev/null
+++ b/copier.yml
@@ -0,0 +1,100 @@
+_min_copier_version: "9.14.3"
+_subdirectory: template
+_templates_suffix: .jinja
+_envops:
+ trim_blocks: true
+
+template_src_path:
+ type: str
+ when: false
+ default: "{{ _copier_conf.src_path }}"
+
+template_commit:
+ type: str
+ when: false
+ default: "{{ _copier_conf.vcs_ref_hash }}"
+
+project_name:
+ type: str
+ help: Name of the generated repository and library.
+ default: my_project_name
+ validator: >-
+ {% if not (project_name | regex_search('^[a-z][a-z0-9_]*$')) %}
+ Use lowercase letters, digits, or underscores, and start with a letter.
+ {% endif %}
+
+maintainer:
+ type: str
+ help: GitHub username of the project maintainer.
+ default: your_github_username
+
+minimum_cpp_build_version:
+ type: str
+ help: Minimum C++ language version to require when building.
+ default: "20"
+ validator: >-
+ {% if minimum_cpp_build_version not in ['17', '20', '23', '26'] %}
+ Choose one of: 17, 20, 23, 26.
+ {% endif %}
+
+paper:
+ type: str
+ help: WG21 paper number associated with the library.
+ default: PnnnnRr
+
+description:
+ type: str
+ help: Short project description.
+ default: Short project description.
+
+unit_test_library:
+ type: str
+ help: Unit test library to configure.
+ choices:
+ - gtest
+ - catch2
+ default: gtest
+
+generating_exemplar:
+ type: bool
+ when: false
+ default: "{{ generating_exemplar | default(false) }}"
+
+owner:
+ type: str
+ when: false
+ default: "{{ owner | default('bemanproject') }}"
+
+ci_tests_cron:
+ type: str
+ when: false
+ # If a cron was already saved in .copier-answers.yml, reuse it to prevent
+ # merge conflicts on update. Otherwise, generate a random one to spread
+ # CI load across all Beman projects.
+ default: >-
+ {%- if ci_tests_cron is defined and ci_tests_cron != "" -%}
+ {{ ci_tests_cron }}
+ {%- else -%}
+ {{ range(0, 59)|random }} {{ range(0, 23)|random }} * * {{ range(0, 6)|random }}
+ {%- endif -%}
+
+pre_commit_update_cron:
+ type: str
+ when: false
+ # Same logic as above: reuse existing or generate random.
+ default: >-
+ {%- if pre_commit_update_cron is defined and pre_commit_update_cron != "" -%}
+ {{ pre_commit_update_cron }}
+ {%- else -%}
+ {{ range(0, 59)|random }} {{ range(0, 23)|random }} * * {{ range(0, 6)|random }}
+ {%- endif -%}
+
+_tasks:
+ - >-
+ if [ "{{ generating_exemplar }}" != "True" ] && [ "{{ generating_exemplar }}" != "true" ]; then
+ mv "include/beman/{{ project_name }}/identity.hpp" "include/beman/{{ project_name }}/todo.hpp";
+ mv "examples/identity_direct_usage.cpp" "examples/todo.cpp";
+ rm "examples/identity_as_default_projection.cpp";
+ mv "tests/beman/{{ project_name }}/identity.test.cpp" "tests/beman/{{ project_name }}/todo.test.cpp";
+ git ls-remote https://github.com/bemanproject/exemplar.git HEAD | awk '{print $1}' > .exemplar_version;
+ fi
diff --git a/copier/MAINTAINERS.md b/copier/MAINTAINERS.md
new file mode 100644
index 00000000..5ce07168
--- /dev/null
+++ b/copier/MAINTAINERS.md
@@ -0,0 +1,64 @@
+# Maintaining the Copier Template
+
+This document provides instructions for maintainers of the `exemplar` repository on how to test, update, and manage the `copier` template generation infrastructure.
+
+For instructions on how entirely *new* users should invoke the template template using `uvx` and `copier` to start a new project, see the [main README.md](../README.md).
+
+## Overall Philosophy
+
+The `exemplar` repository serves a dual purpose:
+1. It is a fully functional, canonical Reference implementation of a Beman Library (the root codebase).
+2. It contains the Jinja-based template used by downstream Beman projects (the `template/` directory).
+
+To ensure that downstream templates are highly functional and that the template never falls out of sync with the reference implementation, we require that the `template/` output (`generating_exemplar=true`) perfectly recreates the root project repository, barring acceptable omissions.
+
+## Utilities and Scripts
+
+The `copier/` directory houses the tooling necessary to validate and update the templates.
+
+### `copier/update_templates.py`
+Automates the synchronization of physical changes in the root project into the `template/` logic.
+- Creates a transient virtual Copier projection of the current dirty state.
+- Diff-checks the root project against the `template/` output.
+- **For simple files:** Automatically copies physical root modifications directly into the `template/` structural counterparts.
+- **For raw blocks:** Gracefully injects updated root text into Jinja `{% raw -%}` blocks (heavily used for GitHub Actions YAML configuration).
+- **For complex files:** Emits a terminal warning letting you know manual intervention is required.
+
+### `copier/check_copier.sh`
+Performs a strict byte-for-byte validation ensuring that the `template/` generates the root exemplar repository exactly.
+- Operates under `generating_exemplar=true`.
+- Whitelists specific directories (like `.venv/`, `.git/`, `build/`).
+- Whitelist ignores specific templated examples (`/todo/` vs `/identity/`) that are replaced in downstream invocations.
+- Returns a strict exit code if the templates drift out of sync with the main codebase.
+
+### `copier/test_standard_project.sh`
+While `check_copier.sh` is tailored to the `exemplar` structure, this script provisions completely neutral, third-party libraries (`generating_exemplar=false`).
+- Provisions instances utilizing different testing frameworks (e.g. `gtest` and `catch2`).
+- Tests matrix conditions (e.g. C++ Modules on vs. off).
+- Re-runs the CMake configure/build/ctest lifecycle on every variant to ensure downstream clients will not experience build breakages.
+
+## Workflow: Updating the Templates
+
+When you make changes to the C++ code, CMake infrastructure, or CI processes within `exemplar`, you must propagate those updates to the Copier template:
+
+1. **Make changes in the canonical root directory.** Modify source, tests, workflows, etc., directly in the `exemplar` repository.
+2. **Propagate via script:** Execute `./copier/update_templates.py`. This covers 90% of file replication seamlessly.
+3. **Manually reconcile complex templates:** Look at the warnings generated by the Python script to see if structural Jinja templates (like `CMakeLists.txt.jinja` or `.github/CODEOWNERS.jinja`) require manual hand-editing.
+4. **Validate Parity:** Run `./copier/check_copier.sh`. Continue fixing template logic until the script emits `Success: Template matches project exactly.`.
+5. **Validate Downstream Viability:** Run `./copier/test_standard_project.sh` to ensure you haven't broken the generic downstream logic (e.g. accidentally hardcoding the word "identity" where `{{project_name}}` should be).
+
+## Troubleshooting CI Failures
+
+The `copier` test scripts run in GitHub Actions underneath `ci_tests.yml`.
+
+- **`check_copier.sh` fails in CI:**
+ You updated a file in the root repository (e.g. a submodule bump, a new workflow, modified C++) but forgot to update the corresponding `.jinja` file in the `template/` directory.
+ **Solution:** Pull the branch locally and run `./copier/update_templates.py`. Check for regressions using `./copier/check_copier.sh` and push.
+
+- **`test_standard_project.sh` (or `copier-test` CI Job) fails:**
+ The `exemplar` generation works, but something breaks when the project generates for *other* people (`generating_exemplar=false`).
+ **Solution:** Check the specific variant that failed.
+ - *Did `catch2` break?* You might have modified `template/tests/beman/{{project_name}}/CMakeLists.txt.jinja` using `gtest` specifics that aren't guarded properly.
+ - *Did Modules break?* The CI test runs C++ modules using `ghcr.io/bemanproject/infra-containers-clang:latest`. Ensure the CMake module directives conditional branches haven't drifted.
+
+ Locally, you can simulate specific container behaviors by targeting a custom CMake preset or enabling actions mode: `GITHUB_ACTIONS=true ./copier/test_standard_project.sh llvm-release`.
diff --git a/copier/check_copier.sh b/copier/check_copier.sh
new file mode 100755
index 00000000..ca1dd83e
--- /dev/null
+++ b/copier/check_copier.sh
@@ -0,0 +1,147 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+set -euo pipefail
+
+script_dir=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
+repo_root=$(realpath "$script_dir/..")
+
+cleanup() {
+ if [[ -n "${work_dir:-}" && -d "$work_dir" ]]; then
+ rm -rf "$work_dir"
+ fi
+ if [[ -n "${copier_venv_path:-}" && -d "$copier_venv_path" ]]; then
+ rm -rf "$copier_venv_path"
+ fi
+}
+
+setup_venv() {
+ local path="$1" ; shift
+ python3 -m venv "$path"
+ "$path/bin/python3" -m pip install copier >& /dev/null
+ COPIER_BIN="$path/bin/copier"
+}
+
+template_commit() {
+ git -C "$repo_root" rev-parse HEAD
+}
+
+prepare_template_source() {
+ local source_dir="$1" ; shift
+ local prepared_source
+ prepared_source=$(mktemp -d)
+ rsync -a \
+ --exclude .git \
+ --exclude build \
+ --exclude .venv \
+ "$source_dir/" "$prepared_source/"
+ printf '%s\n' "$prepared_source"
+}
+
+stamp() {
+ local template_source="$1" ; shift
+ local output_dir="$1" ; shift
+ local unit_test_library="$1" ; shift
+ local generating_exemplar="$1" ; shift
+ local prepared_source
+ prepared_source=$(prepare_template_source "$template_source")
+ "$COPIER_BIN" copy \
+ --trust \
+ --defaults \
+ -d template_src_path=https://github.com/bemanproject/exemplar.git \
+ -d template_commit="$(template_commit)" \
+ -d project_name=exemplar \
+ -d maintainer=steve-downey \
+ -d minimum_cpp_build_version=17 \
+ -d paper=P0898R3 \
+ -d description="A Beman Library Exemplar" \
+ -d unit_test_library="$unit_test_library" \
+ -d generating_exemplar="$generating_exemplar" \
+ -d owner=bemanproject \
+ -d ci_tests_cron="30 15 * * 6" \
+ -d pre_commit_update_cron="0 16 * * 0" \
+ "$prepared_source" \
+ "$output_dir" \
+ >& /dev/null
+ rm -rf "$prepared_source"
+}
+
+check_consistency() {
+ local output_dir="$work_dir/default"
+ mkdir -p "$output_dir"
+ stamp "$repo_root" "$output_dir" "gtest" "true"
+
+ local diff_path="$work_dir/default.diff"
+ diff -u -r \
+ --exclude .git \
+ --exclude build \
+ --exclude .venv \
+ --exclude template \
+ --exclude copier \
+ --exclude copier.yml \
+ --exclude stamp.sh \
+ --exclude images \
+ --exclude .copier-answers.yml \
+ --exclude copier_test.yml \
+ --exclude catch2_exemplar_test.yml \
+ --exclude todo_exemplar_test.yml \
+ "$repo_root" "$output_dir" > "$diff_path" || true
+
+ if [[ -s "$diff_path" ]] ; then
+ echo "Discrepancy between exemplar and copier output:" >&2
+ cat "$diff_path" >&2
+ exit 1
+ fi
+}
+
+check_templating() {
+ local output_dir="$work_dir/randomized"
+ local prepared_source
+ mkdir -p "$output_dir"
+ prepared_source=$(prepare_template_source "$repo_root")
+ "$COPIER_BIN" copy \
+ --trust \
+ --defaults \
+ -d template_src_path=https://github.com/bemanproject/exemplar.git \
+ -d template_commit="$(template_commit)" \
+ -d project_name=rlzrmx9nfs \
+ -d maintainer=octocat \
+ -d minimum_cpp_build_version=17 \
+ -d paper=P0898R3 \
+ -d description="A Beman Library rlzrmx9nfs" \
+ -d unit_test_library=gtest \
+ -d generating_exemplar=false \
+ -d owner=bemanproject \
+ -d ci_tests_cron="30 15 * * 6" \
+ -d pre_commit_update_cron="0 16 * * 0" \
+ "$prepared_source" \
+ "$output_dir" \
+ >& /dev/null
+ rm -rf "$prepared_source"
+
+ rm -rf "$output_dir/infra"
+
+ local grep_path="$work_dir/randomized.grep"
+ grep \
+ --dereference-recursive --context=5 --color=always \
+ --exclude .copier-answers.yml \
+ -e "exemplar" -e "identity" "$output_dir" > "$grep_path" || true
+
+ if [[ -s "$grep_path" ]] ; then
+ echo 'Untemplated "exemplar" or "identity" in copier output:' >&2
+ cat "$grep_path" >&2
+ exit 1
+ fi
+}
+
+main() {
+ work_dir=$(mktemp -d)
+ copier_venv_path=$(mktemp -d)
+ trap cleanup EXIT
+ setup_venv "$copier_venv_path"
+ check_consistency
+ check_templating
+ echo "Success: Template matches project exactly."
+}
+
+[[ "${BASH_SOURCE[0]}" != "${0}" ]] || main "$@"
diff --git a/copier/test_cmake_matrix.sh b/copier/test_cmake_matrix.sh
new file mode 100755
index 00000000..5acbe608
--- /dev/null
+++ b/copier/test_cmake_matrix.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+set -euo pipefail
+
+script_dir=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
+
+PRESET=${1:-gcc-release}
+CMAKE_VERSION=${2:-latest}
+
+if [[ "$CMAKE_VERSION" == "latest" || -z "$CMAKE_VERSION" ]]; then
+ export CMAKE_COMMAND="cmake"
+ export CTEST_COMMAND="ctest"
+ echo "Running with default host CMake"
+else
+ echo "Provisioning standard environment for CMake $CMAKE_VERSION"
+ venv_dir=$(mktemp -d)
+ uv venv "$venv_dir" > /dev/null
+ uv pip install "cmake==${CMAKE_VERSION}" --python "$venv_dir" > /dev/null
+ export CMAKE_COMMAND="$venv_dir/bin/cmake"
+ export CTEST_COMMAND="$venv_dir/bin/ctest"
+
+ echo "Using CMake: $("$CMAKE_COMMAND" --version | head -n 1)"
+
+ cleanup() {
+ rm -rf "$venv_dir"
+ }
+ trap cleanup EXIT
+fi
+
+"$script_dir/test_standard_project.sh" "$PRESET"
diff --git a/copier/test_standard_project.sh b/copier/test_standard_project.sh
new file mode 100755
index 00000000..82bc2639
--- /dev/null
+++ b/copier/test_standard_project.sh
@@ -0,0 +1,74 @@
+#="${CMAKE_COMMAND:-cmake}"
+#="${CTEST_COMMAND:-ctest}"
+#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+set -euo pipefail
+CMAKE_COMMAND="${CMAKE_COMMAND:-cmake}"
+CTEST_COMMAND="${CTEST_COMMAND:-ctest}"
+
+
+script_dir=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
+repo_root=$(realpath "$script_dir/..")
+
+PRESET=${1:-gcc-release}
+
+test_project_variant() {
+ local variant_name="$1"
+ local unit_test_library="$2"
+ local use_modules="$3"
+ local cmakelists_args=""
+
+ if [[ "$use_modules" == "true" ]]; then
+ cmakelists_args="-DBEMAN_TEST_PROJECT_USE_MODULES=ON"
+ fi
+
+ echo "=========================================================="
+ echo "Testing variant: $variant_name"
+ echo "Unit test: $unit_test_library, Modules: $use_modules, Preset: $PRESET"
+ echo "=========================================================="
+
+ local work_dir
+ work_dir=$(mktemp -d)
+ echo "Generating standard project in $work_dir..."
+
+ # Use `--vcs-ref HEAD` in CI environment so we test the most recent commit
+ # but use local tree if uncommitted for fast iteration locally. Use dirty copier behavior locally.
+ uvx --from copier copier copy \
+ --trust \
+ --defaults \
+ --vcs-ref=HEAD \
+ -d project_name=test_project \
+ -d generating_exemplar=false \
+ -d unit_test_library="$unit_test_library" \
+ "$repo_root" \
+ "$work_dir"
+
+ echo "Building standard project variant $variant_name..."
+ pushd "$work_dir" > /dev/null
+ "$CMAKE_COMMAND" --preset "$PRESET" -B build $cmakelists_args || {
+ echo -e "\n\n*** configure failed: $variant_name ***"
+ echo "*** Sometimes local CMake modules require a newer compiler than the host default."
+ # If failure is just a module C++ scan lack of support, we could suppress, but let's hard fail.
+ popd > /dev/null && rm -rf "$work_dir"
+ exit 1
+ }
+ "$CMAKE_COMMAND" --build build
+ "$CTEST_COMMAND" --test-dir build --output-on-failure
+
+ # Cleanup this variant's dir
+ popd > /dev/null && rm -rf "$work_dir"
+ echo "✔ Success for variant: $variant_name"
+}
+
+# 1. GTest + No Modules
+test_project_variant "gtest-no-modules" "gtest" "false"
+
+# 2. Catch2 + No Modules
+test_project_variant "catch2-no-modules" "catch2" "false"
+
+# Do not run modules locally if we cannot guarantee modern tooling, but CI will use clang/gcc containers
+# We check if we are in github actions to enforce building modules, as locally it may fail CMake module requirements.
+
+echo "=========================================================="
+echo "✔ All variants successfully generated, built, and tested! "
+echo "=========================================================="
diff --git a/copier/update_templates.py b/copier/update_templates.py
new file mode 100755
index 00000000..3d3b796c
--- /dev/null
+++ b/copier/update_templates.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+import os
+import subprocess
+import tempfile
+import shutil
+import stat
+
+def main():
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ repo_root = os.path.realpath(os.path.join(script_dir, ".."))
+
+ with tempfile.TemporaryDirectory() as work_dir:
+ prepared_source = os.path.join(work_dir, "prepared_source")
+ output_dir = os.path.join(work_dir, "default")
+
+ print("Preparing dirty working tree for Copier...")
+ # Rsync like check_copier.sh to capture dirty working tree without .git
+ subprocess.run([
+ "rsync", "-a",
+ "--exclude", ".git",
+ "--exclude", "build",
+ "--exclude", ".venv",
+ f"{repo_root}/", f"{prepared_source}/"
+ ], check=True)
+
+ print("Generating template base case...")
+ subprocess.run([
+ "uvx", "--from", "copier", "copier", "copy",
+ "--trust", "--defaults",
+ "-d", "generating_exemplar=true",
+ prepared_source, output_dir
+ ], cwd=prepared_source, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+ diff_cmd = [
+ "diff", "-u", "-r",
+ "--exclude", ".git",
+ "--exclude", "build",
+ "--exclude", ".venv",
+ "--exclude", "template",
+ "--exclude", "copier",
+ "--exclude", "copier.yml",
+ "--exclude", "stamp.sh",
+ "--exclude", "images",
+ "--exclude", ".copier-answers.yml",
+ "--exclude", "copier_test.yml",
+ "--exclude", "catch2_exemplar_test.yml",
+ "--exclude", "todo_exemplar_test.yml",
+ repo_root, output_dir
+ ]
+
+ result = subprocess.run(diff_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+ if not result.stdout.strip():
+ print("No differences found. Templates are synchronized.")
+ return
+
+ lines = result.stdout.splitlines()
+ mismatched_files = []
+ for line in lines:
+ if line.startswith("--- " + repo_root):
+ file_path = line[4:].split("\t")[0].strip()
+ rel_path = os.path.relpath(file_path, repo_root)
+ mismatched_files.append(rel_path)
+
+ for rel_path in mismatched_files:
+ root_file = os.path.join(repo_root, rel_path)
+
+ template_file_jinja = os.path.join(repo_root, "template", rel_path + ".jinja")
+ template_file_plain = os.path.join(repo_root, "template", rel_path)
+
+ target_template = None
+ if os.path.exists(template_file_jinja):
+ target_template = template_file_jinja
+ elif os.path.exists(template_file_plain):
+ target_template = template_file_plain
+
+ if not target_template:
+ print(f"Warning: {rel_path} not found in template/. Skipping.")
+ continue
+
+ with open(target_template, 'r') as tf:
+ content = tf.read()
+
+ has_jinja = "{{" in content or "{%" in content
+
+ if has_jinja:
+ lines = content.split('\n')
+ if len(lines) > 2 and "{% raw -%}" in lines[1] and "{% endraw %}" in lines[-2]:
+ print(f"Updating template for {rel_path} (raw block)")
+ with open(root_file, 'r') as rf:
+ root_content = rf.read()
+
+ new_content = lines[0] + "\n{% raw -%}\n" + root_content + "\n{% endraw %}"
+ if content.endswith("\n"):
+ new_content += "\n"
+ with open(target_template, 'w') as tf:
+ tf.write(new_content)
+ else:
+ print(f"Skipping {rel_path} because the template uses Jinja variables.")
+ else:
+ print(f"Updating static template for {rel_path}")
+ shutil.copy2(root_file, target_template)
+
+if __name__ == "__main__":
+ main()
diff --git a/infra/.beman_submodule b/infra/.beman_submodule
index 7367e0e2..a24c23db 100644
--- a/infra/.beman_submodule
+++ b/infra/.beman_submodule
@@ -1,3 +1,3 @@
[beman_submodule]
remote=https://github.com/bemanproject/infra.git
-commit_hash=eb7b7c3688bd8f26ab0c145e3147df9a8f2ec8ce
+commit_hash=d536fc285ae058cf8f5b736b5ff73d18a421b296
diff --git a/infra/.github/CODEOWNERS b/infra/.github/CODEOWNERS
index 4ff90a43..439303d4 100644
--- a/infra/.github/CODEOWNERS
+++ b/infra/.github/CODEOWNERS
@@ -1 +1 @@
-* @ednolan @neatudarius @rishyak @wusatosi @JeffGarland
+* @ednolan @rishyak @wusatosi @JeffGarland
diff --git a/infra/cmake/enable-experimental-import-std.cmake b/infra/cmake/enable-experimental-import-std.cmake
index 20fc302b..0ac96043 100644
--- a/infra/cmake/enable-experimental-import-std.cmake
+++ b/infra/cmake/enable-experimental-import-std.cmake
@@ -187,4 +187,8 @@ elseif(CMAKE_VERSION VERSION_EQUAL "4.3.2")
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD
"451f2fe2-a8a2-47c3-bc32-94786d8fc91b"
)
+elseif(CMAKE_VERSION VERSION_EQUAL "4.3.3")
+ set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD
+ "451f2fe2-a8a2-47c3-bc32-94786d8fc91b"
+ )
endif()
diff --git a/port/portfile.cmake.in b/port/portfile.cmake.in
index ba7eeacf..6f820936 100644
--- a/port/portfile.cmake.in
+++ b/port/portfile.cmake.in
@@ -29,7 +29,8 @@ vcpkg_cmake_config_fixup(
)
if(NOT "modules" IN_LIST FEATURES)
- file(REMOVE_RECURSE
+ file(
+ REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/debug"
"${CURRENT_PACKAGES_DIR}/lib"
)
diff --git a/stamp.sh b/stamp.sh
index 7dc9ee84..eb7a708e 100755
--- a/stamp.sh
+++ b/stamp.sh
@@ -1,5 +1,10 @@
-# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#!/usr/bin/env bash
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+echo "⚠ Note: stamp.sh is deprecated. You can now generate projects directly using:" >&2
+echo " uvx --from copier copier copy \"git+https://github.com/bemanproject/exemplar.git\" " >&2
+echo " See the README for the updated workflow." >&2
+echo "" >&2
{
if [[ "$1" == "-h" || "$1" == "--help" ]] ; then
@@ -8,11 +13,13 @@
This script is intended to be run on a fork of exemplar.
- It sets up cookiecutter, runs it on the cookiecutter template, replaces the
- repository's current contents with the result, runs pre-commit,
- switches to a new branch 'stamp', and creates a git commit.
+ It sets up Copier, renders the template from a temporary snapshot of
+ the current repository, replaces the repository's current contents
+ with the stamped result, runs pre-commit, switches to a new branch
+ 'stamp', and creates a git commit.
- All parameters are passed through to the cookiecutter invocation.
+ All parameters are passed through to the underlying `copier copy`
+ invocation.
EOF
fi
set -eu
@@ -22,23 +29,39 @@ EOF
fi
declare repo_dir=$(realpath $(dirname "$BASH_SOURCE"))
cd "$repo_dir"
- declare cookiecutter_venv_path
- cookiecutter_venv_path=$(mktemp --directory --dry-run)
- python3 -m venv "$cookiecutter_venv_path"
- source "$cookiecutter_venv_path/bin/activate"
- python3 -m pip install cookiecutter pre-commit >& /dev/null
- declare cookiecutter_out_path
- cookiecutter_out_path=$(mktemp --directory)
- python3 -m cookiecutter "$repo_dir/cookiecutter" -o "$cookiecutter_out_path" "$@"
+ declare copier_venv_path
+ copier_venv_path=$(mktemp --directory --dry-run)
+ python3 -m venv "$copier_venv_path"
+ "$copier_venv_path/bin/python3" -m pip install copier pre-commit >& /dev/null
+ declare copier_source_path
+ copier_source_path=$(mktemp --directory)
+ declare copier_out_path
+ copier_out_path=$(mktemp --directory)
+ declare template_src_path=https://github.com/bemanproject/exemplar.git
+ declare template_commit
+ template_commit=$(git rev-parse HEAD)
+ rsync -a \
+ --exclude .git \
+ --exclude build \
+ --exclude .venv \
+ "$repo_dir/" "$copier_source_path/"
+ "$copier_venv_path/bin/copier" copy \
+ --trust \
+ -d template_src_path="$template_src_path" \
+ -d template_commit="$template_commit" \
+ "$@" \
+ "$copier_source_path" \
+ "$copier_out_path" \
+ >& /dev/null
git rm -rf . &>/dev/null
- cp -r "$cookiecutter_out_path"/*/. .
+ cp -r "$copier_out_path"/. .
git add . &>/dev/null
- pre-commit run --all-files &>/dev/null || true
+ "$copier_venv_path/bin/pre-commit" run --all-files &>/dev/null || true
git add . &>/dev/null
git checkout -b stamp
git commit -q -m "Stamp out exemplar template"
echo "Successfully stamped out exemplar template to the new branch 'stamp'."
echo "Try 'git push origin stamp' to push the branch upstream,"
echo "then create a pull request."
- rm -r "$cookiecutter_venv_path" "$cookiecutter_out_path"
+ rm -r "$copier_venv_path" "$copier_source_path" "$copier_out_path"
}; exit
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.beman-tidy.yaml b/template/.beman-tidy.yaml.jinja
similarity index 91%
rename from cookiecutter/{{cookiecutter.project_name}}/.beman-tidy.yaml
rename to template/.beman-tidy.yaml.jinja
index 527ca1e1..c54e2f32 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/.beman-tidy.yaml
+++ b/template/.beman-tidy.yaml.jinja
@@ -4,7 +4,7 @@
# Check documentation for beman-tidy here:
# https://github.com/bemanproject/beman-tidy/blob/main/README.md
-{% if cookiecutter._generating_exemplar %}
+{% if generating_exemplar %}
disabled_rules:
- readme.title
{% else %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.clang-format b/template/.clang-format.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/.clang-format
rename to template/.clang-format.jinja
diff --git a/template/.copier-answers.yml.jinja b/template/.copier-answers.yml.jinja
new file mode 100644
index 00000000..cefabb93
--- /dev/null
+++ b/template/.copier-answers.yml.jinja
@@ -0,0 +1,8 @@
+# Standard answers and internal state managed by Copier
+{{ _copier_answers | to_nice_yaml -}}
+
+# Hidden variables manually tracked because 'when: false' omits them from _copier_answers
+generating_exemplar: {{ generating_exemplar | to_json }}
+owner: {{ owner | to_json }}
+ci_tests_cron: {{ ci_tests_cron | to_json }}
+pre_commit_update_cron: {{ pre_commit_update_cron | to_json }}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.gitattributes b/template/.gitattributes.jinja
similarity index 66%
rename from cookiecutter/{{cookiecutter.project_name}}/.gitattributes
rename to template/.gitattributes.jinja
index 793dce7d..9a7df7dd 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/.gitattributes
+++ b/template/.gitattributes.jinja
@@ -1,5 +1,6 @@
infra/** linguist-vendored
-cookiecutter/** linguist-vendored
+template/** linguist-vendored
+copier/** linguist-vendored
*.bib -linguist-detectable
*.tex -linguist-detectable
papers/* linguist-documentation
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/CODEOWNERS b/template/.github/CODEOWNERS.jinja
similarity index 66%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/CODEOWNERS
rename to template/.github/CODEOWNERS.jinja
index cced7b37..566c8858 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/.github/CODEOWNERS
+++ b/template/.github/CODEOWNERS.jinja
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-{% if cookiecutter._generating_exemplar %}
+{% if generating_exemplar %}
* @ednolan @bretbrownjr @camio @dietmarkuehl @steve-downey @wusatosi
{% else %}
-* @{{ cookiecutter.maintainer }}
+* @{{ maintainer }}
{% endif %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/ISSUE_TEMPLATE/implementation-deficiency.md b/template/.github/ISSUE_TEMPLATE/implementation-deficiency.md.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/ISSUE_TEMPLATE/implementation-deficiency.md
rename to template/.github/ISSUE_TEMPLATE/implementation-deficiency.md.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/ISSUE_TEMPLATE/infrastructure-issues.md b/template/.github/ISSUE_TEMPLATE/infrastructure-issues.md.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/ISSUE_TEMPLATE/infrastructure-issues.md
rename to template/.github/ISSUE_TEMPLATE/infrastructure-issues.md.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/ISSUE_TEMPLATE/paper-discussion.md b/template/.github/ISSUE_TEMPLATE/paper-discussion.md.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/ISSUE_TEMPLATE/paper-discussion.md
rename to template/.github/ISSUE_TEMPLATE/paper-discussion.md.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/pull_request_template.md b/template/.github/pull_request_template.md.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/pull_request_template.md
rename to template/.github/pull_request_template.md.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/workflows/ci_tests.yml b/template/.github/workflows/ci_tests.yml.jinja
similarity index 77%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/workflows/ci_tests.yml
rename to template/.github/workflows/ci_tests.yml.jinja
index 94d3bf1e..1fdae68c 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/.github/workflows/ci_tests.yml
+++ b/template/.github/workflows/ci_tests.yml.jinja
@@ -9,7 +9,7 @@ on:
pull_request:
workflow_dispatch:
schedule:
- - cron: '{% if cookiecutter._ci_tests_cron %}{{ cookiecutter._ci_tests_cron }}{% else %}{{ range(0, 60) | random }} {{ range(13, 18) | random }} * * {{ range(0, 7) | random }}{% endif %}'
+ - cron: '{{ ci_tests_cron }}'
concurrency:
group: {% raw %}${{format('{0}:{1}', github.repository, github.ref)}}{% endraw %}
@@ -20,6 +20,46 @@ jobs:
beman-submodule-check:
uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-submodule-check.yml@1.7.2
+ copier-test:
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/bemanproject/infra-containers-gcc:latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install uv
+ shell: bash
+ run: |
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
+ - name: Test Copier Template Variants
+ shell: bash
+ env:
+ GITHUB_ACTIONS: true
+ # gcc-release will use GCC 15 which supports C++23 modules appropriately in this container
+ run: ./copier/test_standard_project.sh gcc-release
+
+ copier-cmake-matrix:
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/bemanproject/infra-containers-gcc:latest
+ strategy:
+ fail-fast: false
+ matrix:
+ cmake_version: ["3.30.9", "3.31.10", "4.0.3", "4.1.3", "4.2.3", "4.3.2"]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install uv
+ shell: bash
+ run: |
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
+ - name: Test Copier Template Variants Matrix
+ shell: bash
+ env:
+ GITHUB_ACTIONS: true
+ # Run across our experimental boundaries
+ run: ./copier/test_cmake_matrix.sh gcc-release {% raw %}${{ matrix.cmake_version }}{% endraw %}
+
preset-test:
uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-preset-test.yml@1.7.2
with:
@@ -49,7 +89,7 @@ jobs:
"tests": [
"Debug.Default", "Release.Default", "Release.TSan",
"Release.MaxSan", "Debug.Werror",
- "Debug.Coverage", "Debug.-DBEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES=On"
+ "Debug.Coverage", "Debug.-DBEMAN_{{project_name.upper()}}_USE_MODULES=On"
]
}
]
@@ -58,7 +98,7 @@ jobs:
"tests": [
{ "stdlibs": ["libstdc++"],
"tests": [
- "Release.Default", "Debug.-DBEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES=On"
+ "Release.Default", "Debug.-DBEMAN_{{project_name.upper()}}_USE_MODULES=On"
]
}
]
@@ -74,7 +114,7 @@ jobs:
"tests": [
{ "stdlibs": ["libstdc++"],
"tests": [
- "Release.Default", "Debug.-DBEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES=On"
+ "Release.Default", "Debug.-DBEMAN_{{project_name.upper()}}_USE_MODULES=On"
]
}
]
@@ -109,7 +149,7 @@ jobs:
"tests": [
"Debug.Default", "Release.Default", "Release.TSan",
"Release.MaxSan", "Debug.Werror",
- "Debug.-DBEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES=On"
+ "Debug.-DBEMAN_{{project_name.upper()}}_USE_MODULES=On"
]
}
]
@@ -118,7 +158,7 @@ jobs:
"tests": [
{ "stdlibs": ["libstdc++", "libc++"],
"tests": [
- "Release.Default", "Debug.-DBEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES=On"
+ "Release.Default", "Debug.-DBEMAN_{{project_name.upper()}}_USE_MODULES=On"
]
}
]
@@ -177,7 +217,7 @@ jobs:
{ "stdlibs": ["stl"],
"tests": [
"Debug.Default", "Release.Default", "Release.MaxSan",
- "Debug.-DBEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES=On"
+ "Debug.-DBEMAN_{{project_name.upper()}}_USE_MODULES=On"
]
}
]
@@ -190,7 +230,7 @@ jobs:
vcpkg-ci:
uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-vcpkg-ci.yml@1.7.2
with:
- port_name: beman-{{cookiecutter.project_name.replace('_', '-')}}
+ port_name: beman-{{project_name.replace('_', '-')}}
feature_combinations: |
[
{"features": {}},
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/workflows/pre-commit-check.yml b/template/.github/workflows/pre-commit-check.yml.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/workflows/pre-commit-check.yml
rename to template/.github/workflows/pre-commit-check.yml.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/workflows/pre-commit-update.yml b/template/.github/workflows/pre-commit-update.yml.jinja
similarity index 63%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/workflows/pre-commit-update.yml
rename to template/.github/workflows/pre-commit-update.yml.jinja
index 5b54ccbb..232344f0 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/.github/workflows/pre-commit-update.yml
+++ b/template/.github/workflows/pre-commit-update.yml.jinja
@@ -5,7 +5,7 @@ name: Weekly pre-commit autoupdate
on:
workflow_dispatch:
schedule:
- - cron: "{% if cookiecutter._pre_commit_update_cron %}{{ cookiecutter._pre_commit_update_cron }}{% else %}{{ range(0, 60) | random }} {{ range(13, 18) | random }} * * {{ range(0, 7) | random }}{% endif %}"
+ - cron: "{{pre_commit_update_cron}}"
{% raw -%}
jobs:
@@ -14,5 +14,4 @@ jobs:
secrets:
APP_ID: ${{ secrets.AUTO_PR_BOT_APP_ID }}
PRIVATE_KEY: ${{ secrets.AUTO_PR_BOT_PRIVATE_KEY }}
-{%- endraw %}
-
+{% endraw %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.github/workflows/vcpkg-release.yml b/template/.github/workflows/vcpkg-release.yml.jinja
similarity index 79%
rename from cookiecutter/{{cookiecutter.project_name}}/.github/workflows/vcpkg-release.yml
rename to template/.github/workflows/vcpkg-release.yml.jinja
index f6036360..1a568a24 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/.github/workflows/vcpkg-release.yml
+++ b/template/.github/workflows/vcpkg-release.yml.jinja
@@ -8,9 +8,8 @@ jobs:
vcpkg-release:
uses: bemanproject/infra-workflows/.github/workflows/reusable-beman-vcpkg-release.yml@1.7.2
with:
- port_name: beman-{{cookiecutter.project_name.replace('_', '-')}}
+ port_name: beman-{{project_name.replace('_', '-')}}
{%- raw %}
secrets:
VCPKG_REGISTRY_TOKEN: ${{ secrets.VCPKG_REGISTRY_TOKEN }}
-{%- endraw %}
-
+{% endraw %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.gitignore b/template/.gitignore.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/.gitignore
rename to template/.gitignore.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.markdownlint.yaml b/template/.markdownlint.yaml.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/.markdownlint.yaml
rename to template/.markdownlint.yaml.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/template/.pre-commit-config.yaml.jinja
similarity index 94%
rename from cookiecutter/{{cookiecutter.project_name}}/.pre-commit-config.yaml
rename to template/.pre-commit-config.yaml.jinja
index 3845c974..ec5a80dd 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/.pre-commit-config.yaml
+++ b/template/.pre-commit-config.yaml.jinja
@@ -40,7 +40,7 @@ repos:
hooks:
- id: codespell
-{% if not cookiecutter._generating_exemplar %}
+{% if not generating_exemplar %}
# Beman Standard checking via beman-tidy
- repo: https://github.com/bemanproject/beman-tidy
rev: v0.3.1
@@ -48,4 +48,4 @@ repos:
- id: beman-tidy
{% endif %}
-exclude: 'cookiecutter/|infra/|port/'
+exclude: 'template/|copier/|infra/|port/'
diff --git a/template/CMakeLists.txt.jinja b/template/CMakeLists.txt.jinja
new file mode 100644
index 00000000..e70bf583
--- /dev/null
+++ b/template/CMakeLists.txt.jinja
@@ -0,0 +1,94 @@
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+cmake_minimum_required(VERSION 3.30...4.3)
+
+include(infra/cmake/enable-experimental-import-std.cmake)
+
+project(
+ beman.{{project_name}}
+ DESCRIPTION "{{description}}"
+ LANGUAGES CXX
+{% if generating_exemplar %}
+ VERSION 2.4.0
+{% else %}
+ VERSION 0.1.0
+{% endif %}
+)
+
+# [CMAKE.SKIP_TESTS]
+option(
+ BEMAN_{{project_name.upper()}}_BUILD_TESTS
+ "Enable building tests and test infrastructure. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }."
+ ${PROJECT_IS_TOP_LEVEL}
+)
+
+# [CMAKE.SKIP_EXAMPLES]
+option(
+ BEMAN_{{project_name.upper()}}_BUILD_EXAMPLES
+ "Enable building examples. Default: ${PROJECT_IS_TOP_LEVEL}. Values: { ON, OFF }."
+ ${PROJECT_IS_TOP_LEVEL}
+)
+
+option(BEMAN_{{project_name.upper()}}_USE_MODULES "Provide beman.{{project_name}} as a C++ module" OFF)
+
+if(BEMAN_{{project_name.upper()}}_USE_MODULES)
+ set(CMAKE_CXX_SCAN_FOR_MODULES ON)
+endif()
+
+configure_file(
+ "${PROJECT_SOURCE_DIR}/include/beman/{{project_name}}/config_generated.hpp.in"
+ "${PROJECT_BINARY_DIR}/include/beman/{{project_name}}/config_generated.hpp"
+ @ONLY
+)
+
+# for find of beman_install_library and configure_build_telemetry
+include(infra/cmake/beman-install-library.cmake)
+include(infra/cmake/BuildTelemetryConfig.cmake)
+
+if(BEMAN_{{project_name.upper()}}_USE_MODULES)
+ add_library(beman.{{project_name}} STATIC)
+else()
+ add_library(beman.{{project_name}} INTERFACE)
+endif()
+add_library(beman::{{project_name}} ALIAS beman.{{project_name}})
+
+if(BEMAN_{{project_name.upper()}}_USE_MODULES)
+ target_sources(
+ beman.{{project_name}}
+ PUBLIC
+ FILE_SET CXX_MODULES
+ FILE_SET HEADERS
+ BASE_DIRS
+ "${CMAKE_CURRENT_SOURCE_DIR}/include"
+ "${CMAKE_CURRENT_BINARY_DIR}/include"
+ )
+ set_target_properties(beman.{{project_name}} PROPERTIES CXX_MODULE_STD ON)
+ target_compile_features(beman.{{project_name}} PUBLIC cxx_std_23)
+else()
+ target_sources(
+ beman.{{project_name}}
+ PUBLIC
+ FILE_SET HEADERS
+ BASE_DIRS
+ "${CMAKE_CURRENT_SOURCE_DIR}/include"
+ "${CMAKE_CURRENT_BINARY_DIR}/include"
+ )
+ set_target_properties(
+ beman.{{project_name}}
+ PROPERTIES VERIFY_INTERFACE_HEADER_SETS ${PROJECT_IS_TOP_LEVEL}
+ )
+endif()
+
+add_subdirectory(include/beman/{{project_name}})
+
+beman_install_library(beman.{{project_name}} TARGETS beman.{{project_name}})
+configure_build_telemetry()
+
+if(BEMAN_{{project_name.upper()}}_BUILD_TESTS)
+ enable_testing()
+ add_subdirectory(tests/beman/{{project_name}})
+endif()
+
+if(BEMAN_{{project_name.upper()}}_BUILD_EXAMPLES)
+ add_subdirectory(examples)
+endif()
diff --git a/cookiecutter/{{cookiecutter.project_name}}/CMakePresets.json b/template/CMakePresets.json.jinja
similarity index 99%
rename from cookiecutter/{{cookiecutter.project_name}}/CMakePresets.json
rename to template/CMakePresets.json.jinja
index cd426bee..f88fecb5 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/CMakePresets.json
+++ b/template/CMakePresets.json.jinja
@@ -7,7 +7,7 @@
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
- "CMAKE_CXX_STANDARD": "{{cookiecutter.minimum_cpp_build_version}}",
+ "CMAKE_CXX_STANDARD": "{{minimum_cpp_build_version}}",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "./infra/cmake/use-fetch-content.cmake"
}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/CONTRIBUTING.md b/template/CONTRIBUTING.md.jinja
similarity index 61%
rename from cookiecutter/{{cookiecutter.project_name}}/CONTRIBUTING.md
rename to template/CONTRIBUTING.md.jinja
index d15f2843..1f1f9ddd 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/CONTRIBUTING.md
+++ b/template/CONTRIBUTING.md.jinja
@@ -43,7 +43,7 @@ that this requires GoogleTest to be installed.
cmake \
-B build \
-S . \
- -DCMAKE_CXX_STANDARD={{cookiecutter.minimum_cpp_build_version}} \
+ -DCMAKE_CXX_STANDARD={{minimum_cpp_build_version}} \
# Your extra arguments here.
cmake --build build
ctest --test-dir build
@@ -85,7 +85,7 @@ vcpkg.
### FetchContent
Instead of installing the project's dependencies via a package manager, you can optionally
-configure beman.{{cookiecutter.project_name}} to fetch them automatically via CMake FetchContent.
+configure beman.{{project_name}} to fetch them automatically via CMake FetchContent.
To do so, specify
`-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake`. This will
@@ -97,7 +97,7 @@ Example commands:
cmake \
-B build \
-S . \
- -DCMAKE_CXX_STANDARD={{cookiecutter.minimum_cpp_build_version}} \
+ -DCMAKE_CXX_STANDARD={{minimum_cpp_build_version}} \
-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=./infra/cmake/use-fetch-content.cmake
cmake --build build
ctest --test-dir build
@@ -106,34 +106,76 @@ ctest --test-dir build
The file `./lockfile.json` configures the list of dependencies and versions that will be
acquired by FetchContent.
+{% if generating_exemplar %}## Developing The Copier Template
+
+This repository doubles as both the exemplar library and the Copier template used to stamp
+new Beman libraries.
+
+The template workflow is organized as follows:
+
+* `copier.yml` defines the Copier questions, defaults, validation rules, and post-copy
+ tasks.
+* `template/` contains the files rendered into generated projects.
+* `stamp.sh` is the user-facing wrapper for stamping a forked exemplar repository into a
+ new project.
+* `./copier/check_copier.sh` validates that exemplar regenerates cleanly and that a
+ non-exemplar project does not leak exemplar-specific names like `identity`.
+
+When testing local template changes, do not render directly from the Git checkout. The
+Copier helper scripts intentionally render from a temporary snapshot with `.git`, `build`,
+and `.venv` excluded so that validation uses the current worktree contents.
+
+Run the template checks with:
+
+```shell
+./copier/check_copier.sh
+```
+
+If you need to smoke-test generated projects manually, start from the same validated flow:
+
+```shell
+tmpdir=$(mktemp -d)
+python3 -m venv "$tmpdir/venv"
+"$tmpdir/venv/bin/python3" -m pip install copier
+src=$(mktemp -d)
+rsync -a --exclude .git --exclude build --exclude .venv ./ "$src/"
+"$tmpdir/venv/bin/copier" copy --trust --defaults \
+ -d template_src_path=https://github.com/bemanproject/exemplar.git \
+ -d template_commit="$(git rev-parse HEAD)" \
+ "$src" "$tmpdir/project"
+```
+
+Then configure and build the generated project as usual.
+{% endif %}
+
## Project-specific configure arguments
-Project-specific options are prefixed with `BEMAN_{{cookiecutter.project_name.upper()}}`.
+Project-specific options are prefixed with `BEMAN_{{project_name.upper()}}`.
You can see the list of available options with:
```bash
-cmake -LH -S . -B build | grep "BEMAN_{{cookiecutter.project_name.upper()}}" -C 2
+cmake -LH -S . -B build | grep "BEMAN_{{project_name.upper()}}" -C 2
```
Some project-specific configure arguments
-### `BEMAN_{{cookiecutter.project_name.upper()}}_BUILD_TESTS`
+### `BEMAN_{{project_name.upper()}}_BUILD_TESTS`
Enable building tests and test infrastructure. Default: `ON`.
Values: `{ ON, OFF }`.
-### `BEMAN_{{cookiecutter.project_name.upper()}}_BUILD_EXAMPLES`
+### `BEMAN_{{project_name.upper()}}_BUILD_EXAMPLES`
Enable building examples. Default: `ON`. Values: `{ ON, OFF }`.
-### `BEMAN_{{cookiecutter.project_name.upper()}}_INSTALL_CONFIG_FILE_PACKAGE`
+### `BEMAN_{{project_name.upper()}}_INSTALL_CONFIG_FILE_PACKAGE`
Enable installing the CMake config file package. Default: `ON`.
Values: `{ ON, OFF }`.
-This is required so that users of `beman.{{cookiecutter.project_name}}` can use
-`find_package(beman.{{cookiecutter.project_name}})` to locate the library.
+This is required so that users of `beman.{{project_name}}` can use
+`find_package(beman.{{project_name}})` to locate the library.
diff --git a/cookiecutter/{{cookiecutter.project_name}}/LICENSE b/template/LICENSE.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/LICENSE
rename to template/LICENSE.jinja
diff --git a/template/README.md.jinja b/template/README.md.jinja
new file mode 100644
index 00000000..261fc59d
--- /dev/null
+++ b/template/README.md.jinja
@@ -0,0 +1,365 @@
+{% if generating_exemplar %}
+# How to Use This Template
+
+This repository uses [Copier](https://copier.readthedocs.io/) as its templating engine to generate, manage, and update Beman library boilerplate. The template inputs are defined in `copier.yml`, and the rendered project files live under `template/`.
+
+> [!NOTE]
+> ?? **`stamp.sh` is deprecated.** If you are used to the legacy workflow of forking this repository and running `./stamp.sh`, please transition to the natively supported `copier` workflow below. The script remains for legacy CI compatibility but will not receive future updates.
+
+## ? Quick Start: Create a New Library
+
+If you are already familiar with the Beman project lifecycle, you can generate a new library immediately using `uvx` (via the [uv package manager](https://docs.astral.sh/uv/)):
+
+```bash
+uvx --from copier copier copy "git+[https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git)" my-new-library
+cd my-new-library
+git init
+git add .
+git commit -m "Initial commit from Beman Exemplar template"
+uvx pre-commit install
+```
+
+---
+
+## ?? The Beman Project Lifecycle
+
+To create a new library and get it officially adopted into the Beman project, you will generate the project locally, push it to your personal GitHub for active development, and eventually transfer it to the `bemanproject` organization.
+
+> **Why this specific workflow?**
+> Developing on your personal account first gives you a sandbox to experiment and clean up your Git history. Transferring the repository (rather than creating it directly in the org) ensures the `bemanproject` organization ultimately holds the canonical, unbroken Git history, which prevents downstream forks from breaking later.
+
+### Step 1: Push to Your Personal GitHub
+After running the Quick Start commands above, push the fresh template to your personal account to begin development.
+
+**Option A: Using the GitHub CLI (`gh`)**
+```bash
+gh repo create my-new-library --public --source=. --remote=origin --push
+```
+
+**Option B: Using the GitHub Browser Interface**
+1. [Create a new repository](https://github.com/new). Name it exactly what you named your local folder. Leave it completely empty.
+2. Link and push your local code:
+ ```bash
+ git remote add origin [https://github.com/](https://github.com/)/my-new-library.git
+ git branch -M main
+ git push -u origin main
+ ```
+
+### Step 2: Incubation & Implementation
+Before transferring the project to the Beman organization, take the time to build out the actual proposal.
+* Expand beyond the default placeholder headers (e.g., `my-new-library.hpp`).
+* Stub out the necessary files for your library's domain.
+* Write out the skeleton of the library's design and documentation.
+* *Optional:* Once you have a working sandbox, feel free to squash or clean up your Git history using an interactive rebase (`git rebase -i`) before handing it over.
+
+### Step 3: Transfer to the Beman Project
+Once the repository has enough substance to be reviewed or collaborated on, hand it over to the organization to establish it as the canonical source.
+
+1. On your GitHub repository page, go to **Settings** > **General**.
+2. Scroll to the bottom to the **Danger Zone** and click **Transfer ownership**.
+3. Type `bemanproject` as the new owner and confirm.
+
+### Step 4: Fork It Back for Ongoing Development
+Now that `bemanproject/my-new-library` is the official upstream repository, standard open-source contribution rules apply.
+
+1. Navigate to the new URL: `https://github.com/bemanproject/my-new-library`.
+2. Click **Fork** in the top right corner to create a fork in your personal namespace.
+3. Update your local repository's remotes so you can pull from upstream and push to your new fork:
+ ```bash
+ # Rename your current remote (the org repo) to 'upstream'
+ git remote rename origin upstream
+
+ # Add your new personal fork as 'origin'
+ git remote add origin [https://github.com/](https://github.com/)/my-new-library.git
+ ```
+
+---
+
+## ?? Reconfiguring or Renaming Your Project
+
+One of the biggest advantages of using `copier` is that it actively maintains your project's boilerplate. If you start out with a placeholder name and later decide to rename your library (or change your minimum C++ version), you can instruct Copier to update the structural boilerplate using the `--data` flag.
+
+For example, to rename your project to `range_discombobulator`:
+
+```bash
+uvx --from copier copier update --trust --skip-answered --data project_name=range_discombobulator
+```
+
+**What this does:**
+* `--data project_name=...`: Overrides your previous answer for the project name.
+* `--skip-answered`: Skips the interactive prompt for all the questions you aren't changing.
+
+Copier will calculate the difference and automatically update the template-provided filesrenaming CMake configurations, GitHub Actions matrices, standard folder paths, and core macros.
+
+?? **Important Caveat:** Copier only manages the files it originally generated. If you have added custom files (like `detail/internal.hpp`), or manually typed the old project name into new C++ namespaces or include guards, you will need to update those manually. Always review the changes with `git diff`, and use your IDE or `grep` to hunt down any lingering references to the old name before committing!
+
+## Rebasing An Older Exemplar Clone Onto Copier
+
+If your repository predates the Copier migration and was created as a plain GitHub template copy, bootstrap a fresh stamped baseline at the original exemplar split point, then rebase your project commits onto that baseline.
+
+Start from a clean worktree and make sure you can identify the branch you want to migrate; the examples below assume `main`.
+
+```shell
+git remote add exemplar [https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git)
+git fetch exemplar
+base=$(git merge-base main exemplar/main)
+git branch pre-copier-backup main
+git switch --detach "$base"
+
+tmpdir=$(mktemp -d)
+python3 -m venv "$tmpdir/venv"
+"$tmpdir/venv/bin/python3" -m pip install copier pre-commit
+"$tmpdir/venv/bin/copier" copy --trust --overwrite \
+ -d project_name=your_project_name \
+ -d maintainer=your_github_username \
+ -d minimum_cpp_build_version=20 \
+ -d paper=PnnnnRr \
+ -d description="Short project description." \
+ -d unit_test_library=gtest \
+ -d generating_exemplar=false \
+ -d owner=bemanproject \
+ [https://github.com/bemanproject/exemplar.git](https://github.com/bemanproject/exemplar.git) \
+ .
+"$tmpdir/venv/bin/pre-commit" run --all-files || true
+git add .
+git switch -c stamp
+git commit -m "Bootstrap Copier template"
+
+git switch main
+git rebase --rebase-merges --onto stamp "$base" main
+```
+
+After the rebase, commit the generated `.copier-answers.yml`. That file is what enables future template updates with:
+
+```shell
+uvx --from copier copier update --trust
+```
+
+If you want to track a fork of exemplar rather than `bemanproject/exemplar`, edit `_src_path` in `.copier-answers.yml` before your first `copier update`.
+
+## Template Maintenance
+
+If you are changing the template itself rather than developing a library, use the Copier workflow directly:
+
+* Edit `copier.yml` for template questions, defaults, validators, and post-copy tasks.
+* Edit `template/` for files that should be rendered into stamped projects.
+* Run `./copier/check_copier.sh` to verify exemplar self-regeneration and non-exemplar templating.
+* Create a new project locally using the Quick Start commands when you want to use the template for real work.
+
+The consistency check renders from a `.git`-free temporary snapshot of the repository. That keeps local validation aligned with the current worktree contents, rather than only the last committed Git state.
+
+---
+
+What follows is an example of a Beman library README.
+
+# beman.exemplar: A Beman Library Exemplar
+
+   [](https://coveralls.io/github/bemanproject/exemplar?branch=main)  [](https://godbolt.org/z/4qEPK87va)
+
+`beman.exemplar` is a minimal C++ library conforming to [The Beman Standard](https://github.com/bemanproject/beman/blob/main/docs/beman_standard.md).
+This can be used as a template for those intending to write Beman libraries.
+It may also find use as a minimal and modern C++ project structure.
+
+**Implements**: `std::identity` proposed in [Standard Library Concepts (P0898R3)](https://wg21.link/P0898R3).
+
+**Status**: [Under development and not yet ready for production use.](https://github.com/bemanproject/beman/blob/main/docs/beman_library_maturity_model.md#under-development-and-not-yet-ready-for-production-use)
+
+## License
+
+`beman.exemplar` is licensed under the Apache License v2.0 with LLVM Exceptions.
+
+## Usage
+
+`std::identity` is a function object type whose `operator()` returns its argument unchanged.
+`std::identity` serves as the default projection in constrained algorithms.
+Its direct usage is usually not needed.
+
+### Usage: default projection in constrained algorithms
+
+The following code snippet illustrates how we can achieve a default projection using `beman::exemplar::identity`:
+
+```cpp
+#include
+
+namespace exe = beman::exemplar;
+
+// Class with a pair of values.
+struct Pair
+{
+ int n;
+ std::string s;
+
+ // Output the pair in the form {n, s}.
+ // Used by the range-printer if no custom projection is provided (default: identity projection).
+ friend std::ostream &operator<<(std::ostream &os, const Pair &p)
+ {
+ return os << "Pair" << '{' << p.n << ", " << p.s << '}';
+ }
+};
+
+// A range-printer that can print projected (modified) elements of a range.
+// All the elements of the range are printed in the form {element1, element2, ...}.
+// e.g., pairs with identity: Pair{1, one}, Pair{2, two}, Pair{3, three}
+// e.g., pairs with custom projection: {1:one, 2:two, 3:three}
+template
+void print(const std::string_view rem, R &&range, Projection projection = exe::identity>)
+{
+ std::cout << rem << '{';
+ std::ranges::for_each(
+ range,
+ [O = 0](const auto &o) mutable
+ { std::cout << (O++ ? ", " : "") << o; },
+ projection);
+ std::cout << "}\n";
+};
+
+int main()
+{
+ // A vector of pairs to print.
+ const std::vector pairs = {
+ {1, "one"},
+ {2, "two"},
+ {3, "three"},
+ };
+
+ // Print the pairs using the default projection.
+ print("\tpairs with beman: ", pairs);
+
+ return 0;
+}
+
+```
+
+{% else %}
+TODO
+
+{% endif %}
+Full runnable examples can be found in [`examples/`](examples/).
+
+## Dependencies
+
+### Build Environment
+
+This project requires at least the following to build:
+
+* A C++ compiler that conforms to the C++{{minimum_cpp_build_version}} standard or greater
+* CMake 3.30 or later
+* (Test Only) GoogleTest
+
+You can disable building tests by setting CMake option `BEMAN_{{project_name.upper()}}_BUILD_TESTS` to
+`OFF` when configuring the project.
+
+### Supported Platforms
+
+| Compiler | Version | C++ Standards | Standard Library |
+|------------|---------|---------------|-------------------|
+| GCC | 16-13 | C++26-C++17 | libstdc++ |
+| GCC | 12-11 | C++23-C++17 | libstdc++ |
+| Clang | 22-19 | C++26-C++17 | libstdc++, libc++ |
+| Clang | 18 | C++26-C++17 | libc++ |
+| Clang | 18 | C++23-C++17 | libstdc++ |
+| Clang | 17 | C++26-C++17 | libc++ |
+| Clang | 17 | C++20, C++17 | libstdc++ |
+| AppleClang | latest | C++26-C++17 | libc++ |
+| MSVC | latest | C++23 | MSVC STL |
+
+## Development
+
+See the [Contributing Guidelines](CONTRIBUTING.md).
+
+## Integrate beman.{{project_name}} into your project
+
+### Build
+
+You can build {{project_name}} using a CMake workflow preset:
+
+```bash
+cmake --workflow --preset gcc-release
+```
+
+To list available workflow presets, you can invoke:
+
+```bash
+cmake --list-presets=workflow
+```
+
+For details on building beman.{{project_name}} without using a CMake preset, refer to the
+[Contributing Guidelines](CONTRIBUTING.md).
+
+### Installation
+
+#### Vcpkg
+
+The preferred way to install {{project_name}} is via vcpkg. To do so, after installing vcpkg
+itself, you need to add support for the Beman project's [vcpkg
+registry](https://github.com/bemanproject/vcpkg-registry) by configuring a
+`vcpkg-configuration.json` file (which {{project_name}} [provides](vcpkg-configuration.json)).
+
+Then, simply run `vcpkg install beman-{{project_name.replace("_", "-")}}`.
+
+#### Manual
+
+To install beman.{{project_name}} globally after building with the `gcc-release` preset, you can
+run:
+
+```bash
+sudo cmake --install build/gcc-release
+```
+
+Alternatively, to install to a prefix, for example `/opt/beman`, you can run:
+
+```bash
+sudo cmake --install build/gcc-release --prefix /opt/beman
+```
+
+This will generate the following directory structure:
+
+```txt
+/opt/beman
+├── include
+│ └── beman
+│ └── {{project_name}}
+│ ├── {{project_name}}.hpp
+│ └── ...
+└── lib
+ └── cmake
+ └── beman.{{project_name}}
+ ├── beman.{{project_name}}-config-version.cmake
+ ├── beman.{{project_name}}-config.cmake
+ └── beman.{{project_name}}-targets.cmake
+```
+
+### CMake Configuration
+
+If you installed beman.{{project_name}} to a prefix, you can specify that prefix to your CMake
+project using `CMAKE_PREFIX_PATH`; for example, `-DCMAKE_PREFIX_PATH=/opt/beman`.
+
+You need to bring in the `beman.{{project_name}}` package to define the `beman::{{project_name}}` CMake
+target:
+
+```cmake
+find_package(beman.{{project_name}} REQUIRED)
+```
+
+You will then need to add `beman::{{project_name}}` to the link libraries of any libraries or
+executables that include `beman.{{project_name}}` headers.
+
+```cmake
+target_link_libraries(yourlib PUBLIC beman::{{project_name}})
+```
+
+### Using beman.{{project_name}}
+
+To use `beman.{{project_name}}` in your C++ project,
+include an appropriate `beman.{{project_name}}` header from your source code.
+
+```c++
+#include
+```
+
+> [!NOTE]
+>
+> `beman.{{project_name}}` headers are to be included with the `beman/{{project_name}}/` prefix.
+> Altering include search paths to spell the include target another way (e.g.
+> `#include <{{project_name}}.hpp>`) is unsupported.
diff --git a/cookiecutter/{{cookiecutter.project_name}}/examples/CMakeLists.txt b/template/examples/CMakeLists.txt.jinja
similarity index 60%
rename from cookiecutter/{{cookiecutter.project_name}}/examples/CMakeLists.txt
rename to template/examples/CMakeLists.txt.jinja
index fddbf1fd..dc289913 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/examples/CMakeLists.txt
+++ b/template/examples/CMakeLists.txt.jinja
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-{% if cookiecutter._generating_exemplar %}
+{% if generating_exemplar %}
set(ALL_EXAMPLES identity_direct_usage)
# Example `identity_as_default_projection` need ranges support:
@@ -22,15 +22,15 @@ set(ALL_EXAMPLES todo)
message("Examples to be built: ${ALL_EXAMPLES}")
foreach(example ${ALL_EXAMPLES})
- add_executable(beman.{{cookiecutter.project_name}}.examples.${example})
- target_sources(beman.{{cookiecutter.project_name}}.examples.${example} PRIVATE ${example}.cpp)
+ add_executable(beman.{{project_name}}.examples.${example})
+ target_sources(beman.{{project_name}}.examples.${example} PRIVATE ${example}.cpp)
target_link_libraries(
- beman.{{cookiecutter.project_name}}.examples.${example}
- PRIVATE beman::{{cookiecutter.project_name}}
+ beman.{{project_name}}.examples.${example}
+ PRIVATE beman::{{project_name}}
)
- if(BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES)
+ if(BEMAN_{{project_name.upper()}}_USE_MODULES)
set_target_properties(
- beman.{{cookiecutter.project_name}}.examples.${example}
+ beman.{{project_name}}.examples.${example}
PROPERTIES CXX_MODULE_STD ON
)
endif()
diff --git a/cookiecutter/{{cookiecutter.project_name}}/examples/identity_as_default_projection.cpp b/template/examples/identity_as_default_projection.cpp.jinja
similarity index 87%
rename from cookiecutter/{{cookiecutter.project_name}}/examples/identity_as_default_projection.cpp
rename to template/examples/identity_as_default_projection.cpp.jinja
index 20a95e8b..3007dfb5 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/examples/identity_as_default_projection.cpp
+++ b/template/examples/identity_as_default_projection.cpp.jinja
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-// This example demonstrates the usage of beman::{{cookiecutter.project_name}}::identity as a default projection in a range-printer.
+// This example demonstrates the usage of beman::{{project_name}}::identity as a default projection in a range-printer.
// Requires: range support (C++20) and std::identity support (C++20).
-#include
-#include
+#include
+#include
-#if BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES()
+#if BEMAN_{{project_name.upper()}}_USE_MODULES()
import std;
#else
#include
@@ -17,7 +17,7 @@ import std;
#include
#endif
-namespace exe = beman::{{cookiecutter.project_name}};
+namespace exe = beman::{{project_name}};
// Class with a pair of values.
struct Pair {
diff --git a/template/examples/identity_direct_usage.cpp.jinja b/template/examples/identity_direct_usage.cpp.jinja
new file mode 100644
index 00000000..e1985ad2
--- /dev/null
+++ b/template/examples/identity_direct_usage.cpp.jinja
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+{% set identity = "identity" if generating_exemplar else "todo" %}
+
+#include
+#include
+
+{% if generating_exemplar %}
+#if BEMAN_{{project_name.upper()}}_USE_MODULES()
+import std;
+#else
+ #include
+#endif
+
+namespace exe = beman::{{project_name}};
+
+int main() {
+ std::cout << exe::identity()(2024) << '\n';
+ return 0;
+}
+{% else %}
+int main() {
+ // TODO
+}
+{% endif %}
diff --git a/template/include/beman/{{ project_name }}/CMakeLists.txt.jinja b/template/include/beman/{{ project_name }}/CMakeLists.txt.jinja
new file mode 100644
index 00000000..8853f4e3
--- /dev/null
+++ b/template/include/beman/{{ project_name }}/CMakeLists.txt.jinja
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+{% set identity = "identity" if generating_exemplar else "todo" %}
+
+if(BEMAN_{{project_name.upper()}}_USE_MODULES)
+ target_sources(
+ beman.{{project_name}}
+ PUBLIC
+ FILE_SET CXX_MODULES FILES {{project_name}}.cppm
+ FILE_SET HEADERS
+ FILES
+ config.hpp
+ {{project_name}}.hpp
+ {{identity}}.hpp
+ "${PROJECT_BINARY_DIR}/include/beman/{{project_name}}/config_generated.hpp"
+ )
+else()
+ target_sources(
+ beman.{{project_name}}
+ PUBLIC
+ FILE_SET HEADERS
+ FILES
+ config.hpp
+ {{project_name}}.hpp
+ {{identity}}.hpp
+ "${PROJECT_BINARY_DIR}/include/beman/{{project_name}}/config_generated.hpp"
+ )
+endif()
diff --git a/template/include/beman/{{ project_name }}/config.hpp.jinja b/template/include/beman/{{ project_name }}/config.hpp.jinja
new file mode 100644
index 00000000..7761e19b
--- /dev/null
+++ b/template/include/beman/{{ project_name }}/config.hpp.jinja
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef BEMAN_{{project_name.upper()}}_CONFIG_HPP
+#define BEMAN_{{project_name.upper()}}_CONFIG_HPP
+
+#if !defined(__has_include) || __has_include()
+ #include
+#else
+ #define BEMAN_{{project_name.upper()}}_USE_MODULES() 0
+#endif
+
+#endif
diff --git a/template/include/beman/{{ project_name }}/config_generated.hpp.in.jinja b/template/include/beman/{{ project_name }}/config_generated.hpp.in.jinja
new file mode 100644
index 00000000..6b296ed1
--- /dev/null
+++ b/template/include/beman/{{ project_name }}/config_generated.hpp.in.jinja
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef BEMAN_{{project_name.upper()}}_CONFIG_GENERATED_HPP
+#define BEMAN_{{project_name.upper()}}_CONFIG_GENERATED_HPP
+
+#cmakedefine01 BEMAN_{{project_name.upper()}}_USE_MODULES()
+
+#endif
diff --git a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/identity.hpp b/template/include/beman/{{ project_name }}/identity.hpp.jinja
similarity index 52%
rename from cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/identity.hpp
rename to template/include/beman/{{ project_name }}/identity.hpp.jinja
index bdea366c..71d8773a 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/include/beman/{{cookiecutter.project_name}}/identity.hpp
+++ b/template/include/beman/{{ project_name }}/identity.hpp.jinja
@@ -1,18 +1,18 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-{% set identity = "identity" if cookiecutter._generating_exemplar else "todo" %}
+{% set identity = "identity" if generating_exemplar else "todo" %}
-#ifndef BEMAN_{{cookiecutter.project_name.upper()}}_{{identity.upper()}}_HPP
-#define BEMAN_{{cookiecutter.project_name.upper()}}_{{identity.upper()}}_HPP
+#ifndef BEMAN_{{project_name.upper()}}_{{identity.upper()}}_HPP
+#define BEMAN_{{project_name.upper()}}_{{identity.upper()}}_HPP
-#include
+#include
-#if BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES() && !defined(BEMAN_{{cookiecutter.project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT)
+#if BEMAN_{{project_name.upper()}}_USE_MODULES() && !defined(BEMAN_{{project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT)
-import beman.{{cookiecutter.project_name}};
+import beman.{{project_name}};
#else
-{% if cookiecutter._generating_exemplar %}
+{% if generating_exemplar %}
// C++ Standard Library: std::identity equivalent.
// See https://eel.is/c++draft/func.identity:
//
@@ -35,9 +35,9 @@ import beman.{{cookiecutter.project_name}};
#endif
{% endif %}
-namespace beman::{{cookiecutter.project_name}} {
+namespace beman::{{project_name}} {
-{% if cookiecutter._generating_exemplar %}
+{% if generating_exemplar %}
struct __is_transparent; // not defined
// A function object that returns its argument unchanged.
@@ -55,9 +55,9 @@ struct identity {
// TODO
{% endif %}
-} // namespace beman::{{cookiecutter.project_name}}
+} // namespace beman::{{project_name}}
-#endif // BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES() &&
- // !defined(BEMAN_{{cookiecutter.project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT)
+#endif // BEMAN_{{project_name.upper()}}_USE_MODULES() &&
+ // !defined(BEMAN_{{project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT)
-#endif // BEMAN_{{cookiecutter.project_name.upper()}}_{{identity.upper()}}_HPP
+#endif // BEMAN_{{project_name.upper()}}_{{identity.upper()}}_HPP
diff --git a/template/include/beman/{{ project_name }}/{{ project_name }}.cppm.jinja b/template/include/beman/{{ project_name }}/{{ project_name }}.cppm.jinja
new file mode 100644
index 00000000..ef9b3751
--- /dev/null
+++ b/template/include/beman/{{ project_name }}/{{ project_name }}.cppm.jinja
@@ -0,0 +1,11 @@
+export module beman.{{project_name}};
+
+import std;
+
+#define BEMAN_{{project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT
+export {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Winclude-angled-in-module-purview"
+#include
+#pragma clang diagnostic pop
+}
diff --git a/template/include/beman/{{ project_name }}/{{ project_name }}.hpp.jinja b/template/include/beman/{{ project_name }}/{{ project_name }}.hpp.jinja
new file mode 100644
index 00000000..6de86a51
--- /dev/null
+++ b/template/include/beman/{{ project_name }}/{{ project_name }}.hpp.jinja
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+{% set identity = "identity" if generating_exemplar else "todo" %}
+
+#ifndef BEMAN_{{project_name.upper()}}_{{project_name.upper()}}_HPP
+#define BEMAN_{{project_name.upper()}}_{{project_name.upper()}}_HPP
+
+#include
+
+#if BEMAN_{{project_name.upper()}}_USE_MODULES() && !defined(BEMAN_{{project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT)
+
+import beman.{{project_name}};
+
+#else
+
+ #include
+
+#endif // BEMAN_{{project_name.upper()}}_USE_MODULES() &&
+ // !defined(BEMAN_{{project_name.upper()}}_INCLUDED_FROM_INTERFACE_UNIT)
+
+#endif // BEMAN_{{project_name.upper()}}_{{project_name.upper()}}_HPP
diff --git a/template/infra/.beman_submodule.jinja b/template/infra/.beman_submodule.jinja
new file mode 100644
index 00000000..437c3824
--- /dev/null
+++ b/template/infra/.beman_submodule.jinja
@@ -0,0 +1,3 @@
+[beman_submodule]
+remote=https://github.com/steve-downey/infra.git
+commit_hash=5ff6d99b926f616aec54b103b5b79506f994f88b
diff --git a/template/infra/.github/CODEOWNERS.jinja b/template/infra/.github/CODEOWNERS.jinja
new file mode 100644
index 00000000..439303d4
--- /dev/null
+++ b/template/infra/.github/CODEOWNERS.jinja
@@ -0,0 +1 @@
+* @ednolan @rishyak @wusatosi @JeffGarland
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/.github/workflows/pre-commit.yml b/template/infra/.github/workflows/pre-commit.yml.jinja
similarity index 98%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/.github/workflows/pre-commit.yml
rename to template/infra/.github/workflows/pre-commit.yml.jinja
index 7051c132..5f9ae08c 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/infra/.github/workflows/pre-commit.yml
+++ b/template/infra/.github/workflows/pre-commit.yml.jinja
@@ -1,4 +1,6 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+{% raw -%}
+
name: Lint Check (pre-commit)
on:
@@ -77,3 +79,4 @@ jobs:
tool_name: pre-commit
level: warning
reviewdog_flags: "-fail-level=error"
+{% endraw %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml b/template/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml.jinja
similarity index 98%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml
rename to template/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml.jinja
index 024a51f4..4e3117aa 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml
+++ b/template/infra/.github/workflows/reusable-beman-create-issue-when-fault.yml.jinja
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-
+{% raw %}
name: 'Beman issue creation workflow'
on:
workflow_call:
@@ -26,3 +26,4 @@ jobs:
fi
env:
GH_TOKEN: ${{ github.token }}
+{% endraw %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/.gitignore b/template/infra/.gitignore.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/.gitignore
rename to template/infra/.gitignore.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/.pre-commit-config.yaml b/template/infra/.pre-commit-config.yaml.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/.pre-commit-config.yaml
rename to template/infra/.pre-commit-config.yaml.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/LICENSE b/template/infra/LICENSE.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/LICENSE
rename to template/infra/LICENSE.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/README.md b/template/infra/README.md.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/README.md
rename to template/infra/README.md.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/BuildTelemetry.cmake b/template/infra/cmake/BuildTelemetry.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/BuildTelemetry.cmake
rename to template/infra/cmake/BuildTelemetry.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/BuildTelemetryConfig.cmake b/template/infra/cmake/BuildTelemetryConfig.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/BuildTelemetryConfig.cmake
rename to template/infra/cmake/BuildTelemetryConfig.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/Config.cmake.in b/template/infra/cmake/Config.cmake.in.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/Config.cmake.in
rename to template/infra/cmake/Config.cmake.in.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/appleclang-toolchain.cmake b/template/infra/cmake/appleclang-toolchain.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/appleclang-toolchain.cmake
rename to template/infra/cmake/appleclang-toolchain.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/beman-install-library.cmake b/template/infra/cmake/beman-install-library.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/beman-install-library.cmake
rename to template/infra/cmake/beman-install-library.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/enable-experimental-import-std.cmake b/template/infra/cmake/enable-experimental-import-std.cmake.jinja
similarity index 97%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/enable-experimental-import-std.cmake
rename to template/infra/cmake/enable-experimental-import-std.cmake.jinja
index 20fc302b..0ac96043 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/enable-experimental-import-std.cmake
+++ b/template/infra/cmake/enable-experimental-import-std.cmake.jinja
@@ -187,4 +187,8 @@ elseif(CMAKE_VERSION VERSION_EQUAL "4.3.2")
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD
"451f2fe2-a8a2-47c3-bc32-94786d8fc91b"
)
+elseif(CMAKE_VERSION VERSION_EQUAL "4.3.3")
+ set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD
+ "451f2fe2-a8a2-47c3-bc32-94786d8fc91b"
+ )
endif()
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/gnu-toolchain.cmake b/template/infra/cmake/gnu-toolchain.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/gnu-toolchain.cmake
rename to template/infra/cmake/gnu-toolchain.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/llvm-libc++-toolchain.cmake b/template/infra/cmake/llvm-libc++-toolchain.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/llvm-libc++-toolchain.cmake
rename to template/infra/cmake/llvm-libc++-toolchain.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/llvm-toolchain.cmake b/template/infra/cmake/llvm-toolchain.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/llvm-toolchain.cmake
rename to template/infra/cmake/llvm-toolchain.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/msvc-toolchain.cmake b/template/infra/cmake/msvc-toolchain.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/msvc-toolchain.cmake
rename to template/infra/cmake/msvc-toolchain.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/telemetry.sh b/template/infra/cmake/telemetry.sh.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/telemetry.sh
rename to template/infra/cmake/telemetry.sh.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/infra/cmake/use-fetch-content.cmake b/template/infra/cmake/use-fetch-content.cmake.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/infra/cmake/use-fetch-content.cmake
rename to template/infra/cmake/use-fetch-content.cmake.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/lockfile.json b/template/lockfile.json.jinja
similarity index 82%
rename from cookiecutter/{{cookiecutter.project_name}}/lockfile.json
rename to template/lockfile.json.jinja
index f3388cff..cbd6495e 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/lockfile.json
+++ b/template/lockfile.json.jinja
@@ -1,6 +1,6 @@
{
"dependencies": [
-{% if cookiecutter.unit_test_library == "gtest" %}
+{% if unit_test_library == "gtest" %}
{
"name": "googletest",
"package_name": "GTest",
@@ -10,7 +10,7 @@
"INSTALL_GTEST": "OFF"
}
}
-{% elif cookiecutter.unit_test_library == "catch2" %}
+{% elif unit_test_library == "catch2" %}
{
"name": "Catch2",
"package_name": "Catch2",
diff --git a/cookiecutter/{{cookiecutter.project_name}}/port/portfile.cmake.in b/template/port/portfile.cmake.in.jinja
similarity index 58%
rename from cookiecutter/{{cookiecutter.project_name}}/port/portfile.cmake.in
rename to template/port/portfile.cmake.in.jinja
index 1f13b31b..8307fe98 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/port/portfile.cmake.in
+++ b/template/port/portfile.cmake.in.jinja
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
- REPO bemanproject/{{cookiecutter.project_name}}
+ REPO bemanproject/{{project_name}}
REF "v@VERSION@"
SHA512 @SHA512@
HEAD_REF main
@@ -10,26 +10,27 @@ vcpkg_from_github(
vcpkg_check_features(
OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
- modules BEMAN_{{cookiecutter.project_name.upper()}}_USE_MODULES
+ modules BEMAN_{{project_name.upper()}}_USE_MODULES
)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
OPTIONS
${FEATURE_OPTIONS}
- -DBEMAN_{{cookiecutter.project_name.upper()}}_BUILD_TESTS=OFF
- -DBEMAN_{{cookiecutter.project_name.upper()}}_BUILD_EXAMPLES=OFF
+ -DBEMAN_{{project_name.upper()}}_BUILD_TESTS=OFF
+ -DBEMAN_{{project_name.upper()}}_BUILD_EXAMPLES=OFF
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(
- PACKAGE_NAME beman.{{cookiecutter.project_name}}
- CONFIG_PATH lib/cmake/beman.{{cookiecutter.project_name}}
+ PACKAGE_NAME beman.{{project_name}}
+ CONFIG_PATH lib/cmake/beman.{{project_name}}
)
if(NOT "modules" IN_LIST FEATURES)
- file(REMOVE_RECURSE
+ file(
+ REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/debug"
"${CURRENT_PACKAGES_DIR}/lib"
)
diff --git a/cookiecutter/{{cookiecutter.project_name}}/port/vcpkg.json.in b/template/port/vcpkg.json.in.jinja
similarity index 50%
rename from cookiecutter/{{cookiecutter.project_name}}/port/vcpkg.json.in
rename to template/port/vcpkg.json.in.jinja
index 81a58a5a..36836ffe 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/port/vcpkg.json.in
+++ b/template/port/vcpkg.json.in.jinja
@@ -1,8 +1,8 @@
{
- "name": "beman-{{cookiecutter.project_name.replace('_', '-')}}",
+ "name": "beman-{{project_name.replace('_', '-')}}",
"version-semver": "@VERSION@",
- "description": "{{cookiecutter.description}}",
- "homepage": "https://github.com/bemanproject/{{cookiecutter.project_name}}",
+ "description": "{{description}}",
+ "homepage": "https://github.com/bemanproject/{{project_name}}",
"license": "Apache-2.0 WITH LLVM-exception",
"dependencies": [
{
@@ -16,7 +16,7 @@
],
"features": {
"modules": {
- "description": "Provide beman.{{cookiecutter.project_name}} as a C++ module"
+ "description": "Provide beman.{{project_name}} as a C++ module"
}
}
}
diff --git a/template/tests/beman/{{ project_name }}/CMakeLists.txt.jinja b/template/tests/beman/{{ project_name }}/CMakeLists.txt.jinja
new file mode 100644
index 00000000..d36cf0a2
--- /dev/null
+++ b/template/tests/beman/{{ project_name }}/CMakeLists.txt.jinja
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+{% set identity = "identity" if generating_exemplar else "todo" %}
+
+{% if unit_test_library == "gtest" %}
+find_package(GTest REQUIRED)
+{% elif unit_test_library == "catch2" %}
+find_package(Catch2 3 REQUIRED)
+{% endif %}
+
+add_executable(beman.{{project_name}}.tests.{{identity}})
+target_sources(beman.{{project_name}}.tests.{{identity}} PRIVATE {{identity}}.test.cpp)
+target_link_libraries(
+ beman.{{project_name}}.tests.{{identity}}
+{% if unit_test_library == "gtest" %}
+ PRIVATE beman::{{project_name}} GTest::gtest_main
+{% elif unit_test_library == "catch2" %}
+ PRIVATE beman::{{project_name}} Catch2::Catch2WithMain
+{% endif %}
+)
+if(BEMAN_EXEMPLAR_USE_MODULES)
+ set_target_properties(
+ beman.{{project_name}}.tests.{{identity}}
+ PROPERTIES CXX_MODULE_STD ON
+ )
+endif()
+
+{% if unit_test_library == "gtest" %}
+include(GoogleTest)
+gtest_discover_tests(beman.{{project_name}}.tests.{{identity}} DISCOVERY_TIMEOUT 60)
+{% elif unit_test_library == "catch2" %}
+include(Catch)
+catch_discover_tests(beman.{{project_name}}.tests.{{identity}})
+{% endif %}
diff --git a/template/tests/beman/{{ project_name }}/identity.test.cpp.jinja b/template/tests/beman/{{ project_name }}/identity.test.cpp.jinja
new file mode 100644
index 00000000..57b6581b
--- /dev/null
+++ b/template/tests/beman/{{ project_name }}/identity.test.cpp.jinja
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+{% set identity = "identity" if generating_exemplar else "todo" %}
+
+#include
+{% if unit_test_library == "gtest" %}
+#include
+{% elif unit_test_library == "catch2" %}
+#include
+{% endif %}
+#include
+
+{% if generating_exemplar %}
+#if BEMAN_EXEMPLAR_USE_MODULES()
+import std;
+#else
+ #include
+ #include
+#endif
+
+namespace exe = beman::{{project_name}};
+
+{% if unit_test_library == "gtest" %}
+TEST(IdentityTest, call_identity_with_int) {
+{% elif unit_test_library == "catch2" %}
+TEST_CASE("can call identity with int", "[{{project_name}}::call_identity_with_int]") {
+{% endif %}
+ for (int i = -100; i < 100; ++i) {
+{% if unit_test_library == "gtest" %}
+ EXPECT_EQ(i, exe::identity()(i));
+{% elif unit_test_library == "catch2" %}
+ CHECK(i == exe::identity()(i));
+{% endif %}
+ }
+}
+
+{% if unit_test_library == "gtest" %}
+TEST(IdentityTest, call_identity_with_custom_type) {
+{% elif unit_test_library == "catch2" %}
+TEST_CASE("can call identity with custom type", "[{{project_name}}::call_identity_with_custom_type]") {
+{% endif %}
+ struct S {
+ int i;
+ };
+
+ for (int i = -100; i < 100; ++i) {
+ const S s{i};
+ const S s_id = exe::identity()(s);
+{% if unit_test_library == "gtest" %}
+ EXPECT_EQ(s.i, s_id.i);
+{% elif unit_test_library == "catch2" %}
+ CHECK(s.i == s_id.i);
+{% endif %}
+ }
+}
+
+{% if unit_test_library == "gtest" %}
+TEST(IdentityTest, compare_std_vs_beman) {
+{% elif unit_test_library == "catch2" %}
+TEST_CASE("compare std vs beman", "[{{project_name}}::compare_std_vs_beman]") {
+{% endif %}
+// Requires: std::identity support.
+#if defined(__cpp_lib_type_identity)
+ std::identity std_id;
+ exe::identity beman_id;
+ for (int i = -100; i < 100; ++i) {
+{% if unit_test_library == "gtest" %}
+ EXPECT_EQ(std_id(i), beman_id(i));
+{% elif unit_test_library == "catch2" %}
+ CHECK(std_id(i) == beman_id(i));
+{% endif %}
+ }
+#endif
+}
+
+{% if unit_test_library == "gtest" %}
+TEST(IdentityTest, check_is_transparent) {
+{% elif unit_test_library == "catch2" %}
+TEST_CASE("check is transparent", "[{{project_name}}::check_is_transparent]") {
+{% endif %}
+// Requires: transparent operators support.
+#if defined(__cpp_lib_transparent_operators)
+
+ exe::identity id;
+
+ const auto container = {1, 2, 3, 4, 5};
+ auto it = std::find(std::begin(container), std::end(container), 3);
+{% if unit_test_library == "gtest" %}
+ EXPECT_EQ(3, *it);
+{% elif unit_test_library == "catch2" %}
+ CHECK(3 == *it);
+{% endif %}
+ auto it_with_id = std::find(std::begin(container), std::end(container), id(3));
+{% if unit_test_library == "gtest" %}
+ EXPECT_EQ(3, *it_with_id);
+
+ EXPECT_EQ(it, it_with_id);
+{% elif unit_test_library == "catch2" %}
+ CHECK(3 == *it_with_id);
+
+ CHECK(it == it_with_id);
+{% endif %}
+#endif
+}
+{% else %}
+{% if unit_test_library == "gtest" %}
+TEST(TodoTest, todo) {
+{% elif unit_test_library == "catch2" %}
+TEST_CASE("todo", "[{{project_name}}::todo]") {
+{% endif %}
+ const bool todo = true;
+{% if unit_test_library == "gtest" %}
+ EXPECT_TRUE(todo);
+{% elif unit_test_library == "catch2" %}
+ CHECK(todo);
+{% endif %}
+}
+{% endif %}
diff --git a/cookiecutter/{{cookiecutter.project_name}}/vcpkg-configuration.json b/template/vcpkg-configuration.json.jinja
similarity index 100%
rename from cookiecutter/{{cookiecutter.project_name}}/vcpkg-configuration.json
rename to template/vcpkg-configuration.json.jinja
diff --git a/cookiecutter/{{cookiecutter.project_name}}/vcpkg.json b/template/vcpkg.json.jinja
similarity index 54%
rename from cookiecutter/{{cookiecutter.project_name}}/vcpkg.json
rename to template/vcpkg.json.jinja
index 89ea514f..24970d1d 100644
--- a/cookiecutter/{{cookiecutter.project_name}}/vcpkg.json
+++ b/template/vcpkg.json.jinja
@@ -1,18 +1,18 @@
{
- "name": "beman-{{cookiecutter.project_name.replace('_', '-')}}",
-{% if cookiecutter._generating_exemplar %}
+ "name": "beman-{{project_name.replace('_', '-')}}",
+{% if generating_exemplar %}
"version-semver": "2.4.0",
{% else %}
"version-semver": "0.1.0",
{% endif %}
-{% if cookiecutter.unit_test_library == "gtest" %}
+{% if unit_test_library == "gtest" %}
"dependencies": [
{
"name": "gtest",
"host": true
}
]
-{% elif cookiecutter.unit_test_library == "catch2" %}
+{% elif unit_test_library == "catch2" %}
"dependencies": [
{
"name": "catch2",