1 Star 0 Fork 0

天雨流芳 / go-micro-framework

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
main.go 9.35 KB
一键复制 编辑 原始数据 按行查看 历史
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/types"
"golang.org/x/tools/go/packages"
"log"
"os"
"path/filepath"
"strings"
)
func main() {
initParams()
// flag.Usage 是一个函数类型的变量,它定义了如何输出在用户输入无效参数时应该显示的消息
flag.Usage = Usage
// 解析命令行参数
flag.Parse()
fmt.Println(param.String())
if len(param.fileNames) == 0 {
flag.Usage()
os.Exit(2)
}
files := strings.Split(param.fileNames, ",")
// 转成相对路径
files = TransToRedirectPaths(files)
var tags []string
if len(param.buildTags) > 0 { // 只有携带了 tag 参数的文件才能编译运行 如 这样 // +build jsoniter
tags = strings.Split(param.buildTags, ",")
}
// 只需解析一次包
var dir string
g := Generator{}
if len(files) == 1 && isDirectory(files[0]) { // 如果只有一个字符串 并且是目录
dir = files[0]
} else {
dir = filepath.Dir(files[0]) // 尝试 返回上层目录
}
fmt.Println(dir)
g.parsePackage(files, tags) // args:目录 tags:应用的构建标记 // 对于每个类型运行生成代码
for _, file := range files {
g.generate(file)
}
var src []byte // 输出流临时存放
// Format the output.
src = g.format()
// Write to file.
outputName := param.output // 写入的文件路径
if outputName == "" { // 如果为空 则创建一个文件
// strings.ReplaceAll:替换所有 Base:返回path的最后一个元素
baseName := fmt.Sprintf("%s_init_registry.go", strings.ReplaceAll(filepath.Base(g.pkg.name), "-", "_"))
outputName = filepath.Join(dir, strings.ToLower(baseName))
}
err := os.WriteFile(outputName, src, 0600)
if err != nil {
log.Fatalf("writing output: %s", err)
}
}
var (
param = Param{
fileNames: "",
output: "",
buildTags: "",
must: true,
}
)
type Param struct {
fileNames string
output string
buildTags string
must bool
}
func (p *Param) String() string {
return fmt.Sprintf("fileNames: %s \noutput: %s \nbuildTags: %s \nmust: %t \n", p.fileNames, p.output, p.buildTags, p.must)
}
func initParams() {
flag.StringVar(&param.fileNames, "n", "", "需要生成的code的文件名称,多个用(,)隔开,必填字段")
flag.StringVar(&param.output, "o", "", "输出路径,默认为格式为:默认名称_init_registry.go")
flag.StringVar(&param.buildTags, "t", "", "应用的一组逗号分隔的构建标签")
flag.BoolVar(&param.must, "m", true, "是否必须注册成功,true调用MustRegistry方法")
}
func Usage() {
fmt.Fprintf(os.Stderr, "Usage of codegen:\n")
fmt.Fprintf(os.Stderr, "\tcode_init_gen [flags] -name T [directory]\n")
fmt.Fprintf(os.Stderr, "\tcode_init_gen [flags] -name T files... # Must be a single package\n")
fmt.Fprintf(os.Stderr, "Flags:\n")
}
// isDirectory 报告命名文件是否为目录
func isDirectory(name string) bool {
info, err := os.Stat(name)
if err != nil {
log.Fatal(err)
}
return info.IsDir() // 是否是 目录
}
// Generator 结构体用于保存代码分析状态。主要用于缓存输出结果以便通过 format.Source 进行格式化。
type Generator struct {
buf bytes.Buffer // 积累输出 output.
pkg *Package // Package 扫描包
}
// parsePackage 函数分析由给定的模式和标记构造的单个包
// 如果出现错误,parsePackage 函数将终止程序
func (g *Generator) parsePackage(patterns []string, tags []string) {
cfg := &packages.Config{
// packages.NeedSyntax:在加载包的元数据的基础上,还会加载包的源码文件,并解析成语法树
// packages.NeedTypes:在加载包的源码文件的基础上,还会对其进行类型检查,生成包的类型信息
// packages.NeedTypesInfo | packages.NeedImports | packages.NeedDeps: 加载完整的类型信息,包名信息和所有直接/间接依赖的包信息
Mode: packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedTypes | packages.NeedImports | packages.NeedDeps | packages.NeedName,
// in a separate pass? For later.
Tests: false, // 是否加载测试文件 默认值为 false 不加载
BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))}, //
}
pkgs, err := packages.Load(cfg, patterns...) // 函数返回的信息是一个 []*packages.Package 类型的切片,其中每个元素都表示一个被分析的包。每个 *packages.Package 类型的对象包含了对应包的语法树、类型信息、依赖关系等各种信息
if err != nil {
log.Fatal(err)
}
if len(pkgs) != 1 {
log.Fatalf("error: %d packages found", len(pkgs))
}
g.addPackage(pkgs[0]) // 获取 当前 main.go 解析的代码内容
}
// addPackage 函数将一个类型检查后的 Package 和它的语法文件添加到生成器中
func (g *Generator) addPackage(pkg *packages.Package) {
g.pkg = &Package{
name: pkg.Name, // 包名 code
defs: pkg.TypesInfo.Defs, // pkg.TypesInfo 包的类型信息
files: make([]*File, len(pkg.Syntax)), // pkg.Syntax 包下的的语法树
}
for i, file := range pkg.Syntax { // 遍历此包下的语法树 一个文件生成一个语法树
g.pkg.files[i] = &File{
file: file, // 此语法树 所有内容 指针
pkg: g.pkg, // 此包 所有内容 指针
}
}
}
// generate 会为指定的类型生成注册函数的代码
func (g *Generator) generate(typeName string) {
values := make([]string, 0, 100)
imports := make([]string, 0)
for _, file := range g.pkg.files { // 在 ast.File 上做了一层封装 遍历 pkg 下的所有 语法树
log.Printf("find file %s %d", file.file.Name, file.file.Name.NamePos)
// 设置本次 walker 的状态
file.typeName = typeName
file.values = nil
if file.file != nil { // 判断是否有语法树
ast.Inspect(file.file, file.genDecl)
values = append(values, file.values...)
imports = append(imports, file.imports...)
}
}
if len(values) == 0 {
log.Fatalf("no values defined for type %s", typeName)
}
// Generate code
g.Printf("// Code generated by \"codegen %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " "))
g.Printf("\n")
g.Printf("package %s", g.pkg.name)
g.Printf("\n")
if len(imports) > 1 {
g.Printf("import (")
for _, s := range imports {
g.Printf("%s\n", s)
}
g.Printf(")")
}
if len(imports) == 1 {
g.Printf("import %s\n", imports[0])
}
g.Printf("\n")
g.Printf("\n")
g.Printf("\t// init register error codes defines in this source code to `czc/pkg/errors`\n")
g.Printf("func init() {\n")
for _, v := range values {
if param.must {
g.Printf("\tcode.MustRegister(%s)\n", v)
} else {
g.Printf("\tcode.Register(%s)\n", v)
}
}
g.Printf("}\n")
}
// Printf like fmt.Printf, but add the string to g.buf.
// Printf 将格式化后的字符串写入 g.buf.
func (g *Generator) Printf(format string, args ...interface{}) {
fmt.Fprintf(&g.buf, format, args...)
}
// format returns the gofmt-ed contents of the Generator's buffer.
func (g *Generator) format() []byte {
// 其作用是对 Go 语言的源代码进行格式化,并返回格式化后的源代码
src, err := format.Source(g.buf.Bytes())
if err != nil {
// Should never happen, but can arise when developing this code.
// The user can compile the output to see the error.
log.Printf("warning: internal error: invalid Go generated: %s", err)
log.Printf("warning: compile the package to analyze the error")
return g.buf.Bytes()
}
return src
}
// Package defines options for package.
type Package struct {
name string
defs map[*ast.Ident]types.Object
files []*File
}
// File holds a single parsed file and associated data.
type File struct {
pkg *Package // Package to which this file belongs.
file *ast.File // Parsed AST.
// These fields are reset for each type being generated.
typeName string // Name of the constant type.
imports []string
values []string // Accumulator for constant values of that type.
}
// nolint: gocognit
// genDecl processes one declaration clause.
func (f *File) genDecl(node ast.Node) bool {
// 断言成imports
imps, ok := node.(*ast.ImportSpec)
if ok {
f.imports = append(f.imports, imps.Path.Value)
return true
}
decl, ok := node.(*ast.GenDecl) // 断言是 GenDecl
if !ok || decl.Tok != token.VAR { // 并且判断是否是 const 类型
// We only care about const declarations.
return true
}
for _, spec := range decl.Specs { // 遍历 decl 中的 元素 即 decl.Specs
vspec, _ := spec.(*ast.ValueSpec) // 保证成功,因为这是CONST
// 如果 元素类型为 空 且 有值的情况下
if vspec.Type == nil && len(vspec.Values) > 0 {
for _, name := range vspec.Names {
f.values = append(f.values, name.Name)
}
}
}
return false
}
// GetCurrentDirectory 获取当前执行文件的路径
func GetCurrentDirectory() string {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
fmt.Printf("get path error. %v", err)
return dir
}
return strings.Replace(dir, "\\", "/", -1) + "/"
}
func TransToRedirectPaths(paths []string) []string {
if len(paths) == 0 {
return []string{GetCurrentDirectory()}
}
redirectPath := make([]string, 0, len(paths))
for _, path := range paths {
if path == "." {
redirectPath = append(redirectPath, GetCurrentDirectory())
continue
}
if strings.HasPrefix(path, "./") {
path = strings.Replace(path, "./", GetCurrentDirectory(), 1)
redirectPath = append(redirectPath, path)
}
}
return redirectPath
}
1
https://gitee.com/tylf2018/go-micro-framework.git
git@gitee.com:tylf2018/go-micro-framework.git
tylf2018
go-micro-framework
go-micro-framework
bd95c43b90bc

搜索帮助