-
Notifications
You must be signed in to change notification settings - Fork 108
/
apidoc.go
186 lines (155 loc) · 5.79 KB
/
apidoc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// SPDX-License-Identifier: MIT
// Package apidoc RESTful API 文档生成工具
//
// 从代码文件的注释中提取特定格式的内容,生成 RESTful API 文档,支持大部分的主流的编程语言。
package apidoc
import (
"bytes"
"log"
"net/http"
"path/filepath"
"regexp"
"time"
"golang.org/x/text/language"
"github.com/caixw/apidoc/v7/build"
"github.com/caixw/apidoc/v7/core"
"github.com/caixw/apidoc/v7/internal/ast"
"github.com/caixw/apidoc/v7/internal/docs"
"github.com/caixw/apidoc/v7/internal/locale"
"github.com/caixw/apidoc/v7/internal/lsp"
)
const (
// LSPVersion 当前支持的 language server protocol 版本
LSPVersion = lsp.Version
// DocVersion 文档的版本
DocVersion = ast.Version
)
// Config 配置文件 apidoc.yaml 所表示的内容
type Config = build.Config
// SetLocale 设置当前的本地化 ID
//
// 如果不调用此函数,则默认会采用 internal/locale.DefaultLocaleID 的值。
// 如果想采用当前系统的本地化信息,可以使用
// github.com/issue9/localeutil.SystemLanguageTag 函数。
func SetLocale(tag language.Tag) { locale.SetTag(tag) }
// Locale 获取当前设置的本地化 ID
func Locale() language.Tag { return locale.Tag() }
// Locales 返回当前所有支持的本地化信息
func Locales() []language.Tag { return locale.Tags() }
// Version 当前程序的版本号
//
// full 表示是否需要在版本号中包含编译日期和编译时的 Git 记录 ID。
func Version(full bool) string {
if full {
return core.FullVersion()
}
return core.Version()
}
// Build 解析文档并输出文档内容
//
// 如果是文档语法错误,则相关的错误信息会反馈给 h,由 h 处理错误信息;
// 如果是配置项(o 和 i)有问题,则以 *core.Error 类型返回错误信息。
//
// NOTE: 如果需要从配置文件进行构建文档,可以采用 Config.Build
func Build(h *core.MessageHandler, o *build.Output, i ...*build.Input) error {
return build.Build(h, o, i...)
}
// Buffer 生成文档内容并返回
//
// 如果是文档语法错误,则相关的错误信息会反馈给 h,由 h 处理错误信息;
// 如果是配置项(o 和 i)有问题,则以 *core.Error 类型返回错误信息。
//
// NOTE: 如果需要从配置文件进行构建文档,可以采用 Config.Buffer
func Buffer(h *core.MessageHandler, o *build.Output, i ...*build.Input) (*bytes.Buffer, error) {
return build.Buffer(h, o, i...)
}
// CheckSyntax 测试文档语法
func CheckSyntax(h *core.MessageHandler, i ...*build.Input) error {
return build.CheckSyntax(h, i...)
}
// ServeLSP 提供 language server protocol 服务
//
// header 表示传递内容是否带报头;
// t 表示允许连接的类型,目前可以是 tcp、udp、stdio 和 unix;
// timeout 表示服务端每次读取客户端时的超时时间,如果为 0 表示不会超时。
// 超时并不会出错,而是重新开始读取数据,防止被读取一直阻塞,无法结束进程;
func ServeLSP(header bool, t, addr string, timeout time.Duration, info, erro *log.Logger) error {
return lsp.Serve(header, t, addr, timeout, info, erro)
}
// Static 为 dir 指向的路径内容搭建一个静态文件服务
//
// dir 为静态文件的根目录,一般指向 /docs
// 用于搭建一个本地版本的 https://apidoc.tools,默认页为 index.xml。
// 如果 dir 值为空,则会采用内置的文档内容作为静态文件服务的内容。
//
// stylesheet 表示是否只展示 XSL 及相关的内容。
//
// 用户可以通过以下代码搭建一个简易的 https://apidoc.tools 网站:
//
// http.Handle("/apidoc", apidoc.Static(...))
func Static(dir core.URI, stylesheet bool, erro *log.Logger) http.Handler {
return docs.Handler(dir, stylesheet, erro)
}
// Server 用于生成查看文档中间件的配置项
type Server struct {
Status int // 默认值为 200
Path string // 文档在路由中的地址,默认值为 apidoc.xml
ContentType string // 文档的 ContentType,为空表示采用 application/xml
Dir core.URI // 除文档不之外的附加项,比如 xsl,css 等内容的所在位置,如果为空表示采用内嵌的数据;
Stylesheet bool // 是否只采用 Dir 中的 xsl 和 css 等样式数据,而忽略其它文件
Erro *log.Logger // 服务出错时的错误信息输出通道,默认采用 log.Default()
}
func (srv *Server) sanitize() {
if srv.Status == 0 {
srv.Status = http.StatusOK
}
if srv.Path == "" {
srv.Path = "/apidoc.xml"
}
if srv.ContentType == "" {
srv.ContentType = "application/xml"
}
if srv.Erro == nil {
srv.Erro = log.Default()
}
}
// Buffer 将 buf 作为文档内容生成中间件
func (srv *Server) Buffer(buf []byte) http.Handler {
srv.sanitize()
buf = addStylesheet(buf)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == srv.Path {
w.Header().Set("Content-Type", srv.ContentType)
w.WriteHeader(srv.Status)
w.Write(buf)
return
}
Static(srv.Dir, srv.Stylesheet, srv.Erro).ServeHTTP(w, r)
})
}
// File 将 path 指向的内容作为文档内容生成中间件
func (srv *Server) File(path core.URI) (http.Handler, error) {
data, err := path.ReadAll(nil)
if err != nil {
return nil, err
}
if srv.Path == "" {
file, err := path.File()
if err != nil {
return nil, err
}
srv.Path = "/" filepath.Base(file)
}
return srv.Buffer(data), nil
}
// 用于查找 <?xml 指令
var procInst = regexp.MustCompile(`<\?xml . ?>`)
func addStylesheet(data []byte) []byte {
pi := `
<?xml-stylesheet type="text/xsl" href="` docs.StylesheetURL("./") `"?>`
if rslt := procInst.Find(data); len(rslt) > 0 {
return procInst.ReplaceAll(data, append(rslt, []byte(pi)...))
}
ret := make([]byte, 0, len(data) len(pi))
return append(append(ret, pi...), data...)
}