设为首页 加入收藏

TOP

Go 终极指南:编写一个 Go 工具(五)
2017-10-28 06:06:45 】 浏览:1122
Tags:终极 指南 编写 一个 工具
s: make([]error, 0)} rewriteFunc := func(n ast.Node) bool { // rewrite the node ... } if len(errs.errs) == 0 { return node, nil } ast.Inspect(node, rewriteFunc) return node, errs }

正如你所看到的,我们再次使用 ast.Inspect() 来逐步处理给定节点的树。我们重写 rewriteFunc 函数中的每个字段的标签(更多内容在后面)。

因为传递给 ast.Inspect() 的函数不会返回错误,因此我们将创建一个错误映射(使用 errs 变量定义),之后在我们逐步遍历树并处理每个单独的字段时收集错误。现在让我们来谈谈 rewriteFunc 的内部原理:

rewriteFunc := func(n ast.Node) bool {
    x, ok := n.(*ast.StructType)
    if !ok {
        return true
    }

    for _, f := range x.Fields.List {
        line := c.fset.Position(f.Pos()).Line

        if !(start <= line && line <= end) {
            continue
        }

        if f.Tag == nil {
            f.Tag = &ast.BasicLit{}
        }

        fieldName := ""
        if len(f.Names) != 0 {
            fieldName = f.Names[0].Name
        }

        // anonymous field
        if f.Names == nil {
            ident, ok := f.Type.(*ast.Ident)
            if !ok {
                continue
            }

            fieldName = ident.Name
        }

        res, err := c.process(fieldName, f.Tag.Value)
        if err != nil {
            errs.Append(fmt.Errorf("%s:%d:%d:%s",
                c.fset.Position(f.Pos()).Filename,
                c.fset.Position(f.Pos()).Line,
                c.fset.Position(f.Pos()).Column,
                err))
            continue
        }

        f.Tag.Value = res
    }

    return true
}

记住,AST 树中的每一个节点都会调用这个函数。因此,我们只寻找类型为 *ast.StructType 的节点。一旦我们拥有,就可以开始迭代结构体字段。

这里我们使用 startend 变量。这定义了我们是否要修改该字段。如果字段位置位于 start-end 之间,我们将继续,否则我们将忽略:

if !(start <= line && line <= end) {
    continue // skip processing the field
}

接下来,我们检查是否存在标签。如果标签字段为空(也就是 nil),则初始化标签字段。这在有助于后面的 cfg.process() 函数避免 panic:

if f.Tag == nil {
    f.Tag = &ast.BasicLit{}
}

现在让我先解释一下一个有趣的地方,然后再继续。gomodifytags 尝试获取字段的字段名称并处理它。然而,当它是一个匿名字段呢?:

type Bar string

type Foo struct {
    Bar //this is an anonymous field
}

在这种情况下,因为没有字段名称,我们尝试从类型名称中获取字段名称

// if there is a field name use it
fieldName := ""
if len(f.Names) != 0 {
    fieldName = f.Names[0].Name
}

// if there is no field name, get it from type's name
if f.Names == nil {
    ident, ok := f.Type.(*ast.Ident)
    if !ok {
        continue
    }

    fieldName = ident.Name
}

一旦我们获得了字段名称和标签值,就可以开始处理该字段。cfg.process() 函数负责处理有字段名称和标签值(如果有的话)的字段。在它返回处理结果后(在我们的例子中是 struct tag 格式),我们使用它来覆盖现有的标签值:

res, err := c.process(fieldName, f.Tag.Value)
if err != nil {
    errs.Append(fmt.Errorf("%s:%d:%d:%s",
        c.fset.Position(f.Pos()).Filename,
        c.fset.Position(f.Pos()).Line,
        c.fset.Position(f.Pos()).Column,
        err))
    continue
}

// rewrite the field with the new result,i.e: json:"foo"
f.Tag.Value = res

实际上,如果你记得 structtag,它返回标签实例的 String() 表述。在我们返回标签的最终表述之前,我们根据需要使用 structtag 包的各种方法修改结构体。以下是一个简单的说明图示:

用 structtag 包修改每个字段

例如,我们要扩展 process() 中的 removeTags() 函数。此功能使用以下配置来创建要删除的标签数组(键名称):

flagRemoveTags = flag.String("remove-tags", "", "Remove tags for the comma separated list of keys")

if *flagRemoveTags != "" {
    cfg.remove = strings.Split(*flagRemoveTags, ",")
}

removeTags() 中,我们检查是否使用了 --remove-tags。如果有,我们将使用 structtag 的 tags.Delete() 方法来删除标签:

func (c *config) removeTags(tags *structtag.Tags) *structtag.Tags {
    if c.remove == nil || len(c.remove) == 0 {
        return tags
    }

    tags.Delete(c.remove...)
    return tags
}

此逻辑同样适用于 cfg.Process() 中的所有函数。


我们已经有了一个重写的节点,让我们来讨论最后一个话题。输出和格式化结果:

在 main 函数中,我们将使用上一步重写的节点来调用 cfg.format() 函数:

func main() {
    // ... rewrite the node

    out, err := cfg.format(rewrittenNode, errs)
    if err != nil {
        return err
    }

    fmt.Println(out)
}

您需要注意的一

首页 上一页 2 3 4 5 6 下一页 尾页 5/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇go 代码的调试---打印调用堆栈 下一篇程序员如何打造属于自己的云笔记..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目