Gogen
- Type:library
- Technologies:
- Repository:github.com/randallmlough/gogen
What is GoGen?
Gogen, short for, Go Generate, is a simple file generation library written in Go. This library aims to provide an easy solution to modular file generation. Whether you want to generate your next projects scaffolding, or leverage conditional functions, make use of file bundling, or even create a word document, gogen is up for the task.
Features
- Single file generation
- Code generation (dyanmic imports, dynamic types, etc.)
- File bundling (multiple files get put into one file)
- Bulk generation (generate whole directories and subdirectories)
- List generation (generate n number of files based on inputs)
Installation
go get github.com/randallmlough/gogen
Creating a single file
Creating a Go file
package main
import (
"github.com/randallmlough/gogen"
"log"
)
func main() {
data := map[string]interface{}{
"Name": "john",
"Greeting": "Hello to you too.",
}
contents, _ := gogen.LoadTemplate("path/to/templates/someGoFile.gotpl")
gocode := &gogen.Go{
Template: contents,
Filename: "relative/output/path/hello.go",
PackageName: "testing",
TemplateData: data,
}
gogen.Generate(gocode, &gogen.Config{
GeneratedHeader: true,
Description: "// file generation is awesome"})
}
Example template file
{{ reserveImport "fmt" }}
func talk() {
name := "{{.Name}}"
fmt.Println("Hello there", name)
greeting("{{$.Greeting}}")
}
func greeting(say string) {
fmt.Println(say)
}
reserveImport
makes sure that the fmt
package will be loaded and added to the generated file.
Output
hello.go
// Code generated by github.com/randallmlough/gogen, DO NOT EDIT.
// file generation is awesome
package testing
import (
"fmt"
)
func talk() {
name := "john"
fmt.Println("Hello there", name)
greeting("Hello to you too.")
}
func greeting(say string) {
fmt.Println(say)
}
Creating another type of file
For this example let's create a YAML file.
package main
import (
"github.com/randallmlough/gogen"
"log"
)
func main() {
type Data struct {
Name string
Location string
Age *int
FavoriteThings []string
}
data := Data{
Name: "john snow",
Location: "the wall",
Age: nil,
FavoriteThings: []string{"dogs", "cold places", "sam"},
}
contents, _ := gogen.LoadTemplate("examples/simple/file.gotpl")
doc := &gogen.Doc{
Template: contents,
Filename: "examples/simple/output/file.yml",
TemplateData: data,
}
gogen.Generate(doc)
}
The template
name: {{.Name}}
location: {{.Location}}
{{- if .Age }}
age: {{.Age}}
{{ else }}
# make sure to checkout the template. I was conditionally rendered
{{ end -}}
{{- with .FavoriteThings}}
favorites:
{{ range . -}}
- {{.}}
{{ end }}
{{ end -}}
The output
name: john snow
location: the wall
# make sure to checkout the template. I was conditionally rendered
favorites:
- dogs
- cold places
- sam
Generating files from a directory
Let's say we have the following project structure...
├── main.go
├── output
└── templates
├── file.yml.gotpl
├── gocode.go.gotpl
└── types
└── type.go.gotpl
And we want to generate all the template files and put them into the output directory. It's crazy easy.
func main() {
type Data struct {
Name string
Location string
Age *int
FavoriteThings []string
Greeting string
PrimaryKey interface{}
}
data := Data{
Name: "john snow",
Location: "the wall",
Age: nil,
FavoriteThings: []string{"dogs", "cold places", "sam"},
Greeting: "Hello to you too.",
PrimaryKey: int(1),
}
dir := &gogen.Dir{
OutputDir: "examples/directory/output",
TemplateDir: "examples/directory/templates",
}
if err := gogen.Generate(dir, gogen.SkipChildren(false)); err != nil {
log.Fatal(err)
}
}
Output structure
├── output
│ ├── file.yml
│ ├── gocode.go
│ └── types
│ └── type.go
The big difference between generating a single file and multiple files is the data you are passing into each template. When you generate a single file you can be selective on the data you pass in, but when you generate from a directory, the data will be shared across all the templates. However, that's rarely an issue.
Bundling files
We can also bundle any number of files into one file.
├── main.go
├── output
└── templates
├── file
│ ├── part-one.yml.gotpl
│ └── part-two.yml.gotpl
├── part-one.go.gotpl
├── part-three.go.gotpl
└── part-two.go.gotpl
Let's bundle everything from the templates directory and put the result bundled file into the output directory.
func main(){
data := map[string]interface{}{
"PrimaryKey": 1,
"PartTwoAnswer": 2,
"PartThreeCtxValue": "some-random-key",
}
gocode := &gogen.Go{
Bundle: "examples/bundles/templates",
Filename: "examples/bundles/output/bundle.go",
PackageName: "testing",
TemplateData: data,
}
if err := gogen.Generate(gocode); err != nil {
return err
}
return nil
}
Output
├── output
│ ├── bundle.go
Super cool!
Extending gogen
By design, gogen accepts and returns interfaces for most of the heavy lifting. So as long as you fulfill the Gen
and File
interface you can extend gogen however you see fit.
The three core interfaces are:
The File
interface builds and creates the data and returns the Document
interface
type File interface {
Generate(cfg *Config) (Document, error)
}
The File
interface is a simple interface that just tells gogen where to put this file, and the data to write into it.
type Document interface {
Path() string
Data
}
type Data interface {
Bytes() []byte
}
The DocWriter
interface is an optional interface. Gogen will attempt to write the file if your type hasn't implemented it, but if you need a custom writing procedure to be done, like our Go
type does, then implement this interface as well.
type DocWriter interface {
Write(file Document) error
}