设为首页 加入收藏

TOP

Go 终极指南:编写一个 Go 工具(二)
2017-10-28 06:06:45 】 浏览:1126
Tags:终极 指南 编写 一个 工具
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
首页 上一页 1 2 3 4 5 6 下一页 尾页 2/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇go 代码的调试---打印调用堆栈 下一篇程序员如何打造属于自己的云笔记..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目