# Ason - Project Scaffolding Tool Ason is a powerful, lightweight project scaffolding tool built in Go that transforms templates into fully-formed projects. Named after the sacred rattle used in Haitian Vodou ceremonies, Ason catalyzes the transformation of templates into living code through variable substitution and intelligent file processing. The tool uses Pongo2 (a Django-syntax like templating library in Go) for Jinja2-style template rendering, making it familiar to developers who have used Python-based templating systems. The project provides both a CLI tool and a comprehensive Go library API, making it suitable for direct command-line use as well as programmatic integration into other Go applications. Ason emphasizes security with path traversal prevention, thread-safe operations, and proper error handling. It follows the XDG Base Directory specification for template registry storage and preserves binary files during generation without attempting to render them as templates. ## Core Library APIs ### Generator API - Create Projects from Templates The Generator provides template rendering and project generation capabilities with context-aware cancellation support. ```go package main import ( "context" "log" "time" "github.com/madstone-tech/ason/pkg" ) func main() { // Create generator with default Pongo2 engine engine := pkg.NewDefaultEngine() gen, err := pkg.NewGenerator(engine) if err != nil { log.Fatal(err) } // Setup variables for template substitution variables := map[string]interface{}{ "project_name": "my-api", "author": "Alice Johnson", "version": "1.0.0", "features": []string{"auth", "logging", "metrics"}, } // Use context for timeout and cancellation support ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Generate project from template err = gen.Generate( ctx, "/templates/golang-api", // template directory variables, // template variables "/output/my-api-project", // output directory ) if err != nil { log.Fatalf("Generation failed: %v", err) } log.Println("Project generated successfully!") } ``` ### Registry API - Manage Template Collection The Registry provides persistent storage and management of template locations using XDG-compliant directories. ```go package main import ( "fmt" "log" "github.com/madstone-tech/ason/pkg" ) func main() { // Create registry (uses ~/.local/share/ason/registry.toml) reg, err := pkg.NewRegistry() if err != nil { log.Fatal(err) } // Register a new template err = reg.Register( "golang-api", // template name (identifier) "/home/user/templates/go-api", // template path "REST API template with Go", // description ) if err != nil { log.Fatalf("Failed to register: %v", err) } fmt.Println("Template registered successfully") // List all registered templates templates, err := reg.List() if err != nil { log.Fatal(err) } fmt.Println("\nRegistered templates:") for _, tmpl := range templates { fmt.Printf(" Name: %s\n", tmpl.Name) fmt.Printf(" Path: %s\n", tmpl.Path) fmt.Printf(" Description: %s\n", tmpl.Description) fmt.Printf(" Created: %s\n\n", tmpl.Created.Format("2006-01-02 15:04:05")) } // Remove a template from registry err = reg.Remove("golang-api") if err != nil { log.Fatal(err) } fmt.Println("Template removed successfully") } ``` ### Template Rendering - Direct String Rendering Render templates directly without full project generation for testing or one-off use cases. ```go package main import ( "context" "fmt" "log" "github.com/madstone-tech/ason/pkg" ) func main() { engine := pkg.NewDefaultEngine() ctx := context.Background() // Simple variable substitution simpleTemplate := "Hello {{ name }}! Welcome to {{ project }}." simpleVars := map[string]interface{}{ "name": "World", "project": "Ason", } output, err := pkg.RenderWithEngine(ctx, engine, simpleTemplate, simpleVars) if err != nil { log.Fatal(err) } fmt.Println(output) // Output: Hello World! Welcome to Ason. // Complex template with loops and conditionals configTemplate := ` # {{ project_name }} Configuration version: {{ version }} environment: {{ environment }} features: {% for feature in features %} - {{ feature }} {% endfor %} {% if enable_debug %} debug: true log_level: debug {% else %} debug: false log_level: info {% endif %} ` configVars := map[string]interface{}{ "project_name": "MyApp", "version": "2.1.0", "environment": "production", "features": []string{"authentication", "database", "cache", "api"}, "enable_debug": false, } rendered, err := engine.Render(configTemplate, configVars) if err != nil { log.Fatalf("Render failed: %v", err) } fmt.Println(rendered) } ``` ### Custom Engine Implementation - Pluggable Template Systems Implement custom template engines to support alternative template syntaxes beyond Pongo2. ```go package main import ( "context" "fmt" "log" "os" "strings" "github.com/madstone-tech/ason/pkg" ) // SimpleEngine implements basic ${var} substitution type SimpleEngine struct{} func (e *SimpleEngine) Render(template string, ctx map[string]interface{}) (string, error) { result := template for key, value := range ctx { placeholder := fmt.Sprintf("${%s}", key) result = strings.ReplaceAll(result, placeholder, fmt.Sprint(value)) } return result, nil } func (e *SimpleEngine) RenderFile(filePath string, ctx map[string]interface{}) (string, error) { content, err := os.ReadFile(filePath) if err != nil { return "", err } return e.Render(string(content), ctx) } func main() { // Use custom engine instead of default Pongo2 customEngine := &SimpleEngine{} gen, err := pkg.NewGenerator(customEngine) if err != nil { log.Fatal(err) } variables := map[string]interface{}{ "app_name": "MyCustomApp", "version": "1.0.0", "author": "Custom Engine User", } ctx := context.Background() err = gen.Generate(ctx, "./template", variables, "./output") if err != nil { log.Fatalf("Generation with custom engine failed: %v", err) } fmt.Println("Project generated with custom template engine!") } ``` ### Registry with Custom Location - Non-Standard Storage Create a registry at a custom path for testing or non-standard deployment scenarios. ```go package main import ( "fmt" "log" "os" "path/filepath" "github.com/madstone-tech/ason/pkg" ) func main() { // Create custom registry location (e.g., in project directory) projectDir := "/opt/myapp" registryPath := filepath.Join(projectDir, "templates", "registry.toml") // Ensure directory exists if err := os.MkdirAll(filepath.Dir(registryPath), 0755); err != nil { log.Fatal(err) } // Create registry at custom location reg, err := pkg.NewRegistryAt(registryPath) if err != nil { log.Fatal(err) } // Register multiple templates templates := map[string]struct { path string desc string }{ "microservice": { path: "/opt/myapp/templates/microservice", desc: "Microservice template with gRPC", }, "lambda": { path: "/opt/myapp/templates/lambda", desc: "AWS Lambda function template", }, "webapp": { path: "/opt/myapp/templates/webapp", desc: "Full-stack web application", }, } for name, info := range templates { // Create template directories if err := os.MkdirAll(info.path, 0755); err != nil { log.Printf("Warning: couldn't create %s: %v", info.path, err) continue } err = reg.Register(name, info.path, info.desc) if err != nil { log.Printf("Failed to register %s: %v", name, err) continue } fmt.Printf("✓ Registered: %s\n", name) } fmt.Printf("\nRegistry location: %s\n", registryPath) } ``` ### Complete Workflow Example - End-to-End Usage Demonstrates the full lifecycle: registry setup, template registration, and project generation with error handling. ```go package main import ( "context" "errors" "fmt" "log" "os" "path/filepath" "time" "github.com/madstone-tech/ason/pkg" ) func main() { // Setup: Create a temporary registry for this example tmpDir := os.TempDir() registryPath := filepath.Join(tmpDir, "example-registry.toml") templateDir := filepath.Join(tmpDir, "my-template") outputDir := filepath.Join(tmpDir, "generated-project") defer func() { os.RemoveAll(templateDir) os.RemoveAll(outputDir) os.Remove(registryPath) }() // Step 1: Create a simple template directory if err := os.MkdirAll(templateDir, 0755); err != nil { log.Fatal(err) } // Create a template file templateContent := `# {{ project_name }} Created by: {{ author }} Version: {{ version }} Date: {{ date }} ## Features {% for feature in features %} - {{ feature }} {% endfor %} ` templateFile := filepath.Join(templateDir, "README.md") if err := os.WriteFile(templateFile, []byte(templateContent), 0644); err != nil { log.Fatal(err) } // Step 2: Initialize registry and register template fmt.Println("Step 1: Setting up registry...") reg, err := pkg.NewRegistryAt(registryPath) if err != nil { log.Fatal(err) } err = reg.Register("my-template", templateDir, "Example template") if err != nil { log.Fatal(err) } fmt.Println("✓ Template registered") // Step 3: List templates to verify fmt.Println("\nStep 2: Listing templates...") templates, err := reg.List() if err != nil { log.Fatal(err) } for _, tmpl := range templates { fmt.Printf(" - %s: %s\n", tmpl.Name, tmpl.Description) } // Step 4: Generate project from template fmt.Println("\nStep 3: Generating project...") engine := pkg.NewDefaultEngine() gen, err := pkg.NewGenerator(engine) if err != nil { log.Fatal(err) } variables := map[string]interface{}{ "project_name": "Awesome Project", "author": "Development Team", "version": "1.0.0", "date": time.Now().Format("2006-01-02"), "features": []string{"Fast", "Reliable", "Scalable"}, } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() err = gen.Generate(ctx, templateDir, variables, outputDir) if err != nil { // Handle specific error types if errors.Is(err, context.DeadlineExceeded) { log.Fatal("Generation timed out") } log.Fatalf("Generation failed: %v", err) } fmt.Println("✓ Project generated") // Step 5: Verify output fmt.Println("\nStep 4: Verifying output...") generatedFile := filepath.Join(outputDir, "README.md") content, err := os.ReadFile(generatedFile) if err != nil { log.Fatal(err) } fmt.Println("\nGenerated README.md:") fmt.Println("---") fmt.Println(string(content)) fmt.Println("---") fmt.Println("\n✓ Complete workflow executed successfully!") } ``` ## Command-Line Interface ### Create Project from Registry Template Generate a new project using a registered template with variable substitution. ```bash # Register a template first ason register golang-api /templates/golang-api "Go REST API template" # Create project with inline variables ason new golang-api my-service \ --var project_name=my-service \ --var author="Dev Team" \ --var version=1.0.0 # Create project with variable file ason new golang-api my-service --var-file prod.yaml # Variable file (prod.yaml): # project_name: my-service # author: Dev Team # version: 1.0.0 # environment: production # enable_metrics: true # Override file variables with CLI flags ason new golang-api my-service \ --var-file base.yaml \ --var environment=production \ --var enable_debug=false # Preview without creating files ason new golang-api my-service --dry-run --var-file dev.yaml ``` ### Template Registry Management Manage your collection of templates with register, list, and remove operations. ```bash # Register templates ason register microservice ./templates/microservice "gRPC microservice" ason register lambda ./templates/aws-lambda "AWS Lambda function" ason register webapp ./templates/react-app "React web application" # List all registered templates ason list # Output: # lambda: /home/user/templates/aws-lambda # microservice: /home/user/templates/microservice # webapp: /home/user/templates/react-app # Remove a template ason remove microservice # Use local template without registration ason new ./local-template ./output --var name=test ``` ### Shell Autocompletion Setup Enable tab completion for faster CLI interaction with template names and flags. ```bash # Bash - Add to ~/.bashrc ason completion bash > ~/.local/share/bash-completion/completions/ason # Zsh - For Oh My Zsh users mkdir -p ~/.oh-my-zsh/custom/plugins/ason ason completion zsh > ~/.oh-my-zsh/custom/plugins/ason/_ason # Then add 'ason' to plugins in ~/.zshrc # Fish - Add completion ason completion fish > ~/.config/fish/completions/ason.fish # Test completion ason # Shows: new, list, register, remove, validate, completion ason new # Shows registered template names ason -- # Shows available flags ``` ### Validate Template Syntax Check template syntax before using it to catch errors early. ```bash # Validate template directory ason validate ./templates/my-template # Output on success: # ✓ Template is valid # ✓ All files parsed successfully # Output on error: # ✗ Template validation failed # Error in file: config.yaml.tmpl # Line 5: unexpected token '}' ``` ## Integration Patterns and Use Cases Ason serves as a foundation for project scaffolding in development workflows, CI/CD pipelines, and automation systems. Common use cases include creating microservice templates with consistent structure across teams, generating infrastructure-as-code projects for Terraform or CloudFormation, scaffolding API projects with authentication and database boilerplate, and bootstrapping serverless functions with proper configuration. The tool excels in scenarios where project structure consistency matters and teams need to quickly spin up new services following established patterns. For programmatic integration, the library API enables building higher-level tools like project wizards, IDE plugins, or automated deployment systems. The thread-safe design supports concurrent project generation in multi-tenant systems or batch operations. Organizations use Ason to create internal developer portals where teams can select from approved templates and generate projects with pre-configured security policies, monitoring, and CI/CD pipelines. The XDG-compliant registry storage allows templates to be shared across a system while maintaining user-specific overrides, and the context-aware cancellation support makes it reliable for long-running generation tasks in web services or background workers.