Documentation home · Project README · User manual · API reference
cli-fp-gen turns a small JSON command specification into a native Free
Pascal project. It creates the program entry point, command registry, and
user-owned command classes, while keeping generated and hand-written code
separate.
Use the generator when you want a working project layout immediately or expect the command tree to evolve. For a single-file integration into an existing program, the manual quick start may be simpler.
Run these commands from the cli-fp repository root.
fpc -Futools/cli-fp-gen/src tools/cli-fp-gen/cli_fp_gen.lpr
./tools/cli-fp-gen/cli_fp_gen init ./build-temp/myapp --name myapp
cd build-temp/myapp
fpc -Fu../../src -Fu./src -Fu./src/generated -Fu./src/commands ./src/Myapp.lpr
./src/Myapp greet --helpfpc "-Futools\cli-fp-gen\src" .\tools\cli-fp-gen\cli_fp_gen.lpr
.\tools\cli-fp-gen\cli_fp_gen.exe init .\build-temp\myapp --name myapp
Set-Location .\build-temp\myapp
fpc "-Fu..\..\src" "-Fu.\src" "-Fu.\src\generated" "-Fu.\src\commands" .\src\Myapp.lpr
.\src\Myapp.exe greet --helpYou have now compiled both the generator and a generated application with FPC.
Next, edit src/commands/Myapp_Command_Greet.pas and implement its Execute
method. Change command metadata in clifp.json, then regenerate:
cli-fp-gen generate --project .
Use the platform-specific generator path shown above if it is not installed on your command path.
The generated project uses ordinary Object Pascal source files:
| File | Role |
|---|---|
src/Myapp.lpr |
Program entry point passed to fpc |
src/generated/*.pas |
Generated units that register the command tree and its parameters |
src/commands/*.pas |
User-owned command classes where Execute does the work |
clifp.json |
Language-neutral source of truth for command metadata |
FPC compiles all of these units into one native executable. The four -Fu
arguments in the build command expose, respectively, the cli-fp framework,
the program units, generated units, and command implementations to the
compiler.
Generation currently targets command-line projects. It does not install a
Lazarus design-time wizard; Lazarus users may open the generated .lpr as a
project and use the runtime package separately.
- Tool source:
tools/cli-fp-gen/
cli-fp-gen init <target-dir> [--name <app-name>] [--version <x.y.z>] [--dry-run] [--force]
cli-fp-gen generate [--project <dir-or-spec-file>] [--dry-run] [--force]
cli-fp-gen add command <name> [--parent <cmd/path>] [--description <text>] [--project <dir-or-spec-file>] [--dry-run] [--force]
cli-fp-gen remove command <cmd/path> [--cascade] [--project <dir-or-spec-file>] [--dry-run] [--force]
Generated projects use clifp.json as the source of truth.
Example:
{
"schemaVersion": 1,
"app": {
"name": "myapp",
"version": "0.1.0",
"programFile": "src/Myapp.lpr"
},
"rootCommand": {
"description": "Run the default greeting",
"parameters": [
{
"kind": "string",
"short": "-n",
"long": "--name",
"description": "Name to greet",
"required": false,
"default": "World",
"allowedValues": ""
}
]
},
"commands": [
{
"name": "greet",
"description": "Say hello",
"parent": "",
"parameters": [
{
"kind": "string",
"short": "-n",
"long": "--name",
"description": "Name to greet",
"required": false,
"default": "World",
"allowedValues": ""
}
]
}
]
}rootCommand is optional in schema version 1. When present, the generated
application can execute without a named command:
Myapp
Myapp --name Gus
The object accepts description and parameters; it deliberately has no
name or parent. Generation creates
src/commands/<App>_RootCommand.pas as a user-owned implementation stub and
wires it into the three-argument CreateCLIApplication overload. Named
commands in commands remain available alongside it.
Removing rootCommand restores the traditional command-first generated
application. The former root stub is retained because user-owned files are
never removed as stale generated output.
Supported kind values:
stringintegerfloatflagbooleanpathenum(requiresallowedValues)datetimearraypasswordurl
clifp.json: project source of truth;initrefuses to replace an existing spec unless--forceis suppliedsrc/generated/*.pas: generator-owned, overwritten ongeneratesrc/generated/.clifp-manifest.json: generator-owned manifest for cleanupsrc/commands/*.pas: user-owned command and optional root-command stubs, created once and not overwritten unless--forcesrc/*.lpr: generator-owned by the current generator
The manifest is used only to remove stale generator-owned files. Before
deleting a manifest entry, cli-fp-gen verifies that its normalized path is
inside the project directory and that no child path component is a symbolic
link or Windows reparse point (including directory junctions). If either check
fails, generation stops and reports the unsafe path.
This protection is deliberately conservative: a stale generated file reached
through a link is not deleted, even when that link points to another location
inside the project. Edit or remove the unexpected manifest entry or link, then
run generate again.
--force allows overwrite operations that normally protect existing files,
including replacement of an existing spec during init and regeneration of
user-owned command stubs. It does not bypass manifest path safety checks.
<project>/
clifp.json
src/
<App>.lpr
commands/
<App>_RootCommand.pas # only when rootCommand is configured
<App>_Command_*.pas
generated/
<App>_CommandRegistry_Generated.pas
.clifp-manifest.json
From the generated project directory, compile with the framework source path plus local generated/unit paths.
fpc -Fu../../src -Fu./src -Fu./src/generated -Fu./src/commands ./src/Myapp.lprfpc "-Fu..\..\src" "-Fu.\src" "-Fu.\src\generated" "-Fu.\src\commands" .\src\Myapp.lprAdjust the first -Fu path (../../src or ..\..\src) to point at the
cli-fp framework src/ directory.
The repository includes focused codegen checks under tests/codegen/:
run_unit_tests.shrun_golden_test.shrun_compile_smoke.shrun_ops_test.sh
Use the Windows-native verification script from the repository root:
powershell -ExecutionPolicy Bypass -File .\tests\codegen\run_all_tests.ps1This script compiles cli-fp-gen, runs the focused unit tests,
verifies golden output, compiles a generated app, and checks init / generate
/ add command / remove command behavior plus overwrite and path validation
guards.
GitHub Actions runs the focused suite on Linux and Windows for pushes and pull requests that change the generator, its fixtures, the framework source, or the workflow. The workflow can also be started manually.
The operations tests include manifest cleanup escape attempts through a Unix symbolic link and a Windows directory junction. They assert that the generator fails safely and leaves the external file untouched.
The generator is split into small units with one main responsibility:
CliFpGen.App: command-line parsing and command dispatchCliFpGen.Generate: project operations and generation workflowCliFpGen.Model: in-memory project and parameter typesCliFpGen.SpecIO:clifp.jsonloading and savingCliFpGen.Validate: semantic and path validationCliFpGen.Naming: Pascal identifiers, unit names, and command pathsCliFpGen.Renderer: Pascal source renderingCliFpGen.Filesystem: managed writes, deletions, and dry-run behaviorCliFpGen.Manifest: generated-file tracking and safe stale-file cleanup
TProjectSpec owns its root-command specification and named commands. The
root-command specification and each TCommandSpec own their parameters.
When parsing JSON, construct an object completely before transferring it to
its owning list. If parsing raises an exception before that transfer, free the
partially constructed object in the same routine.
Use this checklist when the framework gains a new parameter type:
- Add the enum value and both text mappings in
CliFpGen.Model. - Add any kind-specific defaults or semantic rules in
CliFpGen.Validate. - Render the matching framework registration call in
CliFpGen.Renderer.RenderParameterCall. - If the kind needs new JSON fields, add them symmetrically to load and save
in
CliFpGen.SpecIO; update the project-spec example above. - Add the kind to
tests/codegen-fixtures/golden-basic/clifp.jsonand update the expected registry intests/codegen-golden/golden-basic/. - Add focused validation or parsing tests when the kind has unique rules.
- Update the supported-kind lists here and in the root README.
- Run all Linux scripts under
tests/codegen/and the Windowsrun_all_tests.ps1script. The compile smoke test confirms that the generated call still matches the current framework units insrc/.
- Commands are defined in a flat list with
parentpaths (slash-delimited, e.g.repo/remote). app.programFilemust stay project-relative undersrc/and point to an.lprfile.remove commanddeletes command entries fromclifp.json; use--cascadeto remove a command subtree.- Default command stubs automatically show help when they have subcommands at runtime.
- This avoids stale stub behavior when a command later becomes a command group.
- Parameter registrations and command descriptions are generated in the registry unit (not user stubs), so editing
clifp.jsonand re-runninggenerateupdates metadata without overwriting user code.