On this page
Read configuration and assemble a generator
Use cmd/sgl/config and cmd/sgl/assemble when your tool accepts the same configuration as sgl. They share its defaults and validation, so you do not need a separate YAML model.
Select a configured database
Start with this sgl.yaml and existing migrations and queries in the named directories. All generated-code settings belong to a database entry.
version: 1
databases:
- name: app
dialect: sqlite
migrations: db/migrations
queries: db/queries
go:
package: store
out: internal/store
interface:
enabled: true
Save the following program as cmd/generate/main.go. It supports migration-derived schemas and chooses one database by name.
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"seagull.sh/seagull"
"seagull.sh/seagull/cmd/sgl/assemble"
"seagull.sh/seagull/cmd/sgl/config"
)
func main() {
filename := flag.String("config", "sgl.yaml", "configuration file")
database := flag.String("database", "app", "configured database name")
flag.Parse()
if err := generate(context.Background(), *filename, *database); err != nil {
log.Fatal(err)
}
}
func generate(ctx context.Context, filename, name string) error {
cfg, err := config.Load(filename)
if err != nil {
return err
}
database, found := cfg.Database(name)
if !found {
return fmt.Errorf("database %q is not configured", name)
}
if database.SchemaFrom != "" {
return fmt.Errorf("this command expects migrations; configure a schema source for schema_from")
}
options, engine, err := assemble.Options(cfg, database, assemble.Factories{})
if err != nil {
return err
}
generator, err := seagull.New(engine, options...)
if err != nil {
return err
}
result, err := generator.Generate(ctx, seagull.Input{
Migrations: os.DirFS(database.Migrations), Queries: os.DirFS(database.Queries),
Package: database.Go.Package,
})
if err != nil {
return err
}
if err := result.Err(); err != nil {
return err
}
if err := seagull.WriteFiles(database.Go.Out, result); err != nil {
return err
}
fmt.Printf("generated %d queries for %s\n", result.QueryCount, name)
return nil
}
Load validates the file, resolves its directories relative to the file's location and expands environment references in connection strings. The example therefore uses each directory as a separate file-system root, leaving MigrationDir and QueryDir empty. A reference to a variable that is not set stays as written; before a tool connects with migrate.dsn or schema_from, it passes the key and the value to config.RequireExpanded, which reports such a reference, as sgl does.
Run the assembled generator
go run ./cmd/generate -config sgl.yaml -database app
go test ./...
assemble.Options creates the dialect, emitter and preset choices; the program still supplies input, handles both error channels and publishes the result. It does not implement the CLI's live connection handling or partial-generation policy. Add those deliberately if the embedding tool needs them; see live schemas and generator errors.
Read configuration from another system
Use config.Parse for bytes or config.ReadFS for an fs.FS. Both validate without expanding ${env:NAME}. Call cfg.ExpandEnvironment(lookup) with your own lookup function when expansion is intended; it returns an error wrapping config.ErrUnsetVariable, naming each reference the lookup could not resolve, and leaves those references as written. Neither resolves relative directories: they stay as the file writes them, relative to the configuration's location, so a caller using virtual files should supply virtual input file systems rather than os.DirFS.
To find the file or check directive prefixes the way sgl does, use config.FileNames(), the names Discover looks for in order, and config.DefaultPrefixes(), the prefixes a file that sets none accepts. Both return copies you may change.
Custom binaries pass registered factories through assemble.Factories. The configuration API describes the parsing and assembly boundaries.