Add --version flag and display version on startup
This commit is contained in:
95
README.md
95
README.md
@@ -4,46 +4,48 @@ Type-safe GORM column descriptors and model generation utilities.
|
|||||||
|
|
||||||
## Library
|
## 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
|
||||||
|
|
||||||
`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
|
```go
|
||||||
var PsAccess = struct {
|
var ProductCols = struct {
|
||||||
IDProfile gormcol.Field
|
ID gormcol.Field
|
||||||
IDAuthorizationRole gormcol.Field
|
Name gormcol.Field
|
||||||
|
Price gormcol.Field
|
||||||
}{
|
}{
|
||||||
IDProfile: gormcol.Field{Table: "ps_access", Column: "id_profile"},
|
ID: gormcol.Field{}.Set((&Product{}).TableName(), "id_product"),
|
||||||
IDAuthorizationRole: gormcol.Field{Table: "ps_access", Column: "id_authorization_role"},
|
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
|
```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
|
```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
|
```go
|
||||||
gormcol.TableField(dbmodel.PsAccess.IDAuthorizationRole) // "ps_access"
|
model.ProductCols.Price.Tab() // "ps_product"
|
||||||
```
|
```
|
||||||
|
|
||||||
## CLI
|
## 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
|
- **Interactive (default)**: select tables with fzf
|
||||||
- **Batch (`--filter` or `--all`)**: generate matching tables with confirmation
|
- **Batch (`--filter` or `--all`)**: generate matching tables with confirmation
|
||||||
|
|
||||||
| Flag | Default | Description |
|
| Flag | Default | Description |
|
||||||
|----------|------------------------|--------------------------------------------------|
|
|-----------|------------------------|---------------------------------------------------|
|
||||||
| `--dsn` | *(from DSN env)* | MySQL/MariaDB DSN, e.g. `user:pass@tcp(localhost:3306)/dbname` |
|
| `--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) |
|
| `--filter`| *(interactive)* | Regex matching table names to generate (triggers batch mode) |
|
||||||
| `--all` | *(interactive)* | Generate all tables matching filter (shows confirmation) |
|
| `--all` | *(interactive)* | Generate all tables matching filter (shows confirmation) |
|
||||||
| `--out` | `./app/model/dbmodel` | Output directory for generated files |
|
| `--out` | `./app/model/dbmodel` | Output directory for generated files |
|
||||||
| `--pkg` | `dbmodel` | Go package name for generated files |
|
| `--pkg` | `dbmodel` | Go package name for generated files |
|
||||||
|
| `--clean` | false | Remove existing model files before generation |
|
||||||
|
|
||||||
### Interactive Mode (Default)
|
### Interactive Mode (Default)
|
||||||
|
|
||||||
@@ -189,6 +192,18 @@ ctx := context.Background()
|
|||||||
err := gg.GenModels(ctx)
|
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
|
## Generated Models
|
||||||
|
|
||||||
After generation, each model file contains a struct and a `Cols` variable:
|
After generation, each model file contains a struct and a `Cols` variable:
|
||||||
@@ -202,57 +217,57 @@ type Product struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ProductCols = struct {
|
var ProductCols = struct {
|
||||||
ID Field
|
ID gormcol.Field
|
||||||
Name Field
|
Name gormcol.Field
|
||||||
Price Field
|
Price gormcol.Field
|
||||||
}{
|
}{
|
||||||
ID: Field{Table: "ps_product", Column: "id_product"},
|
ID: gormcol.Field{}.Set((&Product{}).TableName(), "id_product"),
|
||||||
Name: Field{Table: "ps_product", Column: "name"},
|
Name: gormcol.Field{}.Set((&Product{}).TableName(), "name"),
|
||||||
Price: Field{Table: "ps_product", Column: "price"},
|
Price: gormcol.Field{}.Set((&Product{}).TableName(), "price"),
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using Generated Models
|
## 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
|
```go
|
||||||
import "git.ma-al.com/goc_marek/gormcol"
|
import "git.ma-al.com/goc_marek/gormcol"
|
||||||
|
|
||||||
// Where clauses
|
// Where clauses
|
||||||
db.Where(
|
db.Where(
|
||||||
gormcol.ColumnOnTable(model.ProductCols.Price) + " > ?",
|
model.ProductCols.Price.TabCol() + " > ?",
|
||||||
100.0,
|
100.0,
|
||||||
).Find(&products)
|
).Find(&products)
|
||||||
|
|
||||||
// Order
|
// Order
|
||||||
db.Order(gormcol.ColumnOnTable(model.ProductCols.Name) + " ASC").Find(&products)
|
db.Order(model.ProductCols.Name.TabCol() + " ASC").Find(&products)
|
||||||
|
|
||||||
// Joins
|
// Joins
|
||||||
db.Joins("JOIN ps_category ON " +
|
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)
|
).Find(&products)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Unqualified column names
|
### Unqualified column names
|
||||||
|
|
||||||
Use `Column` when the table is already scoped:
|
Use `Col()` when the table is already scoped:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
db.Select(gormcol.Column(model.ProductCols.Name)).Find(&products)
|
db.Select(model.ProductCols.Name.Col()).Find(&products)
|
||||||
|
|
||||||
// Raw queries
|
// 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
|
### 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
|
```go
|
||||||
table := gormcol.TableField(model.ProductCols.ID) // "ps_product"
|
table := model.ProductCols.ID.Tab() // "ps_product"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|||||||
@@ -16,11 +16,33 @@ import (
|
|||||||
"gorm.io/gorm"
|
"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.
|
// main is the entry point for the gormcol-gen CLI tool.
|
||||||
// It parses flags, loads configuration from .env, connects to the database,
|
// It parses flags, loads configuration from .env, connects to the database,
|
||||||
// and generates GORM models with column descriptors.
|
// and generates GORM models with column descriptors.
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
|
versionFlag := flag.Bool("version", false, "display version information")
|
||||||
dsn := flag.String("dsn", "", "database DSN (e.g. user:pass@tcp(host:3306)/dbname)")
|
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)")
|
filter := flag.String("filter", "", "regex to match table names (triggers batch mode)")
|
||||||
all := flag.Bool("all", false, "generate all tables matching filter (shows confirmation)")
|
all := flag.Bool("all", false, "generate all tables matching filter (shows confirmation)")
|
||||||
@@ -44,7 +66,15 @@ func main() {
|
|||||||
|
|
||||||
flag.Parse()
|
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
|
dsnValue := *dsn
|
||||||
if dsnValue == "" {
|
if dsnValue == "" {
|
||||||
dsnValue = os.Getenv("DSN")
|
dsnValue = os.Getenv("DSN")
|
||||||
|
|||||||
41
gencols.go
41
gencols.go
@@ -181,37 +181,6 @@ func generateColsVarBlock(si *structInfo) string {
|
|||||||
return b.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 <Model>Cols variables to generated model files.
|
// generateCols appends <Model>Cols variables to generated model files.
|
||||||
// It parses each .go file in the output directory, extracts struct fields
|
// It parses each .go file in the output directory, extracts struct fields
|
||||||
// with gorm column tags, and generates type-safe Field descriptors.
|
// with gorm column tags, and generates type-safe Field descriptors.
|
||||||
@@ -231,15 +200,7 @@ func (m *GormGen) generateCols() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
goModPath, err := findGoMod(absDir)
|
gormcolImport := `"git.ma-al.com/goc_marek/gormcol"`
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
modulePath, err := readModulePath(goModPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
gormcolImport := fmt.Sprintf("%q", modulePath+"")
|
|
||||||
|
|
||||||
var fileFilter *regexp.Regexp
|
var fileFilter *regexp.Regexp
|
||||||
if len(m.cfg.SelectedTables) > 0 {
|
if len(m.cfg.SelectedTables) > 0 {
|
||||||
|
|||||||
21
gormcol.go
21
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
|
package gormcol
|
||||||
|
|
||||||
// Field represents a GORM column descriptor.
|
// Field represents a GORM column descriptor.
|
||||||
@@ -21,18 +7,25 @@ type Field struct {
|
|||||||
column string // Database column name
|
column string // Database column name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TabCol returns the fully qualified "table.column" reference.
|
||||||
func (f Field) TabCol() string {
|
func (f Field) TabCol() string {
|
||||||
return f.table + "." + f.column
|
return f.table + "." + f.column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Col returns the column name without table qualification.
|
||||||
func (f Field) Col() string {
|
func (f Field) Col() string {
|
||||||
return f.column
|
return f.column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tab returns the table name.
|
||||||
func (f Field) Tab() string {
|
func (f Field) Tab() string {
|
||||||
return f.table
|
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 {
|
func (f Field) Set(tab, field string) Field {
|
||||||
return Field{
|
return Field{
|
||||||
table: tab,
|
table: tab,
|
||||||
|
|||||||
Reference in New Issue
Block a user