{"id":35176269,"url":"https://github.com/pgfeng/annotation","last_synced_at":"2026-05-21T02:31:11.107Z","repository":{"id":322188052,"uuid":"1088482171","full_name":"pgfeng/annotation","owner":"pgfeng","description":"Go annotation utils.","archived":false,"fork":false,"pushed_at":"2025-11-03T03:20:46.000Z","size":14,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-03T05:24:20.407Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pgfeng.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-03T02:50:42.000Z","updated_at":"2025-11-03T03:20:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pgfeng/annotation","commit_stats":null,"previous_names":["pgfeng/annotation"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/pgfeng/annotation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgfeng%2Fannotation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgfeng%2Fannotation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgfeng%2Fannotation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgfeng%2Fannotation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pgfeng","download_url":"https://codeload.github.com/pgfeng/annotation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pgfeng%2Fannotation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33285996,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-20T15:12:43.734Z","status":"online","status_checked_at":"2026-05-21T02:00:07.181Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-28T22:20:11.770Z","updated_at":"2026-05-21T02:31:11.099Z","avatar_url":"https://github.com/pgfeng.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go Annotation Helper\n\nA small helper library to parse and extract annotations / structured comments from Go source code.  \nDesigned to make it easy to read custom annotations (e.g. `@route`, `@accept`) and convert them into Go structures for generation, validation or runtime configuration.\n\n## Features\n\n- Parse Go source files and directories for annotation-style comments.\n- Convert common annotation patterns into typed Go structures.\n- Simple API for parsing from source string, file, or directory.\n- Includes lightweight test helpers and examples.\n\n## Installation\n\nInstall the package (replace with the actual import path if different):\n\n```sh\ngo install github.com/pgfeng/annotation@latest\n```\n\n## Usage\nUse the package build gin web framework route annotation:\n\n### Example Annotation in Handler File\n```go\n// @Route /account/info get\n// @Summary Get Current User Info\n// @Description Get the information of the currently logged-in user.\n// @FormParam name=\"username\" required=true summary=\"Username\"\n// @FormParam name=\"password\" required=true summary=\"Password\"\n// @Tags Account User\n// @Rules AuthRequired\nfunc Info(c *gin.Context) {\n    c.JSON(200, gin.H{\n    \"status\": false,\n    \"msg\":    \"Not logged in!\",\n    })\n}\n```\n\n### Generating Routes File\n```go\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/printer\"\n\t\"go/token\"\n\t\"log/slog\"\n\t\"os\"\n\t\"reflect\"\n\n\t\"github.com/pgfeng/annotation\"\n\t\"github.com/pgfeng/annotation/pkg\"\n\t\"github.com/pgfeng/annotation/types\"\n)\n\nfunc getPackageFunctions() *pkg.PackageFunctions {\n\treturn annotation.LoadPackageByPath(\"./backend/app/...\", []pkg.Type{\n\t\t\u0026types.Route{},\n\t\t\u0026types.QueryParam{},\n\t\t\u0026types.FormParam{},\n\t\t\u0026types.FileParam{},\n\t\t\u0026types.Summary{},\n\t\t\u0026types.Description{},\n\t\t\u0026types.PathParam{},\n\t\t\u0026types.HeaderParam{},\n\t\t\u0026types.BodyParam{},\n\t\t\u0026types.CookieParam{},\n\t\t\u0026types.Tags{},\n\t\t\u0026types.Rules{},\n\t\t\u0026types.Accept{},\n\t\t\u0026types.ContentType{},\n\t})\n}\n\nvar pkgFunks = getPackageFunctions()\n\nfunc GenerateRouteFile() {\n\tfunks := pkgFunks.Filter(\u0026types.Route{})\n\tif funks == nil || len(*funks) == 0 {\n\t\treturn\n\t}\n\tfset := token.NewFileSet()\n\tfile := \u0026ast.File{\n\t\tName:  ast.NewIdent(\"route\"),\n\t\tDecls: []ast.Decl{},\n\t}\n\timportSpecs := funks.GetImportSpecs()\n\t// Add Gin import\n\timportSpecs = append(importSpecs, \u0026ast.ImportSpec{\n\t\tPath: \u0026ast.BasicLit{\n\t\t\tKind:  token.STRING,\n\t\t\tValue: `\"github.com/gin-gonic/gin\"`,\n\t\t},\n\t})\n\tfile.Decls = append(file.Decls, \u0026ast.GenDecl{\n\t\tTok:   token.IMPORT,\n\t\tSpecs: importSpecs,\n\t})\n\tvar funcBodyList []ast.Stmt\n\tfor _, pf := range *funks {\n\t\tfor i := 0; i \u003c len(pf.Annotations); i++ {\n\t\t\tslog.Log(context.Background(), slog.LevelInfo, \"Matched\", *pf.Annotations[i])\n\t\t\tan := pf.Annotations[i]\n\t\t\tif an == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tanType := reflect.TypeOf(\u0026types.Route{})\n\t\t\tif anType == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif an.Instance == nil || reflect.TypeOf(an.Instance) != anType {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Add gin route initialization statement\n\t\t\trouteInstance := an.Instance.(*types.Route)\n\t\t\troutePath := routeInstance.Path\n\t\t\trouteMethod := routeInstance.Method\n\t\t\thandlerName := pf.FunctionName\n\t\t\t// construct: router.GET(\"/path\", handlerName)\n\t\t\texprStmt := \u0026ast.ExprStmt{\n\t\t\t\tX: \u0026ast.CallExpr{\n\t\t\t\t\tFun: \u0026ast.SelectorExpr{\n\t\t\t\t\t\tX:   ast.NewIdent(\"router\"),\n\t\t\t\t\t\tSel: ast.NewIdent(string(routeMethod)),\n\t\t\t\t\t},\n\t\t\t\t\tArgs: []ast.Expr{\n\t\t\t\t\t\t\u0026ast.BasicLit{\n\t\t\t\t\t\t\tKind:  token.STRING,\n\t\t\t\t\t\t\tValue: fmt.Sprintf(`\"%s\"`, routePath),\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\u0026ast.Ident{\n\t\t\t\t\t\t\tName: handlerName,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\tfuncBodyList = append(funcBodyList, exprStmt)\n\t\t}\n\t}\n\tmainFunc := \u0026ast.FuncDecl{\n\t\tName: ast.NewIdent(\"InitRoutes\"),\n\t\tType: \u0026ast.FuncType{\n\t\t\tParams: \u0026ast.FieldList{\n\t\t\t\tList: []*ast.Field{\n\t\t\t\t\t{\n\t\t\t\t\t\tNames: []*ast.Ident{ast.NewIdent(\"router\")},\n\t\t\t\t\t\tType: \u0026ast.SelectorExpr{\n\t\t\t\t\t\t\tX:   ast.NewIdent(\"*gin\"),\n\t\t\t\t\t\t\tSel: ast.NewIdent(\"RouterGroup\"),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tResults: nil,\n\t\t},\n\t\tBody: \u0026ast.BlockStmt{\n\t\t\tList: funcBodyList,\n\t\t},\n\t}\n\tfile.Decls = append(file.Decls, mainFunc)\n\t// Write to routes_gen.go\n\tout, err := os.Create(\"./backend/route/routes_gen.go\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer func(out *os.File) {\n\t\terr := out.Close()\n\t\tif err != nil {\n\n\t\t}\n\t}(out)\n\n\tif err := printer.Fprint(out, fset, file); err != nil {\n\t\tpanic(err)\n\t}\n\tslog.Log(context.Background(), slog.LevelInfo, \"generated routes_gen.go\")\n}\n\n```\n\n### Generate Api Documentation\n```go\n\ntype ApiPathItem struct {\n\tHash        string                 `json:\"hash\"`\n\tPath        string                 `json:\"path,omitempty\"`\n\tMethod      string                 `json:\"method,omitempty\"`\n\tSummary     string                 `json:\"summary,omitempty\"`\n\tDescription string                 `json:\"description,omitempty\"`\n\tAccept      []string               `json:\"accept,omitempty\"`\n\tContentType string                 `json:\"contentType,omitempty\"`\n\tRules       []string               `json:\"rules,omitempty\"`\n\tTags        map[string]interface{} `json:\"tags,omitempty\"`\n\tParameters  []map[string]string    `json:\"parameters,omitempty\"`\n}\n\ntype ApiDoc struct {\n\tPaths []ApiPathItem `json:\"api,omitempty\"`\n}\n\nfunc GenerateApiJson() string {\n\tfunks := pkgFunks.GetFunctionMap()\n\tfmt.Println(\"Generating api json...\")\n\tvar apiDoc ApiDoc\n\tfor _, ans := range funks {\n\t\troute := ans.Get(\u0026types.Route{})\n\t\tif route == nil {\n\t\t\tcontinue\n\t\t}\n\t\trouteInstance := route.Instance.(*types.Route)\n\t\tvar pathItem ApiPathItem\n\n\t\tpath := routeInstance.Path\n\t\tpathItem.Path = path\n\t\tmethod := string(routeInstance.Method)\n\t\tpathItem.Method = method\n\t\tsummary := ans.Get(\u0026types.Summary{})\n\t\tif summary != nil {\n\t\t\tpathItem.Summary = summary.Instance.(*types.Summary).Text\n\t\t}\n\t\tdescription := ans.Get(\u0026types.Description{})\n\t\tif description != nil {\n\t\t\tpathItem.Description = description.Instance.(*types.Description).Text\n\t\t}\n\t\taccept := ans.Get(\u0026types.Accept{})\n\t\tif accept != nil {\n\t\t\tpathItem.Accept = accept.Instance.(*types.Accept).MediaTypes\n\t\t} else {\n\t\t\tpathItem.Accept = []string{\"application/json\"}\n\t\t}\n\t\tcontentType := ans.Get(\u0026types.ContentType{})\n\t\tif contentType != nil {\n\t\t\tpathItem.ContentType = contentType.Instance.(*types.ContentType).MediaType\n\t\t}\n\t\trules := ans.Get(\u0026types.Rules{})\n\t\tif rules != nil {\n\t\t\tpathItem.Rules = rules.Instance.(*types.Rules).Rules\n\t\t}\n\t\ttags := ans.Get(\u0026types.Tags{})\n\t\tif tags != nil {\n\t\t\tpathItem.Tags = map[string]interface{}{\n\t\t\t\t\"tags\":   tags.Instance.(*types.Tags).Tags,\n\t\t\t\t\"hashes\": tags.Instance.(*types.Tags).Hashes,\n\t\t\t\t\"hash\":   tags.Instance.(*types.Tags).Hash,\n\t\t\t}\n\t\t}\n\t\tpathItem.Hash = routeInstance.Hash\n\t\t// Parameters\n\t\tvar parameters []map[string]string\n\t\tfor _, pType := range []pkg.Type{\n\t\t\t\u0026types.PathParam{},\n\t\t\t\u0026types.QueryParam{},\n\t\t\t\u0026types.HeaderParam{},\n\t\t\t\u0026types.CookieParam{},\n\t\t\t\u0026types.FormParam{},\n\t\t\t\u0026types.BodyParam{},\n\t\t\t\u0026types.FileParam{},\n\t\t} {\n\t\t\tparams := ans.Filter(pType)\n\t\t\tif len(params) \u003e 0 {\n\t\t\t\tfor _, p := range params {\n\t\t\t\t\tv := reflect.ValueOf(p.Instance)\n\t\t\t\t\tif v.IsValid() {\n\t\t\t\t\t\tm := v.MethodByName(\"ToMap\")\n\t\t\t\t\t\tif m.IsValid() \u0026\u0026 m.Type().NumIn() == 0 {\n\t\t\t\t\t\t\tout := m.Call(nil)\n\t\t\t\t\t\t\tif len(out) \u003e 0 {\n\t\t\t\t\t\t\t\tif paramMap, ok := out[0].Interface().(map[string]string); ok {\n\t\t\t\t\t\t\t\t\tparamMap[\"location\"] = strings.TrimSuffix(reflect.TypeOf(p.Instance).Elem().Name(), \"Param\")\n\t\t\t\t\t\t\t\t\tparameters = append(parameters, paramMap)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tpathItem.Parameters = parameters\n\t\tapiDoc.Paths = append(apiDoc.Paths, pathItem)\n\t}\n\tjsonData, err := json.MarshalIndent(apiDoc, \"\", \"  \")\n\tif err != nil {\n\t\tslog.Log(context.Background(), slog.LevelError, \"Failed to marshal API doc to JSON\", \"error\", err)\n\t\tpanic(\"failed to marshal api doc to json\")\n\t}\n\terr = os.WriteFile(\"./.document/doc.json\", jsonData, 0644)\n\tif err != nil {\n\t\tslog.Log(context.Background(), slog.LevelError, \"Failed to write API doc to file\", \"error\", err)\n\t\tpanic(\"failed to write api doc to file\")\n\t}\n\tslog.Log(context.Background(), slog.LevelInfo, \"Generated api_doc.json\")\n\treturn string(jsonData)\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpgfeng%2Fannotation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpgfeng%2Fannotation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpgfeng%2Fannotation/lists"}