227 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			227 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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
 | 
						|
	sb.WriteString("// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.\n\n")
 | 
						|
	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(")")
 | 
						|
 | 
						|
	sb.WriteString(`
 | 
						|
	func DerivedTable(tblName string, fromQry pgm.Query) pgm.Table {
 | 
						|
		t := pgm.Table{
 | 
						|
			Name:         tblName,
 | 
						|
			DerivedTable: fromQry,
 | 
						|
		}
 | 
						|
		return t
 | 
						|
}`)
 | 
						|
 | 
						|
	// 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
 | 
						|
	sb.WriteString("// Code generated by code.patial.tech/go/pgm/cmd DO NOT EDIT.\n\n")
 | 
						|
	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 (")
 | 
						|
	sb.WriteString("\n // All fields in table " + tblName)
 | 
						|
	sb.WriteString(fmt.Sprintf("\n All pgm.Field = %q", tblName+".*"))
 | 
						|
	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
 | 
						|
}
 |