en"
)
func main() {
src := `package main
type Example struct {
Foo string` + " `json:\"foo\"` }"
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "demo", src, parser.ParseComments)
if err != nil {
panic(err)
}
ast.Inspect(file, func(x ast.Node) bool {
s, ok := x.(*ast.StructType)
if !ok {
return true
}
for _, field := range s.Fields.List {
fmt.Printf("Field: %s\n", field.Names[0].Name)
fmt.Printf("Tag: %s\n", field.Tag.Value)
}
return false
})
}
输出结果:
Field: Foo
Tag: `json:"foo"`
代码执行以下操作:
- 我们使用一个单独的结构体定义了一个 Go 包示例
- 我们使用 go/parser 包来解析这个字符串。
parser 包也可以从磁盘读取文件(或整个包)。
- 在解析后,我们处理了节点(分配给变量文件)并查找由 ast.StructType 定义的 AST 节点(参考 AST 图)。通过
ast.Inspect() 函数完成树的处理。它会遍历所有节点,直到它收到 false 值。 这是非常方便的,因为它不需要知道每个节点。
- 我们打印了结构体的字段名称和结构体标签。
我们现在可以做两件重要的事,首先,我们知道了如何解析一个 Go 源文件并检索结构体标签(通过 go/parser )。其次,我们知道了如何解析 Go 结构体标签,并根据需要进行修改(通过 github.com/fatih/structtag)。
我们有了这些,现在可以通过使用这两个知识点开始构建我们的工具(命名为 gomodifytags)。该工具应按顺序执行以下操作
- 获取配置,用于告诉我们要修改哪个结构体
- 根据配置查找和修改结构体
- 输出结果
由于 gomodifytags 将主要应用于编辑器,我们将通过 CLI 标志传入配置。第二步包含多个步骤,如解析文件,找到正确的结构体,然后修改结构体(通过修改 AST)。最后,我们将结果输出,无论结果的格式是原始的 Go 源文件还是某种自定义协议(如 JSON,稍后再说)。
以下是简化版 gomodifytags 的主要功能:
让我们更详细地解释每一个步骤。为了简单起见,我将尝试以概括的形式来解释重要部分。 原理都一样,一旦你读完这篇博文,你将能够在没有任何指导情况下阅整个源码(指南末尾附带了所有资源)
让我们从第一步开始,了解如何获取配置。以下是我们的配置,包含所有必要的信息
type config struct {
// first section - input & output
file string
modified io.Reader
output string
write bool
// second section - struct selection
offset int
structName string
line string
start, end int
// third section - struct modification
remove []string
add []string
override bool
transform string
sort bool
clear bool
addOpts []string
removeOpts []string
clearOpt bool
}
它分为三个主要部分:
第一部分包含有关如何读取和读取哪个文件的设置。这可以是本地文件系统的文件名,也可以直接来自 stdin(主要用在编辑器中)。 它还设置如何输出结果(go 源文件或 JSON),以及是否应该覆盖文件而不是输出到 stdout。
第二部分定义了如何选择一个结构体及其字段。有多种方法可以做到这一点。 我们可以通过它的偏移(光标位置)、结构体名称、一行单行(仅选择字段)或一系列行来定义它。最后,我们无论如何都得到开始行/结束行。例如在下面的例子中,您可以看到,我们使用它的名字来选择结构体,然后提取开始行和结束行以选择正确的字段:
如果是用于编辑器,则最好使用字节偏移量。例如下面你可以发现我们的光标刚好在 port 字段名称后面,从那里我们可以很容易地得到开始行/结束行:
配置中的第三个部分实际上是一个映射到 structtag 包的一对一映射。它基本上允许我们在读取字段后将配置传给 structtag 包。 如你所知,structtag 包允许我们解析一个结构体标签并对各个部分进行修改。但它不会覆盖或更新结构体字段。
我们如何获得配置?我们只需使用 flag 包,然后为配置中的每个字段创建一个标志,然后分配它们。举个例子:
flagFile := flag.String("file", "", "Filename to be parsed")
cfg := &config{
file: *flagFile,
}
我们对配置中的每个字段执行相同操作。有关完整内容,请查看 gomodifytag 当前 master 分支的标志定义
一旦我们有了配置,就可以做些基本的验证:
func main() {
cfg := config{ ... }
err := cfg.validate()
if err != nil {
log.Fatalln(err)
}
// continue parsing
}
// validate validates whether the config is valid or not
func (c *config) validate() error {
if c.file == "" {
return errors.New("no file is passed")
}
if c.line == "" && c.offset == 0 && c.structName == "" {
return errors.New("-line, -offset or -struct is not passed")
}
if c.line != "" && c.offset != 0 ||
c.line != "" && c.structName != "" ||
c.offset != 0 && c.structName != "" {
return errors.New("-line, -offset or -struct cannot be used together. pick one")
}
if (c.add == nil || len(c.add) == 0) &&
(c.addOpt
|