Skip to content

Commit

Permalink
Merge pull request #13637 from clarketm/genyaml
Browse files Browse the repository at this point in the history
Create `genyaml` library to generate commented YAML from Go structs.
  • Loading branch information
k8s-ci-robot committed Aug 14, 2019
2 parents 9501e29 + ecfe6c9 commit 9981dc3
Show file tree
Hide file tree
Showing 68 changed files with 13,222 additions and 0 deletions.
1 change: 1 addition & 0 deletions BUILD.bazel
Expand Up @@ -119,6 +119,7 @@ filegroup(
"//metrics:all-srcs",
"//pkg/benchmarkjunit:all-srcs",
"//pkg/flagutil:all-srcs",
"//pkg/genyaml:all-srcs",
"//pkg/ghclient:all-srcs",
"//pkg/io:all-srcs",
"//prow:all-srcs",
Expand Down
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -99,6 +99,7 @@ require (
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107
google.golang.org/grpc v1.19.1
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -498,6 +498,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22 h1:0efs3hwEZhFKsCoP8l6dDB1AZWMgnEl3yWXWRZTOaEA=
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
80 changes: 80 additions & 0 deletions pkg/genyaml/BUILD.bazel
@@ -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"],
)
190 changes: 190 additions & 0 deletions pkg/genyaml/README.md
@@ -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.

0 comments on commit 9981dc3

Please sign in to comment.