package docs import ( "encoding/json" "fmt" "go/ast" "go/parser" "go/token" "os" "os/exec" "path/filepath" "strings" "testing" ) func TestGenerateXlog(t *testing.T) { workDir := panelWorkDir(t) fset := token.NewFileSet() apiDirs := []string{ filepath.Join(workDir, "agent/app/api/v2"), filepath.Join(workDir, "core/app/api/v2"), filepath.Join(workDir, "agent/xpack/app/api/v2"), filepath.Join(workDir, "core/xpack/app/api/v2"), filepath.Join(workDir, "agent/enterprise/app/api/v2"), filepath.Join(workDir, "core/enterprise/app/api/v2"), } xlogMap := make(map[string]operationJson) for _, dir := range apiDirs { entries, _ := os.ReadDir(dir) for _, info := range entries { if info.IsDir() { continue } fileItem, err := parser.ParseFile(fset, filepath.Join(dir, info.Name()), nil, parser.ParseComments) if err != nil { continue } for _, decl := range fileItem.Decls { switch d := decl.(type) { case *ast.FuncDecl: if d.Doc != nil { routerContent := "" logContent := "" for _, comment := range d.Doc.List { if strings.HasPrefix(comment.Text, "// @Router") { routerContent = replaceStr(comment.Text, "// @Router", "[post]", "[get]") } if strings.HasPrefix(comment.Text, "// @x-panel-log") { logContent = replaceStr(comment.Text, "// @x-panel-log") } } if len(routerContent) != 0 && len(logContent) != 0 { var item operationJson if err := json.Unmarshal([]byte(logContent), &item); err != nil { panic(fmt.Sprintf("json unmarshal failed, err: %v", err)) } xlogMap[routerContent] = item } } } } } } newJson, err := json.MarshalIndent(xlogMap, "", "\t") if err != nil { panic(fmt.Sprintf("json marshal for new file failed, err: %v", err)) } if err := os.WriteFile("x-log.json", newJson, 0640); err != nil { panic(fmt.Sprintf("write core x-log.json failed, err: %v", err)) } if err := os.WriteFile(filepath.Join(workDir, "agent/cmd/server/docs/x-log.json"), newJson, 0640); err != nil { panic(fmt.Sprintf("write agent x-log.json failed, err: %v", err)) } } func TestGenerateSwaggerDoc(t *testing.T) { workDir := panelWorkDir(t) swagBin := swagBin(t) cmd1 := exec.Command(swagBin, "init", "-o", filepath.Join(workDir, "core/cmd/server/docs/docs_agent"), "-d", filepath.Join(workDir, "agent"), "-g", "../agent/cmd/server/main.go") cmd1.Dir = workDir std1, err := cmd1.CombinedOutput() if err != nil { fmt.Printf("generate swagger doc of agent failed, std1: %v, err: %v", string(std1), err) return } cmd2 := exec.Command(swagBin, "init", "-o", filepath.Join(workDir, "core/cmd/server/docs/docs_core"), "-d", filepath.Join(workDir, "core"), "-g", "./cmd/server/main.go") cmd2.Dir = workDir std2, err := cmd2.CombinedOutput() if err != nil { fmt.Printf("generate swagger doc of core failed, std2: %v, err: %v", string(std2), err) return } agentJson := filepath.Join(workDir, "core/cmd/server/docs/docs_agent/swagger.json") agentFile, err := os.ReadFile(agentJson) if err != nil { fmt.Printf("read file docs_agent failed, err: %v", err) return } var agentSwagger Swagger if err := json.Unmarshal(agentFile, &agentSwagger); err != nil { fmt.Printf("agent json unmarshal failed, err: %v", err) return } coreJson := filepath.Join(workDir, "core/cmd/server/docs/docs_core/swagger.json") coreFile, err := os.ReadFile(coreJson) if err != nil { fmt.Printf("read file docs_core failed, err: %v", err) return } var coreSwagger Swagger if err := json.Unmarshal(coreFile, &coreSwagger); err != nil { fmt.Printf("core json unmarshal failed, err: %v", err) return } newSwagger := Swagger{ Swagger: agentSwagger.Swagger, Info: agentSwagger.Info, Host: agentSwagger.Host, BasePath: agentSwagger.BasePath, Paths: agentSwagger.Paths, Definitions: agentSwagger.Definitions, } for key, val := range coreSwagger.Paths { if _, ok := newSwagger.Paths[key]; !ok { newSwagger.Paths[key] = val } } for key, val := range coreSwagger.Definitions { if _, ok := newSwagger.Definitions[key]; !ok { newSwagger.Definitions[key] = val } } newJson, err := json.MarshalIndent(newSwagger, "", "\t") if err != nil { fmt.Printf("json marshal for new file failed, err: %v", err) return } if err := os.WriteFile("swagger.json", newJson, 0640); err != nil { fmt.Printf("write new swagger.json failed, err: %v", err) return } docTemplate := strings.ReplaceAll(loadDefaultDocs(), "const docTemplate = \"aa\"", fmt.Sprintf("const docTemplate = `%s`", string(newJson))) if err := os.WriteFile(filepath.Join(workDir, "core/cmd/server/docs/docs.go"), []byte(docTemplate), 0640); err != nil { fmt.Printf("write new docs.go failed, err: %v", err) return } _ = os.RemoveAll(filepath.Join(workDir, "core/cmd/server/docs/docs_agent")) _ = os.RemoveAll(filepath.Join(workDir, "core/cmd/server/docs/docs_core")) } func panelWorkDir(t *testing.T) string { t.Helper() wd, err := os.Getwd() if err != nil { t.Fatalf("get current work dir failed: %v", err) } workDir, err := filepath.Abs(filepath.Join(wd, "../../../..")) if err != nil { t.Fatalf("resolve 1Panel work dir failed: %v", err) } return workDir } func swagBin(t *testing.T) string { t.Helper() swagBin, err := exec.LookPath("swag") if err != nil { t.Skip("swag is not installed or not in PATH") } return swagBin } type Swagger struct { Swagger string `json:"swagger"` Info interface{} `json:"info"` Host string `json:"host"` BasePath string `json:"basePath"` Paths map[string]interface{} `json:"paths"` Definitions map[string]interface{} `json:"definitions"` } func loadDefaultDocs() string { return `package docs import "github.com/swaggo/swag" const docTemplate = "aa" var SwaggerInfo = &swag.Spec{ Version: "2.0", Host: "localhost", BasePath: "/api/v2", Schemes: []string{}, Title: "1Panel", Description: "Top-Rated Web-based Linux Server Management Tool", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", RightDelim: "}}", } func init() { swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) }` } func replaceStr(val string, rep ...string) string { for _, item := range rep { val = strings.ReplaceAll(val, item, "") } val = strings.TrimSpace(val) return val } type operationJson struct { BodyKeys []string `json:"bodyKeys"` ParamKeys []string `json:"paramKeys"` BeforeFunctions []functionInfo `json:"beforeFunctions"` FormatZH string `json:"formatZH"` FormatEN string `json:"formatEN"` } type functionInfo struct { InputColumn string `json:"input_column"` InputValue string `json:"input_value"` IsList bool `json:"isList"` DB string `json:"db"` OutputColumn string `json:"output_column"` OutputValue string `json:"output_value"` }