Appearance
Nested Generators
Loci generators are designed to be composable. A generator can declare, configure, and execute other generators as part of its own workflow.
There are two primary ways to nest generators:
- Declarative composition using the
genmacro (recommended). - Direct runtime invocation for manual control.
Declarative Nesting with gen
The gen macro declares a nested generator as a dependency of the parent. This ensures that the nested generator is parsed and validated before execution.
ruby
gen :other_generatorBy default, all inputs from the parent are passed through to the child generator.
The skip_flag Option
You can make nested generator optional by using the skip_flag option. This automatically adds a CLI flag based on the provided symbol.
ruby
class MyGenerator < Loci::BaseGenerator
# Adds a `--skip-model` flag to the CLI
gen :model_generator, skip_flag: :model
endIf the user passes --skip-model in the terminal, Loci will bypass validation and execution of that specific nested generator.
Customizing Parameters
To modify how parameters are passed to the child, provide a block. This block yields the generator class and the parsed parameters (matched against the parent schema but not yet transformed and type-casted). Note that the child is initialized using keyword arguments.
ruby
gen :other_generator do |g, params|
g.new(**params, user: params.user.titleize, address: "Static Address")
endWorking with Raw CLI Arguments
If you need full control over the CLI layer (useful when generators follow different positional argument conventions) you can operate on the raw arguments and options. Note that args and opts are passed as first and second positional argument.
ruby
gen :other_generator do |g, _, args, opts|
args[0] = "Jane Doe"
opts[:address] = ["Override Address"]
# Note: Raw option values are always arrays
# e.g., `--verbose` becomes { verbose: [true] }
g.new(args, opts)
endThis approach bypasses parameter normalization and interacts directly with the command-line parser.
Multiple Invocations
The same generator can be declared multiple times. Each instance is independently validated, configured, and executed. This is particularly useful for generating sets of related files:
ruby
class Rails::ControllerGenerator < Loci::BaseGenerator
gen "rails:view", skip_flag: :index do |g|
g.new template: "index"
end
gen "rails:view", skip_flag: :show do |g|
g.new template: "show"
end
endRuntime Invocation
For edge cases where declarative logic isn't sufficient, you can initialize and execute generators manually within your logic.
ruby
class MyGenerator < Loci::BaseGenerator
def call
AnotherGenerator.new(name: "John Doe").call
end
endThe Runtime Invocation Trade-off
The primary difference lies in when errors are caught:
- Declarative (
gen): Performs a "pre-flight" check. Loci validates all nested parameters before any execution begins. If something is wrong, nothing runs. - Runtime (
.new): Validation is deferred. The parent generator starts running immediately and only fails when it hits the nested call. This can leave your generator in a "half-finished" state if an error occurs mid-stream.