Initial commit (code only without large binaries)
This commit is contained in:
465
EdgeCommon/cmd/langs/main.go
Normal file
465
EdgeCommon/cmd/langs/main.go
Normal file
@@ -0,0 +1,465 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"go/format"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var args = os.Args
|
||||
if len(args) >= 2 {
|
||||
switch args[1] {
|
||||
case "generate":
|
||||
// generate go codes from json files
|
||||
runGenerate()
|
||||
case "search":
|
||||
// search hans from dir path
|
||||
runSearch()
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Usage: langs [generate|search]")
|
||||
}
|
||||
}
|
||||
|
||||
func runGenerate() {
|
||||
var rootDir = filepath.Clean(Tea.Root + "/../pkg/langs/protos")
|
||||
dir, err := os.Open(rootDir)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]read dir failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = dir.Close()
|
||||
}()
|
||||
|
||||
files, err := dir.Readdir(0)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]read dir failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var dirRegexp = regexp.MustCompile(`^[a-z]+-[a-z]+$`)
|
||||
var jsonFileNameRegexp = regexp.MustCompile(`^([a-zA-Z0-9]+)(_([a-zA-Z0-9]+))*\.json$`)
|
||||
var messageCodeRegexp = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||
var jsonCommentRegexp = regexp.MustCompile(`//\s+.+`)
|
||||
|
||||
var messageCodes = []string{}
|
||||
var langMaps = map[string]*langs.Lang{} // lang => *langs.Lang
|
||||
var defaultLang = langs.DefaultManager().DefaultLang()
|
||||
|
||||
const maxMessageCodeLen = 128
|
||||
|
||||
for _, file := range files {
|
||||
var dirName = file.Name()
|
||||
|
||||
if !file.IsDir() || !dirRegexp.MatchString(dirName) {
|
||||
continue
|
||||
}
|
||||
var langCode = dirName
|
||||
var isBaseLang = langCode == defaultLang
|
||||
|
||||
var processOk = func() bool {
|
||||
jsonFiles, err := filepath.Glob(rootDir + "/" + dirName + "/*.json")
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]list json files failed: " + err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
for _, jsonFile := range jsonFiles {
|
||||
var jsonFileName = filepath.Base(jsonFile)
|
||||
if len(jsonFileName) == 0 || !jsonFileNameRegexp.MatchString(jsonFileName) {
|
||||
continue
|
||||
}
|
||||
var module = strings.TrimSuffix(jsonFileName, ".json")
|
||||
|
||||
data, err := os.ReadFile(jsonFile)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]read json file '" + jsonFile + "' failed: " + err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
// remove comments in json
|
||||
data = jsonCommentRegexp.ReplaceAll(data, []byte{})
|
||||
|
||||
var m = map[string]string{} // code => value
|
||||
err = json.Unmarshal(data, &m)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]decode json file '" + jsonFile + "' failed: " + err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
var newM = map[string]string{}
|
||||
for code, value := range m {
|
||||
if !messageCodeRegexp.MatchString(code) {
|
||||
fmt.Println("[ERROR]invalid message code '" + code + "'")
|
||||
return false
|
||||
}
|
||||
|
||||
var fullCode = module + "@" + code
|
||||
|
||||
if len(fullCode) > maxMessageCodeLen {
|
||||
fmt.Println("[ERROR]message code '" + fullCode + "' too long, max length: " + types.String(maxMessageCodeLen))
|
||||
return false
|
||||
}
|
||||
|
||||
if isBaseLang {
|
||||
messageCodes = append(messageCodes, fullCode)
|
||||
}
|
||||
newM[fullCode] = value
|
||||
}
|
||||
|
||||
finalLang, ok := langMaps[langCode]
|
||||
if !ok {
|
||||
finalLang = langs.NewLang(langCode)
|
||||
langMaps[langCode] = finalLang
|
||||
}
|
||||
for code, value := range newM {
|
||||
if finalLang.Has(langs.MessageCode(code)) {
|
||||
fmt.Println("[ERROR]message code '" + code + "' duplicated")
|
||||
return false
|
||||
}
|
||||
finalLang.Set(langs.MessageCode(code), value)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}()
|
||||
if !processOk {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// compile
|
||||
for langCode, lang := range langMaps {
|
||||
err = lang.Compile()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]compile '" + langCode + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check message codes
|
||||
fmt.Println("checking message codes ...")
|
||||
var defaultMessageMap = map[langs.MessageCode]string{}
|
||||
for langCode, messageLang := range langMaps {
|
||||
if langCode == defaultLang { // only check lang not equal to 'en-us'
|
||||
defaultMessageMap = messageLang.GetAll()
|
||||
continue
|
||||
}
|
||||
|
||||
for messageCode := range messageLang.GetAll() {
|
||||
if !lists.ContainsString(messageCodes, messageCode.String()) {
|
||||
fmt.Println("[ERROR]message code '" + messageCode.String() + "' in lang '" + langCode + "' not exist in default lang file ('" + defaultLang + "')")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("found '" + types.String(len(messageCodes)) + "' message codes")
|
||||
|
||||
// generate codes/codes.go
|
||||
sort.Strings(messageCodes)
|
||||
var codesSource = `
|
||||
// generated by run 'langs generate'
|
||||
|
||||
package codes
|
||||
|
||||
import(
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
)
|
||||
|
||||
const (
|
||||
`
|
||||
|
||||
for index, messageCode := range messageCodes {
|
||||
// add comment to message code
|
||||
comment, _, _ := strings.Cut(defaultMessageMap[langs.MessageCode(messageCode)], "\n")
|
||||
|
||||
codesSource += upperWords(messageCode) + " langs.MessageCode = " + strconv.Quote(messageCode) + " // " + comment
|
||||
|
||||
// add NL
|
||||
if index != len(messageCodes)-1 {
|
||||
codesSource += "\n"
|
||||
}
|
||||
}
|
||||
|
||||
codesSource += `
|
||||
)
|
||||
`
|
||||
|
||||
formattedCodesSource, err := format.Source([]byte(codesSource))
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]format 'codes.go' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("generating 'codes/codes.go' ...")
|
||||
err = os.WriteFile(Tea.Root+"/../pkg/langs/codes/codes.go", formattedCodesSource, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]write to 'codes.go' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// generate messages_LANG.go
|
||||
for langCode, messageLang := range langMaps {
|
||||
var langFile = strings.ReplaceAll("messages/messages_"+langCode+".go", "-", "_")
|
||||
|
||||
fmt.Println("generating '" + langFile + "' ...")
|
||||
var source = `
|
||||
// generated by run 'langs generate'
|
||||
|
||||
package messages
|
||||
|
||||
import(
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
)
|
||||
|
||||
func init() {
|
||||
langs.Load("` + langCode + `", map[langs.MessageCode]string{
|
||||
`
|
||||
|
||||
for _, code := range messageCodes {
|
||||
var value = messageLang.Get(langs.MessageCode(code))
|
||||
source += strconv.Quote(code) + ": " + strconv.Quote(value) + ",\n"
|
||||
}
|
||||
|
||||
source += `
|
||||
})
|
||||
}
|
||||
`
|
||||
|
||||
formattedSource, err := format.Source([]byte(source))
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]format '" + langFile + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(Tea.Root+"/../pkg/langs/"+langFile, formattedSource, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]write file '" + langFile + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// generate language javascript files for EdgeAdmin and EdgeUser (commercial versions)
|
||||
for lang, messageLang := range langMaps {
|
||||
if lang != defaultLang {
|
||||
// TODO merge messageMap with default message map
|
||||
}
|
||||
|
||||
for _, component := range []string{"EdgeAdmin", "EdgeUser"} {
|
||||
fmt.Println("generating '" + lang + ".js' for " + component + " ...")
|
||||
var targetFile = filepath.Clean(Tea.Root + "/../../" + component + "/web/public/js/langs/" + lang + ".js")
|
||||
var targetDir = filepath.Dir(targetFile)
|
||||
dirStat, _ := os.Stat(targetDir)
|
||||
if dirStat != nil {
|
||||
var prefix = ""
|
||||
switch component {
|
||||
case "EdgeAdmin":
|
||||
prefix = "admin_"
|
||||
case "EdgeUser":
|
||||
prefix = "user_"
|
||||
}
|
||||
if len(prefix) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var filteredMessages = map[langs.MessageCode]string{}
|
||||
for code, value := range messageLang.GetAll() {
|
||||
if strings.HasPrefix(code.String(), prefix) && strings.Contains(code.String(), "@ui_") /** must contains 'ui' **/ {
|
||||
filteredMessages[code] = value
|
||||
}
|
||||
}
|
||||
|
||||
messageMapJSON, jsonErr := json.Marshal(filteredMessages)
|
||||
if jsonErr != nil {
|
||||
fmt.Println("[ERROR]marshal message map failed: " + jsonErr.Error())
|
||||
return
|
||||
}
|
||||
err = os.WriteFile(targetFile, []byte(`// generated by 'langs generate'
|
||||
window.LANG_MESSAGES = `+string(messageMapJSON)+";\n"), 0666)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]write file '" + targetFile + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// base.js
|
||||
if lang == "zh-cn" {
|
||||
var baseJSFile = filepath.Dir(targetFile) + "/base.js"
|
||||
err = os.WriteFile(baseJSFile, []byte(`// generated by 'langs generate'
|
||||
window.LANG_MESSAGES_BASE = `+string(messageMapJSON)+";\n"), 0666)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]write file '" + baseJSFile + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("success")
|
||||
}
|
||||
|
||||
func upperWords(s string) string {
|
||||
var pieces = strings.Split(s, "@")
|
||||
for pieceIndex, piece := range pieces {
|
||||
var words = strings.Split(piece, "_")
|
||||
for index, word := range words {
|
||||
words[index] = upperWord(word)
|
||||
}
|
||||
pieces[pieceIndex] = strings.Join(words, "")
|
||||
}
|
||||
return strings.Join(pieces, "_")
|
||||
}
|
||||
|
||||
func upperWord(word string) string {
|
||||
var l = len(word)
|
||||
if l == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if l == 1 {
|
||||
return strings.ToUpper(word)
|
||||
}
|
||||
|
||||
// special words
|
||||
switch word {
|
||||
case "api", "http", "https", "tcp", "tls", "ssl", "udp", "ip", "dns", "ns",
|
||||
"waf", "acme", "ssh", "toa", "http2", "http3", "uam", "cc",
|
||||
"db", "isp", "sni", "ui", "soa", "ocsp", "en", "zh", "ad", "tsig",
|
||||
"rpc", "dao":
|
||||
return strings.ToUpper(word)
|
||||
case "ipv6":
|
||||
return "IPv6"
|
||||
case "ddos":
|
||||
return "DDoS"
|
||||
case "webp":
|
||||
return "WebP"
|
||||
case "doh":
|
||||
return "DoH"
|
||||
}
|
||||
|
||||
return strings.ToUpper(word[:1]) + word[1:]
|
||||
}
|
||||
|
||||
func runSearch() {
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println("Usage: langs search DIR")
|
||||
return
|
||||
}
|
||||
var dir = os.Args[2]
|
||||
stat, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]could not find dir '" + dir + "': " + err.Error())
|
||||
return
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
fmt.Println("[ERROR]could not find dir '" + dir + "'")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("searching '" + dir + "' ...")
|
||||
|
||||
var ext = ".go"
|
||||
|
||||
var resultFiles = []string{}
|
||||
for _, pattern := range []string{
|
||||
"*" + ext,
|
||||
strings.Repeat("*/", 1) + "*" + ext,
|
||||
strings.Repeat("*/", 2) + "*" + ext,
|
||||
strings.Repeat("*/", 3) + "*" + ext,
|
||||
strings.Repeat("*/", 4) + "*" + ext,
|
||||
strings.Repeat("*/", 5) + "*" + ext,
|
||||
strings.Repeat("*/", 6) + "*" + ext,
|
||||
strings.Repeat("*/", 7) + "*" + ext,
|
||||
strings.Repeat("*/", 8) + "*" + ext,
|
||||
strings.Repeat("*/", 9) + "*" + ext,
|
||||
strings.Repeat("*/", 10) + "*" + ext,
|
||||
} {
|
||||
goFiles, err := filepath.Glob(dir + "/" + pattern)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]search error: " + err.Error())
|
||||
return
|
||||
}
|
||||
resultFiles = append(resultFiles, goFiles...)
|
||||
}
|
||||
|
||||
if len(resultFiles) == 0 {
|
||||
fmt.Println("no files found in the dir")
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]search dir '" + dir + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
var hansRegexp = regexp.MustCompile(`\p{Han}+`)
|
||||
var countMatches = 0
|
||||
for _, goFile := range resultFiles {
|
||||
if strings.HasSuffix(goFile, "_test.go") ||
|
||||
strings.HasSuffix(goFile, "_plus_test.go") ||
|
||||
strings.Contains(goFile, "/messages/messages_") {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(goFile)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]read file '" + goFile + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
var matches = hansRegexp.FindAllSubmatchIndex(data, -1)
|
||||
if len(matches) > 0 {
|
||||
for _, match := range matches {
|
||||
// ignore comment
|
||||
switch ext {
|
||||
case ".go":
|
||||
if checkIsInGoComment(data, match[0]) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
countMatches++
|
||||
|
||||
fmt.Printf("%s %s\n", goFile+":"+types.String(bytes.Count(data[:match[0]], []byte{'\n'})+1), string(data[match[0]:match[1]]))
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println(countMatches, "matches")
|
||||
}
|
||||
|
||||
func checkIsInGoComment(data []byte, start int) bool {
|
||||
if start <= 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
for {
|
||||
start--
|
||||
if start <= 1 || data[start] == '\n' {
|
||||
return false
|
||||
}
|
||||
|
||||
// 'SPACE //'
|
||||
if data[start] == '/' && data[start-1] == '/' {
|
||||
return true
|
||||
}
|
||||
|
||||
// '/** SOMETHING **/'
|
||||
if data[start] == '*' && data[start-1] == '/' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
448
EdgeCommon/cmd/proto-json/main.go
Normal file
448
EdgeCommon/cmd/proto-json/main.go
Normal file
@@ -0,0 +1,448 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ServiceInfo struct {
|
||||
Name string `json:"name"`
|
||||
Methods []*MethodInfo `json:"methods"`
|
||||
Filename string `json:"filename"`
|
||||
Doc string `json:"doc"`
|
||||
}
|
||||
|
||||
type MethodInfo struct {
|
||||
Name string `json:"name"`
|
||||
RequestMessageName string `json:"requestMessageName"`
|
||||
ResponseMessageName string `json:"responseMessageName"`
|
||||
Code string `json:"code"`
|
||||
Doc string `json:"doc"`
|
||||
Roles []string `json:"roles"`
|
||||
IsDeprecated bool `json:"isDeprecated"`
|
||||
}
|
||||
|
||||
type MessageInfo struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Doc string `json:"doc"`
|
||||
}
|
||||
|
||||
type LinkInfo struct {
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type RPCList struct {
|
||||
Services []*ServiceInfo `json:"services"`
|
||||
Messages []*MessageInfo `json:"messages"`
|
||||
Links []*LinkInfo `json:"links"`
|
||||
}
|
||||
|
||||
func readComments(data []byte) string {
|
||||
var lines = bytes.Split(data, []byte{'\n'})
|
||||
var comments = [][]byte{}
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
var line = bytes.TrimLeft(lines[i], " \t")
|
||||
if len(line) == 0 {
|
||||
comments = append([][]byte{{' '}}, comments...)
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(line, []byte("//")) {
|
||||
line = bytes.TrimSpace(bytes.TrimLeft(line, "/"))
|
||||
comments = append([][]byte{line}, comments...)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return string(bytes.TrimSpace(bytes.Join(comments, []byte{'\n'})))
|
||||
}
|
||||
|
||||
func removeDuplicates(s []string) []string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
var m = map[string]bool{}
|
||||
var result = []string{}
|
||||
for _, item := range s {
|
||||
_, ok := m[item]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
result = append(result, item)
|
||||
m[item] = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 生成JSON格式API列表
|
||||
func main() {
|
||||
var quiet = false
|
||||
flag.BoolVar(&quiet, "quiet", false, "")
|
||||
flag.Parse()
|
||||
|
||||
var methodRolesMap = map[string][]string{} // method => roles
|
||||
{
|
||||
var rootDir = filepath.Clean(Tea.Root + "/../../EdgeAPI/internal/rpc/services")
|
||||
entries, err := os.ReadDir(rootDir)
|
||||
if err != nil {
|
||||
logs.Println("[ERROR]read api services from '" + rootDir + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var rootDirs = []string{rootDir}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
rootDirs = append(rootDirs, rootDir+string(os.PathSeparator)+entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// 排序以保证输出内容的稳定性
|
||||
sort.Strings(rootDirs)
|
||||
|
||||
for _, rootDir := range rootDirs {
|
||||
files, err := filepath.Glob(rootDir + "/service_*.go")
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]list service implementation files failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 排序以保证输出内容的稳定性
|
||||
sort.Strings(files)
|
||||
|
||||
var methodNameReg = regexp.MustCompile(`func\s*\(\w+\s+\*\s*(\w+Service)\)\s*(\w+)\s*\(`) // $1: serviceName, $2 methodName
|
||||
for _, file := range files {
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]read file '" + file + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
var sourceCode = string(data)
|
||||
|
||||
var locList = methodNameReg.FindAllStringIndex(sourceCode, -1)
|
||||
for index, loc := range locList {
|
||||
var methodSource = ""
|
||||
if index == len(locList)-1 { // last one
|
||||
methodSource = sourceCode[loc[0]:]
|
||||
} else {
|
||||
methodSource = sourceCode[loc[0]:locList[index+1][0]]
|
||||
}
|
||||
|
||||
// 方法名
|
||||
var submatch = methodNameReg.FindStringSubmatch(methodSource)
|
||||
if len(submatch) == 0 {
|
||||
continue
|
||||
}
|
||||
var serviceName = submatch[1]
|
||||
if serviceName == "BaseService" {
|
||||
continue
|
||||
}
|
||||
var methodName = submatch[2]
|
||||
if methodName[0] < 'A' || methodName[0] > 'Z' {
|
||||
continue
|
||||
}
|
||||
var roles = []string{}
|
||||
if strings.Contains(methodSource, ".ValidateNode(") {
|
||||
roles = append(roles, "node")
|
||||
}
|
||||
if strings.Contains(methodSource, ".ValidateUserNode(") {
|
||||
var hasRoles = false
|
||||
var wordIndex = strings.Index(methodSource, ".ValidateUserNode(")
|
||||
if wordIndex > 0 {
|
||||
if len(methodSource[wordIndex:]) > 100 {
|
||||
if strings.Contains(methodSource[wordIndex:wordIndex+100], ".ValidateUserNode(ctx, false)") {
|
||||
hasRoles = true
|
||||
}
|
||||
}
|
||||
if !hasRoles {
|
||||
roles = append(roles, "user")
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(methodSource, ".ValidateAdmin(") {
|
||||
roles = append(roles, "admin")
|
||||
}
|
||||
if strings.Contains(methodSource, ".ValidateAdminAndUser(") {
|
||||
var hasRoles = false
|
||||
var wordIndex = strings.Index(methodSource, ".ValidateAdminAndUser(")
|
||||
if wordIndex > 0 {
|
||||
if len(methodSource[wordIndex:]) > 100 {
|
||||
if strings.Contains(methodSource[wordIndex:wordIndex+100], ".ValidateAdminAndUser(ctx, false)") {
|
||||
roles = append(roles, "admin")
|
||||
hasRoles = true
|
||||
}
|
||||
}
|
||||
if !hasRoles {
|
||||
roles = append(roles, "admin", "user")
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(methodSource, ".ValidateNSNode(") {
|
||||
roles = append(roles, "dns")
|
||||
}
|
||||
if strings.Contains(methodSource, ".ValidateMonitorNode(") {
|
||||
roles = append(roles, "monitor")
|
||||
}
|
||||
if strings.Contains(methodSource, "rpcutils.UserTypeDNS") {
|
||||
roles = append(roles, "dns")
|
||||
}
|
||||
if strings.Contains(methodSource, "rpcutils.UserTypeUser") {
|
||||
roles = append(roles, "user")
|
||||
}
|
||||
if strings.Contains(methodSource, "rpcutils.UserTypeNode") {
|
||||
roles = append(roles, "node")
|
||||
}
|
||||
if strings.Contains(methodSource, "rpcutils.UserTypeMonitor") {
|
||||
roles = append(roles, "monitor")
|
||||
}
|
||||
if strings.Contains(methodSource, "rpcutils.UserTypeReport") {
|
||||
roles = append(roles, "report")
|
||||
}
|
||||
if strings.Contains(methodSource, "rpcutils.UserTypeCluster") {
|
||||
roles = append(roles, "cluster")
|
||||
}
|
||||
if strings.Contains(methodSource, "rpcutils.UserTypeAdmin") {
|
||||
roles = append(roles, "admin")
|
||||
}
|
||||
|
||||
methodRolesMap[strings.ToLower(methodName)] = removeDuplicates(roles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var services = []*ServiceInfo{}
|
||||
var messages = []*MessageInfo{}
|
||||
|
||||
{
|
||||
var dirs = []string{Tea.Root + "/../pkg/rpc/protos/", Tea.Root + "/../pkg/rpc/protos/models"}
|
||||
for _, dir := range dirs {
|
||||
func(dir string) {
|
||||
dir = filepath.Clean(dir)
|
||||
|
||||
files, err := filepath.Glob(dir + "/*.proto")
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]list proto files failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 排序以保持稳定性
|
||||
sort.Strings(files)
|
||||
|
||||
for _, path := range files {
|
||||
func(path string) {
|
||||
var filename = filepath.Base(path)
|
||||
if filename == "service_authority_key.proto" || filename == "service_authority_node.proto" {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 先将rpc代码替换成临时代码
|
||||
var methodCodeMap = map[string][]byte{} // code => method
|
||||
var methodIndex = 0
|
||||
var methodReg = regexp.MustCompile(`(?s)rpc\s+(\w+)\s*\(\s*(\w+)\s*\)\s*returns\s*\(\s*(\w+)\s*\)\s*(\{.+})?\s*;`)
|
||||
data = methodReg.ReplaceAllFunc(data, func(methodData []byte) []byte {
|
||||
methodIndex++
|
||||
var code = "METHOD" + types.String(methodIndex)
|
||||
methodCodeMap[code] = methodData
|
||||
return []byte("\n" + code)
|
||||
})
|
||||
|
||||
// 服务列表
|
||||
// TODO 这里需要改进一下,当前实现方法如果方法注释里有括号(}),就会导致部分方法解析不到
|
||||
var serviceNameReg = regexp.MustCompile(`(?sU)\n\s*service\s+(\w+)\s*\{(.+)}`)
|
||||
var serviceMatches = serviceNameReg.FindAllSubmatch(data, -1)
|
||||
var serviceNamePositions = serviceNameReg.FindAllIndex(data, -1)
|
||||
for serviceMatchIndex, serviceMatch := range serviceMatches {
|
||||
var serviceName = string(serviceMatch[1])
|
||||
var serviceNamePosition = serviceNamePositions[serviceMatchIndex][0]
|
||||
var comment = readComments(data[:serviceNamePosition])
|
||||
|
||||
// 方法列表
|
||||
var methods = []*MethodInfo{}
|
||||
var serviceData = serviceMatch[2]
|
||||
var methodCodeReg = regexp.MustCompile(`\b(METHOD\d+)\b`)
|
||||
var methodCodeMatches = methodCodeReg.FindAllSubmatch(serviceData, -1)
|
||||
var methodCodePositions = methodCodeReg.FindAllIndex(serviceData, -1)
|
||||
for methodMatchIndex, methodMatch := range methodCodeMatches {
|
||||
var methodCode = string(methodMatch[1])
|
||||
var methodData = methodCodeMap[methodCode]
|
||||
var methodPieces = methodReg.FindSubmatch(methodData)
|
||||
var methodCodePosition = methodCodePositions[methodMatchIndex]
|
||||
|
||||
var roles = methodRolesMap[strings.ToLower(string(methodPieces[1]))]
|
||||
if roles == nil {
|
||||
roles = []string{}
|
||||
}
|
||||
|
||||
methods = append(methods, &MethodInfo{
|
||||
Name: string(methodPieces[1]),
|
||||
RequestMessageName: string(methodPieces[2]),
|
||||
ResponseMessageName: string(methodPieces[3]),
|
||||
IsDeprecated: strings.Contains(string(methodPieces[4]), "deprecated"),
|
||||
Code: string(methodData),
|
||||
Doc: readComments(serviceData[:methodCodePosition[0]]),
|
||||
Roles: roles,
|
||||
})
|
||||
}
|
||||
|
||||
services = append(services, &ServiceInfo{
|
||||
Name: serviceName,
|
||||
Methods: methods,
|
||||
Filename: filename,
|
||||
Doc: comment,
|
||||
})
|
||||
}
|
||||
|
||||
// 消息列表
|
||||
var topMessageCodeMap = map[string][]byte{} // code => message
|
||||
var allMessageCodeMap = map[string][]byte{}
|
||||
var messageCodeIndex = 0
|
||||
var messagesReg = regexp.MustCompile(`(?sU)\n\s*message\s+(\w+)\s*\{([^{}]+)\n\s*}`)
|
||||
var firstMessagesReg = regexp.MustCompile(`message\s+(\w+)`)
|
||||
var messageCodeREG = regexp.MustCompile(`MESSAGE\d+`)
|
||||
for {
|
||||
var hasMessage = false
|
||||
|
||||
data = messagesReg.ReplaceAllFunc(data, func(messageData []byte) []byte {
|
||||
messageCodeIndex++
|
||||
hasMessage = true
|
||||
|
||||
// 是否包含子Message
|
||||
var subMatches = messageCodeREG.FindAllSubmatch(messageData, -1)
|
||||
for _, subMatch := range subMatches {
|
||||
var subMatchCode = string(subMatch[0])
|
||||
delete(topMessageCodeMap, subMatchCode)
|
||||
}
|
||||
|
||||
var code = "MESSAGE" + types.String(messageCodeIndex)
|
||||
topMessageCodeMap[code] = messageData
|
||||
allMessageCodeMap[code] = messageData
|
||||
return []byte("\n" + code)
|
||||
})
|
||||
if !hasMessage {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for messageCode, messageData := range topMessageCodeMap {
|
||||
// 替换其中的子Message
|
||||
for {
|
||||
if messageCodeREG.Match(messageData) {
|
||||
messageData = messageCodeREG.ReplaceAllFunc(messageData, func(messageCodeData []byte) []byte {
|
||||
return allMessageCodeMap[string(messageCodeData)]
|
||||
})
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 注释
|
||||
var index = bytes.Index(data, []byte(messageCode))
|
||||
var messageName = string(firstMessagesReg.FindSubmatch(messageData)[1])
|
||||
messages = append(messages, &MessageInfo{
|
||||
Name: messageName,
|
||||
Code: string(bytes.TrimSpace(messageData)),
|
||||
Doc: readComments(data[:index]),
|
||||
})
|
||||
}
|
||||
}(path)
|
||||
}
|
||||
}(dir)
|
||||
}
|
||||
}
|
||||
|
||||
var countServices = len(services)
|
||||
var countMethods = 0
|
||||
var countMessages = len(messages)
|
||||
for _, service := range services {
|
||||
countMethods += len(service.Methods)
|
||||
}
|
||||
|
||||
// 链接
|
||||
var links = []*LinkInfo{}
|
||||
|
||||
// json links
|
||||
{
|
||||
var dirs = []string{Tea.Root + "/../pkg/rpc/jsons"}
|
||||
for _, dir := range dirs {
|
||||
func(dir string) {
|
||||
dir = filepath.Clean(dir)
|
||||
|
||||
files, err := filepath.Glob(dir + "/*.md")
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]list .md files failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 排序以保持稳定性
|
||||
sort.Strings(files)
|
||||
|
||||
for _, path := range files {
|
||||
func(path string) {
|
||||
var name = strings.TrimSuffix(filepath.Base(path), ".md")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]read '" + path + "' failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
links = append(links, &LinkInfo{
|
||||
Name: "json:" + name,
|
||||
Content: string(data),
|
||||
})
|
||||
}(path)
|
||||
}
|
||||
}(dir)
|
||||
}
|
||||
}
|
||||
|
||||
// 对消息进行排序,以保持稳定性
|
||||
sort.Slice(messages, func(i, j int) bool {
|
||||
return messages[i].Name < messages[j].Name
|
||||
})
|
||||
|
||||
var rpcList = &RPCList{
|
||||
Services: services,
|
||||
Messages: messages,
|
||||
Links: links,
|
||||
}
|
||||
jsonData, err := json.MarshalIndent(rpcList, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]marshal to json failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var jsonFile = Tea.Root + "/rpc.json"
|
||||
err = os.WriteFile(jsonFile, jsonData, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]write json to file failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !quiet {
|
||||
fmt.Println("services:", countServices, "methods:", countMethods, "messages:", countMessages)
|
||||
fmt.Println("===")
|
||||
fmt.Println("generated " + filepath.Base(jsonFile) + " successfully")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user