Files
pgm/cmd/generate.go

215 lines
5.6 KiB
Go
Raw Normal View History

2025-07-26 18:34:56 +05:30
// Patial Tech.
// Author, Ankit Patial
package main
import (
"fmt"
"go/format"
"os"
"path/filepath"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
func generate(scheamPath, outDir string) error {
// read schame.sql
f, err := os.ReadFile(scheamPath)
if err != nil {
return err
}
// Parse scheam.sql
tbls, err := parse(f)
if err != nil {
return err
}
// Output dir, create if not exists.
if _, err := os.Stat(outDir); os.IsNotExist(err) {
if err := os.MkdirAll(outDir, 0740); err != nil {
return err
}
}
// schema.go will hold all tables info
var sb strings.Builder
2025-07-26 20:22:16 +05:30
sb.WriteString("// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.\n\n")
2025-07-26 18:34:56 +05:30
sb.WriteString(fmt.Sprintf("package %s \n", filepath.Base(outDir)))
sb.WriteString(`
import "code.patial.tech/go/pgm"
var (
`)
caser := cases.Title(language.English)
var (
modalDir string
fieldCount int
)
for _, t := range tbls {
// skip schema_migrations
if t.Table == "schema_migrations" {
continue
}
name := pluralToSingular(t.Table)
name = strings.ReplaceAll(name, "_", " ")
name = caser.String(name)
name = strings.ReplaceAll(name, " ", "")
fieldCount = len(t.Columns)
sb.WriteString(fmt.Sprintf(
" %s = pgm.Table{Name: %q, FieldCount: %d", name, t.Table, fieldCount,
))
if len(t.PrimaryKey) > 0 {
sb.WriteString(", PK: []string{}")
sb.WriteString("}\n")
} else {
sb.WriteString("}\n")
}
modalDir = strings.ToLower(name)
os.Mkdir(filepath.Join(outDir, modalDir), 0740)
if err = writeColFile(t.Table, t.Columns, filepath.Join(outDir, modalDir), caser); err != nil {
return err
}
}
sb.WriteString(")")
// Format code before saving
code, err := formatGoCode(sb.String())
if err != nil {
return err
}
// Save file to disk
os.WriteFile(filepath.Join(outDir, "schema.go"), code, 0640)
return nil
}
func writeColFile(tblName string, cols []*Column, outDir string, caser cases.Caser) error {
var sb strings.Builder
2025-07-26 20:22:16 +05:30
sb.WriteString("// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.\n\n")
2025-07-26 18:34:56 +05:30
sb.WriteString(fmt.Sprintf("package %s\n\n", filepath.Base(outDir)))
sb.WriteString(fmt.Sprintf("import %q\n\n", "code.patial.tech/go/pgm"))
sb.WriteString("const (")
var name string
for _, c := range cols {
name = strings.ReplaceAll(c.Name, "_", " ")
name = caser.String(name)
name = strings.ReplaceAll(name, " ", "")
if strings.HasSuffix(name, "Id") {
name = name[0:len(name)-2] + "ID"
}
sb.WriteString(fmt.Sprintf("\n // %s field has db type %q", name, c.Type))
sb.WriteString(fmt.Sprintf("\n %s pgm.Field = %q", name, tblName+"."+c.Name))
}
sb.WriteString("\n)")
// Format code before saving
code, err := formatGoCode(sb.String())
if err != nil {
return err
}
// Save file to disk.
return os.WriteFile(filepath.Join(outDir, tblName+".go"), code, 0640)
}
// pluralToSingular converts plural table names to singular forms
func pluralToSingular(plural string) string {
// Handle special irregular plurals
irregulars := map[string]string{
"people": "person",
"children": "child",
"men": "man",
"women": "woman",
"feet": "foot",
"teeth": "tooth",
"mice": "mouse",
"geese": "goose",
"oxen": "ox",
"data": "datum",
"criteria": "criterion",
"phenomena": "phenomenon",
}
lower := strings.ToLower(plural)
if singular, exists := irregulars[lower]; exists {
return singular
}
// Handle words ending in -ies (cities -> city, companies -> company)
if strings.HasSuffix(lower, "ies") && len(plural) > 3 {
return plural[:len(plural)-3] + "y"
}
// Handle words ending in -ves (lives -> life, wives -> wife)
if strings.HasSuffix(lower, "ves") && len(plural) > 3 {
return plural[:len(plural)-3] + "fe"
}
// Handle words ending in -ses (classes -> class, addresses -> address)
if strings.HasSuffix(lower, "ses") && len(plural) > 3 {
return plural[:len(plural)-2]
}
// Handle words ending in -xes (boxes -> box, taxes -> tax)
if strings.HasSuffix(lower, "xes") && len(plural) > 3 {
return plural[:len(plural)-2]
}
// Handle words ending in -ches (watches -> watch, benches -> bench)
if strings.HasSuffix(lower, "ches") && len(plural) > 4 {
return plural[:len(plural)-2]
}
// Handle words ending in -shes (dishes -> dish, bushes -> bush)
if strings.HasSuffix(lower, "shes") && len(plural) > 4 {
return plural[:len(plural)-2]
}
// Handle words ending in -oes (heroes -> hero, potatoes -> potato)
if strings.HasSuffix(lower, "oes") && len(plural) > 3 {
return plural[:len(plural)-2]
}
// Handle general -es endings
if strings.HasSuffix(lower, "es") && len(plural) > 2 {
withoutEs := plural[:len(plural)-2]
if len(withoutEs) > 0 {
lastChar := strings.ToLower(string(withoutEs[len(withoutEs)-1]))
// Only remove -es (not just -s) if the word ends in s, x, ch, sh, o (those need the -es)
if lastChar == "s" || lastChar == "x" || lastChar == "o" ||
strings.HasSuffix(strings.ToLower(withoutEs), "ch") ||
strings.HasSuffix(strings.ToLower(withoutEs), "sh") {
return withoutEs
}
}
// For words like "references", "preferences" - just remove the -s
return plural[:len(plural)-1]
}
// Handle simple -s endings (users -> user, books -> book)
if strings.HasSuffix(lower, "s") && len(plural) > 1 {
return plural[:len(plural)-1]
}
// If no rules match, return as-is (might already be singular)
return plural
}
// formatGoCode formats the input Go code using go/format.
func formatGoCode(str string) ([]byte, error) {
formatted, err := format.Source([]byte(str))
if err != nil {
return nil, fmt.Errorf("formatting code: %w", err)
}
return formatted, nil
}