Stop copy-pasting boilerplate. Register a template once, generate it anywhere.
CFGen is a lightweight CLI tool that generates files from templates you define. You write the template once, register it, then call cfgen -gen to stamp out a fresh copy with all your macros, timestamps, filenames, and naming-convention transforms automatically filled in.
It was built out of one simple frustration: every new C++ header, Python module, or config file starts with the same block of metadata that you always forget to update, or paste wrong, or forget to change from the last file. CFGen fixes that at the root.
File templates already exist in IDEs, snippets managers, and shell scripts. None of them work cleanly across tools, terminals, and projects.
- IDE snippets are locked to one editor. Switch tools, lose your templates.
- Shell scripts work, but writing a new one for each template is tedious and they don't compose well.
- Cookiecutter / Yeoman are overkill. They require Python environments, config files, prompts, and learning a framework. You just want to generate
main.cpp.
CFGen is a single static binary. You register a template file. You optionally define named macros (like author or company). Then you generate. No runtime, no config, no YAML soup.
The template system resolves macros at generation time, applies transforms (uppercase, snake_case, slug, etc.), and handles nested transforms correctly — all without a scripting language.
- Template registry — Register any file as a named template. CFGen copies and stores it internally so you can call it from anywhere.
- Macro system — Define named key-value pairs (
author,company,project, etc.) that get substituted into any template on generation. - Built-in default macros —
${filename},${date},${year},${iso_date},${cwd}, and more are resolved automatically without you defining anything. - Transform functions — Apply
upper,lower,snake,camel,pascal,title,slug,trim, andreversedirectly inside the template syntax. - Chainable / nested transforms — You can nest one transform inside another:
${upper:${snake:${classname}}}works exactly as you'd expect. - Zero runtime dependencies — The compiled binary is standalone. No interpreters, no package managers at runtime.
- Cross-platform — Works on Windows, Linux, and macOS. Platform-specific paths (
%APPDATA%,~/.config) are handled automatically.
The mental model is simple: CFGen is a two-registry tool with a template engine.
[ Template Registry ] [ Macro Registry ]
name → script file name → value
| |
└──────────┬───────────────────┘
▼
[ Generator ]
reads template content
scans for ${...} tokens
resolves macros + transforms
writes output file
When you run cfgen -gen output.h MyTemplate:
- CFGen looks up
MyTemplatein the template registry. - It reads the stored template file.
- It scans the content for
${...}expressions. - Each expression is resolved — first against built-in macros, then your registered macros.
- Any transform (
upper:,snake:, etc.) is applied to the resolved value. - The processed content is written to
output.h.
Templates and macros are stored in your system's standard app data directory:
- Windows:
%APPDATA%\CFGen\ - Linux:
~/.config/CFGen/ - macOS:
~/Library/Application Support/CFGen/
You never have to touch these directories manually.
If you just want to use CFGen without building from source, grab the latest binary from the Releases page.
| Platform | File |
|---|---|
| Windows | cfgen-windows |
| Linux | cfgen-linux |
| macOS | cfgen-macos |
Download the binary for your platform, rename it to cfgen (or cfgen.exe on Windows), and put it somewhere on your PATH. That's it — no compiler needed.
On Linux/macOS you'll need to mark it executable first:
chmod +x cfgenmacOS only — Gatekeeper quarantine: macOS flags binaries downloaded from the internet and blocks them from running, even after chmod +x. You'll know you've hit this if you see:
zsh: operation not permitted: ./cfgen
Remove the quarantine flag with:
xattr -d com.apple.quarantine ./cfgenIf that doesn't fully work (some Apple Silicon Macs are stricter), clear all extended attributes:
xattr -c ./cfgenAfter either command, ./cfgen will run normally. You only need to do this once — the flag doesn't come back.
Note:
cfgen(without./) will still saycommand not founduntil you add the binary's directory to yourPATH. That's a separate step — see Environment Variables below.
If you'd rather build it yourself, you need a C++20-capable compiler:
- Windows: GCC via MSYS2 or MinGW-w64 (recommended)
- Linux: GCC (
g++) — usually pre-installed or available via your package manager - macOS: GCC via Homebrew (
brew install gcc) or Apple Clang (both work)
Verify with:
g++ --version # should say GCC 10+ or Clang 12+git clone https://github.com/AmashOnBlitz/cfgen.git
cd cfgen/scripts/
build.batThe executable will be at build\cfgen.exe.
To use cfgen from anywhere, add the build\ directory to your PATH (see Environment Variables section below).
git clone https://github.com/AmashOnBlitz/cfgen.git
cd cfgen/
chmod +x scripts/build_linux.sh
./scripts/build_linux.shThe executable will be at build/cfgen.
Windows line endings issue: If you cloned this repo on Windows and are now building on Linux, or if your editor saved the build script with Windows-style line endings (
\r\n), you'll get an error likeenv: bash\r: No such file or directorywhen running the script. The\ris invisible but breaks the shebang line. Fix it with:sed -i 's/\r$//' scripts/build_linux.shThen run
./scripts/build_linux.shagain. You only need to do this once per clone.
cd cfgen/
chmod +x scripts/build_mac.sh
./scripts/build_mac.shThe build script auto-detects whether you have GCC or Clang and adjusts accordingly. If you only have Apple's Clang (no Homebrew GCC), it falls back to clang++ automatically. C++20 support on Apple Clang requires Xcode 13+ / macOS 12+.
Windows line endings issue: Same situation as Linux — if the script was ever touched on Windows, you may hit
env: bash\r: No such file or directory. Fix it with:sed -i '' $'s/\r$//' scripts/build_mac.shThe
''after-iis required on macOS (BSD sed syntax). Linux usessed -iwithout it.
Both build scripts share the same behavior:
- Compile source files in parallel for speed.
- Use incremental builds — only recompile files that changed (based on timestamps).
- Output colored status (
[BUILD]/[CACHE]) per file. - Fail fast and show the compiler error if any file fails.
To run cfgen from any terminal without specifying the full path, add its directory to your system PATH.
Why this matters: Without this, you'd have to type C:\path\to\cfgen\build\cfgen.exe every time. With it, you just type cfgen.
$env:PATH += ";C:\path\to\cfgen\build"- Open Start, search for "Edit the system environment variables".
- Click Environment Variables.
- Under User variables, find
Pathand click Edit. - Click New and paste the full path to
cfgen\build\. - Click OK on all dialogs.
- Open a new terminal — the change takes effect immediately.
[System.Environment]::SetEnvironmentVariable(
"PATH",
$env:PATH + ";C:\path\to\cfgen\build",
[System.EnvironmentVariableTarget]::User
)export PATH="$PATH:/path/to/cfgen/build"Add the export line to your shell's config file:
# For bash:
echo 'export PATH="$PATH:/path/to/cfgen/build"' >> ~/.bashrc
source ~/.bashrc
# For zsh:
echo 'export PATH="$PATH:/path/to/cfgen/build"' >> ~/.zshrc
source ~/.zshrcAll commands follow this structure:
cfgen <command> [sub-command] [arguments]
cfgen -hcfgen -reg -t MyHeader header_template.hThe -reg -t command takes two things: a name you're assigning to the template, and a path to the file that will serve as the template. The name is what you'll use to refer to this template later — in -gen, -del, or -show. Pick something short and descriptive.
What happens internally: CFGen copies your file into its own storage directory under that name. After registration, the original file is no longer referenced — CFGen has its own copy. You can safely move or delete the source file and the template will still work.
cfgen -reg -t CppHeader templates/cpp_header.h ← name, then file path
cfgen -reg -t PythonMod ~/boilerplate/py_mod.py
cfgen -reg -t GitIgnore .gitignore
Any plain text file can be a template. The file extension doesn't matter to CFGen — it copies the content verbatim and substitutes macros when you generate. A .gitignore, a CMakeLists.txt, a Markdown file — all valid.
cfgen -reg -m author "Ada Lovelace"
cfgen -reg -m company "Babbage & Co."
cfgen -reg -m project "AnalyticalEngine"The -reg -m command takes a name and a value. The name is what you write inside ${...} in your templates. The value is what gets substituted in its place at generation time.
Macros are persistent — you register them once and they're available to every template, every time. Think of them as your personal variables: author, company, license, email, whatever you put in every file header. Register them once when you set up CFGen and forget about them.
A few things worth knowing:
- Values with spaces must be quoted on the command line, otherwise the shell splits them as separate arguments.
- Names are case-sensitive.
Authorandauthorare two different macros. - There's no concept of scope. All macros are global. If you need a different value for a specific generation (like
classname), you update the macro before generating — either by deleting and re-registering, or by simply using a different name per use case.
# Project-level macros — register once
cfgen -reg -m author "Ada Lovelace"
cfgen -reg -m company "Babbage & Co."
cfgen -reg -m license "MIT"
# Per-file macros — update as you work
cfgen -reg -m classname "Socket"
cfgen -gen src/Socket.h CppHeader
cfgen -del -m classname
cfgen -reg -m classname "EventLoop"
cfgen -gen src/EventLoop.h CppHeadercfgen -gen output/MyClass.h MyHeaderThis reads the MyHeader template, resolves all macros and transforms, and writes the result to output/MyClass.h. The directory will be created if it doesn't exist.
cfgen -show -tOutput:
Templates:
----------------------------------------------------
| # | Template Name | File Path |
----------------------------------------------------
| 1 | MyHeader | header_template.h |
| 2 | CppClass | class_template.cpp |
----------------------------------------------------
cfgen -show -mcfgen -del -t MyHeadercfgen -del -m authorcfgen -show -trdir # Where templates.map is stored
cfgen -show -mrdir # Where macros.map is storedA template is any plain text file — a .h, .cpp, .py, .json, .md, whatever. Inside it, you write ${macroname} wherever you want a value substituted. Everything else in the file is kept exactly as-is.
When you register a template with -reg -t, CFGen does two things: it copies your file into its own internal directory, and it adds an entry to its record file so it knows the name-to-file mapping.
The internal storage location is:
Windows: %APPDATA%\CFGen\<TemplateName>\<original_filename>
Linux: ~/.config/CFGen/<TemplateName>/<original_filename>
macOS: ~/Library/Application Support/CFGen/<TemplateName>/<original_filename>
Each template gets its own subdirectory named after the template. So if you register MyHeader from header_template.h, the stored copy lives at something like %APPDATA%\CFGen\MyHeader\header_template.h. The original filename is preserved inside that folder.
The full registry of template names is kept in templates.map in the same base directory. It's a plain text file — you can open it and read it, though you shouldn't need to edit it manually. Use -show -trdir to find out exactly where it is on your machine.
When you delete a template with -del -t, CFGen removes both the entry from the record file and the stored script file. It cleans up after itself.
Macros use ${...} syntax:
${macroname}
Example template:
// File : ${filename}
// Author : ${author}
// Created : ${date}
#ifndef ${upper:${stem}}_H
#define ${upper:${stem}}_H
class ${pascal:${classname}}
{
public:
${pascal:${classname}}();
~${pascal:${classname}}();
};
#endif // ${upper:${stem}}_HWhen you run cfgen -gen MyClass.h MyHeader, and you have classname registered as my_class, this generates:
// File : MyClass.h
// Author : Ada Lovelace
// Created : 2026-02-17
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass
{
public:
MyClass();
~MyClass();
};
#endif // MYCLASS_HTemplate names and macro names are case-sensitive strings. Recommended conventions:
- Template names:
PascalCaseorsnake_case(e.g.,CppHeader,python_module) - Macro names:
lowercase(e.g.,author,company,classname)
These are resolved automatically — no registration needed. They reflect the current state at the moment of generation.
| Macro | Example Value | Description |
|---|---|---|
${filename} |
MyClass.h |
Full filename with extension |
${stem} |
MyClass |
Filename without extension |
${ext} |
.h |
Extension only |
${abspath} |
/home/user/project/MyClass.h |
Absolute path of the output file |
${dir} |
./src/include |
Directory of the output file |
${cwd} |
/home/user/project |
Current working directory when cfgen is called |
| Macro | Example Value | Description |
|---|---|---|
${date} |
2026-02-17 |
Today's date (YYYY-MM-DD) |
${time} |
17:37:15 |
Current local time (HH:MM:SS) |
${year} |
2026 |
Current year |
${month} |
February |
Full month name |
${weekday} |
Tuesday |
Full weekday name |
${iso_date} |
2026-02-17T17:37:15 |
ISO 8601 timestamp |
${unix_time} |
1771330035 |
UNIX epoch timestamp |
Transforms follow this syntax:
${transform:${macroname}}
The transform name comes before the colon. The macro to transform comes after, wrapped in ${}.
${upper:${author}} → ADA LOVELACE
${lower:${author}} → ada lovelace
${snake:${project}} → analytical_engine
${camel:${project}} → analyticalEngine
${pascal:${project}} → AnalyticalEngine
${title:${project}} → Analytical Engine
${slug:${project}} → analytical-engine
${reverse:${author}} → ecalevoL adA
${trim:${whitespace_macro}} → trimmed value
| Transform | Input Example | Output |
|---|---|---|
upper |
hello world |
HELLO WORLD |
lower |
HELLO WORLD |
hello world |
title |
hello world |
Hello World |
snake |
HelloWorld or Hello World |
hello_world |
camel |
hello_world or hello world |
helloWorld |
pascal |
hello_world or hello world |
HelloWorld |
slug |
Hello World! |
hello-world |
reverse |
abc |
cba |
trim |
padded |
padded |
snake on space-separated input: Spaces become underscores, uppercase letters get an underscore prepended. Code File Generator → code__file__generator. If you want clean snake_case, use a value without capital letters or spaces.
slug: Strips everything except alphanumerics, spaces, underscores, and dashes. Replaces them all with hyphens. Good for URLs or file slugs.
You can nest transforms to apply multiple operations:
${upper:${snake:${classname}}}
This first resolves ${classname}, applies snake to it, then applies upper to that result. Nesting is supported up to 32 levels deep (you'll never realistically hit this).
The syntax rule: The inner expression must always be a full ${...} expression, not a raw string. This is valid:
${upper:${snake:${classname}}} ✓
This is not:
${upper:snake:${classname}} ✗ (invalid — ambiguous parse)
Built-in macros can also be transformed:
${upper:${stem}} → MYCLASS (from output filename)
${slug:${month}} → february
${lower:${weekday}} → tuesday
A common pattern for C++ include guards:
#ifndef ${upper:${snake:${classname}}}_H
#define ${upper:${snake:${classname}}}_H
// ...
#endif // ${upper:${snake:${classname}}}_HIf classname is PressureTest, this produces:
#ifndef PRESSURE_TEST_H
#define PRESSURE_TEST_H
// ...
#endif // PRESSURE_TEST_H# Register your project-level macros once
cfgen -reg -m author "Ada Lovelace"
cfgen -reg -m company "Babbage & Co."
cfgen -reg -m project "AnalyticalEngine"
cfgen -reg -m license "MIT"
# Register templates
cfgen -reg -t CppHeader templates/cpp_header.h
cfgen -reg -t CppClass templates/cpp_class.cpp
cfgen -reg -t CppTest templates/cpp_test.cpp
# Generate files as you work
cfgen -reg -m classname "Millwork"
cfgen -gen src/Millwork.h CppHeader
cfgen -gen src/Millwork.cpp CppClass
cfgen -gen tests/Millwork_test.cpp CppTestMacros are global, so you update them before generating a context-specific file:
cfgen -del -m classname
cfgen -reg -m classname "Gearbox"
cfgen -gen src/Gearbox.h CppHeaderThis is intentional. CFGen doesn't have per-file variable scoping — it's a simple tool. For complex templating with prompts and branching, you'd reach for something heavier.
Before generating, confirm your macros are what you expect:
cfgen -show -m
cfgen -show -t${cwd}is where you're runningcfgenfrom — your project root, usually.${dir}is the directory of the output file you specified in-gen.
If you run cfgen -gen src/net/Socket.h MyHeader from /home/user/project:
${cwd}→/home/user/project${dir}→./src/net${filename}→Socket.h${stem}→Socket
If the output path's parent directory doesn't exist, CFGen creates one level automatically:
cfgen -gen output/new_dir/file.h MyTemplate # creates output/new_dir/ if neededIf you use ${something} in a template and something isn't a built-in macro and isn't registered, CFGen prints a warning and substitutes an empty string:
[Warning] Macro Not Registered : something | Ignoring Macro
The file still gets generated — the field just ends up blank. Fix it by registering the macro:
cfgen -reg -m something "my value"If you write ${upper:${stem} (missing closing brace), CFGen will detect it:
[Error] Macro braces are not balanced. Missing '${' or '}'.
Check the template file for unclosed ${.
The transform syntax requires the inner expression to be a full ${} token. This will error:
${upper:stem} ← wrong, 'stem' is not wrapped in ${}
Correct form:
${upper:${stem}}
Error message: [Error] Invalid transform format. Use: ${transform:${macro}}
ERROR : Template With This Name Already Exists!
You already registered a template with that name. Either delete it first (cfgen -del -t Name) or pick a different name.
ERROR : Cannot Read The Template Requested!
The template is registered in the record but its stored script file is missing. This can happen if you manually deleted files from the CFGen data directory. Delete the broken registration and re-register:
cfgen -del -t BrokenTemplate
cfgen -reg -t BrokenTemplate path/to/template/file.hsnake inserts an underscore before each uppercase letter and replaces spaces with underscores. If your input is Code File Generator (space-separated words starting with capitals), you'll get code__file__generator — one underscore from the capital, one from the space.
To avoid this, store your macro value in all-lowercase if you plan to snake_case it, or just be aware of the output format.
This is a Windows line endings problem. It happens when the build script (build.sh or build_mac.sh) was saved or edited on Windows, which writes \r\n line endings instead of the Unix \n. The \r character attaches invisibly to the shebang line (#!/usr/bin/env bash\r), and the OS tries to find an interpreter literally named bash\r — which doesn't exist.
The error looks like:
env: bash\r: No such file or directory
or sometimes just:
./build.sh: line 1: $'\r': command not found
Fix on Linux:
sed -i 's/\r$//' build.shFix on macOS (BSD sed requires the '' argument and a slightly different syntax):
sed -i '' $'s/\r$//' scripts/build_mac.shRun the script again after fixing — you only need to do this once per clone. If you want to prevent it from happening again, configure Git to not convert line endings on checkout:
git config core.autocrlf falseOr add a .gitattributes file to the repo root:
*.sh text eol=lf
cfgen -show -trdir # template record file path
cfgen -show -mrdir # macro record file pathYou can open these .map files in any text editor — they're plain text with a simple name.key<=@=>val.value format.
| Tool | Use Case | When to Pick It |
|---|---|---|
| CFGen | Static file generation from registered templates | You want speed, simplicity, and a binary you can drop anywhere |
| Cookiecutter | Full project scaffolding with prompts | You're generating an entire directory structure, not individual files |
| IDE snippets | In-editor template insertion | You never leave your editor and don't need cross-tool consistency |
| Shell scripts | Custom one-off generation | You need conditional logic, loops, or external tool integration |
| Jinja2 / Tera | Template rendering as part of a build pipeline | You need full template language features inside a larger system |
CFGen is the right choice when:
- You're generating individual files (headers, modules, configs) repeatedly across projects.
- You want templates that are editor-agnostic and terminal-native.
- You don't want to install a Python environment or configure a framework just to stamp out a header.
CFGen may not be enough when:
- You need conditional blocks inside templates (
if debug ... else ...). - You're generating entire project trees with nested structure.
- You need interactive prompts or validation during generation.
If you find a bug, have a feature idea, or want to add a transform — contributions are welcome.
A few expectations:
- Keep it C++20. No third-party libraries unless there's a really good reason.
- Match the existing code style. It's consistent — keep it that way.
- If you add a transform, add a test case in
Tests/Input/and verify the output matchesTests/Outputs/. - Error handling should be explicit. Don't silently swallow failures.
- If you're fixing a bug, a minimal repro case in the issue is appreciated before a PR.
To build and test locally:
./build.sh # or build.bat on Windows
./build/cfgen -h # sanity checkCFGen was written by a student who was tired of typing the same header comment block for the fifth time this week. If it saves you that same annoyance, that's exactly what it's for.
And yes if you are wondering, I took help from AI to build this Readme but not with the code
If something's broken, missing, or confusing — open an issue. Feedback from actual use is more useful than anything else.