1

GitHub - fatih/gomodifytags: Go tool to modify struct field tags

 1 year ago
source link: https://github.com/fatih/gomodifytags
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

gomodifytags

Go tool to modify/update field tags in structs. gomodifytags makes it easy to update, add or delete the tags in a struct field. You can easily add new tags, update existing tags (such as appending a new key, i.e: db, xml, etc..) or remove existing tags. It also allows you to add and remove tag options. It's intended to be used by an editor, but also has modes to run it from the terminal. Read the usage section below for more information.

Install

go get github.com/fatih/gomodifytags

Supported editors

  • vim-go with :GoAddTags and :GoRemoveTags
  • go-plus (atom) with commands golang:add-tags and golang:remove-tags
  • vscode-go with commands Go: Add Tags and Go: Remove Tags
  • A (Acme) with commands addtags and rmtags
  • emacs-go-tag with commands go-tag-add and go-tag-remove

Usage

gomodifytags has multiple ways to modify a tag. Let's start with an example package:

package main

type Server struct {
	Name        string
	Port        int
	EnableLogs  bool
	BaseDomain  string
	Credentials struct {
		Username string
		Password string
	}
}

We have to first pass a file. For that we can use the -file flag:

$ gomodifytags -file demo.go
-line, -offset, -struct or -all is not passed

What are these? There are four different ways of defining which field tags to change:

  • -struct: This accepts the struct name. i.e: -struct Server. The name should be a valid type name. The -struct flag selects the whole struct, and thus it will operate on all fields.
  • -field: This accepts a field name. i.e: -field Address. Useful to select a certain field. The name should be a valid field name. The -struct flag is required.
  • -offset: This accepts a byte offset of the file. Useful for editors to pass the position under the cursor. i.e: -offset 548. The offset has to be inside a valid struct. The -offset selects the whole struct. If you need more granular option see -line
  • -line: This accepts a string that defines the line or lines of which fields should be changed. I.e: -line 4 or -line 5,8
  • -all: This is a boolean. The -all flag selects all structs of the given file.

Let's continue by using the -struct tag:

$ gomodifytags -file demo.go -struct Server
one of [-add-tags, -add-options, -remove-tags, -remove-options, -clear-tags, -clear-options] should be defined

Adding tags & options

There are many options on how you can change the struct. Let us start by adding tags. The following will add the json key to all fields. The value will be automatically inherited from the field name and transformed to snake_case:

$ gomodifytags -file demo.go -struct Server -add-tags json
package main

type Server struct {
	Name        string `json:"name"`
	Port        int    `json:"port"`
	EnableLogs  bool   `json:"enable_logs"`
	BaseDomain  string `json:"base_domain"`
	Credentials struct {
		Username string `json:"username"`
		Password string `json:"password"`
	} `json:"credentials"`
}

By default changes will be printed to stdout and can be used for dry-run your changes before making destructive changes. If you want to change it permanently, pass the -w (write) flag.

$ gomodifytags -file demo.go -struct Server -add-tags json -w

You can disable printing the results to stdout with the --quiet flag:

$ gomodifytags -file demo.go -struct Server -add-tags json -w --quiet

You can pass multiple keys to add tags. The following will add json and xml keys:

$ gomodifytags -file demo.go -struct Server -add-tags json,xml
package main

type Server struct {
	Name        string `json:"name" xml:"name"`
	Port        int    `json:"port" xml:"port"`
	EnableLogs  bool   `json:"enable_logs" xml:"enable_logs"`
	BaseDomain  string `json:"base_domain" xml:"base_domain"`
	Credentials struct {
		Username string `json:"username" xml:"username"`
		Password string `json:"password" xml:"password"`
	} `json:"credentials" xml:"credentials"`
}

If you prefer to use camelCase instead of snake_case for the values, you can use the -transform flag to define a different transformation rule. The following example uses the camelcase transformation rule:

$ gomodifytags -file demo.go -struct Server -add-tags json,xml -transform camelcase
package main

type Server struct {
	Name        string `json:"name" xml:"name"`
	Port        int    `json:"port" xml:"port"`
	EnableLogs  bool   `json:"enableLogs" xml:"enableLogs"`
	BaseDomain  string `json:"baseDomain" xml:"baseDomain"`
	Credentials struct {
		Username string `json:"username" xml:"username"`
		Password string `json:"password" xml:"password"`
	} `json:"credentials" xml:"credentials"`
}

Formatting tag values

By default a struct tag's value is transformed from a struct's field and used directly. As an example for the field Server string, we generate a tag in the form: json:"server" (assuming -add-tags=json is used).

However, some third party libraries use tags in a different way and might require to them to have a particular formatting, such as is the case of 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}" 
package main

type Server struct {
	Name        string `gaum:"field_name=name"`
	Port        int    `gaum:"field_name=port"`
	EnableLogs  bool   `gaum:"field_name=enableLogs"`
	BaseDomain  string `gaum:"field_name=baseDomain"`
}

The {field} word is a special keyword that is replaced by the struct tag's value after the transformation.

Transformations

We currently support the following transformations:

  • snakecase: "BaseDomain" -> "base_domain"
  • camelcase: "BaseDomain" -> "baseDomain"
  • lispcase: "BaseDomain" -> "base-domain"
  • pascalcase: "BaseDomain" -> "BaseDomain"
  • titlecase: "BaseDomain" -> "Base Domain"
  • keep: keeps the original field name

You can also pass a static value for each fields. This is useful if you use Go packages that validates the struct fields or extract values for certain operations. The following example adds the json key, a validate key with the value set to gt=1 and the scope key with the value read-only:

$ gomodifytags -file demo.go -struct Server -add-tags json,validate:gt=1,scope:read-only
package main

type Server struct {
	Name        string `json:"name" validate:"gt=1" scope:"read-only"`
	Port        int    `json:"port" validate:"gt=1" scope:"read-only"`
	EnableLogs  bool   `json:"enable_logs" validate:"gt=1" scope:"read-only"`
	BaseDomain  string `json:"base_domain" validate:"gt=1" scope:"read-only"`
	Credentials struct {
		Username string `json:"username" validate:"gt=1" scope:"read-only"`
		Password string `json:"password" validate:"gt=1" scope:"read-only"`
	} `json:"credentials" validate:"gt=1" scope:"read-only"`
}

To add options to for a given key, we use the -add-options flag. In the example below we're going to add the json key and the omitempty option to all json keys:

$ gomodifytags -file demo.go -struct Server -add-tags json -add-options json=omitempty
package main

type Server struct {
	Name        string `json:"name,omitempty"`
	Port        int    `json:"port,omitempty"`
	EnableLogs  bool   `json:"enable_logs,omitempty"`
	BaseDomain  string `json:"base_domain,omitempty"`
	Credentials struct {
		Username string `json:"username,omitempty"`
		Password string `json:"password,omitempty"`
	} `json:"credentials,omitempty"`
}

If the key already exists you don't have to use -add-tags

Skipping unexported fields

By default all fields are processed. This main reason for this is to allow structs to evolve with time and be ready in case a field is exported in the future. However if you don't like this behavior, you can skip it by passing the --skip-unexported flag:

$ gomodifytags -file demo.go -struct Server -add-tags json --skip-unexported
package main

type Server struct {
        Name       string `json:"name"`
        Port       int    `json:"port"`
        enableLogs bool
        baseDomain string
}

Removing tags & options

Let's continue with removing tags. We're going to use the following simple package:

package main

type Server struct {
	Name        string `json:"name,omitempty" xml:"name,attr,cdata"`
	Port        int    `json:"port,omitempty" xml:"port,attr,cdata"`
	EnableLogs  bool   `json:"enable_logs,omitempty" xml:"enable_logs,attr,cdata"`
	BaseDomain  string `json:"base_domain,omitempty" xml:"base_domain,attr,cdata"`
	Credentials struct {
		Username string `json:"username,omitempty" xml:"username,attr,cdata"`
		Password string `json:"password,omitempty" xml:"password,attr,cdata"`
	} `json:"credentials,omitempty" xml:"credentials,attr,cdata"`
}

To remove the xml tags, we're going to use the -remove-tags flag:

$ gomodifytags -file demo.go -struct Server -remove-tags xml
package main

type Server struct {
	Name        string `json:"name"`
	Port        int    `json:"port"`
	EnableLogs  bool   `json:"enable_logs"`
	BaseDomain  string `json:"base_domain"`
	Credentials struct {
		Username string `json:"username"`
		Password string `json:"password"`
	} `json:"credentials"`
}

You can also remove multiple tags. The example below removs json and xml:

$ gomodifytags -file demo.go -struct Server -remove-tags json,xml
package main

type Server struct {
	Name        string
	Port        int
	EnableLogs  bool
	BaseDomain  string
	Credentials struct {
		Username string
		Password string
	}
}

If you want to remove all keys, we can also use the -clear-tags flag. This flag removes all tags and doesn't require to explicitly pass the key names:

$ gomodifytags -file demo.go -struct Server -clear-tags
package main

type Server struct {
	Name        string
	Port        int
	EnableLogs  bool
	BaseDomain  string
	Credentials struct {
		Username string
		Password string
	}
}

To remove any option, we can use the -remove-options flag. The following will remove all omitempty flags from the json key:

$ gomodifytags -file demo.go -struct Server -remove-options json=omitempty
package main

type Server struct {
	Name        string `json:"name" xml:"name,attr,cdata"`
	Port        int    `json:"port" xml:"port,attr,cdata"`
	EnableLogs  bool   `json:"enable_logs" xml:"enable_logs,attr,cdata"`
	BaseDomain  string `json:"base_domain" xml:"base_domain,attr,cdata"`
	Credentials struct {
		Username string `json:"username" xml:"username,attr,cdata"`
		Password string `json:"password" xml:"password,attr,cdata"`
	} `json:"credentials" xml:"credentials,attr,cdata"`
}

To remove multiple options from multiple tags just add another options:

$ gomodifytags -file demo.go -struct Server -remove-options json=omitempty,xml=cdata
package main

type Server struct {
	Name        string `json:"name" xml:"name,attr"`
	Port        int    `json:"port" xml:"port,attr"`
	EnableLogs  bool   `json:"enable_logs" xml:"enable_logs,attr"`
	BaseDomain  string `json:"base_domain" xml:"base_domain,attr"`
	Credentials struct {
		Username string `json:"username" xml:"username,attr"`
		Password string `json:"password" xml:"password,attr"`
	} `json:"credentials" xml:"credentials,attr"`
}

Lastly, 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:

$ gomodifytags -file demo.go -struct Server -clear-options
package main

type Server struct {
	Name        string `json:"name" xml:"name"`
	Port        int    `json:"port" xml:"port"`
	EnableLogs  bool   `json:"enable_logs" xml:"enable_logs"`
	BaseDomain  string `json:"base_domain" xml:"base_domain"`
	Credentials struct {
		Username string `json:"username" xml:"username"`
		Password string `json:"password" xml:"password"`
	} `json:"credentials" xml:"credentials"`
}

Line based modification

So far all examples used the -struct flag. However we also can pass the line numbers to only change certain files. Suppose we only want to remove the tags for the Credentials struct (including the fields) for the following code (lines are included):

01  package main
02  
03  type Server struct {
04  	Name        string `json:"name" xml:"name"`
05  	Port        int    `json:"port" xml:"port"`
06  	EnableLogs  bool   `json:"enable_logs" xml:"enable_logs"`
07  	BaseDomain  string `json:"base_domain" xml:"base_domain"`
08  	Credentials struct {
09  		Username string `json:"username" xml:"username"`
10  		Password string `json:"password" xml:"password"`
11  	} `json:"credentials" xml:"credentials"`
12  }

To remove the tags for the credentials we're going to pass the -line flag:

$ gomodifytags -file demo.go -line 8,11 -clear-tags xml
package main

type Server struct {
	Name        string `json:"name" xml:"name"`
	Port        int    `json:"port" xml:"port"`
	EnableLogs  bool   `json:"enable_logs" xml:"enable_logs"`
	BaseDomain  string `json:"base_domain" xml:"base_domain"`
	Credentials struct {
		Username string
		Password string
	}
}

For removing the xml tags for certain lines, we can use the -remove-tags field. The following example will remove the xml tags for the lines 6 and 7 (fields with names of EnableLogs and BaseDomain):

$ gomodifytags -file demo.go -line 6,7 -remove-tags xml
package main

type Server struct {
	Name        string `json:"name" xml:"name"`
	Port        int    `json:"port" xml:"port"`
	EnableLogs  bool   `json:"enable_logs"`
	BaseDomain  string `json:"base_domain"`
	Credentials struct {
		Username string `json:"username" xml:"username"`
		Password string `json:"password" xml:"password"`
	} `json:"credentials" xml:"credentials"`
}

The same logic applies to adding tags or any other option as well. To add the bson tag to the lines between 5 and 7, we can use the following example:

$ gomodifytags -file demo.go -line 5,7 -add-tags bson
package main

type Server struct {
	Name        string `json:"name" xml:"name"`
	Port        int    `json:"port" xml:"port" bson:"port"`
	EnableLogs  bool   `json:"enable_logs" xml:"enable_logs" bson:"enable_logs"`
	BaseDomain  string `json:"base_domain" xml:"base_domain" bson:"base_domain"`
	Credentials struct {
		Username string `json:"username" xml:"username"`
		Password string `json:"password" xml:"password"`
	} `json:"credentials" xml:"credentials"`
}

Editor integration

Editors can use the tool by calling the tool and then either replace the buffer with the stdout or use the -w flag.

Also -line and -offset flags should be preferred to be used with editors. An editor can select a range of lines and then pass it to -line flag. The editor also can pass the offset under the cursor if it's inside the struct to -offset

Editors also can use the -format flag to output a json output with the changed lines. This is useful if you want to explicitly replace the buffer with the given lines. For the file below:

package main

type Server struct {
	Name        string
	Port        int
	EnableLogs  bool
	BaseDomain  string
	Credentials struct {
		Username string
		Password string
	}
}

If we add the xml tag and tell to output the format in json with the -format flag, the following will be printed:

$ gomodifytags -file demo.go -struct Server -add-tags xml -format json
{
  "start": 3,
  "end": 12,
  "lines": [
    "type Server struct {",
    "\tName        string `xml:\"name\"`",
    "\tPort        int    `xml:\"port\"`",
    "\tEnableLogs  bool   `xml:\"enable_logs\"`",
    "\tBaseDomain  string `xml:\"base_domain\"`",
    "\tCredentials struct {",
    "\t\tUsername string `xml:\"username\"`",
    "\t\tPassword string `xml:\"password\"`",
    "\t} `xml:\"credentials\"`",
    "}"
  ]
}

The output is defined with the following Go struct:

type output struct {
	Start int      `json:"start"`
	End   int      `json:"end"`
	Lines []string `json:"lines"`
}

The start and end specifies the positions in the file the lines will apply. With this information, you can replace the editor buffer by iterating over the lines and set it for the given range. An example how it's done in vim-go in Vimscript is:

let index = 0
for line_number in range(start, end)
  call setline(line_number, lines[index])
  let index += 1
endfor

Unsaved files

Editors can supply gomodifytags with the contents of unsaved buffers by using the -modified flag and writing an archive to stdin. Files in the archive will be preferred over those on disk.

Each archive entry consists of:

  • the file name, followed by a newline
  • the (decimal) file size, followed by a newline
  • the contents of the file

Development

At least Go v1.11.x is required. Older versions might work, but it's not recommended.

gomodifytags uses Go modules for dependency management. This means that you don't have to go get it into a GOPATH anymore. Checkout the repository:

git clone https://github.com/fatih/gomodifytags.git

Start developing the code. To build a binary, execute:

GO111MODULE=on go build -mod=vendor

This will create a gomodifytags binary in the current directory. To test the package, run the following:

GO111MODULE=on go test -v -mod=vendor

If everything works fine, feel free to open a pull request with your changes.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK