A powerful yet simple Go library for programmatically generating and modifying Go source files. Built for creating ORMs, code generators, scaffolding tools, and template-based code generation.
GoGo is the first code generator that respects your changes. Unlike traditional generators that blindly overwrite files, GoGo:
- β¨ Preserves manual edits - Your custom code stays untouched
- π Tracks code across files - Move a struct to another file? GoGo finds it
- π€ Works with you, not against you - Edit generated code freely
- π Shows diffs before applying - See exactly what will change
- π Intelligent merging - Updates only what needs updating
Direct code manipulation with a clean, intuitive API:
- Structs - Create and modify struct definitions with fields, types, and tags
- Methods - Add methods with custom receivers, parameters, and return types
- Functions - Generate standalone functions with any signature
- Variables & Constants - Create package-level declarations
- Types - Define type aliases and custom types
- Smart Diffs - Preview changes before applying with automatic diff generation
- Conflict Resolution - Interactive or automatic change approval workflows
Transform existing Go code into templates for code generation:
- Parse Go Projects - Extract AST from any Go codebase
- Rename Everything - Structs, fields, functions, variables, constants, types
- Modify Structures - Add/remove struct fields dynamically
- Extract Definitions - Convert parsed code into gogo-compatible structs
- Immutable API - All transformations return new instances for safe chaining
- Perfect for ORMs - Use one model as a template for generating others
package main
import (
"github.com/guillermo/gogo"
)
func main() {
// Create a new project (NewFS is a convenience function)
prj, _ := gogo.NewFS("./output", gogo.Options{
InitialPackageName: "models", // Only use the first time.
ConflictFunc: gogo.ConflictAsk, // Allows to accept,reject or ask for each conflict
})
// Create a User struct
prj.Struct(gogo.StructOpts{
Filename: "user.go", // Only for the first time. If the struct was moved to a different file, not a problem
Name: "User",
Fields: []gogo.StructField{
{Name: "ID", Type: "int", Annotation: "`json:\"id\"`"},
{Name: "Name", Type: "string", Annotation: "`json:\"name\"`"},
{Name: "Email", Type: "string", Annotation: "`json:\"email\"`"},
},
})
// Add a method
prj.Method(gogo.MethodOpts{
Filename: "user.go",
Name: "Validate",
ReceiverType: "*User",
ReturnType: "error",
Body: `if u.Email == "" {
return errors.New("email is required")
}
return nil`,
})
}Generated output/user.go:
package models
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func (u *User) Validate() error {
if u.Email == "" {
return errors.New("email is required")
}
return nil
}Input: reference/customer.go
package models
type Customer struct {
CustomerID int
Name string
Email string
}
func GetCustomer(id int) *Customer {
return &Customer{CustomerID: id}
}
const MaxCustomers = 1000Transformation code:
package main
import (
"github.com/guillermo/gogo"
"github.com/guillermo/gogo/template"
)
func main() {
// Load a reference implementation (e.g., Customer model)
referenceFS, _ := gogo.OpenFS("./reference")
tmpl, _ := template.New(referenceFS)
// Transform: Customer -> User
tmpl, _ = tmpl.RenameStruct("Customer", "User")
tmpl, _ = tmpl.RenameStructField("User", "CustomerID", gogo.StructField{
Name: "UserID",
Type: "int",
})
tmpl, _ = tmpl.RenameFunction("GetCustomer", "GetUser")
tmpl, _ = tmpl.RenameConstant("MaxCustomers", "MaxUsers")
// Add new field
tmpl, _ = tmpl.AddStructField("User", gogo.StructField{
Name: "CreatedAt",
Type: "time.Time",
Annotation: "`json:\"created_at\"`",
})
// Extract and generate code
targetFS, _ := gogo.OpenFS("./target")
prj, _ := gogo.New(gogo.Options{FS: targetFS})
userStruct, _ := tmpl.ExtractStruct("User")
prj.Struct(gogo.StructOpts{
Filename: "user.go",
Name: userStruct.Name,
Fields: userStruct.Fields,
})
}Generated target/user.go:
package models
type User struct {
UserID int
Name string
Email string
CreatedAt time.Time `json:"created_at"`
}
func GetUser(id int) *User {
return &User{UserID: id}
}
const MaxUsers = 1000The killer feature: Regenerate code anytime without losing your work.
// 1. Generate initial code
prj.Struct(gogo.StructOpts{
Filename: "user.go",
Name: "User",
Fields: []gogo.StructField{
{Name: "ID", Type: "int"},
{Name: "Name", Type: "string"},
},
})
// 2. You add custom methods, move code around, edit freely
// 3. Later, add more fields - YOUR CODE STAYS INTACT
prj.Struct(gogo.StructOpts{
Filename: "user.go",
Name: "User",
Fields: []gogo.StructField{
{Name: "ID", Type: "int"},
{Name: "Name", Type: "string"},
{Name: "Email", Type: "string"}, // NEW field
},
PreserveExisting: true, // Your custom code is preserved!
})
// GoGo finds the User struct (even if you moved it to models.go)
// Adds only the Email field
// Keeps all your custom methods and modificationsUse one fully-implemented model as a template to generate all other models:
// Reference: Fully implemented Customer model
// Transform -> Generate: User, Product, Order models
// Edit generated models freely, regenerate when schema changesCreate CLI tools that generate boilerplate:
// Generate CRUD operations, repositories, handlers
// Users can customize generated code
// Regenerate when adding new features - customizations preservedProgrammatically rename and restructure code:
// Batch rename types, fields, functions across entire projects
// Safe to run multiple times, respects manual changesMaintain a reference implementation, generate variants:
// Keep one "golden" implementation, generate specialized versions
// Update the template, regenerate variants - custom logic preserved// Project operations
prj.Struct(opts) // Create/modify structs
prj.Method(opts) // Add methods
prj.Function(opts) // Add functions
prj.Variable(opts) // Declare variables
prj.Constant(opts) // Declare constants
prj.Type(opts) // Define types// Transformations (all return new Template)
tmpl.RenameStruct(old, new)
tmpl.RenameStructField(struct, oldField, newFieldDef)
tmpl.RenameVariable(old, new)
tmpl.RenameFunction(old, new)
tmpl.RenameConstant(old, new)
tmpl.RenameType(old, new)
tmpl.AddStructField(struct, field)
tmpl.RemoveStructField(struct, field)
// Extraction (convert to gogo types)
tmpl.ExtractStruct(name)
tmpl.ExtractFunction(name)
tmpl.ExtractMethod(receiver, name)
tmpl.ExtractVariable(name)
tmpl.ExtractConstant(name)
tmpl.ExtractType(name)# Run all tests
go test ./...
# Run with verbose output
go test ./... -v
# Run specific package tests
go test ./template/...All tests pass with 100% coverage of public API.
- Main Package: Comprehensive package comments in
gogo.go - Template Package: Detailed documentation in
template/template.go - Examples: See
examples_test.goandtemplate/example_test.go
# View documentation locally
go doc github.com/guillermo/gogo
go doc github.com/guillermo/gogo/template- No Source Templates - Direct code manipulation through AST
- Type Safety - Leverage Go's type system for correctness
- Immutability - Template transformations never modify originals
- Clean API - Minimal surface area, maximum clarity
- Stdlib Only - No external dependencies (except for gogo internals)
gogo2/
βββ gogo.go # Core package - code generation
βββ project.go # Project operations
βββ parser.go # AST parsing and code generation
βββ diff.go # Diff generation
βββ fs/ # Filesystem interface
βββ template/ # Template transformation package
β βββ template.go # Core Template type + extraction
β βββ rename.go # Rename operations
β βββ modify.go # Add/remove field operations
β βββ internal.go # Private AST helpers
βββ gogotest/ # Testing utilities
βββ tests/ # Comprehensive test suite
Choose how to handle changes:
// Interactive - Ask user for each change
ConflictFunc: gogo.ConflictAsk
// Accept all changes
ConflictFunc: gogo.ConflictAccept
// Reject all changes (dry-run)
ConflictFunc: gogo.ConflictReject
// Custom handler
ConflictFunc: func(fs, old, new, info) bool {
// Your logic here
return shouldApply
}Works with any fs.FS implementation:
// Real filesystem
fs, _ := gogo.OpenFS("./path")
// In-memory for testing
fs := gogotest.NewMemFS()
fs.WriteFile("main.go", []byte("package main"), 0644)
// Custom implementation
type CustomFS struct { /* ... */ }Contributions are welcome! Please:
- Run tests:
go test ./... - Format code:
go fmt ./... - Vet code:
go vet ./... - Follow existing patterns
MIT License - See LICENSE file for details
Built with β€οΈ using Go's excellent go/ast, go/parser, go/token, and go/format packages.
GoGo - The first code generator that respects your changes. Generate code, edit freely, regenerate without fear.
