Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ prefixing them (`field_name=<your_value>`). 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
Expand All @@ -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"`
Expand Down Expand Up @@ -378,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:

Expand All @@ -400,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
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
29 changes: 27 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
126 changes: 100 additions & 26 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,19 @@ type config struct {
valueFormat string
clear bool
clearOption bool

printEnvFile bool
printEnvFileName string
}

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 {
Expand Down Expand Up @@ -114,6 +117,12 @@ func realMain() error {
return err
}

if cfg.printEnvFile {
if err := cfg.printEnvs(out); err != nil {
return err
}
}

fmt.Println(out)
return nil
}
Expand Down Expand Up @@ -150,13 +159,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", "",
Expand All @@ -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.
Expand Down Expand Up @@ -195,6 +210,8 @@ func parseConfig(args []string) (*config, error) {
valueFormat: *flagFormatting,
override: *flagOverride,
skipUnexportedFields: *flagSkipUnexportedFields,
printEnvFile: *flagPrintEnvFile,
printEnvFileName: *flagPrintEnvFileName,
}

if *flagModified {
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -373,33 +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(lowerSplit, "_")
case "envcase":
upperSplit := []string{strings.ToUpper(structName)}
for _, s := range split {
upperSplit = append(upperSplit, strings.ToUpper(s))
}

name = strings.Join(lowerSplitted, "_")
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))
}

Expand All @@ -408,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))
}

Expand All @@ -424,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
Expand Down Expand Up @@ -456,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
Expand Down Expand Up @@ -649,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
Expand Down Expand Up @@ -703,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
Expand Down Expand Up @@ -748,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,
Expand Down Expand Up @@ -903,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
}
10 changes: 10 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
7 changes: 7 additions & 0 deletions test-fixtures/line_envcase_add.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package foo

type foo struct {
bar string `conf:"env:FOO_BAR"`
MyExample bool `conf:"env:FOO_MY_EXAMPLE"`
MyAnother []string
}
7 changes: 7 additions & 0 deletions test-fixtures/line_envcase_add.input
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package foo

type foo struct {
bar string
MyExample bool
MyAnother []string
}