Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP : Write documentation #29

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# GenZ Documentation

> This documentation is a work in progress and will be updated as the project progresses.

Welcome to the GenZ documentation.

## What is GenZ?

GenZ is generic golang generator. It is a tool that can be used to generate code from templates.
The templates are written in golang's template language by users. The templates can be used to generate any kind of code.

## Why GenZ?

Joffref marked this conversation as resolved.
Show resolved Hide resolved
### The problem

> [Clear is better than clever](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=14m35s)

Clear code is easier to understand and modify, which leads to fewer errors and less time spent debugging.
This is essential in industry where projects are often long-term and involve multiple developers.
It also improves communication and collaboration.

But we have observed that, sometimes, particularly on large data/domain models,
simple code tasks (like declaring constructor, setters, check model invariants or provide tooling/utility code around models)
can become repetitive and [Toil](https://sre.google/sre-book/eliminating-toil/),
impacting your velocity and maintainability as your model grows.

### Automate modeling with declaration and generation

Large projects like Kubernetes already automate utility code code by leveraging on static analysis and code generation
(e.g. [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder) and [controller-gen](https://book.kubebuilder.io/reference/controller-gen)).
For example, [adding Go "markers" comments on interfaces declaration](https://book.kubebuilder.io/reference/markers)
indicates business rules to the generator that will create the associated code.
Why wouldn't we create our own markers and generator ?

#### Parenthesis on reflection
Reflection is one way to achieve code introspection and access your model attributes, types and methods
but it's [hard to manipulate and operates at runtime](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=15m22s), which is very unsafe.

#### Parenthesis on tags
Tags is a very common way to add metadata to your model attributes. Thus, they can be introspected at execution time.
There are many libraries that use tags to generate code (e.g. [gorm](https://gorm.io/)).

The issues with tags are:
- they use reflection under the hood
- they only work on structs attributes and not on other types (e.g. methods, interfaces)

**We wanted a simple, generic and testable solution to introspect our models and generate code at compile time.**

We've built GenZ as a simple tool to help eliminate developer [Toil](https://sre.google/sre-book/eliminating-toil/) tied to your models.

## How to use GenZ?

GenZ is a CLI tool. It can be installed using the following command:

```bash
go get -u github.com/Joffref/genz
```

or

```bash
go install github.com/Joffref/genz@latest
```

Once installed, you can use the `genz` command to generate code from templates.

Either using go generate:

```go
package main

//go:generate genz -type Human -template ./human.tmpl -output human_validator.gen.go
type Human struct {
Firstname string
}
```

or directly:

```bash
genz -type Human -template ./human.tmpl -output human_validator.gen.go
```

## How does GenZ work?

GenZ parses the selected type and generates a struct that is injected into the template.

> NOTE: At the moment, GenZ only supports generating code from structs.

68 changes: 0 additions & 68 deletions docs/SPEC.md

This file was deleted.

21 changes: 21 additions & 0 deletions docs/how_to_write_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# How to write GenZ templates?

GenZ is a template-based generator for Go. A single binary that can be called with the native `go generate` to automate generation of your Go code based on templates.
Thus, a key part of GenZ is writing templates that will be used to generate code.

## Template Syntax

GenZ uses [go templates](https://pkg.go.dev/text/template) to generate code. Thus, you can use all the features of go templates to generate your code.
additionally, we included [sprig](http://masterminds.github.io/sprig) to provide additional functions to the templates (the same as in Helm)

## Template Data

The data injected into the templates can vary depending on the type used to generate the code.

## Template Examples

We provide a few examples of templates in the [examples](../examples) directory.

## Template Testing

We provide a few examples of templates testing in the [examples](../examples) directory or in the [testing.md](../testing.md) file.
105 changes: 105 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Test your templates

## Why testing templates?

Testing your templates is a good way to ensure that:
* the generated code is exactly the one you expect
* the generated code does what you expect from it (by running it against unit tests)
It's also a good way catch regressions when you update GenZ or your templates themselves.

## How to test your templates?

GenZ provides a simple way to test your templates using the `genz test` command.

### Test your templates using the CLI

You can test your templates using the CLI with the following command:

```bash
genz test ./path/to/folder/containing/templates
```

Joffref marked this conversation as resolved.
Show resolved Hide resolved
## How to write tests for your templates?

GenZ provides a simple way to write tests for your templates. The tests are written in the same language as your templates: Go.
The tests are written in subfolders of your templates folder, as shown in the following example:

```bash
.
├── human.tmpl
└── human_test
├── human.go
├── expected_test.go # optional
└── expected.go
```

With this structure, GenZ will be able to test your template against the expected output and the expected business logic.

### Output testing

GenZ will test the output of your template against the expected output.
The expected output is the content of the `expected.go` file.

### Business logic testing

GenZ will test the business logic of your template against the expected business logic.
The expected business logic is the content of the `expected_test.go` file.

### Example

Let's take the following template as an example:

```mustache
package main

{{ range .Attributes }}
{{ if has "+getter" .Comments }}{{ $receiverName := substr 0 1 $.Type.InternalName | lower}}
func ({{ $receiverName }} *{{ $.Type.InternalName }}) Get{{ camelcase .Name }}() {{ .Type.InternalName }} {
return {{ $receiverName }}.{{.Name}}
}
{{ end }}
{{ end }}
```

We can write the following `car.go` file to test the output of the template (located in `car_test/test_1/car.go`):

```go
package main

//go:generate genz -type Car -template ../../getters.tmpl -output car.gen.go
type Car struct {
//+getter
model string
}
```

We can write the following `expected.go` file to test the output of the template (located in `car_test/test_1/expected.go`):

```go
package main

func (c *Car) GetModel() string {
return c.model
}
```

We can write the following `expected_test.go` file to test the business logic of the template (located in `car_test/test_1/expected_test.go`):

```go
package main
import "testing"
func TestCarGetModel(t *testing.
c := &Car{}
if c.GetModel() != c.model {
t.Errorf("Expected %s, got %s", c.model, c.GetModel()
}
c.model = "Ford"
if c.GetModel() != "Ford" {
t.Errorf("Expected %s, got %s", c.model, c.GetModel()
}
c.model = "Ferrari"
if c.GetModel() != "Ferrari"
t.Errorf("Expected %s, got %s", c.model, c.GetModel()
}
}
```