feat(templ): ✨ added templ to command line that reads go template and outputs code Reviewed-on: #1 Co-authored-by: Hein <hein.puth@gmail.com> Co-committed-by: Hein <hein.puth@gmail.com>
Template Writer
Custom template-based writer for RelSpec that allows users to generate any output format using Go text templates.
Overview
The template writer provides a powerful and flexible way to transform database schemas into any desired format. It supports multiple execution modes and provides 80+ template functions for data transformation.
For complete user documentation, see: /docs/TEMPLATE_MODE.md
Architecture
Package Structure
pkg/writers/template/
├── README.md # This file
├── writer.go # Core writer with entrypoint mode logic
├── template_data.go # Data structures passed to templates
├── funcmap.go # Template function registry
├── string_helpers.go # String manipulation functions
├── type_mappers.go # SQL type conversion (delegates to commontypes)
├── filters.go # Database object filtering
├── formatters.go # JSON/YAML formatting utilities
├── loop_helpers.go # Iteration and collection utilities
├── safe_access.go # Safe map/array access functions
└── errors.go # Custom error types
Dependencies
pkg/commontypes- Centralized type mappings for Go, TypeScript, Java, Python, Rust, C#, PHPpkg/reflectutil- Reflection utilities for safe type manipulationpkg/models- Database schema models
Writer Interface Implementation
Implements the standard writers.Writer interface:
type Writer interface {
WriteDatabase(db *models.Database) error
WriteSchema(schema *models.Schema) error
WriteTable(table *models.Table) error
}
Execution Modes
The writer supports four execution modes via WriterOptions.Metadata["mode"]:
| Mode | Data Passed | Output | Use Case |
|---|---|---|---|
database |
Full database | Single file | Reports, documentation |
schema |
One schema at a time | File per schema | Schema-specific docs |
script |
One script at a time | File per script | Script processing |
table |
One table at a time | File per table | Model generation |
Configuration
Writer is configured via WriterOptions.Metadata:
metadata := map[string]interface{}{
"template_path": "/path/to/template.tmpl", // Required
"mode": "table", // Default: "database"
"filename_pattern": "{{.Name}}.ts", // Default: "{{.Name}}.txt"
}
Template Data Structure
Templates receive a TemplateData struct:
type TemplateData struct {
// Primary data (one populated based on mode)
Database *models.Database
Schema *models.Schema
Script *models.Script
Table *models.Table
// Parent context
ParentSchema *models.Schema
ParentDatabase *models.Database
// Pre-computed views
FlatColumns []*models.FlatColumn
FlatTables []*models.FlatTable
FlatConstraints []*models.FlatConstraint
FlatRelationships []*models.FlatRelationship
Summary *models.DatabaseSummary
// User metadata
Metadata map[string]interface{}
}
Function Categories
String Utilities (string_helpers.go)
Case conversion, pluralization, trimming, splitting, joining
Type Mappers (type_mappers.go)
SQL type conversion to 7+ programming languages (delegates to pkg/commontypes)
Filters (filters.go)
Database object filtering by pattern, type, constraints
Formatters (formatters.go)
JSON/YAML serialization, indentation, escaping, commenting
Loop Helpers (loop_helpers.go)
Enumeration, batching, reversing, sorting, grouping (uses pkg/reflectutil)
Safe Access (safe_access.go)
Safe map/array access without panics (uses pkg/reflectutil)
Adding New Functions
To add a new template function:
-
Implement the function in the appropriate file:
// string_helpers.go func ToScreamingSnakeCase(s string) string { return strings.ToUpper(ToSnakeCase(s)) } -
Register in funcmap.go:
func BuildFuncMap() template.FuncMap { return template.FuncMap{ // ... existing functions "toScreamingSnakeCase": ToScreamingSnakeCase, } } -
Document in /docs/TEMPLATE_MODE.md
Error Handling
Custom error types in errors.go:
TemplateLoadError- Template file not found or unreadableTemplateParseError- Invalid template syntaxTemplateExecuteError- Error during template execution
All errors wrap the underlying error for context.
Testing
# Run tests
go test ./pkg/writers/template/...
# Test with example data
cat > test.tmpl << 'EOF'
{{ range .Database.Schemas }}
Schema: {{ .Name }} ({{ len .Tables }} tables)
{{ end }}
EOF
relspec templ --from json --from-path schema.json --template test.tmpl
Multi-file Output
For multi-file modes, the writer:
- Iterates through items (schemas/scripts/tables)
- Creates
TemplateDatafor each item - Executes template with item data
- Generates filename using
filename_patterntemplate - Writes output to generated filename
Output directory is created automatically if it doesn't exist.
Filename Pattern Execution
The filename pattern is itself a template:
// Pattern: "{{.Schema}}/{{.Name | toCamelCase}}.ts"
// For table "user_profile" in schema "public"
// Generates: "public/userProfile.ts"
Available in pattern template:
.Name- Item name (schema/script/table).Schema- Schema name (for scripts/tables)- All template functions
Example Usage
As a Library
import (
"git.warky.dev/wdevs/relspecgo/pkg/writers"
"git.warky.dev/wdevs/relspecgo/pkg/writers/template"
)
// Create writer
metadata := map[string]interface{}{
"template_path": "model.tmpl",
"mode": "table",
"filename_pattern": "{{.Name}}.go",
}
opts := &writers.WriterOptions{
OutputPath: "./models/",
PackageName: "models",
Metadata: metadata,
}
writer, err := template.NewWriter(opts)
if err != nil {
// Handle error
}
// Write database
err = writer.WriteDatabase(db)
Via CLI
relspec templ \
--from pgsql \
--from-conn "postgres://localhost/mydb" \
--template model.tmpl \
--mode table \
--output ./models/ \
--filename-pattern "{{.Name | toPascalCase}}.go"
Performance Considerations
- Template Parsing - Template is parsed once in
NewWriter(), not per execution - Reflection - Loop and safe access helpers use reflection; cached where possible
- Pre-computed Views -
FlatColumns,FlatTables, etc. computed once per data item - File I/O - Multi-file mode creates directories as needed
Future Enhancements
Potential improvements:
- Template caching for filename patterns
- Parallel template execution for multi-file mode
- Template function plugins
- Custom function injection via metadata
- Template includes/partials support
- Dry-run mode to preview filenames
- Progress reporting for large schemas
Contributing
When adding new features:
- Follow existing patterns (see similar functions)
- Add to appropriate category file
- Register in
funcmap.go - Update
/docs/TEMPLATE_MODE.md - Add tests
- Consider edge cases (nil, empty, invalid input)
See Also
- User Documentation - Complete template function reference
- Common Types Package - Centralized type mappings
- Reflect Utilities - Reflection helpers
- Models Package - Database schema models
- Go Template Docs - Official Go template documentation