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 }