Generator System
The Azu CLI generator system is responsible for creating new files, code structures, and project scaffolding. It provides a flexible, extensible framework for code generation using templates and dynamic content.
Overview
The generator system follows a modular architecture that separates concerns between different types of generators while sharing common functionality through a base generator class. Each generator is responsible for creating specific types of files and follows consistent patterns.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Generator System │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Base Gen. │ │ Model Gen. │ │ Endpoint Gen│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Service Gen│ │ Page Gen. │ │ Contract Gen│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Middleware │ │ Component │ │ Validator │ │
│ │ Generator │ │ Generator │ │ Generator │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Template Engine (ECR) │
├─────────────────────────────────────────────────────────────┤
│ File System │
└─────────────────────────────────────────────────────────────┘
Core Components
Base Generator
All generators inherit from the base generator class, which provides common functionality:
abstract class Azu::Generators::Base
getter name : String
getter options : Hash(String, String)
getter attributes : Array(Attribute)
def initialize(@name : String, @options : Hash(String, String) = {} of String => String)
@attributes = parse_attributes(@options["attributes"]? || "")
end
# Abstract methods that must be implemented by subclasses
abstract def generate
abstract def template_path : String
abstract def output_path : String
# Common functionality
def render_template(context : Hash(String, String)) : String
ECR.render(template_path, context)
end
def create_file(path : String, content : String)
FileUtils.mkdir_p(File.dirname(path))
File.write(path, content)
Azu::Logger.info("Created: #{path}")
end
def template_context : Hash(String, String)
{
"name" => @name,
"name_camelcase" => @name.camelcase,
"name_underscore" => @name.underscore,
"name_pluralize" => @name.pluralize,
"name_humanize" => @name.humanize,
"attributes" => @attributes.to_json,
"description" => @options["description"]? || "",
"template" => self.class.name.underscore,
"options" => @options.to_json
}
end
private def parse_attributes(attributes_string : String) : Array(Attribute)
return [] of Attribute if attributes_string.empty?
attributes_string.split(",").map do |attr|
name, type = attr.strip.split(":")
Attribute.new(name, type || "string")
end
end
end
Attribute System
The generator system includes a flexible attribute system for defining model fields:
class Azu::Generators::Attribute
getter name : String
getter type : String
getter options : Hash(String, String)
def initialize(@name : String, @type : String, @options : Hash(String, String) = {} of String => String)
end
def column_type : String
case @type.downcase
when "string", "str"
"String"
when "integer", "int"
"Int32"
when "bigint", "int64"
"Int64"
when "float"
"Float32"
when "double", "decimal"
"Float64"
when "boolean", "bool"
"Bool"
when "text"
"String"
when "datetime", "timestamp"
"Time"
when "date"
"Date"
when "json"
"JSON::Any"
else
"String"
end
end
def validation_rules : Array(String)
rules = [] of String
case @type.downcase
when "string", "str"
rules << "presence: true"
rules << "length: {minimum: 2}" if @name.includes?("name")
when "integer", "int", "bigint", "int64"
rules << "presence: true"
rules << "numericality: {greater_than: 0}" if @name.includes?("id")
when "email"
rules << "presence: true"
rules << "format: /^[^@]+@[^@]+\\.[^@]+$/"
end
rules
end
end
Generator Types
Model Generator
Creates CQL ORM models with proper structure and validations:
class Azu::Generators::Model < Azu::Generators::Base
def generate
create_model_file
create_spec_file unless @options["skip_tests"]? == "true"
end
def template_path : String
"src/templates/generators/model/model.cr.ecr"
end
def output_path : String
"src/models/#{@name.underscore}.cr"
end
private def create_model_file
content = render_template(template_context)
create_file(output_path, content)
end
private def create_spec_file
spec_content = render_template(spec_template_context)
spec_path = "spec/models/#{@name.underscore}_spec.cr"
create_file(spec_path, spec_content)
end
private def spec_template_path : String
"src/templates/generators/model/model_spec.cr.ecr"
end
private def spec_template_context : Hash(String, String)
template_context.merge({
"spec_class" => "#{@name.camelcase}Spec"
})
end
end
Endpoint Generator
Creates HTTP endpoints with contracts and pages:
class Azu::Generators::Endpoint < Azu::Generators::Base
getter actions : Array(String)
def initialize(@name : String, @options : Hash(String, String) = {} of String => String)
super
@actions = parse_actions(@options["actions"]? || "index,show,create,update,destroy")
end
def generate
create_endpoint_files
create_contract_files
create_page_files
create_spec_files unless @options["skip_tests"]? == "true"
end
def template_path : String
"src/templates/generators/endpoint/endpoint.cr.ecr"
end
def output_path : String
"src/endpoints/#{@name.underscore.pluralize}/"
end
private def create_endpoint_files
@actions.each do |action|
create_endpoint_file(action)
end
end
private def create_endpoint_file(action : String)
template_file = "src/templates/generators/endpoint/#{action}_endpoint.cr.ecr"
output_file = "#{output_path}#{action}_endpoint.cr"
context = template_context.merge({
"action" => action,
"action_camelcase" => action.camelcase
})
content = ECR.render(template_file, context)
create_file(output_file, content)
end
private def parse_actions(actions_string : String) : Array(String)
actions_string.split(",").map(&.strip)
end
end
Service Generator
Creates domain services for business logic:
class Azu::Generators::Service < Azu::Generators::Base
def generate
create_service_file
create_spec_file unless @options["skip_tests"]? == "true"
end
def template_path : String
"src/templates/generators/service/service.cr.ecr"
end
def output_path : String
"src/services/#{@name.underscore}_service.cr"
end
private def create_service_file
content = render_template(template_context)
create_file(output_path, content)
end
private def create_spec_file
spec_content = render_template(spec_template_context)
spec_path = "spec/services/#{@name.underscore}_service_spec.cr"
create_file(spec_path, spec_content)
end
private def spec_template_path : String
"src/templates/generators/service/service_spec.cr.ecr"
end
end
Component Generator
Creates interactive UI components with real-time features:
class Azu::Generators::Component < Azu::Generators::Base
getter events : Array(String)
getter websocket : Bool
def initialize(@name : String, @options : Hash(String, String) = {} of String => String)
super
@events = parse_events(@options["events"]? || "")
@websocket = @options["websocket"]? == "true"
end
def generate
create_component_file
create_template_file
create_spec_file unless @options["skip_tests"]? == "true"
end
def template_path : String
"src/templates/generators/component/component.cr.ecr"
end
def output_path : String
"src/components/#{@name.underscore}_component.cr"
end
private def create_component_file
content = render_template(template_context)
create_file(output_path, content)
end
private def create_template_file
template_content = render_template(template_template_context)
template_path = "src/templates/components/#{@name.underscore}.jinja.ecr"
create_file(template_path, template_content)
end
private def parse_events(events_string : String) : Array(String)
return [] of String if events_string.empty?
events_string.split(",").map(&.strip)
end
end
Validator Generator
Creates custom CQL validators:
class Azu::Generators::Validator < Azu::Generators::Base
getter validator_type : String
getter model_name : String?
def initialize(@name : String, @options : Hash(String, String) = {} of String => String)
super
@validator_type = @options["type"]? || "custom"
@model_name = @options["model"]?
end
def generate
create_validator_file
create_spec_file unless @options["skip_tests"]? == "true"
end
def template_path : String
"src/templates/generators/validator/validator.cr.ecr"
end
def output_path : String
"src/validators/#{@name.underscore}_validator.cr"
end
private def create_validator_file
content = render_template(template_context)
create_file(output_path, content)
end
end
Template System
ECR Templates
The generator system uses Crystal's ECR (Embedded Crystal) for template rendering:
# Model template example
class <%= @name_camelcase %> < CQL::Model
table :<%= @name_underscore.pluralize %>
<% @attributes.each do |attr| %>
column :<%= attr.name %>, <%= attr.column_type %>
<% end %>
timestamps
<% @attributes.each do |attr| %>
<% attr.validation_rules.each do |rule| %>
validates :<%= attr.name %>, <%= rule %>
<% end %>
<% end %>
end
Template Variables
Templates have access to a rich set of variables:
# Common template variables
@name # Resource name (e.g., "user")
@name_camelcase # CamelCase (e.g., "User")
@name_underscore # snake_case (e.g., "user")
@name_pluralize # Plural form (e.g., "users")
@name_humanize # Human readable (e.g., "User")
# Generator-specific variables
@attributes # Array of attributes
@description # Resource description
@template # Template type
@options # Generator options
@actions # Array of actions (for endpoints)
@events # Array of events (for components)
@validator_type # Validator type
@model_name # Associated model name
Template Organization
Templates are organized by generator type:
src/templates/generators/
├── model/
│ ├── model.cr.ecr
│ └── model_spec.cr.ecr
├── endpoint/
│ ├── index_endpoint.cr.ecr
│ ├── show_endpoint.cr.ecr
│ ├── create_endpoint.cr.ecr
│ ├── update_endpoint.cr.ecr
│ ├── destroy_endpoint.cr.ecr
│ └── endpoint_spec.cr.ecr
├── service/
│ ├── service.cr.ecr
│ └── service_spec.cr.ecr
├── component/
│ ├── component.cr.ecr
│ └── component_spec.cr.ecr
├── validator/
│ ├── validator.cr.ecr
│ └── validator_spec.cr.ecr
└── middleware/
├── middleware.cr.ecr
└── middleware_spec.cr.ecr
Generator Registry
The generator system includes a registry for managing available generators:
class Azu::Generators::Registry
@@generators = {} of String => Azu::Generators::Base.class
def self.register(name : String, generator_class : Azu::Generators::Base.class)
@@generators[name] = generator_class
end
def self.get(name : String) : Azu::Generators::Base.class?
@@generators[name]?
end
def self.available : Array(String)
@@generators.keys
end
def self.create(name : String, generator_type : String, options : Hash(String, String)) : Azu::Generators::Base
generator_class = get(generator_type)
raise ArgumentError.new("Unknown generator: #{generator_type}") unless generator_class
generator_class.new(name, options)
end
end
# Register generators
Azu::Generators::Registry.register("model", Azu::Generators::Model)
Azu::Generators::Registry.register("endpoint", Azu::Generators::Endpoint)
Azu::Generators::Registry.register("service", Azu::Generators::Service)
Azu::Generators::Registry.register("component", Azu::Generators::Component)
Azu::Generators::Registry.register("validator", Azu::Generators::Validator)
Azu::Generators::Registry.register("middleware", Azu::Generators::Middleware)
Command Integration
Generate Command
The generate command orchestrates the generator system:
class Azu::Commands::Generate < Azu::Commands::Base
getter generator_type : String
getter name : String
getter options : Hash(String, String)
def initialize(@generator_type : String, @name : String, @options : Hash(String, String) = {} of String => String)
end
def call
Azu::Logger.info("Generating #{@generator_type}: #{@name}")
generator = Azu::Generators::Registry.create(@name, @generator_type, @options)
generator.generate
Azu::Logger.info("Generated #{@generator_type} successfully")
rescue ex : ArgumentError
Azu::Logger.error("Generator error: #{ex.message}")
show_available_generators
rescue ex : Exception
Azu::Logger.error("Generation failed: #{ex.message}")
Azu::Logger.debug(ex.backtrace.join("\n"))
end
private def show_available_generators
Azu::Logger.info("Available generators:")
Azu::Generators::Registry.available.each do |generator|
Azu::Logger.info(" - #{generator}")
end
end
end
Error Handling
Validation
The generator system includes comprehensive validation:
class Azu::Generators::Validator
def validate_name(name : String)
unless name.match(/^[a-zA-Z][a-zA-Z0-9_]*$/)
raise ArgumentError.new("Invalid name: #{name}. Must start with a letter and contain only letters, numbers, and underscores.")
end
end
def validate_attributes(attributes : Array(Attribute))
attributes.each do |attr|
validate_attribute(attr)
end
end
def validate_attribute(attr : Attribute)
unless attr.name.match(/^[a-z][a-z0-9_]*$/)
raise ArgumentError.new("Invalid attribute name: #{attr.name}. Must be snake_case.")
end
end
end
File Safety
The generator system ensures safe file operations:
class Azu::Generators::FileManager
def self.safe_create_file(path : String, content : String, force : Bool = false)
if File.exists?(path) && !force
raise FileExistsError.new("File already exists: #{path}. Use --force to overwrite.")
end
FileUtils.mkdir_p(File.dirname(path))
File.write(path, content)
end
def self.backup_file(path : String)
return unless File.exists?(path)
backup_path = "#{path}.backup.#{Time.utc.to_unix}"
File.copy(path, backup_path)
Azu::Logger.info("Backed up: #{path} -> #{backup_path}")
end
end
Performance Considerations
Template Caching
Templates are cached for better performance:
class Azu::Generators::TemplateCache
@@cache = {} of String => String
def self.get(template_path : String) : String
@@cache[template_path]? || load_template(template_path)
end
private def self.load_template(template_path : String) : String
content = File.read(template_path)
@@cache[template_path] = content
content
end
def self.clear
@@cache.clear
end
end
Batch Operations
The generator system supports batch operations for multiple files:
class Azu::Generators::BatchGenerator
def self.generate_multiple(generators : Array(Azu::Generators::Base))
generators.each do |generator|
spawn do
generator.generate
end
end
end
end
Extensibility
Custom Generators
Users can create custom generators by extending the base class:
class CustomGenerator < Azu::Generators::Base
def generate
# Custom generation logic
create_custom_files
end
def template_path : String
"src/templates/custom/custom.cr.ecr"
end
def output_path : String
"src/custom/#{@name.underscore}.cr"
end
private def create_custom_files
# Implementation
end
end
# Register custom generator
Azu::Generators::Registry.register("custom", CustomGenerator)
Template Customization
Users can customize templates by creating their own template files:
# Override default model template
class Azu::Generators::Model < Azu::Generators::Base
def template_path : String
# Check for custom template first
custom_path = "templates/generators/model/model.cr.ecr"
return custom_path if File.exists?(custom_path)
# Fall back to default
"src/templates/generators/model/model.cr.ecr"
end
end
Best Practices
Generator Design
Single Responsibility: Each generator should have one clear purpose
Consistent Interface: Follow the base generator interface
Error Handling: Provide meaningful error messages
Validation: Validate inputs before generation
Documentation: Document generator options and behavior
Template Design
Readable Code: Generate clean, readable code
Consistent Style: Follow Crystal coding conventions
Flexible: Make templates adaptable to different use cases
Well-Documented: Include comments in generated code
Testable: Generate code that's easy to test
Performance
Template Caching: Cache frequently used templates
Batch Operations: Support generating multiple files efficiently
Lazy Loading: Load templates only when needed
Memory Management: Clean up resources after generation
Related Documentation
Template Engine (ECR) - ECR template system details
CLI Framework (Topia) - Command-line interface framework
Configuration System - Configuration management
Commands Reference - Generate command documentation
Last updated