Stop copy-pasting boilerplate. Register a template once, generate it anywhere.
CFGen is a lightweight CLI tool that generates files from templates you define. Write a template once, register it, then call cfgen -gen to stamp out a fresh copy — with 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, paste wrong, or forget to change from the last file. CFGen fixes that at the root.
- Why This Tool Exists
- Key Features
- How It Works
- Installation
- Quick Start: Real-World Tutorial
- Command Reference
- Template System
- Built-in Default Macros
- Runtime Variables
- Template Transforms
- Advanced Tips
- Common Pitfalls & Debugging
- Comparison / Alternatives
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 just to stamp out a header.
CFGen is a single static binary. Register a template. Optionally define named macros. Generate. No runtime, no config, no YAML soup.
- 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, etc.) that get substituted into any template on generation. - Runtime variables — Declare
@registervariables inside a template and pass values with-p<...>at generation time. No need to update the macro registry between files. - Built-in default macros —
${filename},${date},${year},${iso_date},${cwd}, and more resolve automatically without any setup. - Transform functions — Apply
upper,lower,snake,camel,pascal,title,slug,trim, andreversedirectly in template syntax. - Chainable / nested transforms —
${upper:${snake:${classname}}}works exactly as you'd expect. - Version checker —
cfgen -versionshows your installed version and checks GitHub for updates. - Zero runtime dependencies — The compiled binary is standalone. No interpreters, no package managers at runtime.
- Cross-platform — Works on Windows, Linux, and macOS.
CFGen is a two-registry tool with a template engine.
[ Template Registry ] [ Macro Registry ]
name → script file name → value
| |
└──────────┬───────────────────┘
▼
[ Generator ]
reads template content
processes @register directives
scans for ${...} tokens
resolves macros + runtime vars + 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 strips and processes any
@registerdirectives, binding runtime values from-p<...>. - It scans the content for
${...}expressions. - Each expression resolves — first against built-in macros, then runtime variables, then registered macros.
- Any transform (
upper:,snake:, etc.) is applied to the resolved value. - The processed content is written to the output file.
Templates and macros are stored in your system's standard app data directory:
| Platform | Location |
|---|---|
| Windows | %APPDATA%\CFGen\ |
| Linux | ~/.config/CFGen/ |
| macOS | ~/Library/Application Support/CFGen/ |
Grab the latest binary from the Releases page.
| Platform | File |
|---|---|
| Windows | cfgen-windows |
| Linux | cfgen-linux |
| macOS | cfgen-macos |
Download, rename to cfgen (or cfgen.exe on Windows), and put it on your PATH. No compiler needed.
On Linux/macOS, mark it executable first:
chmod +x cfgenmacOS — Gatekeeper quarantine: macOS flags binaries downloaded from the internet. If you see zsh: operation not permitted: ./cfgen, remove the quarantine flag:
xattr -d com.apple.quarantine ./cfgen
# If that doesn't work (some Apple Silicon Macs are stricter):
xattr -c ./cfgenYou only need to do this once.
You need a C++20-capable compiler: GCC 10+ or Clang 12+. Verify with g++ --version.
Windows:
git clone https://github.com/AmashOnBlitz/cfgen.git
cd cfgen/scripts/
build.batExecutable: build\cfgen.exe
Linux:
git clone https://github.com/AmashOnBlitz/cfgen.git
cd cfgen/
chmod +x scripts/build_linux.sh
./scripts/build_linux.shExecutable: build/cfgen
macOS:
git clone https://github.com/AmashOnBlitz/cfgen.git
cd cfgen/
chmod +x scripts/build_mac.sh
./scripts/build_mac.shThe script auto-detects GCC or Clang.
Windows line endings issue: If the build script shows
env: bash\r: No such file or directory, the file has Windows-style line endings. Fix it withsed -i 's/\r$//' scripts/build_linux.sh(Linux) orsed -i '' $'s/\r$//' scripts/build_mac.sh(macOS). Run once per clone.
To run cfgen from any terminal without the full path, add its directory to your PATH.
Windows (permanent, PowerShell):
[System.Environment]::SetEnvironmentVariable(
"PATH",
$env:PATH + ";C:\path\to\cfgen\build",
[System.EnvironmentVariableTarget]::User
)Linux / macOS (permanent):
# bash
echo 'export PATH="$PATH:/path/to/cfgen/build"' >> ~/.bashrc && source ~/.bashrc
# zsh
echo 'export PATH="$PATH:/path/to/cfgen/build"' >> ~/.zshrc && source ~/.zshrcThis walkthrough covers the two main use cases: static macros for things that never change (your name, your company), and runtime variables for things that change every file (class name, project name).
These are things you type in every file header. Register them once and never think about them again.
cfgen -reg -m author "Ada Lovelace"
cfgen -reg -m company "Babbage & Co."
cfgen -reg -m license "MIT"
cfgen -reg -m email "ada@babbage.co"author and company are perfect candidates for registered macros because they're the same across every project you ever work on.
Save this as cpp_header.h:
// =======================================================
// File : ${filename}
// Author : ${author}
// Company : ${company}
// License : ${license}
// Created : ${date}
// =======================================================
@register ClassName
@register ProjectName
#ifndef ${upper:${snake:${ClassName}}}_H
#define ${upper:${snake:${ClassName}}}_H
namespace ${snake:${ProjectName}}
{
class ${pascal:${ClassName}}
{
public:
${pascal:${ClassName}}();
~${pascal:${ClassName}}();
};
} // namespace ${snake:${ProjectName}}
#endif // ${upper:${snake:${ClassName}}}_HNotice the split:
${author}and${company}come from your registered macros — you set them once and they appear in every generated file automatically.ClassNameandProjectNameare runtime variables declared with@register. You pass them fresh each time you generate, right on the command line.
cfgen -reg -t CppHeader cpp_header.h# Generate a file for a networking class in your "SocketLib" project
cfgen -gen src/TcpSocket.h CppHeader "-p<TcpSocket,SocketLib>"
# Generate another for a different class, no macro juggling needed
cfgen -gen src/UdpSocket.h CppHeader "-p<UdpSocket,SocketLib>"The -p<ClassName,ProjectName> values bind to @register declarations in order. The output for TcpSocket.h:
// =======================================================
// File : TcpSocket.h
// Author : Ada Lovelace
// Company : Babbage & Co.
// License : MIT
// Created : 2026-02-25
// =======================================================
#ifndef TCP_SOCKET_H
#define TCP_SOCKET_H
namespace socket_lib
{
class Tcpsocket
{
public:
Tcpsocket();
~Tcpsocket();
};
} // namespace socket_lib
#endif // TCP_SOCKET_Hauthor and company filled in from your stored macros. ClassName and ProjectName came from the command line — no registry updates required between files.
All commands follow:
cfgen <command> [sub-command] [arguments]
cfgen -hcfgen -versionShows your installed version, fetches the latest version from GitHub, and tells you whether you're up to date, behind, or running a dev build ahead of the latest release. Requires an internet connection for the remote check.
CFGen - Code/File Generator
---------------------------
Installed Version : 2.0.0
Checking for updates...
Latest Version : 2.0.0
Status : You are using the latest version !
Version checking is not automatic. This decision was made intentionally to avoid unnecessary network calls and startup delays during small or repetitive generation tasks. Developers can invoke version checks explicitly when needed.
cfgen -reg -t <TemplateName> <path/to/file>CFGen copies the file into its own internal storage under that name. The original file is no longer referenced after registration — you can move or delete it.
cfgen -reg -t CppHeader templates/cpp_header.h
cfgen -reg -t PythonMod ~/boilerplate/py_module.py
cfgen -reg -t GitIgnore .gitignore
cfgen -reg -t CMakeLists cmake/CMakeLists.txtcfgen -reg -m <name> <value>Values with spaces must be quoted. Names are case-sensitive.
cfgen -reg -m author "Ada Lovelace"
cfgen -reg -m company "Babbage & Co."
cfgen -reg -m license "MIT"
cfgen -reg -m email "ada@babbage.co"# Standard generation
cfgen -gen <output-path> <TemplateName>
# With runtime variables
cfgen -gen <output-path> <TemplateName> "-p<val1,val2,...>"# No runtime variables needed
cfgen -gen src/config.json JsonConfig
# With runtime variables
cfgen -gen src/Engine.h CppHeader "-p<Engine,GameLib>"The output directory is created automatically if it doesn't exist (one level deep).
cfgen -show -tcfgen -show -mcfgen -del -t <TemplateName>cfgen -del -m <name>cfgen -show -trdir # where templates.map is stored
cfgen -show -mrdir # where macros.map is storedA template is any plain text file — .h, .cpp, .py, .json, .md, anything. Inside it, you write ${macroname} wherever you want a value substituted. Everything else is kept exactly as-is.
${macroname}
Place @register <varname> directives at the top of your template to declare variables that are provided at generation time:
@register ClassName
@register ModuleName
These lines are stripped from the output entirely. The variables they declare are populated from the -p<...> flag in order of appearance.
- Template names:
PascalCaseorsnake_case— e.g.,CppHeader,python_module - Macro names:
lowercase— e.g.,author,company - Runtime variable names:
PascalCaserecommended — e.g.,ClassName,ProjectName
These resolve automatically at generation time — no registration needed.
| 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 |
Runtime variables let you pass per-generation values directly on the command line, without touching the macro registry. They're ideal for things that change with every file: class names, module names, feature flags, version strings.
1. Declare in the template with @register:
@register ClassName
@register ModuleName
@register FeatureFlag
2. Pass values at generation time with -p<...>:
cfgen -gen src/Parser.h CppHeader "-p<Parser,Lexer,true>"Values bind to @register declarations in order. So ClassName=Parser, ModuleName=Lexer, FeatureFlag=true.
3. Use in the template like any other macro:
class ${pascal:${ClassName}} : public ${ModuleName}Base
{
bool m_enabled { ${lower:${FeatureFlag}} };
};| Registered Macros | Runtime Variables | |
|---|---|---|
| Set with | cfgen -reg -m name value |
-p<val1,val2> at generation time |
| Persist | Yes — until deleted | No — per-generation only |
| Best for | Author, company, license, email | Class name, project name, module name |
| Template syntax | ${author} |
${ClassName} (after @register ClassName) |
Template file cpp_class.h:
// Author : ${author} ← from macro registry, always the same
// Company : ${company} ← from macro registry, always the same
// File : ${filename} ← built-in, resolved automatically
// Created : ${date} ← built-in, resolved automatically
@register ClassName
@register BaseClass
#ifndef ${upper:${snake:${ClassName}}}_H
#define ${upper:${snake:${ClassName}}}_H
class ${pascal:${ClassName}} : public ${pascal:${BaseClass}}
{
public:
${pascal:${ClassName}}();
virtual ~${pascal:${ClassName}}() = default;
};
#endifUsage:
# Generate three different classes without touching the registry once
cfgen -gen src/Button.h CppClass "-p<Button,Widget>"
cfgen -gen src/TextInput.h CppClass "-p<TextInput,Widget>"
cfgen -gen src/Checkbox.h CppClass "-p<Checkbox,Widget>"If you provide fewer values than @register declarations, CFGen prints a diagnostic and aborts:
Runtime Variables:
------------------
ClassName ---> Parser
ModuleName ---> ?
[Error] Template requires 2 runtime variables, but only 1 were provided.
If you provide more values than declared, the extras are ignored with a warning.
${transform:${macroname}}
${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 |
Nest transforms to apply multiple operations. The inner expression resolves first:
${upper:${snake:${classname}}}
For classname = PressureTest → snake → pressure_test → upper → PRESSURE_TEST.
This is valid syntax:
${upper:${snake:${classname}}} ✓
This is not:
${upper:snake:${classname}} ✗ (ambiguous parse — will error)
#ifndef ${upper:${snake:${ClassName}}}_H
#define ${upper:${snake:${ClassName}}}_H
// ...
#endif // ${upper:${snake:${ClassName}}}_HWith ClassName = PressureTest:
#ifndef PRESSURE_TEST_H
#define PRESSURE_TEST_H
// ...
#endif // PRESSURE_TEST_H# 1. Register identity macros once (do this when you first install CFGen)
cfgen -reg -m author "Ada Lovelace"
cfgen -reg -m company "Babbage & Co."
cfgen -reg -m license "MIT"
# 2. Register your 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
cfgen -reg -t PythonMod templates/py_module.py
# 3. Generate files as you work — runtime vars for everything per-file
cfgen -gen src/Millwork.h CppHeader "-p<Millwork,MachineLib>"
cfgen -gen src/Millwork.cpp CppClass "-p<Millwork,MachineLib>"
cfgen -gen tests/Millwork_test.cpp CppTest "-p<Millwork>"No registry updates between files. Everything that's unique to each file goes through -p<...>.
Use registered macros for values that are the same across all your work:
- Your full name, email, company name, default license
Use runtime variables for values that change with each file you generate:
- Class name, module name, base class, feature flag, version string, project name
If a value is the same 95% of the time but sometimes differs, register it as a macro and override it via a runtime variable in templates that need the flexibility.
Before generating, confirm macros are what you expect:
cfgen -show -m
cfgen -show -t${cwd} is where you're running cfgen from — your project root, usually. ${dir} is the directory of the output file you specified.
Running 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 it automatically:
cfgen -gen output/new_dir/file.h MyTemplate # creates output/new_dir/ if neededIf ${something} is in a template and something isn't a built-in macro, runtime variable, or registered macro, CFGen warns and substitutes an empty string:
[Warning] Macro Not Registered : something | Ignoring Macro
Fix it by registering the macro or passing the value as a runtime variable.
If a template has more @register declarations than values provided with -p<...>:
[Error] Template requires 3 runtime variables, but only 1 were provided.
Check the count of @register lines in your template and match them in -p<val1,val2,val3>.
If you pass more values than @register declarations:
[Error] Too many runtime parameters provided.
Some values passed with -p<...> were not used.
CFGen warns but still generates the file using only the values it could bind.
[Error] Macro braces are not balanced. Missing '${' or '}'.
Check the template for any unclosed ${.
The inner expression must be a full ${} token:
${upper:stem} ← wrong
${upper:${stem}} ← correct
ERROR : Template With This Name Already Exists!
Delete first or pick a different name: cfgen -del -t Name
snake inserts an underscore before each uppercase letter and replaces spaces with underscores. Input Code File Generator → code__file__generator — one underscore from the capital, one from the space. Store space-separated values in all-lowercase if you plan to snake them.
cfgen -show -trdir # template record file path
cfgen -show -mrdir # macro record file pathThe .map files are plain text with a name.key<=@=>val.value format — readable in any editor.
| 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 inside a build pipeline | You need a full template language as part of 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.
Contributions are welcome. A few expectations:
- Keep it C++20. No third-party libraries without a strong reason.
- Match the existing code style.
- If you add a transform, add a test case in
Tests/Input/and verify 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 — I took help from AI to write this README, but not with the code otherwise I would have have got someting TRASH!
If something's broken, missing, or confusing — open an issue. Feedback from actual use is more useful than anything else.