From f1cbf82e095ecc8ad31e1db6016fe50ea4f09fbe Mon Sep 17 00:00:00 2001 From: Joffref Date: Fri, 24 Nov 2023 19:19:09 +0100 Subject: [PATCH 1/4] WIP : Write documentation Signed-off-by: Joffref --- docs/README.md | 52 +++++++++++++++++++++++++++ docs/SPEC.md | 68 ----------------------------------- docs/how_to_write_template.md | 0 docs/input_cheatsheet.md | 29 +++++++++++++++ docs/testing.md | 0 5 files changed, 81 insertions(+), 68 deletions(-) create mode 100644 docs/README.md delete mode 100644 docs/SPEC.md create mode 100644 docs/how_to_write_template.md create mode 100644 docs/input_cheatsheet.md create mode 100644 docs/testing.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d39cec4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,52 @@ +# 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? + +The rationals behind GenZ are: +- To reduce the amount of boilerplate code that needs to be written. +- To reduce the amount of code that needs to be maintained. +- To reduce the amount of code that needs to be tested. + +## How to use GenZ? + +GenZ is a CLI tool. It can be installed using the following command: + +```bash +go get -u github.com/utkarsh-pro/genz +``` + +or + +```bash +go install github.com/utkarsh-pro/genz@latest +``` + +Once installed, you can use the `genz` command to generate code from templates. + +Either using go generate: + +```go +//go:generate genz -t templates -o output -d data.json +``` + +or directly: + +```bash +genz -t templates -o output -d data.json +``` + +## 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. + diff --git a/docs/SPEC.md b/docs/SPEC.md deleted file mode 100644 index 806d6c7..0000000 --- a/docs/SPEC.md +++ /dev/null @@ -1,68 +0,0 @@ -```go -package spec -// Params is the parameters of the template -type Params struct { - // Type is the name of the type to match - Type Type // struct - // Description is the description of the type to match - Description string - // Attributes is the list of attributes of the type to match - Attributes []Attribute - // Methods is the list of methods of the type to match - Methods []Method -} - -type Type struct { - // Symbol is the symbol of the type - Symbol Symbol - // IsPointer is true if the type is a pointer - IsPointer bool - // IsSlice is true if the type is a slice - IsSlice bool -} - -type Symbol struct { - // Name is the name of the type - Name string // UUID - // Package is the package of the type - Package string // uuid - // ImportPath is the path of the type - ImportPath string // github.com/google/uuid -} - -// Attribute is the representation of an attribute -type Attribute struct { - // Name is the name of the attribute - Name string - // Description is the description of the attribute - Description string - // Type is the type of the attribute - Type Type - // Tags is the list of tags of the attribute - Tags []Tag - // Keys is the list of keys of the attribute - Keys map[string]string // //+custom:rulecore -} - -// Tag is the representation of a tag -type Tag struct { - // Name is the name of the tag - Name string - // Value is the value of the tag - Value string -} - -// Method is the representation of a method -type Method struct { - // Name is the name of the method - Name string - // Description is the description of the method - Description string - // Params is the list of parameters of the method - Params map[string]Type - // Returns is the list of returns of the method - Returns map[string]Type - // Receivers is the receiver of the method - Receivers Type -} -``` diff --git a/docs/how_to_write_template.md b/docs/how_to_write_template.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/input_cheatsheet.md b/docs/input_cheatsheet.md new file mode 100644 index 0000000..7700318 --- /dev/null +++ b/docs/input_cheatsheet.md @@ -0,0 +1,29 @@ +# Input Cheatsheet + +This cheatsheet is a quick reference for the input data injected into the templates by GenZ. + +## Struct + +The struct is injected into the template as `Struct`. +It contains the following fields: +- Type (`Type`): The type of the struct. + - Name (`string`): The name of the type from outside the package. Example `uuid.UUID` or `time.Time`. + - InternalName (`string`): The name of the type from inside the package. Example `UUID` or `Time`. +- Attributes (`[]Attribute`): List of attributes inside the struct + - Name (`string`): The name of the attribute. + - Type (`Type`): The type of the attribute. + - Name (`string`): The name of the type from outside the package. Example `uuid.UUID` or `time.Time`. + - InternalName (`string`): The name of the type from inside the package. Example `UUID` or `Time`. + - Comments (`[]string`): List of comments above the attribute +- Methods (`[]Method`): list of methods associated with the struct. + - Name (`string`): The name of the method. + - Params (`[]Type`): List of function paramaters. + - Name (`string`): The name of the type from outside the package. Example `uuid.UUID` or `time.Time`. + - InternalName (`string`): The name of the type from inside the package. Example `UUID` or `Time`. + - Returns (`[]Type`): List of function return values. + - Name (`string`): The name of the type from outside the package. Example `uuid.UUID` or `time.Time`. + - InternalName (`string`): The name of the type from inside the package. Example `UUID` or `Time`. + - Comments (`[]string`): List of comments above the method + - IsPointerReceiver (`bool`): Whether the method is a pointer receiver or not. + - IsExported (`bool`): Whether the method is exported or not. + - Comments (`[]string`): List of comments above the method \ No newline at end of file diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..e69de29 From 2485b63e4795d7b1300756a68f1ff4cf554ec095 Mon Sep 17 00:00:00 2001 From: Joffref Date: Mon, 27 Nov 2023 08:58:56 +0100 Subject: [PATCH 2/4] Improve documentation Signed-off-by: Joffref --- docs/README.md | 53 ++++++++++++++--- docs/how_to_write_template.md | 22 ++++++++ docs/testing.md | 103 ++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index d39cec4..aa9bc99 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,23 +11,55 @@ The templates are written in golang's template language by users. The templates ## Why GenZ? -The rationals behind GenZ are: -- To reduce the amount of boilerplate code that needs to be written. -- To reduce the amount of code that needs to be maintained. -- To reduce the amount of code that needs to be tested. +### 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 created 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 and not on other types (e.g. 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/utkarsh-pro/genz +go get -u github.com/Joffref/genz ``` or ```bash -go install github.com/utkarsh-pro/genz@latest +go install github.com/Joffref/genz@latest ``` Once installed, you can use the `genz` command to generate code from templates. @@ -35,13 +67,18 @@ Once installed, you can use the `genz` command to generate code from templates. Either using go generate: ```go -//go:generate genz -t templates -o output -d data.json +package main + +//go:generate genz -type Human -template ./human.tmpl -output human_validator.gen.go +type Human struct { + Firstname string +} ``` or directly: ```bash -genz -t templates -o output -d data.json +genz -type Human -template ./human.tmpl -output human_validator.gen.go ``` ## How does GenZ work? diff --git a/docs/how_to_write_template.md b/docs/how_to_write_template.md index e69de29..9f01cd8 100644 --- a/docs/how_to_write_template.md +++ b/docs/how_to_write_template.md @@ -0,0 +1,22 @@ +# 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. +Plus, we use [sprig](http://masterminds.github.io/sprig) to provide additional functions to the templates. + +## Template Data + +The data injected into the templates can vary depending on the type used to generate the code. Thus, we provide a cheatsheet for each type. +You can find the cheatsheets in the [input_cheatsheet.md](./input_cheatsheet.md) file. + +## 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. \ No newline at end of file diff --git a/docs/testing.md b/docs/testing.md index e69de29..e4fb002 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -0,0 +1,103 @@ +# Test your templates + +## Why testing templates? + +Testing your templates is a good way to ensure that your templates are working as expected. +It's also a good way to ensure that your templates are not broken 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 +``` + +## 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() + } +} +``` From 40ed55742655bfd2b54ff2b0040db890f68e2228 Mon Sep 17 00:00:00 2001 From: Mathis Joffre <51022808+Joffref@users.noreply.github.com> Date: Fri, 8 Dec 2023 20:02:56 +0100 Subject: [PATCH 3/4] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems good to me. Co-authored-by: Léo Rolland --- docs/README.md | 4 ++-- docs/how_to_write_template.md | 2 +- docs/testing.md | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index aa9bc99..a04ea3a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,7 +29,7 @@ impacting your velocity and maintainability as your model grows. 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 created the associated code. +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 @@ -42,7 +42,7 @@ There are many libraries that use tags to generate code (e.g. [gorm](https://gor The issues with tags are: - they use reflection under the hood -- they only work on structs and not on other types (e.g. interfaces) +- 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.** diff --git a/docs/how_to_write_template.md b/docs/how_to_write_template.md index 9f01cd8..fcde7fa 100644 --- a/docs/how_to_write_template.md +++ b/docs/how_to_write_template.md @@ -6,7 +6,7 @@ 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. -Plus, we use [sprig](http://masterminds.github.io/sprig) to provide additional functions to the templates. +additionally, we included [sprig](http://masterminds.github.io/sprig) to provide additional functions to the templates (the same as in Helm) ## Template Data diff --git a/docs/testing.md b/docs/testing.md index e4fb002..5c15a12 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -2,8 +2,10 @@ ## Why testing templates? -Testing your templates is a good way to ensure that your templates are working as expected. -It's also a good way to ensure that your templates are not broken when you update GenZ or your templates themselves. +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? From 21c032207bfe2502d377f2409e05655dd7359dbf Mon Sep 17 00:00:00 2001 From: Joffref Date: Fri, 8 Dec 2023 20:06:12 +0100 Subject: [PATCH 4/4] Remove cheatsheet as it's now in pkg.go.dev Signed-off-by: Joffref --- docs/how_to_write_template.md | 3 +-- docs/input_cheatsheet.md | 29 ----------------------------- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 docs/input_cheatsheet.md diff --git a/docs/how_to_write_template.md b/docs/how_to_write_template.md index fcde7fa..8315348 100644 --- a/docs/how_to_write_template.md +++ b/docs/how_to_write_template.md @@ -10,8 +10,7 @@ additionally, we included [sprig](http://masterminds.github.io/sprig) to provide ## Template Data -The data injected into the templates can vary depending on the type used to generate the code. Thus, we provide a cheatsheet for each type. -You can find the cheatsheets in the [input_cheatsheet.md](./input_cheatsheet.md) file. +The data injected into the templates can vary depending on the type used to generate the code. ## Template Examples diff --git a/docs/input_cheatsheet.md b/docs/input_cheatsheet.md deleted file mode 100644 index 7700318..0000000 --- a/docs/input_cheatsheet.md +++ /dev/null @@ -1,29 +0,0 @@ -# Input Cheatsheet - -This cheatsheet is a quick reference for the input data injected into the templates by GenZ. - -## Struct - -The struct is injected into the template as `Struct`. -It contains the following fields: -- Type (`Type`): The type of the struct. - - Name (`string`): The name of the type from outside the package. Example `uuid.UUID` or `time.Time`. - - InternalName (`string`): The name of the type from inside the package. Example `UUID` or `Time`. -- Attributes (`[]Attribute`): List of attributes inside the struct - - Name (`string`): The name of the attribute. - - Type (`Type`): The type of the attribute. - - Name (`string`): The name of the type from outside the package. Example `uuid.UUID` or `time.Time`. - - InternalName (`string`): The name of the type from inside the package. Example `UUID` or `Time`. - - Comments (`[]string`): List of comments above the attribute -- Methods (`[]Method`): list of methods associated with the struct. - - Name (`string`): The name of the method. - - Params (`[]Type`): List of function paramaters. - - Name (`string`): The name of the type from outside the package. Example `uuid.UUID` or `time.Time`. - - InternalName (`string`): The name of the type from inside the package. Example `UUID` or `Time`. - - Returns (`[]Type`): List of function return values. - - Name (`string`): The name of the type from outside the package. Example `uuid.UUID` or `time.Time`. - - InternalName (`string`): The name of the type from inside the package. Example `UUID` or `Time`. - - Comments (`[]string`): List of comments above the method - - IsPointerReceiver (`bool`): Whether the method is a pointer receiver or not. - - IsExported (`bool`): Whether the method is exported or not. - - Comments (`[]string`): List of comments above the method \ No newline at end of file