Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13637 from clarketm/genyaml
Create `genyaml` library to generate commented YAML from Go structs.
- Loading branch information
Showing
68 changed files
with
13,222 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") | ||
|
||
go_library( | ||
name = "go_default_library", | ||
srcs = ["genyaml.go"], | ||
data = ["//prow/plugins:config-src"], | ||
importpath = "k8s.io/test-infra/pkg/genyaml", | ||
visibility = ["//visibility:public"], | ||
deps = ["//vendor/gopkg.in/yaml.v3:go_default_library"], | ||
) | ||
|
||
go_test( | ||
name = "go_default_test", | ||
srcs = ["genyaml_test.go"], | ||
data = [":test-data"], | ||
embed = [":go_default_library"], | ||
deps = [ | ||
"//pkg/genyaml/testdata/alias_types:go_default_library", | ||
"//pkg/genyaml/testdata/embedded_structs:go_default_library", | ||
"//pkg/genyaml/testdata/interface_types:go_default_library", | ||
"//pkg/genyaml/testdata/multiline_comments:go_default_library", | ||
"//pkg/genyaml/testdata/nested_structs:go_default_library", | ||
"//pkg/genyaml/testdata/no_tags:go_default_library", | ||
"//pkg/genyaml/testdata/omit_if_empty:go_default_library", | ||
"//pkg/genyaml/testdata/pointer_types:go_default_library", | ||
"//pkg/genyaml/testdata/primitive_types:go_default_library", | ||
"//pkg/genyaml/testdata/private_members:go_default_library", | ||
"//vendor/gopkg.in/yaml.v3:go_default_library", | ||
], | ||
) | ||
|
||
filegroup( | ||
name = "test-data", | ||
srcs = [ | ||
"//pkg/genyaml/testdata/alias_types:test-data", | ||
"//pkg/genyaml/testdata/embedded_structs:test-data", | ||
"//pkg/genyaml/testdata/inject_comments:test-data", | ||
"//pkg/genyaml/testdata/interface_types:test-data", | ||
"//pkg/genyaml/testdata/multiline_comments:test-data", | ||
"//pkg/genyaml/testdata/multiple_paths:test-data", | ||
"//pkg/genyaml/testdata/nested_structs:test-data", | ||
"//pkg/genyaml/testdata/no_tags:test-data", | ||
"//pkg/genyaml/testdata/omit_if_empty:test-data", | ||
"//pkg/genyaml/testdata/pointer_types:test-data", | ||
"//pkg/genyaml/testdata/primitive_types:test-data", | ||
"//pkg/genyaml/testdata/private_members:test-data", | ||
"//pkg/genyaml/testdata/set_path_overwrite:test-data", | ||
"//pkg/genyaml/testdata/single_path:test-data", | ||
], | ||
) | ||
|
||
filegroup( | ||
name = "package-srcs", | ||
srcs = glob(["**"]), | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:private"], | ||
) | ||
|
||
filegroup( | ||
name = "all-srcs", | ||
srcs = [ | ||
":package-srcs", | ||
"//pkg/genyaml/testdata/alias_types:all-srcs", | ||
"//pkg/genyaml/testdata/embedded_structs:all-srcs", | ||
"//pkg/genyaml/testdata/inject_comments:all-srcs", | ||
"//pkg/genyaml/testdata/interface_types:all-srcs", | ||
"//pkg/genyaml/testdata/multiline_comments:all-srcs", | ||
"//pkg/genyaml/testdata/multiple_paths:all-srcs", | ||
"//pkg/genyaml/testdata/nested_structs:all-srcs", | ||
"//pkg/genyaml/testdata/no_tags:all-srcs", | ||
"//pkg/genyaml/testdata/omit_if_empty:all-srcs", | ||
"//pkg/genyaml/testdata/pointer_types:all-srcs", | ||
"//pkg/genyaml/testdata/primitive_types:all-srcs", | ||
"//pkg/genyaml/testdata/private_members:all-srcs", | ||
"//pkg/genyaml/testdata/set_path_overwrite:all-srcs", | ||
"//pkg/genyaml/testdata/single_path:all-srcs", | ||
], | ||
tags = ["automanaged"], | ||
visibility = ["//visibility:public"], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# Genyaml | ||
|
||
## Description | ||
`genyaml` is a simple documentation tool used to marshal YAML from Golang structs. It extracts *doc comments* from `.go` sources files and adds them as *comment nodes* in the YAML output. | ||
|
||
## Usage | ||
|
||
TODOs are ignored (e.g. TODO(clarketm)... or TODO...) if and only if they are on a **single** line. | ||
|
||
```go | ||
type Employee struct { | ||
// Name of employee | ||
Name string | ||
// Age of employee | ||
Age int | ||
// TODO(clarketm): change this to a float64 | ||
// Salary of employee | ||
Salary int | ||
} | ||
``` | ||
|
||
```yaml | ||
# Age of employee | ||
Age: 22 | ||
|
||
# Name of employee | ||
Name: Jim | ||
|
||
# Salary of employee | ||
Salary: 100000 | ||
``` | ||
|
||
Multiline comments are preserved, albeit *tabs* are converted to *spaces* and *multiple* spaces are compressed into a *single* line. | ||
|
||
```go | ||
type Multiline struct { | ||
// StringField1 comment | ||
// second line | ||
// third line | ||
StringField1 string `json:"string1"` | ||
|
||
/* StringField2 comment | ||
second line | ||
third line | ||
*/ | ||
StringField2 string `json:"string2"` | ||
|
||
/* StringField3 comment | ||
second line | ||
third line | ||
*/ | ||
StringField3 string `json:"string3"` | ||
} | ||
``` | ||
|
||
```yaml | ||
# StringField1 comment | ||
# second line | ||
# third line | ||
string1: string1 | ||
|
||
# StringField2 comment | ||
# second line | ||
# third line | ||
string2: string2 | ||
|
||
# StringField3 comment | ||
# second line | ||
# third line | ||
string3: string3 | ||
``` | ||
|
||
All subsequent lines and blocks after a `---` will be ignored. | ||
|
||
```go | ||
type Person struct { | ||
// Name of person | ||
// --- | ||
// The name of the person is both the first and last name separated | ||
// by a space character | ||
Name string | ||
} | ||
``` | ||
|
||
```yaml | ||
# Name of person | ||
Name: Frank | ||
``` | ||
|
||
Generator instructions prefixed with a `+` are ignored. | ||
|
||
```go | ||
type Dog struct { | ||
// Gender of dog (male|female) | ||
// +optional | ||
Gender string `json:"gender,omitempty"` | ||
// Weight in pounds of dog | ||
Weight int `json:"weight,omitempty"` | ||
} | ||
``` | ||
|
||
```yaml | ||
# Gender of dog (male|female) | ||
gender: male | ||
|
||
# Weight in pounds of dog | ||
weight: 150 | ||
``` | ||
|
||
## Example | ||
|
||
First, assume there is a Go file `config.go` with the following contents: | ||
> NOTE: `genyaml` reads **json** tags for maximum portability. | ||
```go | ||
// config.go | ||
|
||
package example | ||
|
||
type Configuration struct { | ||
// Plugin comment | ||
Plugin []Plugin `json:"plugin,omitempty"` | ||
} | ||
|
||
type Plugin struct { | ||
// StringField comment | ||
StringField string `json:"string,omitempty"` | ||
// BooleanField comment | ||
BooleanField bool `json:"boolean,omitempty"` | ||
// IntegerField comment | ||
IntegerField int `json:"integer,omitempty"` | ||
} | ||
//... | ||
``` | ||
|
||
Next, in a separate `example.go` file, initialize a `Configuration` struct and marshal it to *commented* YAML. | ||
|
||
```go | ||
// example.go | ||
|
||
package example | ||
|
||
// Import genyaml | ||
import "k8s.io/test-infra/pkg/genyaml" | ||
|
||
//... | ||
|
||
// Initialize a `Configuration` struct: | ||
config := &example.Configuration{ | ||
Plugin: []example.Plugin{ | ||
{ | ||
StringField: "string", | ||
BooleanField: true, | ||
IntegerField: 1, | ||
}, | ||
}, | ||
} | ||
|
||
// Initialize a CommentMap instance from the `config.go` source file: | ||
cm := genyaml.NewCommentMap("config.go") | ||
|
||
// Generate a commented YAML snippet: | ||
yamlSnippet, err := cm.GenYaml(config) | ||
``` | ||
|
||
The doc comments are extracted from the `config.go` file and attached to the corresponding YAML fields: | ||
|
||
```go | ||
fmt.Println(yamlSnippet) | ||
``` | ||
|
||
```yaml | ||
# Plugin comment | ||
plugin: | ||
- # BooleanField comment | ||
boolean: true | ||
|
||
# IntegerField comment | ||
integer: 1 | ||
|
||
# StringField comment | ||
string: string | ||
|
||
``` | ||
|
||
## Limitations / Going Forward | ||
|
||
- [ ] Embedded structs must include a tag name (i.e. must not be *spread*), otherwise the type can not be inferred from the YAML output. | ||
- [ ] Interface types, more specifically concrete types implementing a particular interface, can can not be inferred from the YAML output. | ||
- [ ] Upstream this functionality to `go-yaml` (or a fork) to leverage custom formatting of YAML and direct reflection on types for resolving embedded structs and interface types across multiple source files. |
Oops, something went wrong.