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

Generate YAML from Go struct #60

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

saswatamcode
Copy link
Collaborator

@saswatamcode saswatamcode commented Jul 12, 2021

This PR allows mdox to generate YAML from Go structs, using the following semantics(for now).
Thus, structs like below,

type Config struct {
	Version int `yaml:"version"`

	Validator []ValidatorConfig `yaml:"validators"`
	Ignore []IgnoreConfig `yaml:"ignore"`
}

type ValidatorConfig struct {
	Type  string `yaml:"type"`
	Regex string `yaml:"regex"`
	Token string `yaml:"token"`

	r *regexp.Regexp
}

type IgnoreConfig struct {
	Url   string `yaml:"url"`
	ID    int    `yaml:"id"`
	Token string `yaml:"token"`
}

Result in markdown like below,

```yaml mdox-gen-go-struct="main.go:Config"
version: 0
validators:
    - type: ""
      regex: ""
      token: ""
ignore:
    - url: ""
      id: 0
      token: ""
```yaml mdox-gen-go-struct="main.go:ValidatorConfig"
type: ""
regex: ""
token: ""

Dependencies added: jennifer, structtag

Few TODOs:

// TODO(saswatamcode): Add tests.
// TODO(saswatamcode): Check jennifer code for some safety.
// TODO(saswatamcode): Add mechanism for caching output from generated code.
// TODO(saswatamcode): Better errors.

Resolves #23.

Copy link
Owner

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's some next level magic

AI programmer!

LGTM, just let's add tests 👍🏽

// TODO(saswatamcode): Currently takes file names, need to make it module based(something such as https://golang.org/pkg/cmd/go/internal/list/).

// GenGoCode generates Go code for yaml gen from structs in src file.
func GenGoCode(src []byte) (string, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good for start, but I wonder if we can scope to just one desired structure, to limit imports. Maybe it's later optimization - so fine for now

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add todo for that?

init = append(init, jen.Id("configs").Op(":=").Map(jen.String()).Interface().Values())

// Loop through declarations in file.
for _, decl := range f.Decls {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would expect some recursion here (:

Comment on lines 112 to 114
generatedCode.Func().Id("main").Params().Block(
init...,
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
generatedCode.Func().Id("main").Params().Block(
init...,
)
generatedCode.Func().Id("main").Params().Block(init...)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

cmd = exec.CommandContext(ctx, "go", "mod", "tidy")
cmd.Dir = tmpDir
if err := cmd.Run(); err != nil {
return nil, errors.Wrapf(err, "run %v", cmd)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nil, errors.Wrapf(err, "run %v", cmd)
return nil, errors.Wrapf(err, "mod %v", cmd)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

cmd := exec.CommandContext(ctx, "go", "mod", "init", "structgen")
cmd.Dir = tmpDir
if err := cmd.Run(); err != nil {
return nil, errors.Wrapf(err, "run %v", cmd)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return nil, errors.Wrapf(err, "run %v", cmd)
return nil, errors.Wrapf(err, "mod init %v", cmd)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

return checkForOmitEmptyTagOptionRec(reflect.ValueOf(obj))
}

func checkForOmitEmptyTagOptionRec(v reflect.Value) error {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check for emit empty if we fill all the structs? I think in this case we need to also set non default values for other things that slices... so maybe for next iteration

Copy link
Owner

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, let's add recursion and understand what map will work, what not. 👍🏽

// TODO(saswatamcode): Currently takes file names, need to make it module based(something such as https://golang.org/pkg/cmd/go/internal/list/).

// GenGoCode generates Go code for yaml gen from structs in src file.
func GenGoCode(src []byte) (string, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add todo for that?

})

t.Run("struct with unexported field", func(t *testing.T) {
source := []byte("package main\n\nimport \"regexp\"\n\ntype ValidatorConfig struct {\n\tType string `yaml:\"type\"`\n\tRegex string `yaml:\"regex\"`\n\tToken string `yaml:\"token\"`\n\n\tr *regexp.Regexp\n}\n")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use ` and add + backtick + , something like that. Still easier than long string manually crafted.

Another idea - put all in files - that would do as well.

@Dentrax
Copy link

Dentrax commented May 10, 2022

Any update on this? @bwplotka

@bwplotka
Copy link
Owner

I think @saswatamcode has some comments to be addressed, but quite close.

What do you need this for @Dentrax? Curious about your use case and if this PR fixes it (:

@Dentrax
Copy link

Dentrax commented May 11, 2022

I've created a related issue thanos-io/thanos#4751 (comment) and closed due to inactivity. We want to add support for both comments and default values while generating YAML.

@Dentrax
Copy link

Dentrax commented Jan 26, 2023

Kind ping, still looking for this feature! @saswatamcode @bwplotka

@bwplotka
Copy link
Owner

bwplotka commented Jun 7, 2023

cc @saswatamcode

Signed-off-by: Saswata Mukherjee <[email protected]>
Signed-off-by: Saswata Mukherjee <[email protected]>
Signed-off-by: Saswata Mukherjee <[email protected]>
Signed-off-by: Saswata Mukherjee <[email protected]>
@saswatamcode
Copy link
Collaborator Author

I'm trying to look into this, this week and bring it to a nice usable state, rebasing and correcting some of the old code. Plan to clean it up in the next few commits! 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add subcommand that will generate example YAML that will parsable from Go structs
3 participants