From 07eade7d3fe5c8e83ff1fe1a74eee796f9001a33 Mon Sep 17 00:00:00 2001 From: jinzhu Date: Tue, 29 Mar 2022 15:20:24 +0800 Subject: [PATCH] bean + mysql orm --- .gitignore | 1 + Makefile | 17 ++ console/commands/bean.go | 278 +++++++++++++++++++++++ console/commands/orm.go | 128 +++++++++++ console/commands/orm/mysql.go | 343 +++++++++++++++++++++++++++++ console/commands/orm/mysql.go.text | 212 ++++++++++++++++++ console/commands/protoc.go | 291 ++++++++++++++++++++++++ console/commands/route.go | 279 +++++++++++++++++++++++ console/commands/z_help.go | 47 ++++ console/commands/z_inject_gen.go | 49 +++++ console/kernel.go | 39 ++++ console/z_inject_gen.go | 22 ++ examples/bean/controller.go | 31 --- examples/bean/orm.go | 14 -- examples/bean/ults/help.go | 1 - go.mod | 16 ++ go.sum | 201 +++++++++++++++++ main.go | 6 +- parser/go.go | 337 +++++++++++++++++++--------- parser/go.go.abck | 210 ------------------ parser/help_file.go | 131 ++++++++--- parser/help_str.go | 209 ++++++++++++++---- parser/help_str_test.go | 11 +- parser/protoc.go | 327 +++++++++++++++++++++++++++ 24 files changed, 2762 insertions(+), 438 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 console/commands/bean.go create mode 100644 console/commands/orm.go create mode 100644 console/commands/orm/mysql.go create mode 100644 console/commands/orm/mysql.go.text create mode 100644 console/commands/protoc.go create mode 100644 console/commands/route.go create mode 100644 console/commands/z_help.go create mode 100644 console/commands/z_inject_gen.go create mode 100644 console/kernel.go create mode 100755 console/z_inject_gen.go delete mode 100644 examples/bean/controller.go delete mode 100644 examples/bean/orm.go delete mode 100644 examples/bean/ults/help.go create mode 100644 go.sum delete mode 100644 parser/go.go.abck diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..757fee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3976e37 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +GOBIN := $(shell go env GOBIN) +ATDIR := $(shell pwd) + +# 只维护 protoc +protoc: + go run main.go make:protoc + +make-route: + go run main.go make:route + +make-bean: + go run main.go make:bean + +# 调试启动 +dev:protoc make-route make-bean + + diff --git a/console/commands/bean.go b/console/commands/bean.go new file mode 100644 index 0000000..dd452c7 --- /dev/null +++ b/console/commands/bean.go @@ -0,0 +1,278 @@ +package commands + +import ( + "github.com/ctfang/command" + "github.com/go-home-admin/toolset/parser" + "log" + "os" + "sort" + "strconv" + "strings" +) + +// BeanCommand @Bean +type BeanCommand struct{} + +func (BeanCommand) Configure() command.Configure { + return command.Configure{ + Name: "make:bean", + Description: "生成依赖注入的声明源代码文件", + Input: command.Argument{ + Has: []command.ArgParam{ + { + Name: "-f", + Description: "强制更新", + }, + }, + Option: []command.ArgParam{ + { + Name: "skip", + Description: "跳过目录", + Default: "@root/generate", + }, + }, + }, + } +} + +func (BeanCommand) Execute(input command.Input) { + root := getRootPath() + + //if input.GetHas("-f") == true { + // for alias, _ := range parser.NewGoParserForDir(path) { + // if + // } + //} + + skip := make(map[string]bool) + for _, s := range input.GetOptions("proto_path") { + s = strings.Replace(s, "@root", root, 1) + skip[s] = true + } + + for dir, fileParsers := range parser.NewGoParserForDir(root) { + if _, ok := skip[dir]; ok { + break + } + + bc := newBeanCache() + for _, fileParser := range fileParsers { + bc.name = fileParser.PackageName + for _, goType := range fileParser.Types { + for _, attr := range goType.Attrs { + if attr.HasTag("inject") { + for _, impStr := range fileParser.Imports { + bc.imports[impStr] = impStr + } + + break + } + } + + if goType.Doc.HasAnnotation("@Bean") { + bc.structList = append(bc.structList, goType) + } + } + } + + genBean(dir, bc) + } +} + +type beanCache struct { + name string + imports map[string]string + structList []parser.GoType +} + +func newBeanCache() beanCache { + return beanCache{ + imports: map[string]string{ + "github.com/go-home-admin/home/bootstrap/services/app": "github.com/go-home-admin/home/bootstrap/services/app", + }, + structList: make([]parser.GoType, 0), + } +} + +func genBean(dir string, bc beanCache) { + if len(bc.structList) == 0 { + return + } + context := make([]string, 0) + context = append(context, "package "+bc.name) + + // import + importAlias := parser.GenImportAlias(bc.imports) + if len(importAlias) != 0 { + context = append(context, "\nimport ("+getImportStr(bc, importAlias)+"\n)") + } + + // Single + context = append(context, genSingle(bc)) + // Provider + context = append(context, genProvider(bc, importAlias)) + str := "// gen for home toolset" + for _, s2 := range context { + str = str + "\n" + s2 + } + + err := os.WriteFile(dir+"/z_inject_gen.go", []byte(str), 0766) + if err != nil { + log.Fatal(err) + } +} + +func genSingle(bc beanCache) string { + str := "" + allProviderStr := "\n\treturn []interface{}{" + for _, goType := range bc.structList { + if goType.Doc.HasAnnotation("@Bean") { + str = str + "\nvar " + genSingleName(goType.Name) + " *" + goType.Name + allProviderStr += "\n\t\t" + genInitializeNewStr(goType.Name) + "()," + } + } + // 返回全部的提供商 + str += "\n\nfunc GetAllProvider() []interface{} {" + allProviderStr + "\n\t}\n}" + return str +} + +func genSingleName(s string) string { + return "_" + s + "Single" +} + +func genProvider(bc beanCache, m map[string]string) string { + str := "" + for _, goType := range bc.structList { + sVar := genSingleName(goType.Name) + if goType.Doc.HasAnnotation("@Bean") { + str = str + "\nfunc " + genInitializeNewStr(goType.Name) + "() *" + goType.Name + " {" + + "\n\tif " + sVar + " == nil {" + // if _provider == nil { + "\n\t\t" + sVar + " = " + "&" + goType.Name + "{}" // provider := provider{} + + for attrName, attr := range goType.Attrs { + pointer := "" + if !attr.IsPointer() { + pointer = "*" + } + + for tagName, _ := range attr.Tag { + if tagName == "inject" { + str = str + "\n\t\t" + + sVar + "." + attrName + " = " + pointer + getInitializeNewFunName(attr, m) + } + } + } + + constraint := m["github.com/go-home-admin/home/bootstrap/services/app"] + str = str + + "\n\t\t" + constraint + ".AfterProvider(" + sVar + ", \"" + goType.Doc.GetAlias() + "\")" + + "\n\t}" + + "\n\treturn " + sVar + + "\n}" + } + } + + return str +} + +func getInitializeNewFunName(k parser.GoTypeAttr, m map[string]string) string { + alias := "" + name := k.TypeName + + if !k.InPackage { + a := m[k.TypeImport] + alias = a + "." + arr := strings.Split(k.TypeName, ".") + name = arr[len(arr)-1] + } else if name[0:1] == "*" { + name = name[1:] + } + tag := k.Tag["inject"] + if tag.Count() < 2 { + return alias + genInitializeNewStr(name) + "()" + } else { + beanAlias := tag.Get(0) + beanValue := tag.Get(1) + + constraint := m["github.com/go-home-admin/home/bootstrap/services/app"] + + return constraint + ".GetBean(\"" + beanAlias + "\").(" + constraint + ".Bean)" + + ".GetBean(\"" + beanValue + "\").(*" + alias + name + ")" + } +} + +// 控制对完函数名称 +func genInitializeNewStr(name string) string { + if name[0:1] == "*" { + name = name[1:] + } + + return "New" + name +} + +// 生成 import => alias +func genImportAlias(m map[string]string) map[string]string { + aliasMapImport := make(map[string]string) + importMapAlias := make(map[string]string) + for _, imp := range m { + temp := strings.Split(imp, "/") + key := temp[len(temp)-1] + + if _, ok := aliasMapImport[key]; ok { + for i := 1; i < 1000; i++ { + newKey := key + strconv.Itoa(i) + if _, ok2 := aliasMapImport[newKey]; !ok2 { + key = newKey + break + } + } + } + aliasMapImport[key] = imp + } + for s, s2 := range aliasMapImport { + importMapAlias[s2] = s + } + + return importMapAlias +} + +// m = import => alias +func getImportStr(bc beanCache, m map[string]string) string { + has := map[string]bool{ + "github.com/go-home-admin/home/bootstrap/services/app": true, + } + for _, goType := range bc.structList { + if goType.Doc.HasAnnotation("@Bean") { + for _, attr := range goType.Attrs { + if !attr.InPackage { + has[attr.TypeImport] = true + } + } + + } + } + // 删除未使用的import + nm := make(map[string]string) + for s, vv := range m { + if _, ok := has[s]; ok { + nm[s] = vv + } + } + + sk := sortMap(nm) + got := "" + for _, k := range sk { + got += "\n\t" + nm[k] + " \"" + k + "\"" + } + + return got +} + +func sortMap(m map[string]string) []string { + var keys []string + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} diff --git a/console/commands/orm.go b/console/commands/orm.go new file mode 100644 index 0000000..d10bc4f --- /dev/null +++ b/console/commands/orm.go @@ -0,0 +1,128 @@ +package commands + +import ( + "bytes" + "github.com/ctfang/command" + "github.com/go-home-admin/toolset/console/commands/orm" + "github.com/joho/godotenv" + "gopkg.in/yaml.v2" + "os" + "os/exec" + "strings" +) + +// OrmCommand @Bean +type OrmCommand struct{} + +func (OrmCommand) Configure() command.Configure { + return command.Configure{ + Name: "make:orm", + Description: "根据配置文件连接数据库, 生成orm源码", + Input: command.Argument{ + Option: []command.ArgParam{ + { + Name: "config", + Description: "配置文件", + Default: "@root/config/database.yaml", + }, + }, + }, + } +} + +func (OrmCommand) Execute(input command.Input) { + root := getRootPath() + file := input.GetOption("config") + file = strings.Replace(file, "@root", root, 1) + + err := godotenv.Load(root + "/.env") + if err != nil { + panic(err) + } + fileContext, _ := os.ReadFile(file) + fileContext = SetEnv(fileContext) + m := make(map[string]interface{}) + err = yaml.Unmarshal(fileContext, &m) + if err != nil { + panic(err) + } + + connections := m["connections"].(map[interface{}]interface{}) + for s, confT := range connections { + conf := confT.(map[interface{}]interface{}) + driver := conf["driver"] + out := root + "/app/entity/" + s.(string) + switch driver { + case "mysql": + orm.GenMysql(s.(string), conf, out) + case "postgresql": + + } + + cmd := exec.Command("go", []string{"fmt", out}...) + var outBuffer bytes.Buffer + cmd.Stdout = &outBuffer + cmd.Stderr = os.Stderr + cmd.Dir = out + _ = cmd.Run() + } +} + +// SetEnv 对字符串内容进行替换环境变量 +func SetEnv(fileContext []byte) []byte { + str := string(fileContext) + arr := strings.Split(str, "\n") + + for _, s := range arr { + if strings.Index(s, " env(\"") != -1 { + arr2 := strings.Split(s, ": ") + if len(arr2) != 2 { + continue + } + nS := arr2[1] + st, et := GetBrackets(nS, '"', '"') + key := nS[st : et+1] + nS = nS[et+1:] + st, et = GetBrackets(nS, '"', '"') + val := nS[st : et+1] + key = strings.Trim(key, "\"") + val = strings.Trim(val, "\"") + + envVal := os.Getenv(key) + if envVal != "" { + val = envVal + } + + str = strings.Replace(str, s, arr2[0]+": "+val, 1) + } + } + + return []byte(str) +} + +func GetBrackets(str string, start, end int32) (int, int) { + var startInt, endInt int + + bCount := 0 + for i, w := range str { + if bCount == 0 { + if w == start { + startInt = i + bCount++ + } + } else { + switch w { + case end: + bCount-- + if bCount <= 0 { + endInt = i + return startInt, endInt + } + case start: + bCount++ + } + } + } + + return startInt, endInt +} diff --git a/console/commands/orm/mysql.go b/console/commands/orm/mysql.go new file mode 100644 index 0000000..7364846 --- /dev/null +++ b/console/commands/orm/mysql.go @@ -0,0 +1,343 @@ +package orm + +import ( + "database/sql" + _ "embed" + "fmt" + "github.com/go-home-admin/home/bootstrap/services" + "github.com/go-home-admin/toolset/parser" + _ "github.com/go-sql-driver/mysql" + "log" + "os" + "strings" + "time" +) + +// IsExist checks whether a file or directory exists. +// It returns false when the file or directory does not exist. +func IsExist(f string) bool { + _, err := os.Stat(f) + return err == nil || os.IsExist(err) +} + +func GenMysql(name string, conf map[interface{}]interface{}, out string) { + if !IsExist(out) { + os.MkdirAll(out, 0766) + } + + db := NewDb(conf) + tableColumns := db.tableColumns() + + // 计算import + imports := getImports(tableColumns) + for table, columns := range tableColumns { + mysqlTableName := parser.StringToSnake(table) + file := out + "/" + mysqlTableName + + str := "package " + name + str += "\nimport (" + imports[table] + "\n)" + str += "\n" + genOrmStruct(table, columns) + + baseFunStr := baseMysqlFuncStr + for old, new := range map[string]string{ + "MysqlTableName": parser.StringToHump(table), + } { + baseFunStr = strings.ReplaceAll(baseFunStr, old, new) + } + + str += baseFunStr + str += genFieldFunc(table, columns) + str += genListFunc(table, columns) + err := os.WriteFile(file+"_gen.go", []byte(str), 0766) + if err != nil { + log.Fatal(err) + } + } +} + +func genListFunc(table string, columns []tableColumn) string { + TableName := parser.StringToHump(table) + str := "\ntype " + TableName + "List []*" + TableName + for _, column := range columns { + // 索引,或者枚举字段 + if strInStr(column.Field, []string{"id", "code"}) { + str += "\nfunc (l " + TableName + "List) Get" + column.ColumnName + "List() []" + column.GoaType + " {" + + "\n\tgot := make([]" + column.GoaType + ", 0)\n\tfor _, val := range l {" + + "\n\t\tgot = append(got, val." + column.ColumnName + ")" + + "\n\t}" + + "\n\treturn got" + + "\n}" + + str += "\nfunc (l " + TableName + "List) Get" + column.ColumnName + "Map() map[" + column.GoaType + "]*" + TableName + " {" + + "\n\tgot := make(map[" + column.GoaType + "]*" + TableName + ")\n\tfor _, val := range l {" + + "\n\t\tgot[val." + column.ColumnName + "] = val" + + "\n\t}" + + "\n\treturn got" + + "\n}" + } + } + return str +} + +func genFieldFunc(table string, columns []tableColumn) string { + TableName := parser.StringToHump(table) + + str := "" + for _, column := range columns { + // 等于函数 + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "(val " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` = ?\", val)" + + "\n\treturn orm" + + "\n}" + + if column.PrimaryKey != "" { + // if 主键, 生成In, > < + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "In(val []" + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` IN ?\", val)" + + "\n\treturn orm" + + "\n}" + + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "Gt(val " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` > ?\", val)" + + "\n\treturn orm" + + "\n}" + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "Gte(val " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` >= ?\", val)" + + "\n\treturn orm" + + "\n}" + + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "Lt(val " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` < ?\", val)" + + "\n\treturn orm" + + "\n}" + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "Lte(val " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` <= ?\", val)" + + "\n\treturn orm" + + "\n}" + } else { + // 索引,或者枚举字段 + if strInStr(column.Field, []string{"id", "code", "status", "state"}) { + // else if 名称存在 id, code, status 生成in操作 + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "In(val []" + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` IN ?\", val)" + + "\n\treturn orm" + + "\n}" + + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "Ne(val " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` <> ?\", val)" + + "\n\treturn orm" + + "\n}" + } + // 时间字段 + if strInStr(column.Field, []string{"created", "updated", "time", "_at"}) || (column.GoaType == "database.Time") { + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "Between(begin " + column.GoaType + ", end " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` BETWEEN ? AND ?\", begin, end)" + + "\n\treturn orm" + + "\n}" + + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "Lte(val " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` <= ?\", val)" + + "\n\treturn orm" + + "\n}" + + str += "\nfunc (orm *Orm" + TableName + ") Where" + column.ColumnName + "Gte(val " + column.GoaType + ") *Orm" + TableName + " {" + + "\n\torm.db.Where(\"`" + column.Field + "` >= ?\", val)" + + "\n\treturn orm" + + "\n}" + } + } + } + + return str +} + +func strInStr(s string, in []string) bool { + for _, sub := range in { + if strings.Index(s, sub) != -1 { + return true + } + } + return false +} + +//go:embed mysql.go.text +var baseMysqlFuncStr string + +// 字段类型引入 +var alias = map[string]string{ + "database": "github.com/go-home-admin/home/bootstrap/services/database", +} + +// 获得 table => map{alias => github.com/*} +func getImports(tableColumns map[string][]tableColumn) map[string]string { + got := make(map[string]string) + for table, columns := range tableColumns { + // 初始引入 + tm := map[string]string{ + "gorm.io/gorm": "gorm", + "github.com/go-home-admin/home/bootstrap/providers": "providers", + "github.com/sirupsen/logrus": "logrus", + "database/sql": "sql", + } + for _, column := range columns { + index := strings.Index(column.GoaType, ".") + if index != -1 { + as := column.GoaType[:index] + importStr := alias[as] + tm[importStr] = as + } + } + got[table] = parser.GetImportStrForMap(tm) + } + + return got +} + +func genOrmStruct(table string, columns []tableColumn) string { + TableName := parser.StringToHump(table) + + str := `type {TableName} struct {` + for _, column := range columns { + str += "\n\t" + parser.StringToHump(column.Field) + " " + column.GoaType + + "`" + genGormTag(column) + "` // " + + strings.ReplaceAll(column.ColumnComment, "\n", " ") + } + + str = strings.ReplaceAll(str, "{TableName}", TableName) + return "\n" + str + "\n}" +} + +func genGormTag(column tableColumn) string { + var arr []string + if column.PrimaryKey != "" { + arr = append(arr, "primaryKey") + } + + arr = append(arr, "column:"+column.Field) + str := "" + for i := 0; i < len(arr)-1; i++ { + str += arr[i] + ";" + } + str += "" + arr[len(arr)-1] + return "gorm:\"" + str + "\"" +} + +type DB struct { + db *sql.DB +} + +func (d *DB) tableColumns() map[string][]tableColumn { + var sqlStr = `SELECT + COLUMN_NAME, + DATA_TYPE, + IS_NULLABLE, + TABLE_NAME, + COLUMN_COMMENT, + COLUMN_KEY +FROM + information_schema.COLUMNS +WHERE + table_schema = DATABASE () +ORDER BY + TABLE_NAME ASC, + ORDINAL_POSITION ASC` + + rows, err := d.db.Query(sqlStr) + if err != nil { + log.Println("Error reading table information: ", err.Error()) + return nil + } + + defer rows.Close() + tableColumns := make(map[string][]tableColumn) + for rows.Next() { + col := tableColumn{} + err = rows.Scan(&col.ColumnName, &col.MysqlType, &col.Nullable, &col.TableName, &col.ColumnComment, &col.PrimaryKey) + if err != nil { + log.Println(err.Error()) + return nil + } + + col.Field = col.ColumnName + col.ColumnName = parser.StringToHump(col.ColumnName) + col.GoaType = typeForMysqlToGo[col.MysqlType] + + if _, ok := tableColumns[col.TableName]; !ok { + tableColumns[col.TableName] = []tableColumn{} + } + tableColumns[col.TableName] = append(tableColumns[col.TableName], col) + } + return tableColumns +} + +type tableColumn struct { + PrimaryKey string + ColumnName string + GoaType string + MysqlType string + Nullable string + TableName string + ColumnComment string + Field string +} + +var typeForMysqlToGo = map[string]string{ + "int": "int64", + "integer": "int64", + "tinyint": "int32", + "smallint": "int32", + "mediumint": "int64", + "bigint": "int64", + "int unsigned": "uint64", + "integer unsigned": "uint64", + "tinyint unsigned": "uint32", + "smallint unsigned": "uint64", + "mediumint unsigned": "uint64", + "bigint unsigned": "uint64", + "bit": "int64", + "bool": "bool", + "enum": "string", + "set": "string", + "varchar": "string", + "char": "string", + "tinytext": "string", + "mediumtext": "string", + "text": "string", + "longtext": "string", + "blob": "string", + "tinyblob": "string", + "mediumblob": "string", + "longblob": "string", + "date": "database.Time", // time.Time or string + "datetime": "database.Time", // time.Time or string + "timestamp": "database.Time", // time.Time or string + "time": "database.Time", // time.Time or string + "float": "float64", + "double": "float64", + "decimal": "float64", + "binary": "string", + "varbinary": "string", + "json": "database.Json", +} + +func NewDb(conf map[interface{}]interface{}) *DB { + config := services.NewConfig(conf) + db, err := sql.Open("mysql", fmt.Sprintf( + "%s:%s@tcp(%s)/%s", + config.GetString("username", "root"), + config.GetString("password", "123456"), + config.GetString("host", "localhost:"+config.GetString("port", "3306")), + config.GetString("database", "demo"), + )) + if err != nil { + panic(err) + } + // See "Important settings" section. + db.SetConnMaxLifetime(time.Minute * 3) + db.SetMaxOpenConns(10) + db.SetMaxIdleConns(10) + + return &DB{ + db: db, + } +} diff --git a/console/commands/orm/mysql.go.text b/console/commands/orm/mysql.go.text new file mode 100644 index 0000000..6a750ba --- /dev/null +++ b/console/commands/orm/mysql.go.text @@ -0,0 +1,212 @@ + +func (receiver *MysqlTableName) TableName() string { + return "action_logs" +} + +type OrmMysqlTableName struct { + db *gorm.DB +} + +func NewOrmMysqlTableName() *OrmMysqlTableName { + orm := &OrmMysqlTableName{} + orm.db = providers.NewMysqlProvider().GetBean("mysql").(*gorm.DB) + return orm +} + +func (orm *OrmMysqlTableName) GetDB() *gorm.DB { + return orm.db +} + +// Create insert the value into database +func (orm *OrmMysqlTableName) Create(value interface{}) *gorm.DB { + return orm.db.Create(value) +} + +// CreateInBatches insert the value in batches into database +func (orm *OrmMysqlTableName) CreateInBatches(value interface{}, batchSize int) *gorm.DB { + return orm.db.CreateInBatches(value, batchSize) +} + +// Save update value in database, if the value doesn't have primary key, will insert it +func (orm *OrmMysqlTableName) Save(value interface{}) *gorm.DB { + return orm.db.Save(value) +} + +func (orm *OrmMysqlTableName) Row() *sql.Row { + return orm.db.Row() +} + +func (orm *OrmMysqlTableName) Rows() (*sql.Rows, error) { + return orm.db.Rows() +} + +// Scan scan value to a struct +func (orm *OrmMysqlTableName) Scan(dest interface{}) *gorm.DB { + return orm.db.Scan(dest) +} + +func (orm *OrmMysqlTableName) ScanRows(rows *sql.Rows, dest interface{}) error { + return orm.db.ScanRows(rows, dest) +} + +// Connection use a db conn to execute Multiple commands,this conn will put conn pool after it is executed. +func (orm *OrmMysqlTableName) Connection(fc func(tx *gorm.DB) error) (err error) { + return orm.db.Connection(fc) +} + +// Transaction start a transaction as a block, return error will rollback, otherwise to commit. +func (orm *OrmMysqlTableName) Transaction(fc func(tx *gorm.DB) error, opts ...*sql.TxOptions) (err error) { + return orm.db.Transaction(fc, opts...) +} + +// Begin begins a transaction +func (orm *OrmMysqlTableName) Begin(opts ...*sql.TxOptions) *gorm.DB { + return orm.db.Begin(opts...) +} + +// Commit commit a transaction +func (orm *OrmMysqlTableName) Commit() *gorm.DB { + return orm.db.Commit() +} + +// Rollback rollback a transaction +func (orm *OrmMysqlTableName) Rollback() *gorm.DB { + return orm.db.Rollback() +} + +func (orm *OrmMysqlTableName) SavePoint(name string) *gorm.DB { + return orm.db.SavePoint(name) +} + +func (orm *OrmMysqlTableName) RollbackTo(name string) *gorm.DB { + return orm.db.RollbackTo(name) +} + +// Exec execute raw sql +func (orm *OrmMysqlTableName) Exec(sql string, values ...interface{}) *gorm.DB { + return orm.db.Exec(sql, values...) +} + +// ------------ 以下是单表独有的函数, 便捷字段条件, Laravel风格操作 --------- + +func (orm *OrmMysqlTableName) Insert(row *MysqlTableName) *gorm.DB { + return orm.db.Create(row) +} + +func (orm *OrmMysqlTableName) Inserts(rows []*MysqlTableName) *gorm.DB { + return orm.db.Create(rows) +} + +func (orm *OrmMysqlTableName) Limit(limit int) *OrmMysqlTableName { + orm.db.Limit(limit) + return orm +} + +func (orm *OrmMysqlTableName) Offset(offset int) *OrmMysqlTableName { + orm.db.Offset(offset) + return orm +} + +func (orm *OrmMysqlTableName) Get() (MysqlTableNameList, int64) { + return orm.Find() +} + +// Pluck used to query single column from a model as a map +// var ages []int64 +// db.Model(&users).Pluck("age", &ages) +func (orm *OrmMysqlTableName) Pluck(column string, dest interface{}) *gorm.DB { + return orm.db.Model(&MysqlTableName{}).Pluck(column, dest) +} + +// Delete delete value match given conditions, if the value has primary key, then will including the primary key as condition +func (orm *OrmMysqlTableName) Delete(value interface{}, conds ...interface{}) *gorm.DB { + return orm.db.Model(&MysqlTableName{}).Delete(value, conds...) +} + +func (orm *OrmMysqlTableName) Count() int64 { + var count int64 + orm.db.Model(&MysqlTableName{}).Count(&count) + return count +} + +// First 检索单个对象 +func (orm *OrmMysqlTableName) First(conds ...interface{}) (*MysqlTableName, int64) { + dest := &MysqlTableName{} + db := orm.db.Limit(1).Find(dest, conds...) + return dest, db.RowsAffected +} + +// Take return a record that match given conditions, the order will depend on the database implementation +func (orm *OrmMysqlTableName) Take(conds ...interface{}) (*MysqlTableName, int64) { + dest := &MysqlTableName{} + db := orm.db.Take(dest, conds...) + return dest, db.RowsAffected +} + +// Last find last record that match given conditions, order by primary key +func (orm *OrmMysqlTableName) Last(conds ...interface{}) (*MysqlTableName, int64) { + dest := &MysqlTableName{} + db := orm.db.Last(dest, conds...) + return dest, db.RowsAffected +} + +func (orm *OrmMysqlTableName) Find(conds ...interface{}) (MysqlTableNameList, int64) { + list := make([]*MysqlTableName, 0) + tx := orm.db.Model(&MysqlTableName{}).Find(list, conds...) + if tx.Error != nil { + logrus.Error(tx.Error) + } + return list, tx.RowsAffected +} + +// FindInBatches find records in batches +func (orm *OrmMysqlTableName) FindInBatches(dest interface{}, batchSize int, fc func(tx *gorm.DB, batch int) error) *gorm.DB { + return orm.db.FindInBatches(dest, batchSize, fc) +} + +// FirstOrInit gets the first matched record or initialize a new instance with given conditions (only works with struct or map conditions) +func (orm *OrmMysqlTableName) FirstOrInit(dest *MysqlTableName, conds ...interface{}) (*MysqlTableName, *gorm.DB) { + return dest, orm.db.FirstOrInit(dest, conds...) +} + +// FirstOrCreate gets the first matched record or create a new one with given conditions (only works with struct, map conditions) +func (orm *OrmMysqlTableName) FirstOrCreate(dest interface{}, conds ...interface{}) *gorm.DB { + return orm.db.FirstOrCreate(dest, conds...) +} + +// Update update attributes with callbacks, refer: https://gorm.io/docs/update.html#Update-Changed-Fields +func (orm *OrmMysqlTableName) Update(column string, value interface{}) *gorm.DB { + return orm.db.Model(&MysqlTableName{}).Update(column, value) +} + +// Updates update attributes with callbacks, refer: https://gorm.io/docs/update.html#Update-Changed-Fields +func (orm *OrmMysqlTableName) Updates(values interface{}) *gorm.DB { + return orm.db.Model(&MysqlTableName{}).Updates(values) +} + +func (orm *OrmMysqlTableName) UpdateColumn(column string, value interface{}) *gorm.DB { + return orm.db.Model(&MysqlTableName{}).UpdateColumn(column, value) +} + +func (orm *OrmMysqlTableName) UpdateColumns(values interface{}) *gorm.DB { + return orm.db.Model(&MysqlTableName{}).UpdateColumns(values) +} + +func (orm *OrmMysqlTableName) Where(query interface{}, args ...interface{}) *OrmMysqlTableName { + orm.db.Where(query, args...) + return orm +} + +func (orm *OrmMysqlTableName) And(fuc func(orm *OrmMysqlTableName)) *OrmMysqlTableName { + ormAnd := NewOrmMysqlTableName() + fuc(ormAnd) + orm.db.Where(ormAnd.db) + return orm +} + +func (orm *OrmMysqlTableName) Or(fuc func(orm *OrmMysqlTableName)) *OrmMysqlTableName { + ormOr := NewOrmMysqlTableName() + fuc(ormOr) + orm.db.Or(ormOr.db) + return orm +} diff --git a/console/commands/protoc.go b/console/commands/protoc.go new file mode 100644 index 0000000..f47ac47 --- /dev/null +++ b/console/commands/protoc.go @@ -0,0 +1,291 @@ +package commands + +import ( + "bufio" + "bytes" + "fmt" + "github.com/ctfang/command" + "github.com/go-home-admin/toolset/parser" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// ProtocCommand @Bean +type ProtocCommand struct{} + +func (ProtocCommand) Configure() command.Configure { + return command.Configure{ + Name: "make:protoc", + Description: "组装和执行protoc命令", + Input: command.Argument{ + Option: []command.ArgParam{ + { + Name: "proto", + Description: "proto文件存放的目录", + Default: "@root/protobuf", + }, + { + Name: "proto_path", + Description: "protoc后面拼接的proto_path, 可以传入多个", + Default: "@root/protobuf/common/http", + }, + { + Name: "go_out", + Description: "生成文件到指定目录", + Default: "@root/generate/proto", + }, + { + Name: "show", + Description: "是否打印protoc命令", + Default: "false", + }, + }, + }, + } +} + +var show = false + +func (ProtocCommand) Execute(input command.Input) { + show = input.GetOption("false") == "true" + root := getRootPath() + _, err := exec.LookPath("protoc") + if err != nil { + log.Printf("'protoc' 未安装; brew install protobuf") + return + } + out := input.GetOption("go_out") + out = strings.Replace(out, "@root", root, 1) + outTemp, _ := filepath.Abs(out + "/../temp") + _ = os.MkdirAll(outTemp, 0766) + + path := input.GetOption("proto") + path = strings.Replace(path, "@root", root, 1) + + pps := make([]string, 0) + for _, s := range input.GetOptions("proto_path") { + s = strings.Replace(s, "@root", root, 1) + pps = append(pps, "--proto_path="+s) + } + // path/*.proto 不是protoc命令提供的, 如果这里执行需要每一个文件一个命令 + for _, dir := range parser.GetChildrenDir(path) { + for _, info := range dir.GetFiles(".proto") { + cods := []string{"--proto_path=" + dir.Path} + cods = append(cods, pps...) + cods = append(cods, "--go_out="+outTemp) + cods = append(cods, info.Path) + + Cmd("protoc", cods) + } + } + + // 生成后, 从temp目录拷贝到out + _ = os.RemoveAll(out) + rootAlias := strings.Replace(out, root+"/", "", 1) + module := getModModule() + + for _, dir := range parser.GetChildrenDir(outTemp) { + dir2 := strings.Replace(dir.Path, outTemp+"/", "", 1) + dir3 := strings.Replace(dir2, module+"/", "", 1) + if dir2 == dir3 { + continue + } + + if dir3 == rootAlias { + _ = os.Rename(dir.Path, out) + _ = os.RemoveAll(outTemp) + break + } + } + + // 基础proto生成后, 生成Tag + genProtoTag(out) +} + +func genProtoTag(out string) { + byteLineDoc := []byte("//") + bytePackage := []byte("package") + byteT := []byte("\t") + byteProtobuf := []byte("`protobuf:\"") + byteProtobufOneof := []byte("`protobuf_oneof:\"") + byteEq := []byte("=") + + for _, dir := range parser.GetChildrenDir(out) { + for _, file := range dir.GetFiles(".go") { + fd, err := os.Open(file.Path) + defer fd.Close() + if err != nil { + fmt.Println("read error:", err) + } + + fileNewStr := make([]byte, 0) + buff := bufio.NewReader(fd) + packageStart := false + // 初始化文件Tag + fileTags := make([]tag, 0) + fileMapTags := map[string]tag{ + "form": {name: "Tag", key: "form", val: "{name}"}, + } + for _, t := range fileMapTags { + fileTags = append(fileTags, t) + } + // 开始替换文件内容 + lineTags := make([]tag, 0) + lineMapTags := make(map[string]tag) + for { + data, _, eof := buff.ReadLine() + if eof == io.EOF { + break + } + newStr := append([]byte("\n"), data...) + if bytes.Index(data, byteLineDoc) == -1 { + if !packageStart { + if bytes.Index(data, bytePackage) == 0 { + packageStart = true + fileTags = append(fileTags, lineTags...) + for s, t := range lineMapTags { + fileMapTags[s] = t + } + } + } else if bytes.HasPrefix(data, byteT) { + var tagValue []byte + end := 0 + if start := bytes.Index(data, byteProtobuf); start != -1 { + nData := data[start:] + begin := bytes.Index(nData, []byte("name=")) + end = bytes.Index(nData[begin:], []byte(",")) + nData = nData[begin : begin+end] + tagValue = nData[bytes.Index(nData, byteEq)+1:] + end = bytes.Index(data[start+begin:], []byte("`")) + start + begin + } else if start := bytes.Index(data, byteProtobufOneof); start != -1 { + nData := data[start:] + begin := bytes.Index(nData, []byte("\"")) + 1 + end = bytes.Index(nData[begin:], []byte("\"")) + nData = nData[begin : begin+end] + tagValue = nData[bytes.Index(nData, byteEq)+1:] + end = bytes.Index(data[start+begin:], []byte("`")) + start + begin + } + if len(tagValue) != 0 { + for s, t := range fileMapTags { + if _, ok := lineMapTags[s]; !ok { + lineMapTags[s] = t + lineTags = append(lineTags, t) + } + } + endStr := data[end:] + newStr = append([]byte("\n"), data[:end]...) + for _, lineTag := range lineTags { + switch lineTag.key { + case "json": + newStr = []byte(strings.ReplaceAll(string(newStr), "json:\""+string(tagValue)+",omitempty\"", "")) + newStr = append(newStr, []byte(lineTag.key+":\""+ + strings.ReplaceAll(lineTag.val, "{name}", string(tagValue))+ + "\"")...) + default: + newStr = append(newStr, []byte(" "+lineTag.key+":\""+ + strings.ReplaceAll(lineTag.val, "{name}", string(tagValue))+ + "\"")...) + } + } + newStr = append(newStr, endStr...) + } + } + + lineTags = make([]tag, 0) + lineMapTags = make(map[string]tag) + } else { + str := strings.Trim(string(data), "") + tags, mTags := getDocTag(str) + lineTags = append(lineTags, tags...) + for s, t := range mTags { + lineMapTags[s] = t + } + } + + fileNewStr = append(fileNewStr, newStr...) + } + + defer os.WriteFile(file.Path, fileNewStr, 0760) + } + } +} + +type tag struct { + name string + + key string + val string +} + +func getDocTag(doc string) ([]tag, map[string]tag) { + got := make([]tag, 0) + mapt := make(map[string]tag) + + arr := parser.GetWords(strings.ReplaceAll(doc, "//", " ")) + arrLet := len(arr) + for i := 0; i < arrLet; i++ { + w := arr[i] + if w.Str == "@" { + if arrLet < (i + 1) { + continue + } + tag := tag{} + tag.name = arr[i+1].Str + if tag.name != "Tag" { + continue + } + nl := arr[i+1:] + i = i + 1 + st, et, has := parser.GetBracketsOrLn(nl, "(", ")") + if !has { + continue + } + i = i + et + nl = nl[st+1 : et] + for _, word := range nl { + if word.Ty == 0 { + if tag.key == "" { + tag.key = word.Str[1 : len(word.Str)-1] + tag.val = "{name}" + } else { + tag.val = word.Str[1 : len(word.Str)-1] + } + } + } + if tag.key != "" { + got = append(got, tag) + mapt[tag.key] = tag + } + } + } + + return got, mapt +} + +func Cmd(commandName string, params []string) { + // 打印真实命令 + if show { + PrintCmd(commandName, params) + } + + cmd := exec.Command(commandName, params...) + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + log.Fatalln(err) + } +} + +func PrintCmd(commandName string, params []string) { + str := "\n" + commandName + " " + for _, param := range params { + str += param + " " + } + fmt.Print(str + "\n") +} diff --git a/console/commands/route.go b/console/commands/route.go new file mode 100644 index 0000000..3ccf23d --- /dev/null +++ b/console/commands/route.go @@ -0,0 +1,279 @@ +package commands + +import ( + "bytes" + "github.com/ctfang/command" + "github.com/go-home-admin/toolset/parser" + "log" + "os" + "os/exec" + "strings" +) + +// RouteCommand @Bean +type RouteCommand struct{} + +func (RouteCommand) Configure() command.Configure { + return command.Configure{ + Name: "make:route", + Description: "根据protoc文件定义, 生成路由信息和控制器文件", + Input: command.Argument{ + Option: []command.ArgParam{ + { + Name: "path", + Description: "只解析指定目录", + Default: "@root/protobuf", + }, + { + Name: "out_route", + Description: "生成文件到指定目录", + Default: "@root/routes", + }, + { + Name: "out_actions", + Description: "生成文件到指定目录", + Default: "@root/app/http", + }, + }, + }, + } +} + +func (RouteCommand) Execute(input command.Input) { + root := getRootPath() + module := getModModule() + out := input.GetOption("out_route") + out = strings.Replace(out, "@root", root, 1) + + outHttp := input.GetOption("out_actions") + outHttp = strings.Replace(outHttp, "@root", root, 1) + + path := input.GetOption("path") + path = strings.Replace(path, "@root", root, 1) + + agl := map[string]*ApiGroups{} + + for _, parsers := range parser.NewProtocParserForDir(path) { + for _, fileParser := range parsers { + for _, service := range fileParser.Services { + group := "" + + for _, option := range service.Opt { + if option.Key == "http.RouteGroup" { + group = option.Val + if _, ok := agl[group]; !ok { + agl[group] = &ApiGroups{ + name: group, + imports: map[string]string{ + "home_api_1": "github.com/go-home-admin/home/bootstrap/http/api", + "home_gin_1": "github.com/gin-gonic/gin", + }, + controllers: make([]Controller, 0), + servers: make([]parser.Service, 0), + } + } + genController(service, outHttp) + break + } + } + + if group != "" { + g := agl[group] + imports := module + "/app/http/" + fileParser.PackageName + "/" + parser.StringToSnake(service.Name) + g.imports[imports] = imports + + g.controllers = append(g.controllers, Controller{ + name: service.Name, + alias: imports, + }) + + g.servers = append(g.servers, service) + } + } + } + } + + for _, g := range agl { + genRoute(g, out) + } + cmd := exec.Command("go", []string{"fmt", out}...) + var outBuffer bytes.Buffer + cmd.Stdout = &outBuffer + cmd.Stderr = os.Stderr + cmd.Dir = out + _ = cmd.Run() +} + +func genController(server parser.Service, out string) { + page := server.Protoc.PackageName + out += "/" + page + "/" + parser.StringToSnake(server.Name) + + if !parser.DirIsExist(out) { + _ = os.MkdirAll(out, 0760) + } + + conStr := `package {package} + +// Controller @Bean +type Controller struct { +}` + conStr = strings.ReplaceAll(conStr, "{package}", parser.StringToSnake(server.Name)) + err := os.WriteFile(out+"/controller.go", []byte(conStr), 0760) + if err != nil { + panic(err) + } + + methodStr := `package {package} + +import ({import}) + +// {action} {doc} +func (receiver *Controller) {action}(req *{controllerAlias}.{param}, ctx *gin.Context) (*{controllerAlias}.{return}, error) { + // TODO 这里写业务 + return &{controllerAlias}.{return}{}, nil +} + +// GinHandle{action} gin原始路由处理 +// http.{method}({url}) +func (receiver *Controller) GinHandle{action}(ctx *gin.Context) { + req := &{controllerAlias}.{param}{} + err := ctx.ShouldBind(req) + + if err != nil { + {providers}.ErrorRequest(ctx, err) + return + } + + resp, err := receiver.{action}(req, ctx) + if err != nil { + {providers}.ErrorResponse(ctx, err) + return + } + + {providers}.SuccessResponse(ctx, resp) +} +` + gin := "github.com/gin-gonic/gin" + providers := "github.com/go-home-admin/home/app/providers" + imports := map[string]string{gin: gin, providers: providers} + goMod := getModModule() + + for rName, rpc := range server.Rpc { + for _, option := range rpc.Opt { + if strings.Index(option.Key, "http.") == 0 { + serPage := goMod + "/generate/proto/" + server.Protoc.PackageName + imports[serPage] = serPage + importsStr := "" + m := genImportAlias(imports) + sk := sortMap(m) + for _, s := range sk { + importsStr += "\n\t" + m[s] + " \"" + s + "\"" + } + + controllerAlias := m[serPage] + + str := methodStr + actionName := parser.StringToHump(rName) + + i := strings.Index(option.Key, ".") + method := option.Key[i+1:] + url := option.Val + + for s, O := range map[string]string{ + "{package}": parser.StringToSnake(server.Name), + "{import}": importsStr + "\n", + "{doc}": rpc.Doc, + "{method}": method, + "{url}": url, + "{action}": actionName, + "{controllerAlias}": controllerAlias, + "{providers}": m[providers], + "{param}": rpc.Param, + "{return}": rpc.Return, + } { + str = strings.ReplaceAll(str, s, O) + } + + err = os.WriteFile(out+"/"+parser.StringToSnake(actionName)+"_action.go", []byte(str), 0766) + if err != nil { + log.Fatal(err) + } + } + } + } +} + +func genRoute(g *ApiGroups, out string) { + context := make([]string, 0) + context = append(context, "package routes") + + // import + importAlias := genImportAlias(g.imports) + if len(importAlias) != 0 { + context = append(context, "\nimport ("+parser.GetImportStrForMap(importAlias)+"\n)") + } + // Routes struct + context = append(context, genRoutesStruct(g, importAlias)) + // Routes struct func GetRoutes + context = append(context, genRoutesFunc(g, importAlias)) + + str := "// gen for home toolset" + for _, s2 := range context { + str = str + "\n" + s2 + } + err := os.WriteFile(out+"/"+parser.StringToSnake(g.name)+"_gen.go", []byte(str), 0766) + if err != nil { + log.Fatal(err) + } +} + +func genRoutesFunc(g *ApiGroups, m map[string]string) string { + homeGin := m["github.com/gin-gonic/gin"] + homeApi := m["github.com/go-home-admin/home/bootstrap/http/api"] + + str := "func (c *" + parser.StringToHump(g.name) + "Routes) GetGroup() string {" + + "\n\treturn \"" + g.name + "\"" + + "\n}" + + str += "\nfunc (c *" + parser.StringToHump(g.name) + "Routes) GetRoutes() map[*" + homeApi + ".Config]func(c *" + homeGin + ".Context) {" + + "\n\treturn map[*" + homeApi + ".Config]func(c *" + homeGin + ".Context){" + + for _, server := range g.servers { + for rName, rpc := range server.Rpc { + for _, option := range rpc.Opt { + if strings.Index(option.Key, "http.") == 0 { + i := strings.Index(option.Key, ".") + method := option.Key[i+1:] + str += "\n\t\t" + homeApi + "." + method + "(\"" + option.Val + "\"):" + + "c." + parser.StringToSnake(server.Name) + ".GinHandle" + parser.StringToHump(rName) + "," + } + } + } + } + + return str + "\n\t}\n}" +} + +func genRoutesStruct(g *ApiGroups, m map[string]string) string { + str := "\n// @Bean" + + "\ntype " + parser.StringToHump(g.name) + "Routes struct {\n" + for _, controller := range g.controllers { + alias := m[controller.alias] + str += "\t" + parser.StringToSnake(controller.name) + " *" + alias + ".Controller" + " `inject:\"\"`\n" + } + + return str + "}\n" +} + +type ApiGroups struct { + name string + imports map[string]string + controllers []Controller + servers []parser.Service +} + +type Controller struct { + name string + alias string + ty string // *alias.Controller +} diff --git a/console/commands/z_help.go b/console/commands/z_help.go new file mode 100644 index 0000000..eb58442 --- /dev/null +++ b/console/commands/z_help.go @@ -0,0 +1,47 @@ +package commands + +import ( + "bufio" + "os" + "strings" +) + +var rootPath string + +func SetRootPath(root string) { + rootPath = root +} + +// 获取项目跟目录 +func getRootPath() string { + return rootPath +} + +// 获取module +func getModModule() string { + root := getRootPath() + path := root + "/go.mod" + + fin, err := os.Stat(path) + if err != nil { + // no such file or dir + panic("根目录必须存在go.mod") + } + if fin.IsDir() { + panic("根目录必须存在go.mod文件") + } + + file, err := os.Open(path) + if err != nil { + panic(err) + } + defer file.Close() + scanner := bufio.NewScanner(file) + text := "" + for scanner.Scan() { + text = scanner.Text() + break + } + text = strings.Replace(text, "module ", "", 1) + return text +} diff --git a/console/commands/z_inject_gen.go b/console/commands/z_inject_gen.go new file mode 100644 index 0000000..13b8e48 --- /dev/null +++ b/console/commands/z_inject_gen.go @@ -0,0 +1,49 @@ +// gen for home toolset +package commands + +import ( + app "github.com/go-home-admin/home/bootstrap/services/app" +) + +var _BeanCommandSingle *BeanCommand +var _OrmCommandSingle *OrmCommand +var _ProtocCommandSingle *ProtocCommand +var _RouteCommandSingle *RouteCommand + +func GetAllProvider() []interface{} { + return []interface{}{ + NewBeanCommand(), + NewOrmCommand(), + NewProtocCommand(), + NewRouteCommand(), + } +} + +func NewBeanCommand() *BeanCommand { + if _BeanCommandSingle == nil { + _BeanCommandSingle = &BeanCommand{} + app.AfterProvider(_BeanCommandSingle, "") + } + return _BeanCommandSingle +} +func NewOrmCommand() *OrmCommand { + if _OrmCommandSingle == nil { + _OrmCommandSingle = &OrmCommand{} + app.AfterProvider(_OrmCommandSingle, "") + } + return _OrmCommandSingle +} +func NewProtocCommand() *ProtocCommand { + if _ProtocCommandSingle == nil { + _ProtocCommandSingle = &ProtocCommand{} + app.AfterProvider(_ProtocCommandSingle, "") + } + return _ProtocCommandSingle +} +func NewRouteCommand() *RouteCommand { + if _RouteCommandSingle == nil { + _RouteCommandSingle = &RouteCommand{} + app.AfterProvider(_RouteCommandSingle, "") + } + return _RouteCommandSingle +} diff --git a/console/kernel.go b/console/kernel.go new file mode 100644 index 0000000..118da8d --- /dev/null +++ b/console/kernel.go @@ -0,0 +1,39 @@ +package console + +import ( + "github.com/ctfang/command" + "github.com/go-home-admin/toolset/console/commands" + "os" + "path/filepath" +) + +// @Bean +type Kernel struct{} + +func (k *Kernel) Run() { + app := command.New() + app.AddBaseOption(command.ArgParam{ + Name: "root", + Description: "获取项目跟路径", + Call: func(val string, c *command.Console) (string, bool) { + if val == "" { + val, _ = os.Getwd() + } + + val, _ = filepath.Abs(val) + commands.SetRootPath(val) + return val, true + }, + }) + + for _, provider := range commands.GetAllProvider() { + if v, ok := provider.(command.Command); ok { + app.AddCommand(v) + } + } + app.Run() +} + +func (k *Kernel) Exit() { + +} diff --git a/console/z_inject_gen.go b/console/z_inject_gen.go new file mode 100755 index 0000000..96274f0 --- /dev/null +++ b/console/z_inject_gen.go @@ -0,0 +1,22 @@ +// gen for home toolset +package console + +import ( + app "github.com/go-home-admin/home/bootstrap/services/app" +) + +var _KernelSingle *Kernel + +func GetAllProvider() []interface{} { + return []interface{}{ + NewKernel(), + } +} + +func NewKernel() *Kernel { + if _KernelSingle == nil { + _KernelSingle = &Kernel{} + app.AfterProvider(_KernelSingle, "") + } + return _KernelSingle +} diff --git a/examples/bean/controller.go b/examples/bean/controller.go deleted file mode 100644 index 1c991d4..0000000 --- a/examples/bean/controller.go +++ /dev/null @@ -1,31 +0,0 @@ -package bean - -import ( - f "fmt" - "os" -) - -// @Bean fdsaf -type con struct { - orm orm `inject:"" json:"orm"` - orm2 *orm `inject:""` - orm3 []orm `inject:""` - area int `protobuf:"bytes,1,opt,name=area,proto3" form:"area" json:"area,omitempty"` - orm4 func(i int, f map[string]string) -} - -// test api -func (c con) api() { - f.Println("test") - - f.Println(testInt(1)) - - os.Exit(0) -} - -// -type testInt int - -func test() { - -} diff --git a/examples/bean/orm.go b/examples/bean/orm.go deleted file mode 100644 index 03a4789..0000000 --- a/examples/bean/orm.go +++ /dev/null @@ -1,14 +0,0 @@ -package bean - -import ( - f "fmt" -) - -// @Bean -type orm struct { -} - -// test api -func (c orm) api() { - f.Println("test") -} diff --git a/examples/bean/ults/help.go b/examples/bean/ults/help.go deleted file mode 100644 index 6f34c0c..0000000 --- a/examples/bean/ults/help.go +++ /dev/null @@ -1 +0,0 @@ -package ults diff --git a/go.mod b/go.mod index 9fa580f..da125c0 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,19 @@ module github.com/go-home-admin/toolset go 1.17 + +require ( + github.com/ctfang/command v1.0.0 + github.com/go-home-admin/home v0.0.0-20220329042758-02a34d8e19bc +) + +require ( + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-sql-driver/mysql v1.6.0 + github.com/joho/godotenv v1.4.0 + golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 // indirect + golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a610787 --- /dev/null +++ b/go.sum @@ -0,0 +1,201 @@ +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/ctfang/command v1.0.0 h1:CurDflG0o7GntcRWWzbFlKdR3CMZUF/627Rv/Ii8HNE= +github.com/ctfang/command v1.0.0/go.mod h1:iWJcUCwZReswQ7T5IaRE6ZvGzZ/m9v53Z1na20JttV8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= +github.com/go-home-admin/home v0.0.0-20220329042758-02a34d8e19bc h1:ozZX6t4p/WfKGdvjVrpfxqs4GyX6igh6uDLWZ5vw308= +github.com/go-home-admin/home v0.0.0-20220329042758-02a34d8e19bc/go.mod h1:xdiTISDwUhNaIQlvP+Xh67MasSXHuZH1gUCG0VfuHnc= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= +github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s= +golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886 h1:eJv7u3ksNXoLbGSKuv2s/SIO4tJVxc/A+MTpzxDgz/Q= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= +gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.3/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= diff --git a/main.go b/main.go index 1e2faa3..0b7551e 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main -import "github.com/go-home-admin/toolset/parser" +import ( + "github.com/go-home-admin/toolset/console" +) func main() { - parser.NewGoParserForDir("./examples/bean") + console.NewKernel().Run() } diff --git a/parser/go.go b/parser/go.go index 4f29a3f..cf927dc 100644 --- a/parser/go.go +++ b/parser/go.go @@ -5,9 +5,7 @@ import ( "strings" ) -/** -golang parser 非完整token实现 -*/ +// GoFileParser 非完整token实现 type GoFileParser struct { PackageName string PackageDoc string @@ -16,21 +14,51 @@ type GoFileParser struct { Funds map[string]GoFunc } -func NewGoParserForDir(path string) []GoFileParser { - var got []GoFileParser - for _, file := range loadGoFiles(path) { - gof, _ := GetFileParser(file) - got = append(got, gof) +func NewGoParserForDir(path string) map[string][]GoFileParser { + got := make(map[string][]GoFileParser) + for _, dir := range GetChildrenDir(path) { + arr := make([]GoFileParser, 0) + for _, file := range dir.GetFiles(".go") { + gof, _ := GetFileParser(file.Path) + arr = append(arr, gof) + } + got[dir.Path] = arr } return got } -func loadGoFiles(path string) []FileInfo { - return loadFiles(path, ".go") +// go 关键字语法块 +// map\[]\interface{} +func getWordsWitchGo(l *GoWords) GoWords { + var got = GoWords{ + list: make([]*word, 0), + } + + for offset := 0; offset < len(l.list); offset++ { + work := l.list[offset] + switch work.Ty { + case wordT_word: + switch work.Str { + case "interface": + if len(l.list) >= (offset+2) && l.list[offset+1].Str == "{" && l.list[offset+2].Str == "}" { + offset = offset + 2 + work.Str = work.Str + "{}" + got.list = append(got.list, work) + } else { + got.list = append(got.list, work) + } + default: + got.list = append(got.list, work) + } + default: + got.list = append(got.list, work) + } + } + return got } -func GetFileParser(info FileInfo) (GoFileParser, error) { +func GetFileParser(path string) (GoFileParser, error) { d := GoFileParser{ PackageName: "", PackageDoc: "", @@ -39,42 +67,55 @@ func GetFileParser(info FileInfo) (GoFileParser, error) { Funds: make(map[string]GoFunc), } - l := getWordsWitchFile(info.path) + l := getWordsWitchFile(path) + l = getWordsWitchGo(&l) lastDoc := "" for offset := 0; offset < len(l.list); offset++ { work := l.list[offset] // 原则上, 每个块级别的作用域必须自己处理完, 返回的偏移必须是下一个块的开始 - switch work.t { + switch work.Ty { case wordT_line: + lastDoc += work.Str case wordT_division: case wordT_doc: - lastDoc = work.str + lastDoc += work.Str case wordT_word: - switch work.str { + switch work.Str { case "package": d.PackageDoc = lastDoc d.PackageName, offset = handlePackageName(l.list, offset) + lastDoc = "" case "import": var imap map[string]string imap, offset = handleImports(l.list, offset) for k, v := range imap { d.Imports[k] = v } + lastDoc = "" case "type": var imap GoType - imap, offset = handleTypes(l.list, offset) - imap.Doc = lastDoc + imap, offset = handleTypes(l.list, offset, d) + imap.Doc = GoDoc(lastDoc) d.Types[imap.Name] = imap + lastDoc = "" case "func": var gf GoFunc gf, offset = handleFunds(l.list, offset) d.Funds[gf.Name] = gf + lastDoc = "" case "const": _, offset = handleCosts(l.list, offset) + lastDoc = "" case "var": _, offset = handleVars(l.list, offset) + lastDoc = "" default: - fmt.Println("文件块作用域似乎解析有错误", info.path, work.str, offset) + nl := l.list[offset:] + str := "" + for _, w := range nl { + str += w.Str + } + fmt.Println("文件块作用域似乎解析有错误", path, offset, str) } } } @@ -83,64 +124,65 @@ func GetFileParser(info FileInfo) (GoFileParser, error) { } func handlePackageName(l []*word, offset int) (string, int) { - newOffset := offset - name := "" - for i, w := range l[offset:] { - if w.t == wordT_line { - name = l[i-1].str - newOffset = i - break - } + name, i := GetFistWordBehindStr(l[offset:], "package") + return name, offset + i +} + +func getImport(sl []string) (string, string) { + if len(sl) == 2 { + return sl[0], sl[1][1 : len(sl[1])-1] } - return name, newOffset + str := sl[0][1 : len(sl[0])-1] + temp := strings.Split(str, "/") + key := temp[len(temp)-1] + return key, str } func handleImports(l []*word, offset int) (map[string]string, int) { newOffset := offset imap := make(map[string]string) var key, val string - start := 0 -gofer: - for i, w := range l[offset+1:] { - switch w.t { - case wordT_line: - newOffset = i + offset + 1 - switch start { - case 0: - break - case 1: - if l[newOffset+1].str == ")" { - i = newOffset + 1 - break gofer - } - key, val = "", "" - } - case wordT_word: - if w.str[0:1] == "\"" { - val = w.str[1 : len(w.str)-1] - - if key == "" { - temp := strings.Split(val, "/") - key = temp[len(temp)-1] - } + ft, fti := GetFistStr(l[offset+1:]) + if ft != "(" { + arr := make([]string, 0) + for i, w := range l[offset+fti:] { + if wordT_line == w.Ty { + newOffset = offset + fti + i + key, val = getImport(arr) imap[key] = val - } else { - key = w.str + return imap, newOffset } - case wordT_division: - if w.str == "(" { - start = 1 + + if w.Ty == wordT_word { + arr = append(arr, w.Str) + } + } + } else { + st, et := GetBrackets(l[offset+1:], "(", ")") + st = st + offset + 1 + et = et + offset + 1 + newOffset = et + + arr := make([]string, 0) + for _, w := range l[st : et+1] { + if wordT_line == w.Ty && len(arr) != 0 { + key, val = getImport(arr) + imap[key] = val + arr = make([]string, 0) + } + + if w.Ty == wordT_word { + arr = append(arr, w.Str) } } } - - return imap, newOffset + 1 + return imap, newOffset } type GoType struct { - Doc string + Doc GoDoc Name string Attrs map[string]GoTypeAttr } @@ -149,28 +191,76 @@ type GoTypeAttr struct { TypeName string TypeAlias string TypeImport string - Tag map[string]string + InPackage bool // 是否本包的引用 + Tag map[string]TagDoc } -// 普通指针 +type TagDoc string + +func (t TagDoc) Get(num int) string { + s := string(t) + sr := strings.Split(s, ",") + return strings.Trim(sr[num], " ") +} + +func (t TagDoc) Count() int { + s := string(t) + sr := strings.Split(s, ",") + return len(sr) +} + +type GoDoc string + +// HasAnnotation 是否存在某个注解 +func (d GoDoc) HasAnnotation(check string) bool { + return strings.Index(string(d), check) != -1 +} + +func (d GoDoc) GetAlias() string { + l := GetWords(string(d)[2:]) + num := 0 + for i, w := range l { + if w.Ty == wordT_word { + if w.Str == "Bean" { + if l[i-1].Str == "@" { + num = 1 + } + } else if num == 1 { + return w.Str[1 : len(w.Str)-1] + } + } + } + return "" +} + +// IsPointer 普通指针 func (receiver GoTypeAttr) IsPointer() bool { return receiver.TypeName[0:1] == "*" } +func (receiver GoTypeAttr) HasTag(name string) bool { + for s := range receiver.Tag { + if s == name { + return true + } + } + return false +} + // 组装成数组, 只限name type other\n结构 func getArrGoWord(l []*word) [][]string { got := make([][]string, 0) arr := GetArrWord(l) for _, i := range arr { - lis := i[len(i)-1].str + lis := i[len(i)-1].Str if lis[0:1] == "`" && len(i) >= 3 { ty := "" for in := 1; in < len(i)-1; in++ { - if i[in].t != wordT_doc { - ty = ty + i[in].str + if i[in].Ty != wordT_doc { + ty = ty + i[in].Str } } - got = append(got, []string{i[0].str, ty, lis}) + got = append(got, []string{i[0].Str, ty, lis}) } } @@ -186,8 +276,8 @@ func getArrGoTag(source string) [][]string { got := make([][]string, 0) arr := make([]string, 0) for _, w := range wl { - if w.t == wordT_word { - arr = append(arr, w.str) + if w.Ty == wordT_word { + arr = append(arr, w.Str) i++ if i >= 2 { i = 0 @@ -199,7 +289,7 @@ func getArrGoTag(source string) [][]string { return got } -func handleTypes(l []*word, offset int) (GoType, int) { +func handleTypes(l []*word, offset int, d GoFileParser) (GoType, int) { newOffset := offset nl := l[offset:] got := GoType{ @@ -222,17 +312,17 @@ func handleTypes(l []*word, offset int) (GoType, int) { // TODO 当前仅支持有tag的 if len(wordAttrs) == 3 && strings.Index(wordAttrs[2], "`") == 0 { attr := GoTypeAttr{ - Name: wordAttrs[0], - TypeName: wordAttrs[1], - TypeAlias: "", - TypeImport: "", - Tag: map[string]string{}, + Name: wordAttrs[0], + TypeName: wordAttrs[1], + Tag: map[string]TagDoc{}, } + getTypeAlias(wordAttrs[1], d, &attr) // 解析 go tag tagArr := getArrGoTag(wordAttrs[2]) for _, tagStrArr := range tagArr { - attr.Tag[tagStrArr[0]] = tagStrArr[1] + td := tagStrArr[1] + attr.Tag[tagStrArr[0]] = TagDoc(td[1 : len(td)-1]) } got.Attrs[attr.Name] = attr } @@ -246,53 +336,94 @@ func handleTypes(l []*word, offset int) (GoType, int) { return got, newOffset } +// 根据属性声明类型或者类型的引入名称 +func getTypeAlias(str string, d GoFileParser, attr *GoTypeAttr) { + wArr := GetWords(str) + wf := wArr[0] + + if wf.Ty == wordT_word || wf.Str == "*" { + if (wf.Str == "*" && len(wArr) >= 3) || (wf.Str != "*" && len(wArr) >= 2) { + attr.TypeAlias, _ = GetFistWord(wArr) + attr.TypeImport = d.Imports[attr.TypeAlias] + return + } + } + // 本包 + attr.TypeAlias = d.PackageName + attr.TypeImport = "" // TODO + attr.InPackage = true +} + type GoFunc struct { Name string Stu string } func handleFunds(l []*word, offset int) (GoFunc, int) { - ft := 0 - for _, w := range l[offset+1:] { - if w.t == wordT_division && w.str == "(" { - ft = 1 - break - } else if w.t == wordT_word { - break - } - } - if ft == 0 { + ft, _ := GetFistStr(l[offset+1:]) + name := "" + if ft != "(" { // 普通函数 - name, i := GetFistWordBehindStr(l[offset:], "func") - - _, et := GetBrackets(l[offset+i:], "(", ")") - _, et = GetBrackets(l[offset+et+i:], "{", "}") - return GoFunc{Name: name}, offset + et + i + var i int + name, i = GetFistWordBehindStr(l[offset:], "func") + offset = offset + i + _, et := GetBrackets(l[offset:], "(", ")") + offset = offset + et } else { // 结构函数 _, et := GetBrackets(l[offset:], "(", ")") offset = offset + et - name, _ := GetFistWord(l[offset:]) + name, _ = GetFistWord(l[offset:]) _, et = GetBrackets(l[offset:], "(", ")") offset = offset + et - _, et = GetBrackets(l[offset:], "{", "}") - return GoFunc{Name: name}, offset + et } + // 排除返回值的interface{} + st, et := GetBrackets(l[offset:], "{", "}") + interCount := 0 + for _, w := range l[offset : offset+st] { + if w.Str == "interface" { + interCount++ + } + } + if interCount != 0 { + for i := 0; i <= interCount; i++ { + _, et := GetBrackets(l[offset:], "{", "}") + offset = offset + et + } + } else { + offset = offset + et + } + return GoFunc{Name: name}, offset } func handleCosts(l []*word, offset int) (map[string]string, int) { - ok, off := GetLastIsIdentifier(l[offset:], "(") - if ok { - return nil, off + offset - } - _, et := GetBrackets(l[offset:], "(", ")") - return nil, offset + et + return handleVars(l, offset) } func handleVars(l []*word, offset int) (map[string]string, int) { - ok, off := GetLastIsIdentifier(l[offset:], "(") - if ok { - return nil, off + offset + ft, _ := GetFistStr(l[offset+1:]) + if ft != "(" { + ok, _ := GetLastIsIdentifier(l[offset:], "{") + if ok { + last := NextLine(l[offset:]) + ss := l[offset+last-1] + if ss.Str == "{" { + nl := l[offset+last-1:] + offset = offset + last - 1 + _, et := GetBrackets(nl, "{", "}") + return nil, offset + et + 1 + } else { + nl := l[offset:] + _, et := GetBrackets(nl, "{", "}") + return nil, offset + et + 1 + } + } else { + last := NextLine(l[offset:]) + offset = offset + last + return nil, offset + } + } else { + nl := l[offset:] + _, et := GetBrackets(nl, "(", ")") + return nil, offset + et + 1 } - _, et := GetBrackets(l[offset:], "(", ")") - return nil, offset + et } diff --git a/parser/go.go.abck b/parser/go.go.abck deleted file mode 100644 index e2ef40e..0000000 --- a/parser/go.go.abck +++ /dev/null @@ -1,210 +0,0 @@ -package parser - -import ( - "bufio" - "fmt" - "os" -) - -type DirParser struct { - PackageName string - Imports map[string]string - Types map[string]string - Funcs map[string]string -} - -func NewGoParser(path string) DirParser { - d := DirParser{} - for _, file := range loadGoFiles(path) { - gof, _ := parserGoFile(file) - fmt.Println(gof) - } - - return d -} - -func loadGoFiles(path string) []FileInfo { - return loadFiles(path, ".go") -} - -func parserGoFile(info FileInfo) (string, error) { - l := words(info.path) - for _, i := range l.list { - fmt.Print(i.str) - } - - return "", nil -} - -// 分词后的结构 -type word struct { - str string - t wordT -} -type GoWords struct { - list []*word -} -type wordT int - -const ( - // 单词 - wordT_word wordT = 0 - // 分隔符 - wordT_division wordT = 1 - // 换行符 - wordT_line wordT = 2 - wordT_doc wordT = 3 -) - -// 分词过程状态 -type scannerStatus int - -const ( - // 新的一行 - scannerStatus_NewLine scannerStatus = 0 - // 准备注释中 - scannerStatus_DocWait scannerStatus = 1 - // 注释中, 单行, 多行 - scannerStatus_Doc scannerStatus = 2 - scannerStatus_Doc2 scannerStatus = 3 - // 遇到间隔符号 - scannerStatus_NewWork scannerStatus = 4 - // 单词中 - scannerStatus_Work scannerStatus = 5 -) - -func words(path string) GoWords { - var got = GoWords{ - list: make([]*word, 0), - } - file, err := os.Open(path) - if err != nil { - panic(err) - return got - } - defer file.Close() - scanner := bufio.NewScanner(file) - - status := scannerStatus_NewLine - work := "" - lastIsSpe := false - for scanner.Scan() { - for _, s := range scanner.Text() { - str := string(s) - stop := false - switch status { - case scannerStatus_Doc: - work = work + str - stop = true - case scannerStatus_Doc2: - work = work + str - stop = true - // 检查是否*/结束了 - if str == "/" && HasSuffix(work, "*/") { - got.list = append(got.list, &word{ - str: work, - t: wordT_doc, - }) - // 分割后从新开始 - work = "" - status = scannerStatus_NewWork - } - case scannerStatus_DocWait: - switch str { - case "/": - work = work + str - status = scannerStatus_Doc - stop = true - case "*": - work = work + str - status = scannerStatus_Doc2 - stop = true - default: - // 没有进入文档模式, 那么上一个就是分割符号 - got.list = append(got.list, &word{ - str: work, - t: wordT_division, - }) - // 分割后从新开始 - work = "" - status = scannerStatus_NewWork - } - case scannerStatus_NewLine, scannerStatus_NewWork: - if str == "/" { - work = work + str - status = scannerStatus_DocWait - stop = true - } - } - if !stop { - if IsIdentifier(s) { - // 标识符: 字母, 数字, _ - work = work + str - status = scannerStatus_Work - lastIsSpe = false - } else if InArrString(str,[]string{" ","\t"}) { - // 合并多余的空格 - if !lastIsSpe { - got.list = append(got.list, &word{ - str: work, - t: wordT_division, - }, &word{ - str: str, - t: wordT_division, - }) - work = "" - status = scannerStatus_NewWork - lastIsSpe = true - } - }else { - got.list = append(got.list, &word{ - str: work, - t: wordT_word, - }, &word{ - str: str, - t: wordT_division, - }) - work = "" - status = scannerStatus_NewWork - lastIsSpe = false - } - } else { - lastIsSpe = false - } - } - switch status { - case scannerStatus_Work: - got.list = append(got.list, &word{ - str: work, - t: wordT_word, - }, &word{ - str: "\n", - t: wordT_line, - }) - status = scannerStatus_NewLine - work = "" - case scannerStatus_Doc: - got.list = append(got.list, &word{ - str: work, - t: wordT_doc, - }, &word{ - str: "\n", - t: wordT_line, - }) - status = scannerStatus_NewLine - work = "" - case scannerStatus_Doc2: - // 多行注释未结束 - work = work + "\n" - default: - got.list = append(got.list, &word{ - str: "\n", - t: wordT_line, - }) - status = scannerStatus_NewLine - work = "" - } - } - - return got -} diff --git a/parser/help_file.go b/parser/help_file.go index 619067b..7c38371 100644 --- a/parser/help_file.go +++ b/parser/help_file.go @@ -6,15 +6,74 @@ import ( "io/ioutil" "os" path2 "path" + "path/filepath" ) +func DirIsExist(f string) bool { + _, err := os.Stat(f) + return err == nil || os.IsExist(err) +} + type FileInfo struct { fs.FileInfo - path string + Path string } -// 读取目录中的所有文件包括子目录的文件 -func loadFiles(path string, ext string) []FileInfo { +type DirInfo struct { + Name string + Path string +} + +// GetFiles 获取目录下所有文件 +func (di DirInfo) GetFiles(ext string) []FileInfo { + got := make([]FileInfo, 0) + + files, err := ioutil.ReadDir(di.Path) + if err != nil { + panic(err) + } + + for _, file := range files { + if path2.Ext(file.Name()) == ext { + got = append(got, FileInfo{ + FileInfo: file, + Path: di.Path + "/" + file.Name(), + }) + } + } + + return got +} + +// GetChildrenDir 获取目录和所有子目录 +func GetChildrenDir(path string) []DirInfo { + got := []DirInfo{ + { + Name: filepath.Base(path), + Path: path, + }, + } + + files, err := ioutil.ReadDir(path) + if err != nil { + panic(err) + } + + for _, file := range files { + if file.IsDir() && file.Name()[0:1] != "." { + got = append(got, DirInfo{ + Name: file.Name(), + Path: path + "/" + file.Name(), + }) + got = append(got, GetChildrenDir(path+"/"+file.Name())...) + } + } + + return got +} + +// GetDirFiles 读取目录中的所有文件包括子目录的文件 +func GetDirFiles(path string, ext string) []FileInfo { got := make([]FileInfo, 0) files, err := ioutil.ReadDir(path) @@ -24,12 +83,12 @@ func loadFiles(path string, ext string) []FileInfo { for _, file := range files { if file.IsDir() { - t := loadFiles(path+"/"+file.Name(), ext) + t := GetDirFiles(path+"/"+file.Name(), ext) got = append(got, t...) } else if path2.Ext(file.Name()) == ext { got = append(got, FileInfo{ FileInfo: file, - path: path + "/" + file.Name(), + Path: path + "/" + file.Name(), }) } } @@ -39,8 +98,8 @@ func loadFiles(path string, ext string) []FileInfo { // 分词后的结构 type word struct { - str string - t wordT + Str string + Ty wordT } type GoWords struct { list []*word @@ -108,8 +167,8 @@ func getWordsWitchFile(path string) GoWords { // 检查是否*/结束了 if str == "/" && HasSuffix(work, "*/") { got.list = append(got.list, &word{ - str: work, - t: wordT_doc, + Str: work, + Ty: wordT_doc, }) // 分割后从新开始 work = "" @@ -128,8 +187,8 @@ func getWordsWitchFile(path string) GoWords { default: // 没有进入文档模式, 那么上一个就是分割符号 got.list = append(got.list, &word{ - str: work, - t: wordT_division, + Str: work, + Ty: wordT_division, }) // 分割后从新开始 work = "" @@ -140,8 +199,8 @@ func getWordsWitchFile(path string) GoWords { stop = true if str == "\"" && !HasSuffix(work, "\\\"") { got.list = append(got.list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) // 分割后从新开始 work = "" @@ -152,8 +211,8 @@ func getWordsWitchFile(path string) GoWords { stop = true if str == "'" { got.list = append(got.list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) // 分割后从新开始 work = "" @@ -164,8 +223,8 @@ func getWordsWitchFile(path string) GoWords { stop = true if str == "`" { got.list = append(got.list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) // 分割后从新开始 work = "" @@ -202,13 +261,13 @@ func getWordsWitchFile(path string) GoWords { if !lastIsSpe { if len(work) != 0 { got.list = append(got.list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) } got.list = append(got.list, &word{ - str: str, - t: wordT_division, + Str: str, + Ty: wordT_division, }) work = "" status = scannerStatus_NewWork @@ -217,13 +276,13 @@ func getWordsWitchFile(path string) GoWords { } else { if len(work) != 0 { got.list = append(got.list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) } got.list = append(got.list, &word{ - str: str, - t: wordT_division, + Str: str, + Ty: wordT_division, }) work = "" status = scannerStatus_NewWork @@ -236,21 +295,21 @@ func getWordsWitchFile(path string) GoWords { switch status { case scannerStatus_Work: got.list = append(got.list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }, &word{ - str: "\n", - t: wordT_line, + Str: "\n", + Ty: wordT_line, }) status = scannerStatus_NewLine work = "" case scannerStatus_Doc: got.list = append(got.list, &word{ - str: work, - t: wordT_doc, + Str: work, + Ty: wordT_doc, }, &word{ - str: "\n", - t: wordT_line, + Str: "\n", + Ty: wordT_line, }) status = scannerStatus_NewLine work = "" @@ -258,10 +317,10 @@ func getWordsWitchFile(path string) GoWords { // 多行注释未结束 work = work + "\n" default: - if got.list[len(got.list)-1].t != wordT_line { + if len(got.list) != 0 && got.list[len(got.list)-1].Ty != wordT_line { got.list = append(got.list, &word{ - str: "\n", - t: wordT_line, + Str: "\n", + Ty: wordT_line, }) } status = scannerStatus_NewLine diff --git a/parser/help_str.go b/parser/help_str.go index 846229b..102137f 100644 --- a/parser/help_str.go +++ b/parser/help_str.go @@ -1,6 +1,9 @@ package parser import ( + "sort" + "strconv" + "strings" "unicode" ) @@ -42,14 +45,12 @@ func GetLastIsIdentifier(l []*word, start string) (bool, int) { i := 0 var w *word for i, w = range l { - if w.t == wordT_line { + if w.Ty == wordT_line { return ok, i - } else if w.str == start { + } else if w.Str == start { ok = true - } else if !ok || !(w.t == wordT_doc || InArrString(w.str, []string{" ", "\t", "\n"})) { - if ok { - return false, i - } + } else if ok && !(w.Ty == wordT_doc || InArrString(w.Str, []string{" ", "\t"})) { + ok = false } } @@ -59,21 +60,54 @@ func GetLastIsIdentifier(l []*word, start string) (bool, int) { // 获取第一个单词 func GetFistWord(l []*word) (string, int) { for i, w := range l { - if w.t == wordT_word { - return w.str, i + if w.Ty == wordT_word { + return w.Str, i } } return "", len(l) } +// 获取第n个单词 +func GetWord(l []*word, n int) (string, int) { + for i, w := range l { + if w.Ty == wordT_word { + n-- + if n <= 0 { + return w.Str, i + } + } + } + return "", len(l) +} + +// 获取第一个字符(不包括空格, 换行符, 制表) +func GetFistStr(l []*word) (string, int) { + for i, w := range l { + if !InArrString(w.Str, []string{" ", "\n", "\t"}) { + return w.Str, i + } + } + return "", len(l) +} + +// 获取下一行开始 +func NextLine(l []*word) int { + for i, w := range l { + if w.Ty == wordT_line { + return i + } + } + return len(l) +} + // 获取某字符串后第一个单词 func GetFistWordBehindStr(l []*word, behind string) (string, int) { init := false for i, w := range l { - if w.t == wordT_word { + if w.Ty == wordT_word { if init { - return w.str, i - } else if w.str == behind { + return w.Str, i + } else if w.Str == behind { init = true } } @@ -84,9 +118,9 @@ func GetFistWordBehindStr(l []*word, behind string) (string, int) { // 括号引用起来的块, 或者第一行没有块就换行 func GetBracketsOrLn(l []*word, start, end string) (int, int, bool) { for i, w := range l { - if w.t == wordT_line { + if w.Ty == wordT_line { return 0, i, false - } else if w.t == wordT_division && w.str == start { + } else if w.Ty == wordT_division && w.Str == start { startInt, endInt := GetBrackets(l, start, end) return startInt, endInt, true } @@ -96,27 +130,28 @@ func GetBracketsOrLn(l []*word, start, end string) (int, int, bool) { } // 括号引用起来的块, 词性必须是分隔符 +// 返回是开始偏移和结束偏移 func GetBrackets(l []*word, start, end string) (int, int) { var startInt, endInt int bCount := 0 for i, w := range l { if bCount == 0 { - if w.t == wordT_division && w.str == start { + if w.Ty == wordT_division && w.Str == start { startInt = i bCount++ } } else { - if w.t == wordT_division { - switch w.str { + if w.Ty == wordT_division { + switch w.Str { + case start: + bCount++ case end: bCount-- if bCount <= 0 { endInt = i return startInt, endInt } - case start: - bCount++ } } } @@ -130,11 +165,11 @@ func GetArrWord(l []*word) [][]*word { got := make([][]*word, 0) sl := make([]*word, 0) for _, w := range l { - switch w.t { + switch w.Ty { case wordT_word: sl = append(sl, w) case wordT_division: - if !InArrString(w.str, []string{" ", "\t"}) { + if !InArrString(w.Str, []string{" ", "\t"}) { sl = append(sl, w) } case wordT_line: @@ -169,8 +204,8 @@ func GetWords(source string) []*word { // 检查是否*/结束了 if str == "/" && HasSuffix(work, "*/") { list = append(list, &word{ - str: work, - t: wordT_doc, + Str: work, + Ty: wordT_doc, }) // 分割后从新开始 work = "" @@ -189,8 +224,8 @@ func GetWords(source string) []*word { default: // 没有进入文档模式, 那么上一个就是分割符号 list = append(list, &word{ - str: work, - t: wordT_division, + Str: work, + Ty: wordT_division, }) // 分割后从新开始 work = "" @@ -201,8 +236,8 @@ func GetWords(source string) []*word { stop = true if str == "\"" && !HasSuffix(work, "\\\"") { list = append(list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) // 分割后从新开始 work = "" @@ -213,8 +248,8 @@ func GetWords(source string) []*word { stop = true if str == "'" { list = append(list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) // 分割后从新开始 work = "" @@ -225,8 +260,8 @@ func GetWords(source string) []*word { stop = true if str == "`" { list = append(list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) // 分割后从新开始 work = "" @@ -263,13 +298,13 @@ func GetWords(source string) []*word { if !lastIsSpe { if len(work) != 0 { list = append(list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) } list = append(list, &word{ - str: str, - t: wordT_division, + Str: str, + Ty: wordT_division, }) work = "" status = scannerStatus_NewWork @@ -278,13 +313,13 @@ func GetWords(source string) []*word { } else { if len(work) != 0 { list = append(list, &word{ - str: work, - t: wordT_word, + Str: work, + Ty: wordT_word, }) } list = append(list, &word{ - str: str, - t: wordT_division, + Str: str, + Ty: wordT_division, }) work = "" status = scannerStatus_NewWork @@ -295,5 +330,103 @@ func GetWords(source string) []*word { } } + if status == scannerStatus_Work && len(work) != 0 { + list = append(list, &word{ + Str: work, + Ty: wordT_word, + }) + } + return list } + +// 驼峰转蛇形 +func StringToSnake(s string) string { + s = strings.ReplaceAll(s, "-", "_") + data := make([]byte, 0, len(s)*2) + j := false + num := len(s) + for i := 0; i < num; i++ { + d := s[i] + if i > 0 && d >= 'A' && d <= 'Z' && j { + data = append(data, '_') + } + if d != '_' { + j = true + } + data = append(data, d) + } + return strings.ToLower(string(data[:])) +} + +// 蛇形转驼峰 +func StringToHump(s string) string { + data := make([]byte, 0, len(s)) + j := false + k := false + num := len(s) - 1 + for i := 0; i <= num; i++ { + d := s[i] + if k == false && d >= 'A' && d <= 'Z' { + k = true + } + if d >= 'a' && d <= 'z' && (j || k == false) { + d = d - 32 + j = false + k = true + } + if k && (d == '_' || d == '-') && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' { + j = true + continue + } + data = append(data, d) + } + return string(data[:]) +} + +// SortMap 排序map +func SortMap(m map[string]string) []string { + var keys []string + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// GetImportStrForMap 生成import +func GetImportStrForMap(m map[string]string) string { + sk := SortMap(m) + got := "" + for _, k := range sk { + got += "\n\t" + m[k] + " \"" + k + "\"" + } + + return got +} + +// GenImportAlias 生成 import => alias +func GenImportAlias(m map[string]string) map[string]string { + aliasMapImport := make(map[string]string) + importMapAlias := make(map[string]string) + for _, imp := range m { + temp := strings.Split(imp, "/") + key := temp[len(temp)-1] + + if _, ok := aliasMapImport[key]; ok { + for i := 1; i < 1000; i++ { + newKey := key + strconv.Itoa(i) + if _, ok2 := aliasMapImport[newKey]; !ok2 { + key = newKey + break + } + } + } + aliasMapImport[key] = imp + } + for s, s2 := range aliasMapImport { + importMapAlias[s2] = s + } + + return importMapAlias +} diff --git a/parser/help_str_test.go b/parser/help_str_test.go index 5c98a4c..979e708 100644 --- a/parser/help_str_test.go +++ b/parser/help_str_test.go @@ -15,7 +15,7 @@ func TestGetBrackets(t *testing.T) { {" ", wordT_division}, {"TestGetBrackets", wordT_word}, {"(", wordT_word}, - {"t", wordT_word}, + {"Ty", wordT_word}, {" ", wordT_division}, {")", wordT_division}, {" ", wordT_division}, @@ -28,7 +28,12 @@ func TestGetBrackets(t *testing.T) { } start, end := GetBrackets(l, "{", "}") - fmt.Println(start, end) + if l[start].Str != "{" { + t.Error("{解析失败") + } + if l[end].Str != "}" { + t.Error("}解析失败") + } } func TestLastIsIdentifier(t *testing.T) { @@ -37,7 +42,7 @@ func TestLastIsIdentifier(t *testing.T) { {" ", wordT_division}, {"TestGetBrackets", wordT_word}, {"(", wordT_division}, - {"t", wordT_word}, + {"Ty", wordT_word}, {" ", wordT_division}, {")", wordT_division}, {"(", wordT_division}, diff --git a/parser/protoc.go b/parser/protoc.go index 0bfe2c2..3c7ecc7 100644 --- a/parser/protoc.go +++ b/parser/protoc.go @@ -1 +1,328 @@ package parser + +import "fmt" + +/** +golang parser 非完整token实现 +*/ +type ProtocFileParser struct { + Doc string + Syntax string + PackageName string + Imports []string + Option map[string]Option + Services map[string]Service + Messages map[string]Message + Enums map[string]Enum +} + +type Option struct { + Doc string + Key string + Val string + wl []*word + alias string +} + +type Service struct { + Protoc *ProtocFileParser + Doc string + Name string + Opt map[string]Option + Rpc map[string]ServiceRpc +} + +type ServiceRpc struct { + Doc string + Name string + Param string + Return string + Opt map[string]Option +} + +type Message struct { + Doc string + Name string + Attr []Attr + Opt map[string]Option +} +type Attr struct { + Doc string + Name string + ty string + num int +} + +type Enum struct { + Doc string + Name string +} + +func NewProtocParserForDir(path string) map[string][]ProtocFileParser { + got := make(map[string][]ProtocFileParser) + for _, dir := range GetChildrenDir(path) { + arr := make([]ProtocFileParser, 0) + for _, file := range dir.GetFiles(".proto") { + gof, _ := GetProtoFileParser(file.Path) + arr = append(arr, gof) + } + got[dir.Path] = arr + } + + return got +} + +func GetProtoFileParser(path string) (ProtocFileParser, error) { + d := ProtocFileParser{ + Imports: make([]string, 0), + Option: make(map[string]Option), + Services: make(map[string]Service), + Messages: make(map[string]Message), + Enums: make(map[string]Enum), + } + + l := getWordsWitchFile(path) + lastDoc := "" + for offset := 0; offset < len(l.list); offset++ { + work := l.list[offset] + // 原则上, 每个块级别的作用域必须自己处理完, 返回的偏移必须是下一个块的开始 + switch work.Ty { + case wordT_line: + case wordT_division: + case wordT_doc: + lastDoc = work.Str + case wordT_word: + switch work.Str { + case "syntax": + d.Doc = lastDoc + d.Syntax, offset = protoSyntax(l.list, offset) + lastDoc = "" + case "package": + d.PackageName, offset = protoPackageName(l.list, offset) + lastDoc = "" + case "import": + var imports string + imports, offset = protoImport(l.list, offset) + d.Imports = append(d.Imports, imports) + lastDoc = "" + case "option": + var val Option + val.Doc = lastDoc + val, offset = protoOption(l.list, offset) + d.Option[val.Key] = val + lastDoc = "" + case "service": + var val Service + val, offset = protoService(l.list, offset) + val.Protoc = &d + val.Doc = lastDoc + d.Services[val.Name] = val + lastDoc = "" + case "message": + var val Message + val.Doc = lastDoc + val, offset = protoMessage(l.list, offset) + d.Messages[val.Name] = val + lastDoc = "" + case "enum": + var val Enum + val.Doc = lastDoc + val, offset = protoEnum(l.list, offset) + d.Enums[val.Name] = val + lastDoc = "" + case "extend": + _, offset = protoExtend(l.list, offset) + lastDoc = "" + default: + fmt.Println("文件块作用域似乎解析有错误", path, work.Str, offset) + } + } + } + + return d, nil +} +func protoSyntax(l []*word, offset int) (string, int) { + name, i := GetFistWordBehindStr(l[offset:], "syntax") + return name[1 : len(name)-1], offset + i +} +func protoPackageName(l []*word, offset int) (string, int) { + name, i := GetFistWordBehindStr(l[offset:], "package") + return name, offset + i +} +func protoImport(l []*word, offset int) (string, int) { + name, i := GetFistWordBehindStr(l[offset:], "import") + return name[1 : len(name)-1], offset + i +} +func protoOption(l []*word, offset int) (Option, int) { + key, i := GetFistWordBehindStr(l[offset:], "option") + offset = offset + i + 1 + val, i := GetFistWord(l[offset:]) + return Option{Key: key, Val: val[1 : len(val)-1]}, offset + i +} +func serverOption(l []*word, offset int) (Option, int) { + st, et := GetBrackets(l[offset:], "(", ")") + wl := l[offset+st : offset+et+1] + offset = offset + et + 1 + val, i := GetFistWord(l[offset:]) + var key string + var alias string + if len(wl) >= 5 { + alias = wl[1].Str + } + for _, w := range wl { + key = key + w.Str + } + + return Option{ + Key: key[1 : len(key)-1], + Val: val[1 : len(val)-1], + wl: wl, + alias: alias, + }, offset + i +} +func protoService(l []*word, offset int) (Service, int) { + name, i := GetFistWordBehindStr(l[offset:], "service") + offset = offset + i + st, et := GetBrackets(l[offset:], "{", "}") + newOffset := offset + et + nl := l[offset+st : offset+et] + + got := Service{ + Name: name, + Opt: make(map[string]Option, 0), + Rpc: make(map[string]ServiceRpc, 0), + } + for offset := 0; offset < len(nl); offset++ { + work := nl[offset] + switch work.Ty { + case wordT_line: + case wordT_division: + case wordT_doc: + case wordT_word: + switch work.Str { + case "option": + var val Option + val, offset = serverOption(nl, offset) + got.Opt[val.Key] = val + case "rpc": + var val ServiceRpc + val, offset = protoRpc(nl, offset) + got.Rpc[val.Name] = val + } + } + } + + return got, newOffset +} + +func protoRpc(l []*word, offset int) (ServiceRpc, int) { + name, i := GetFistWordBehindStr(l[offset:], "rpc") + offset = offset + i + 1 + Param, i := GetFistWord(l[offset:]) + offset = offset + i + 1 + Return, i := GetWord(l[offset:], 2) + offset = offset + i + 1 + // opt + opt := make(map[string]Option) + st, et := GetBrackets(l[offset:], "{", "}") + newOffset := offset + et + 1 + nl := l[offset+st : newOffset] + for offset := 0; offset < len(nl); offset++ { + work := nl[offset] + switch work.Ty { + case wordT_line: + case wordT_division: + case wordT_doc: + case wordT_word: + switch work.Str { + case "option": + var val Option + val, offset = serverOption(nl, offset) + opt[val.Key] = val + } + } + } + + return ServiceRpc{ + Name: name, + Param: Param, + Return: Return, + Opt: opt, + }, newOffset +} + +func protoMessage(l []*word, offset int) (Message, int) { + name, i := GetFistWordBehindStr(l[offset:], "message") + offset = offset + i + st, et := GetBrackets(l[offset:], "{", "}") + newOffset := offset + et + nl := l[offset+st : offset+et] + + got := Message{ + Name: name, + Attr: nil, + Opt: nil, + } + for offset := 0; offset < len(nl); offset++ { + work := nl[offset] + switch work.Ty { + case wordT_line: + case wordT_division: + case wordT_doc: + case wordT_word: + + } + } + + return got, newOffset +} + +func protoEnum(l []*word, offset int) (Enum, int) { + name, i := GetFistWordBehindStr(l[offset:], "enum") + offset = offset + i + st, et := GetBrackets(l[offset:], "{", "}") + newOffset := offset + et + nl := l[offset+st : offset+et] + + got := Enum{ + Name: name, + } + for offset := 0; offset < len(nl); offset++ { + work := nl[offset] + switch work.Ty { + case wordT_line: + case wordT_division: + case wordT_doc: + case wordT_word: + + } + } + + return got, newOffset +} + +func protoExtend(l []*word, offset int) (Message, int) { + name, i := GetFistWordBehindStr(l[offset:], "enum") + offset = offset + i + st, et := GetBrackets(l[offset:], "{", "}") + newOffset := offset + et + nl := l[offset+st : offset+et] + + got := Message{ + Name: name, + Attr: nil, + Opt: nil, + } + for offset := 0; offset < len(nl); offset++ { + work := nl[offset] + switch work.Ty { + case wordT_line: + case wordT_division: + case wordT_doc: + case wordT_word: + + } + } + + return got, newOffset +}