diff --git a/.env b/.env deleted file mode 100644 index 3deb853..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -DSN=root:Maal12345678@tcp(localhost:3306)/nalu diff --git a/README.md b/README.md index ca72d1c..024c9f5 100644 --- a/README.md +++ b/README.md @@ -4,46 +4,48 @@ Type-safe GORM column descriptors and model generation utilities. ## Library -The library provides [Field](#field) for type-safe column references and helper functions to extract column/table names. +The library provides [Field](#field) for type-safe column references and methods to extract column/table names. ### Field -`Field` represents a GORM column descriptor with table context. Define package-level variables to get type-safe column references: +`Field` represents a GORM column descriptor with table context. Generated code creates package-level variables with typed field references: ```go -var PsAccess = struct { - IDProfile gormcol.Field - IDAuthorizationRole gormcol.Field +var ProductCols = struct { + ID gormcol.Field + Name gormcol.Field + Price gormcol.Field }{ - IDProfile: gormcol.Field{Table: "ps_access", Column: "id_profile"}, - IDAuthorizationRole: gormcol.Field{Table: "ps_access", Column: "id_authorization_role"}, + ID: gormcol.Field{}.Set((&Product{}).TableName(), "id_product"), + Name: gormcol.Field{}.Set((&Product{}).TableName(), "name"), + Price: gormcol.Field{}.Set((&Product{}).TableName(), "price"), } ``` -### Helper Functions +### Field Methods -#### Column +#### TabCol -Returns just the column name from a Field descriptor. +Returns "table.column" format for use in SQL with table qualification. ```go -gormcol.Column(dbmodel.PsAccess.IDAuthorizationRole) // "id_authorization_role" +model.ProductCols.Price.TabCol() // "ps_product.price" ``` -#### ColumnOnTable +#### Col -Returns "table.column" format from a Field descriptor. +Returns just the column name. ```go -gormcol.ColumnOnTable(dbmodel.PsAccess.IDAuthorizationRole) // "ps_access.id_authorization_role" +model.ProductCols.Price.Col() // "price" ``` -#### TableField +#### Tab -Returns the table name from a Field descriptor. +Returns the table name. ```go -gormcol.TableField(dbmodel.PsAccess.IDAuthorizationRole) // "ps_access" +model.ProductCols.Price.Tab() // "ps_product" ``` ## CLI @@ -78,13 +80,14 @@ DSN can be provided via `--dsn` flag or `DSN` env var (from `.env` file). - **Interactive (default)**: select tables with fzf - **Batch (`--filter` or `--all`)**: generate matching tables with confirmation -| Flag | Default | Description | -|----------|------------------------|--------------------------------------------------| -| `--dsn` | *(from DSN env)* | MySQL/MariaDB DSN, e.g. `user:pass@tcp(localhost:3306)/dbname` | -| `--filter` | *(interactive)* | Regex matching table names to generate (triggers batch mode) | -| `--all` | *(interactive)* | Generate all tables matching filter (shows confirmation) | -| `--out` | `./app/model/dbmodel` | Output directory for generated files | -| `--pkg` | `dbmodel` | Go package name for generated files | +| Flag | Default | Description | +|-----------|------------------------|---------------------------------------------------| +| `--dsn` | *(from DSN env)* | MySQL/MariaDB DSN, e.g. `user:pass@tcp(localhost:3306)/dbname` | +| `--filter`| *(interactive)* | Regex matching table names to generate (triggers batch mode) | +| `--all` | *(interactive)* | Generate all tables matching filter (shows confirmation) | +| `--out` | `./app/model/dbmodel` | Output directory for generated files | +| `--pkg` | `dbmodel` | Go package name for generated files | +| `--clean` | false | Remove existing model files before generation | ### Interactive Mode (Default) @@ -189,6 +192,18 @@ ctx := context.Background() err := gg.GenModels(ctx) ``` +## GenConfig + +Configuration for model generation. + +| Field | Default | Description | +|-----------------|--------------------------|---------------------------------------------------| +| `OutputDir` | `./app/model/dbmodel` | Directory for generated files | +| `PkgName` | `dbmodel` | Go package name | +| `TableFilter` | `ps_.*` | Regex pattern to match table names | +| `SelectedTables`| *(none)* | Specific tables to generate (overrides filter) | +| `Clean` | `true` | Remove existing files before generation | + ## Generated Models After generation, each model file contains a struct and a `Cols` variable: @@ -202,57 +217,57 @@ type Product struct { } var ProductCols = struct { - ID Field - Name Field - Price Field + ID gormcol.Field + Name gormcol.Field + Price gormcol.Field }{ - ID: Field{Table: "ps_product", Column: "id_product"}, - Name: Field{Table: "ps_product", Column: "name"}, - Price: Field{Table: "ps_product", Column: "price"}, + ID: gormcol.Field{}.Set((&Product{}).TableName(), "id_product"), + Name: gormcol.Field{}.Set((&Product{}).TableName(), "name"), + Price: gormcol.Field{}.Set((&Product{}).TableName(), "price"), } ``` ## Using Generated Models -### GORM queries with type-safe columns +### Table-qualified columns -Use `ColumnOnTable` for table-qualified column references in GORM clauses: +Use `TabCol()` for table-qualified column references: ```go import "git.ma-al.com/goc_marek/gormcol" // Where clauses db.Where( - gormcol.ColumnOnTable(model.ProductCols.Price) + " > ?", + model.ProductCols.Price.TabCol() + " > ?", 100.0, ).Find(&products) // Order -db.Order(gormcol.ColumnOnTable(model.ProductCols.Name) + " ASC").Find(&products) +db.Order(model.ProductCols.Name.TabCol() + " ASC").Find(&products) // Joins db.Joins("JOIN ps_category ON " + - gormcol.ColumnOnTable(model.ProductCols.ID) + " = ps_category.id_product", + model.ProductCols.ID.TabCol() + " = ps_category.id_product", ).Find(&products) ``` ### Unqualified column names -Use `Column` when the table is already scoped: +Use `Col()` when the table is already scoped: ```go -db.Select(gormcol.Column(model.ProductCols.Name)).Find(&products) +db.Select(model.ProductCols.Name.Col()).Find(&products) // Raw queries -db.Raw("SELECT " + gormcol.Column(model.ProductCols.Name) + " FROM ps_product").Scan(&names) +db.Raw("SELECT " + model.ProductCols.Name.Col() + " FROM ps_product").Scan(&names) ``` ### Table name -Use `TableField` to get the table name from a column descriptor: +Use `Tab()` to get the table name from a column descriptor: ```go -table := gormcol.TableField(model.ProductCols.ID) // "ps_product" +table := model.ProductCols.ID.Tab() // "ps_product" ``` ## Dependencies diff --git a/cmd/gormcol/main.go b/cmd/gormcol/main.go index 5176187..759631b 100644 --- a/cmd/gormcol/main.go +++ b/cmd/gormcol/main.go @@ -16,11 +16,33 @@ import ( "gorm.io/gorm" ) +// version returns the current version from git tag and last modification date. +func version() string { + tag := "dev" + if out, err := exec.Command("git", "describe", "--tags", "--abbrev=0").Output(); err == nil { + tag = strings.TrimSpace(string(out)) + } + + date := "" + if out, err := exec.Command("git", "log", "-1", "--format=%ci").Output(); err == nil { + date = strings.TrimSpace(string(out)) + if len(date) >= 10 { + date = date[:10] + } + } + + if date != "" { + return fmt.Sprintf("%s (%s)", tag, date) + } + return tag +} + // main is the entry point for the gormcol-gen CLI tool. // It parses flags, loads configuration from .env, connects to the database, // and generates GORM models with column descriptors. func main() { + versionFlag := flag.Bool("version", false, "display version information") dsn := flag.String("dsn", "", "database DSN (e.g. user:pass@tcp(host:3306)/dbname)") filter := flag.String("filter", "", "regex to match table names (triggers batch mode)") all := flag.Bool("all", false, "generate all tables matching filter (shows confirmation)") @@ -44,7 +66,15 @@ func main() { flag.Parse() - // Get DSN from flag or environment variable. + // Display version on startup. + fmt.Printf("gormcol %s\n", version()) + + // Display version if requested. + if *versionFlag { + return + } + + // Connect to the database. dsnValue := *dsn if dsnValue == "" { dsnValue = os.Getenv("DSN") diff --git a/gencols.go b/gencols.go index e7da424..b16cc47 100644 --- a/gencols.go +++ b/gencols.go @@ -181,37 +181,6 @@ func generateColsVarBlock(si *structInfo) string { return b.String() } -// findGoMod searches upward from startDir for a go.mod file. -func findGoMod(startDir string) (string, error) { - dir := startDir - for { - path := filepath.Join(dir, "go.mod") - if _, err := os.Stat(path); err == nil { - return path, nil - } - parent := filepath.Dir(dir) - if parent == dir { - return "", fmt.Errorf("go.mod not found from %s", startDir) - } - dir = parent - } -} - -// readModulePath extracts the module path from a go.mod file. -func readModulePath(goModPath string) (string, error) { - content, err := os.ReadFile(goModPath) - if err != nil { - return "", err - } - for _, line := range strings.Split(string(content), "\n") { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "module ") { - return strings.TrimSpace(strings.TrimPrefix(line, "module ")), nil - } - } - return "", fmt.Errorf("module directive not found in %s", goModPath) -} - // generateCols appends Cols variables to generated model files. // It parses each .go file in the output directory, extracts struct fields // with gorm column tags, and generates type-safe Field descriptors. @@ -231,15 +200,7 @@ func (m *GormGen) generateCols() error { return err } - goModPath, err := findGoMod(absDir) - if err != nil { - return err - } - modulePath, err := readModulePath(goModPath) - if err != nil { - return err - } - gormcolImport := fmt.Sprintf("%q", modulePath+"") + gormcolImport := `"git.ma-al.com/goc_marek/gormcol"` var fileFilter *regexp.Regexp if len(m.cfg.SelectedTables) > 0 { diff --git a/gormcol b/gormcol deleted file mode 100755 index 6725cad..0000000 Binary files a/gormcol and /dev/null differ diff --git a/gormcol.go b/gormcol.go index acadde7..6ac564c 100644 --- a/gormcol.go +++ b/gormcol.go @@ -1,17 +1,3 @@ -// Package gormcol provides type-safe GORM column descriptors. -// -// This package enables defining struct-like variables that map Go field names -// to database column names, allowing type-safe queries in GORM. -// -// Example usage: -// -// var PsAccessCols = struct { -// IDProfile gormcol.Field -// IDAuthorizationRole gormcol.Field -// }{ -// IDProfile: gormcol.Field{}.Set((&PsAccess{}).TableName(), "id_profile"), -// IDAuthorizationRole: gormcol.Field{}.Set((&PsAccess{}).TableName(), "id_authorization_role"), -// } package gormcol // Field represents a GORM column descriptor. @@ -21,18 +7,25 @@ type Field struct { column string // Database column name } +// TabCol returns the fully qualified "table.column" reference. func (f Field) TabCol() string { return f.table + "." + f.column } +// Col returns the column name without table qualification. func (f Field) Col() string { return f.column } +// Tab returns the table name. func (f Field) Tab() string { return f.table } +// Set initializes a Field with the given table and column names. +// Use with TableName() for type-safe initialization, e.g.: +// +// Field{}.Set((&MyModel{}).TableName(), "column_name") func (f Field) Set(tab, field string) Field { return Field{ table: tab,