From 44bbc2c03f876a8a6edbb5ad757ef4958d2c9a85 Mon Sep 17 00:00:00 2001 From: cavebeavis Date: Sat, 14 Aug 2021 14:49:03 -0400 Subject: [PATCH 1/2] feat(template): Add envcase like `ENV_VARIABLE` - add envcase to addTags, the flag description, README.md, and the test cases - BREAKING CHANGE: bump go version to 1.16 - update dependencies --- README.md | 3 ++- go.mod | 6 +++--- go.sum | 29 +++++++++++++++++++++++++-- main.go | 15 ++++++++++---- main_test.go | 10 +++++++++ test-fixtures/line_envcase_add.golden | 7 +++++++ test-fixtures/line_envcase_add.input | 7 +++++++ 7 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 test-fixtures/line_envcase_add.golden create mode 100644 test-fixtures/line_envcase_add.input diff --git a/README.md b/README.md index 4342cb1..23bb787 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ prefixing them (`field_name=`). The `--template` flag allows you to specify a custom format for the tag value to be applied. ``` -$ gomodifytags -file demo.go -struct Server -add-tags gaum -template "field_name=$field" +$ gomodifytags -file demo.go -struct Server -add-tags gaum -template 'field_name=$field' ``` ```go @@ -183,6 +183,7 @@ The `$field` is a special keyword that is replaced by the struct tag's value We currently support the following transformations: * `snakecase`: `"BaseDomain"` -> `"base_domain"` +* `envcase`: `"BaseDomain"` -> `"BASE_DOMAIN"` * `camelcase`: `"BaseDomain"` -> `"baseDomain"` * `lispcase`: `"BaseDomain"` -> `"base-domain"` * `pascalcase`: `"BaseDomain"` -> `"BaseDomain"` diff --git a/go.mod b/go.mod index 1a60d8e..dd23e4e 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/fatih/gomodifytags +go 1.16 + require ( github.com/fatih/camelcase v1.0.0 github.com/fatih/structtag v1.2.0 - golang.org/x/tools v0.0.0-20180824175216-6c1c5e93cdc1 + golang.org/x/tools v0.1.5 ) - -go 1.13 diff --git a/go.sum b/go.sum index 3869a39..c7712f8 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,30 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8 github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -golang.org/x/tools v0.0.0-20180824175216-6c1c5e93cdc1 h1:EAPsk8kfGCjxQagrkWjzXlUWe2p3gj5MknO+z2o9GKc= -golang.org/x/tools v0.0.0-20180824175216-6c1c5e93cdc1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go index 5ff7f68..000d0b2 100644 --- a/main.go +++ b/main.go @@ -72,13 +72,13 @@ type config struct { } func main() { - if err := realMain(); err != nil { + if err := run(); err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } } -func realMain() error { +func run() error { cfg, err := parseConfig(os.Args[1:]) if err != nil { if err == flag.ErrHelp { @@ -150,13 +150,13 @@ func parseConfig(args []string) (*config, error) { flagSkipUnexportedFields = flag.Bool("skip-unexported", false, "Skip unexported fields") flagTransform = flag.String("transform", "snakecase", "Transform adds a transform rule when adding tags."+ - " Current options: [snakecase, camelcase, lispcase, pascalcase, keep]") + " Current options: [snakecase, envcase, camelcase, lispcase, pascalcase, keep]") flagSort = flag.Bool("sort", false, "Sort sorts the tags in increasing order according to the key name") // formatting flagFormatting = flag.String("template", "", - "Format the given tag's value. i.e: \"column:$field\", \"field_name=$field\"") + "Format the given tag's value. i.e: 'column:$field', 'field_name=$field'") // option flags flagRemoveOptions = flag.String("remove-options", "", @@ -390,6 +390,13 @@ func (c *config) addTags(fieldName string, tags *structtag.Tags) (*structtag.Tag } name = strings.Join(lowerSplitted, "_") + case "envcase": + var upperSplitted []string + for _, s := range splitted { + upperSplitted = append(upperSplitted, strings.ToUpper(s)) + } + + name = strings.Join(upperSplitted, "_") case "lispcase": var lowerSplitted []string for _, s := range splitted { diff --git a/main_test.go b/main_test.go index ac503be..962714d 100644 --- a/main_test.go +++ b/main_test.go @@ -302,6 +302,16 @@ func TestRewrite(t *testing.T) { transform: "camelcase", }, }, + { + file: "line_envcase_add", + cfg: &config{ + add: []string{"conf"}, + output: "source", + line: "4,5", + transform: "envcase", + valueFormat: "env:$field", + }, + }, { file: "line_value_add", cfg: &config{ diff --git a/test-fixtures/line_envcase_add.golden b/test-fixtures/line_envcase_add.golden new file mode 100644 index 0000000..e0ab220 --- /dev/null +++ b/test-fixtures/line_envcase_add.golden @@ -0,0 +1,7 @@ +package foo + +type foo struct { + bar string `conf:"env:BAR"` + MyExample bool `conf:"env:MY_EXAMPLE"` + MyAnother []string +} diff --git a/test-fixtures/line_envcase_add.input b/test-fixtures/line_envcase_add.input new file mode 100644 index 0000000..77c7967 --- /dev/null +++ b/test-fixtures/line_envcase_add.input @@ -0,0 +1,7 @@ +package foo + +type foo struct { + bar string + MyExample bool + MyAnother []string +} From 0f039b85d21b20cbe1496d26dc1f426624399ca8 Mon Sep 17 00:00:00 2001 From: cavebeavis Date: Sat, 14 Aug 2021 15:37:12 -0400 Subject: [PATCH 2/2] feat(env variables): Add support for printing env templates and modified envcase - add `-print-envs` flag which defaults to false in order to print an env template file - add `-envs-filename` flag to change the default `./.env` file location - modified envcase to add the struct name at the beginning of the env like: `STRUCT_ENV_VARIABLE` - modified test cases - modified README.md - minor spelling change -- splitted to split - changed main.go ln 676 from errors.New(fmt.Sprintf(...)) to fmt.Errorf(...) to make the linter a happy camper --- README.md | 12 ++- main.go | 119 ++++++++++++++++++++------ test-fixtures/line_envcase_add.golden | 4 +- 3 files changed, 106 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 23bb787..3e5febf 100644 --- a/README.md +++ b/README.md @@ -379,7 +379,7 @@ type Server struct { } ``` -Lastly, to remove all options without explicitly defining the keys and names, +To remove all options without explicitly defining the keys and names, we can use the `-clear-options` flag. The following example will remove all options for the given struct: @@ -401,6 +401,16 @@ type Server struct { } ``` +**Environment Variables** + +In order to print an `.env` file template, use the `-print-envs` flag to default +to `./.env` or add `-envs-filename /path/to/file` and it will create a new file +with the path and name specified. + +```bash +$ gomodifytags -file test-fixtures/all_structs.input -all -add-tags conf -template 'env:$field,noprint' -transform envcase -envs-filename 'path/to/.env' -print-envs +``` + ## Line based modification So far all examples used the `-struct` flag. However we also can pass the line diff --git a/main.go b/main.go index 000d0b2..ad2b6c1 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,9 @@ type config struct { valueFormat string clear bool clearOption bool + + printEnvFile bool + printEnvFileName string } func main() { @@ -114,6 +117,12 @@ func run() error { return err } + if cfg.printEnvFile { + if err := cfg.printEnvs(out); err != nil { + return err + } + } + fmt.Println(out) return nil } @@ -166,6 +175,12 @@ func parseConfig(args []string) (*config, error) { "Clear all tag options") flagAddOptions = flag.String("add-options", "", "Add the options per given key. i.e: json=omitempty,hcl=squash") + + // print envfile template + flagPrintEnvFile = flag.Bool("print-envs", false, + "Prints the environmental variables in default file '.env' for use as an env template") + flagPrintEnvFileName = flag.String("envs-filename", ".env", + "Filename where this should save the environmental variable template -- note will truncate file") ) // this fails if there are flags re-defined with the same name. @@ -195,6 +210,8 @@ func parseConfig(args []string) (*config, error) { valueFormat: *flagFormatting, override: *flagOverride, skipUnexportedFields: *flagSkipUnexportedFields, + printEnvFile: *flagPrintEnvFile, + printEnvFileName: *flagPrintEnvFileName, } if *flagModified { @@ -255,7 +272,7 @@ func (c *config) findSelection(node ast.Node) (int, int, error) { } } -func (c *config) process(fieldName, tagVal string) (string, error) { +func (c *config) process(structName, fieldName, tagVal string) (string, error) { var tag string if tagVal != "" { var err error @@ -279,7 +296,7 @@ func (c *config) process(fieldName, tagVal string) (string, error) { tags = c.clearTags(tags) tags = c.clearOptions(tags) - tags, err = c.addTags(fieldName, tags) + tags, err = c.addTags(structName, fieldName, tags) if err != nil { return "", err } @@ -373,40 +390,40 @@ func (c *config) addTagOptions(tags *structtag.Tags) (*structtag.Tags, error) { return tags, nil } -func (c *config) addTags(fieldName string, tags *structtag.Tags) (*structtag.Tags, error) { +func (c *config) addTags(structName, fieldName string, tags *structtag.Tags) (*structtag.Tags, error) { if c.add == nil || len(c.add) == 0 { return tags, nil } - splitted := camelcase.Split(fieldName) + split := camelcase.Split(fieldName) name := "" unknown := false switch c.transform { case "snakecase": - var lowerSplitted []string - for _, s := range splitted { - lowerSplitted = append(lowerSplitted, strings.ToLower(s)) + var lowerSplit []string + for _, s := range split { + lowerSplit = append(lowerSplit, strings.ToLower(s)) } - name = strings.Join(lowerSplitted, "_") + name = strings.Join(lowerSplit, "_") case "envcase": - var upperSplitted []string - for _, s := range splitted { - upperSplitted = append(upperSplitted, strings.ToUpper(s)) + upperSplit := []string{strings.ToUpper(structName)} + for _, s := range split { + upperSplit = append(upperSplit, strings.ToUpper(s)) } - name = strings.Join(upperSplitted, "_") + name = strings.Join(upperSplit, "_") case "lispcase": - var lowerSplitted []string - for _, s := range splitted { - lowerSplitted = append(lowerSplitted, strings.ToLower(s)) + var lowerSplit []string + for _, s := range split { + lowerSplit = append(lowerSplit, strings.ToLower(s)) } - name = strings.Join(lowerSplitted, "-") + name = strings.Join(lowerSplit, "-") case "camelcase": var titled []string - for _, s := range splitted { + for _, s := range split { titled = append(titled, strings.Title(s)) } @@ -415,7 +432,7 @@ func (c *config) addTags(fieldName string, tags *structtag.Tags) (*structtag.Tag name = strings.Join(titled, "") case "pascalcase": var titled []string - for _, s := range splitted { + for _, s := range split { titled = append(titled, strings.Title(s)) } @@ -431,10 +448,10 @@ func (c *config) addTags(fieldName string, tags *structtag.Tags) (*structtag.Tag } for _, key := range c.add { - splitted = strings.SplitN(key, ":", 2) - if len(splitted) >= 2 { - key = splitted[0] - name = strings.Join(splitted[1:], "") + split = strings.SplitN(key, ":", 2) + if len(split) >= 2 { + key = split[0] + name = strings.Join(split[1:], "") } else if unknown { // the user didn't pass any value but want to use an unknown // transform. We don't return above in the default as the user @@ -463,7 +480,7 @@ func (c *config) addTags(fieldName string, tags *structtag.Tags) (*structtag.Tag // collectStructs collects and maps structType nodes to their positions func collectStructs(node ast.Node) map[token.Pos]*structType { - structs := make(map[token.Pos]*structType, 0) + structs := make(map[token.Pos]*structType) collectStructs := func(n ast.Node) bool { var t ast.Expr @@ -656,8 +673,8 @@ func (c *config) fieldSelection(st *ast.StructType) (int, int, error) { } if encField == nil { - return 0, 0, errors.New(fmt.Sprintf("struct %q doesn't have field name %q", - c.structName, c.fieldName)) + return 0, 0, fmt.Errorf("struct %q doesn't have field name %q", + c.structName, c.fieldName) } start := c.fset.Position(encField.Pos()).Line @@ -710,8 +727,14 @@ func isPublicName(name string) bool { // positions func (c *config) rewrite(node ast.Node, start, end int) (ast.Node, error) { errs := &rewriteErrors{errs: make([]error, 0)} + + var structName string rewriteFunc := func(n ast.Node) bool { + if typeSpec, ok := n.(*ast.TypeSpec); ok { + structName = typeSpec.Name.Name + } + x, ok := n.(*ast.StructType) if !ok { return true @@ -755,7 +778,7 @@ func (c *config) rewrite(node ast.Node, start, end int) (ast.Node, error) { f.Tag = &ast.BasicLit{} } - res, err := c.process(fieldName, f.Tag.Value) + res, err := c.process(structName, fieldName, f.Tag.Value) if err != nil { errs.Append(fmt.Errorf("%s:%d:%d:%s", c.fset.Position(f.Pos()).Filename, @@ -910,3 +933,47 @@ func deref(x ast.Expr) ast.Expr { } return x } + +func (c *config) printEnvs(in string) error { + split := strings.Split(in, "\n") + + // Looking for something in the pattern of + // Field {type} `conf:"env:ENV_VARIABLE,no-print"` + // to extract the ENV_VARIABLE part. + var envs []string + for _, val := range split { + match := "env:" + idx := strings.Index(val, match) + if idx != -1 { + val = val[idx + len(match):] + + // Find the ending double quote to grab from example: ENV_VARIABLE,noprint. + idx = strings.Index(val, "\"") + if idx != -1 { + val = val[:idx] + + // Find if there is a comma "," from example: ENV_VARIABLE. + idx = strings.Index(val, ",") + if idx != -1 { + val = val[:idx] + } + + // Make sure there are no empty spaces in the environmental variable. + val = strings.TrimSpace(val) + + env := val + "=" + envs = append(envs, env) + } + } + } + + f, err := os.Create(c.printEnvFileName) + if err != nil { + return err + } + defer f.Close() + + _, err = f.WriteString(strings.Join(envs, "\n")) + + return err +} \ No newline at end of file diff --git a/test-fixtures/line_envcase_add.golden b/test-fixtures/line_envcase_add.golden index e0ab220..796fd10 100644 --- a/test-fixtures/line_envcase_add.golden +++ b/test-fixtures/line_envcase_add.golden @@ -1,7 +1,7 @@ package foo type foo struct { - bar string `conf:"env:BAR"` - MyExample bool `conf:"env:MY_EXAMPLE"` + bar string `conf:"env:FOO_BAR"` + MyExample bool `conf:"env:FOO_MY_EXAMPLE"` MyAnother []string }