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

SIMPLE EXAMPLE

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

SIMPLE EXAMPLE

Generating files from a directory

DIRECTORY EXAMPLE

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.

DIRECTORY EXAMPLE

Bundling files

BUNDLE EXAMPLE

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!

BUNDLE EXAMPLE

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
}

© 2020, randylough.com