Files
relspecgo/cmd/relspec/templ.go
Hein 8c602e3db0
Some checks failed
CI / Lint (push) Successful in -27m59s
CI / Test (1.25) (push) Successful in -27m46s
CI / Test (1.24) (push) Failing after 59s
CI / Build (push) Successful in -28m14s
Integration Tests / Integration Tests (push) Failing after -28m16s
Release / Build and Release (push) Successful in 1m1s
Added go text template writier (#1)
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>
2026-01-03 19:05:53 +00:00

168 lines
5.9 KiB
Go

package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
wtemplate "git.warky.dev/wdevs/relspecgo/pkg/writers/template"
)
var (
templSourceType string
templSourcePath string
templSourceConn string
templTemplatePath string
templOutputPath string
templSchemaFilter string
templMode string
templFilenamePattern string
)
var templCmd = &cobra.Command{
Use: "templ",
Short: "Apply custom templates to database schemas",
Long: `Apply custom Go text templates to database schemas with flexible execution modes.
The templ command allows you to transform database schemas using custom Go text
templates. It supports multiple execution modes for different use cases:
Execution Modes:
database Execute template once for entire database (single output file)
schema Execute template once per schema (one file per schema)
script Execute template once per script (one file per script)
table Execute template once per table (one file per table)
Supported Input Formats:
dbml, dctx, drawdb, graphql, json, yaml, gorm, bun, drizzle, prisma, typeorm, pgsql
Template Functions:
String utilities: toUpper, toLower, toCamelCase, toPascalCase, toSnakeCase, toKebabCase,
pluralize, singularize, title, trim, split, join, replace
Type conversion: sqlToGo, sqlToTypeScript, sqlToJava, sqlToPython, sqlToRust,
sqlToCSharp, sqlToPhp
Filtering: filterTables, filterColumns, filterPrimaryKeys, filterForeignKeys,
filterNullable, filterNotNull, filterColumnsByType
Formatting: toJSON, toJSONPretty, toYAML, indent, escape, comment
Loop helpers: enumerate, batch, reverse, first, last, skip, take, concat,
unique, sortBy, groupBy
Safe access: get, getOr, getPath, has, keys, values, merge, pick, omit,
sliceContains, indexOf, pluck
Examples:
# Generate documentation from PostgreSQL database
relspec templ --from pgsql --from-conn "postgres://user:pass@localhost/db" \
--template docs.tmpl --output schema-docs.md
# Generate one TypeScript model file per table
relspec templ --from dbml --from-path schema.dbml \
--template ts-model.tmpl --mode table \
--output ./models/ \
--filename-pattern "{{.Name | toCamelCase}}.ts"
# Generate schema documentation files
relspec templ --from json --from-path db.json \
--template schema.tmpl --mode schema \
--output ./docs/ \
--filename-pattern "{{.Name}}_schema.md"`,
RunE: runTempl,
}
func init() {
templCmd.Flags().StringVar(&templSourceType, "from", "", "Source format (dbml, pgsql, json, etc.)")
templCmd.Flags().StringVar(&templSourcePath, "from-path", "", "Source file path (for file-based sources)")
templCmd.Flags().StringVar(&templSourceConn, "from-conn", "", "Source connection string (for database sources)")
templCmd.Flags().StringVar(&templTemplatePath, "template", "", "Template file path (required)")
templCmd.Flags().StringVar(&templOutputPath, "output", "", "Output path (file or directory, empty for stdout)")
templCmd.Flags().StringVar(&templSchemaFilter, "schema", "", "Filter to specific schema")
templCmd.Flags().StringVar(&templMode, "mode", "database", "Execution mode: database, schema, script, or table")
templCmd.Flags().StringVar(&templFilenamePattern, "filename-pattern", "{{.Name}}.txt", "Filename pattern for multi-output modes")
_ = templCmd.MarkFlagRequired("from")
_ = templCmd.MarkFlagRequired("template")
}
func runTempl(cmd *cobra.Command, args []string) error {
// Print header
fmt.Fprintf(os.Stderr, "=== RelSpec Template Execution ===\n")
fmt.Fprintf(os.Stderr, "Started at: %s\n\n", getCurrentTimestamp())
// Read database using the same function as convert
fmt.Fprintf(os.Stderr, "Reading from %s...\n", templSourceType)
db, err := readDatabaseForConvert(templSourceType, templSourcePath, templSourceConn)
if err != nil {
return fmt.Errorf("failed to read source: %w", err)
}
// Print database stats
schemaCount := len(db.Schemas)
tableCount := 0
for _, schema := range db.Schemas {
tableCount += len(schema.Tables)
}
fmt.Fprintf(os.Stderr, "✓ Successfully read database: %s\n", db.Name)
fmt.Fprintf(os.Stderr, " Schemas: %d\n", schemaCount)
fmt.Fprintf(os.Stderr, " Tables: %d\n\n", tableCount)
// Apply schema filter if specified
if templSchemaFilter != "" {
fmt.Fprintf(os.Stderr, "Filtering to schema: %s\n", templSchemaFilter)
found := false
for _, schema := range db.Schemas {
if schema.Name == templSchemaFilter {
db.Schemas = []*models.Schema{schema}
found = true
break
}
}
if !found {
return fmt.Errorf("schema not found: %s", templSchemaFilter)
}
}
// Create template writer
fmt.Fprintf(os.Stderr, "Loading template: %s\n", templTemplatePath)
fmt.Fprintf(os.Stderr, "Execution mode: %s\n", templMode)
metadata := map[string]interface{}{
"template_path": templTemplatePath,
"mode": templMode,
"filename_pattern": templFilenamePattern,
}
writerOpts := &writers.WriterOptions{
OutputPath: templOutputPath,
Metadata: metadata,
}
writer, err := wtemplate.NewWriter(writerOpts)
if err != nil {
return fmt.Errorf("failed to create template writer: %w", err)
}
// Execute template
fmt.Fprintf(os.Stderr, "\nExecuting template...\n")
if err := writer.WriteDatabase(db); err != nil {
return fmt.Errorf("failed to execute template: %w", err)
}
// Print success message
fmt.Fprintf(os.Stderr, "\n✓ Template executed successfully\n")
if templOutputPath != "" {
fmt.Fprintf(os.Stderr, "Output written to: %s\n", templOutputPath)
} else {
fmt.Fprintf(os.Stderr, "Output written to stdout\n")
}
fmt.Fprintf(os.Stderr, "Completed at: %s\n", getCurrentTimestamp())
return nil
}