protoc-gen-doc 自定义模板规则详解
protoc-gen-doc 自定义模板规则详解
此项目中所用 proto 文件位于 ./proto
目录下,来源于 官方proto示例
此项目中所列所有模板case文件位于 ./tmpl
目录下
此教程均基于 markdown 文本演示
前言
最近有通过 proto 文件生成其接口文档的需求,而 protoc-gen-doc
所生成的格式不能很好地满足需要。看到它支持自定义模板,但几乎没有一篇文章能详细解释该如何自定义,该用什么字段自定义,里面各种遍历、条件语句怎么写,如何取用 ServiceMethod Options 等。
经过近一天的梳理和踩坑,产出本文,记载了自定义模板所需的常用语法。
protoc-gen-doc 介绍
protoc-gen-doc 是一个用于生成 proto 的 protoc 插件,通过解析 proto 文件定义,快速生成多种类型的接口文档。
生成文档的原理,就是基于一个文件模板,通过解析 proto 文件的属性,填充到模板中指定位置。可以基于内置模板生成,也可以自定义模板使用。
内置模板支持:
- json
- html
- markdown
- docbook
使用方式:
- 首先通过 go get 下载
protoc-gen-doc
二进制文件,使用时将传入此文件的路径。。为了演示方便,我在配套代码工程的根目录下放了一个protoc-gen-doc
文件。
go get -u github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc
- 使用 protoc 命令
protoc \
--plugin=protoc-gen-doc=./protoc-gen-doc \
--proto_path=./proto \
--proto_path=./third_party \
--doc_out=./out \
--doc_opt=markdown,index.md \
./proto/*.proto
参数解析:
--plugin
: 指定 protoc-gen-doc,并指定 protoc-gen-doc 可执行文件的路径--proto_path
: 包含目标 proto 文件的目录路径,如果依赖了其他目录的 proto 文件,也要使用此参数指定其目录。例如third_party
目录。--doc_out
: 接口文档的输出目录--doc_opt
: 指定待输出的文档模板和文件名- 最后一行: 指定目标 proto 文件
指定自定义模板
如果要指定自定义模型,则可修改 --doc_opt
的第一个参数。
例如,我在 ./api
目录下自定义了模板文件 md.tmpl
,则 --doc_opt
应设置为:
--doc_opt=./tmpl/case1.tmpl,index.md
官方自定义模板示例
官方在这里有两个简单的自定义模板,里面可见常用用法,但没有太多解释,想编写自己的自定义模板还是难度颇大。可供参考。
数据结构与函数定义
想要理解模板,就要将其中每个占位的参数所代表的含义。
模板中所有的占位参数,均可以在源码中找到:templates.go
这里不需要理解源码做了什么,不需要知道它是如何运行的,只需要看一个结构体:
type Template struct {
// The files that were parsed
Files []*File `json:"files"`
// Details about the scalar values and their respective types in supported languages.
Scalars []*ScalarValue `json:"scalarValueTypes"`
}
这就是从 proto 解析出的结构 Files 为一个文件,从 File 进入,可以见更多细节,亦可更深入 Services、Messages 查看。
type File struct {
Name string `json:"name"`
Description string `json:"description"`
Package string `json:"package"`
HasEnums bool `json:"hasEnums"`
HasExtensions bool `json:"hasExtensions"`
HasMessages bool `json:"hasMessages"`
HasServices bool `json:"hasServices"`
Enums orderedEnums `json:"enums"`
Extensions orderedExtensions `json:"extensions"`
Messages orderedMessages `json:"messages"`
Services orderedServices `json:"services"`
Options map[string]interface{} `json:"options,omitempty"`
}
与官方示例进行对比便可发现,那些占位的字段,皆为这里所定义的字段。这里字段名名如其意,应该无需详解。
其中Description
字段,对应的是 proto 文件中的注释。当注释写在 service 上方时,便可从 Service 结构的 Desctiption
字段读取。
另外,每个结构体还定义了诸多方法,也可以用于在自定义模板中调用。详情见后文。
基本语法
注解
主要以要生成的文档类型决定。
比如在 markdown 与 html 中,<!-- -->
无法被渲染出来,则可以用 <!-- -->
写注解
输出字段值
想获取到某个字段值,可以使用 {{}}
符号。在一个自定义模型文件中,
{{.}}
初始表示 Template 结构
{{.Files}}
表示输出 Template 的 Files 字段。
如果想要拿到单独一个 File 进行操作,就要使用遍历语句进行遍历。
遍历语句
从 Template 中遍历 Files 列表,如下所示:
<!-- case1.tmpl -->
{{range .Files}}
# {{.Name}}
{{.Description}}
{{.}}
{{end}}
上面例子,使用 {{Range .Files}}
表示遍历 Files 列表,到 {{end}}
处结束。在 range 和 end 之间,{{.}}
表示 File 的结构,{{.Name}}
即为 File.Name。
修改 --doc_opt=./tmpl/case1.tmpl,case1.md
参数,查看运行结果:
遍历语句非常常用,它是可以嵌套的,不光 File,连 Service、Message、ServiceMethod 等结构,都需要使用 Range 遍历进入,获取其内部结构。
例如,进入File,并进入 File.Services,打印 Service 的信息:
<!-- case2.tmpl -->
{{range .Files}}
# {{.Name}}
{{.Description}}
{{.}}
{{range .Services}}
## ServiceName: {{.Name}}
{{.Description}}
{{.}}
{{end}} <!-- end Services -->
{{end}} <!-- end Files -->
这里我们遍历打印了 Service.Name、Service.Description 和 Service 结构体
修改 --doc_opt=./tmpl/case2.tmpl,case2.md
参数,查看运行结果:
参数赋值
可以看到,每次 range 内部,{{.}}
都变成了 range 的目标列表的一个 Item。当 range 嵌套较多时,容易混淆。可以将 Item 赋值给一个参数,在之后使用参数进行调用。
基于 case2 进行改造:
<!-- case3.tmpl -->
{{range .Files}}
{{$file := .}}
# {{$file.Name}}
{{$file.Description}}
{{$file}}
{{range $file.Services}}
{{$service := .}}
## ServiceName: {{$service.Name}}
{{$service.Description}}
{{$service}}
{{end}} <!-- end Services -->
{{end}} <!-- end Files -->
查看运行结果,可以看到用了参数的 case3 结果与 case2 完全一致:
条件语句
使用 {{if}} {{end}}
,也可以在其中加入 {{else}}
{{if}}
目前发现有两种用法:
{{if eq param1 param2}}
{{end}}
{{if boolParam}}
{{end}}
前者对比两个参数是否相等。后者判断 boolParam 是否为 true。
下面示例基于 case3,将判断 file.Name 是否等于 Booking.proto
,若是,则输出相关信息,否则输出忽略 xxx
在 Booking.proto
中继续判断 HasServices 是否存在 service,如果存在则输出相关信息
<!-- case4.tmpl -->
{{range .Files}}
{{$file := .}}
{{if eq $file.Name "Booking.proto"}}
# {{$file.Name}}
{{$file.Description}}
{{$file}}
{{if $file.HasServices}}
{{range $file.Services}}
{{$service := .}}
## ServiceName: {{$service.Name}}
{{$service.Description}}
{{$service}}
{{end}} <!-- end Services -->
{{end}} <!-- end if -->
{{else}}
# 忽略 {{$file.Name}}
{{end}} <!-- end if else -->
{{end}} <!-- end Files -->
调用函数
想知道某个函数的功能,还要亲自阅读一下源码关于各结构体函数的实现:templates.go
使用方式:
{{functionName params... }}
函数大多是用来读取 Options 内容的。几乎每个结构都有 Options,为 map[string]interface{}
结构。
比较常用的 Options,如下图对 Service 进行 http 的定义:
为了取出这里的 Options,可以使用 ServiceMethod 的 Option
方法。
基于 case3 进行改造:
<!-- case5.tmpl -->
{{range .Files}}
{{$file := .}}
# {{$file.Name}}
{{$file.Description}}
{{$file}}
{{range $file.Services}}
{{$service := .}}
## ServiceName: {{$service.Name}}
{{$service.Description}}
{{range $service.Methods}}
{{$method := .}}
### ServiceMethod: {{$method.Name}}
{{range ($method.Option "google.api.http").Rules}}
- {{.Method}}
- {{.Pattern}}
- \{{.Body}} <!-- body 为 * 时会被认为是 markdown 语法 -->
{{end}} <!-- end Rules -->
{{end}} <!-- end Methods -->
{{end}} <!-- end Services -->
{{end}} <!-- end Files -->
运行后结果:
($method.Option "google.api.http").Rules
对取出的 interface{}
类型做了类型转换。为什么用 Rules 来转换,可以在源码中找到:
其他
<!-- 设置默认值 -->
{{ .XXX | default ""}}
<!-- 取值小写 -->
{{ .XXX | lower}}
<!-- 替换 a 字符为空 -->
{{ .XXX | replace "a" ""}}
<!-- 取值小写并替换 a 字符为空 -->
{{ .XXX | lower | replace "a" ""}}
除此之外,还有不少其他用法,暂时没有更深入探索,但掌握以上已经能够满足自定义模板之需求。
如看者有其他补充,不胜感激。
参考文档汇总
- 配套演示工程 https://github.com/csuqiyuan/custom-protoc-doc-example
- proto 文件官方示例 https://github.com/pseudomuto/protoc-gen-doc/tree/master/examples/proto
- 自定义模板官方示例 https://github.com/pseudomuto/protoc-gen-doc/tree/master/examples/templates
- templates.go https://github.com/pseudomuto/protoc-gen-doc/blob/master/template.go
- google.api.http Option 类型转换 https://github.com/pseudomuto/protoc-gen-doc/blob/master/extensions/google_api_http/google_api_http.go
protoc-gen-doc 自定义模板规则详解的更多相关文章
- (转)ThinkPHP自定义模板标签详解
转之--http://www.thinkphp.cn/topic/6258.html 模板标签让网站前台开发更加快速和简单,这让本该由程序猿才能完成的工作,现在只要稍懂得HTM的人也能轻易做到,这也就 ...
- 在ubuntu16.04中安装apache2+modsecurity以及自定义WAF规则详解
一.Modsecurity规则语法示例 SecRule是ModSecurity主要的指令,用于创建安全规则.其基本语法如下: SecRule VARIABLES OPERATOR [ACTIONS] ...
- yii2中的rules 自定义验证规则详解
yii2的一个强大之处之一就是他的Form组件,既方便又安全.有些小伙伴感觉用yii一段时间了,好嘛,除了比tp"难懂"好像啥都没有. 领导安排搞一个注册的功能,这家伙刷刷刷的又是 ...
- Nginx 常用全局变量 及Rewrite规则详解
每次都很容易忘记Nginx的变量,下面列出来了一些常用 $remote_addr //获取客户端ip $binary_remote_addr //客户端ip(二进制) $remote_port //客 ...
- 【转】Eclipse Java注释模板设置详解
Eclipse Java注释模板设置详解 设置注释模板的入口: Window->Preference->Java->Code Style->Code Template 然后 ...
- BaseAdapter自定义适配器——思路详解
BaseAdapter自定义适配器——思路详解 引言: Adapter用来把数据绑定到扩展了AdapterView类的视图组.系统自带了几个原生的Adapter. 由于原生的Adapter视图功能太少 ...
- 53个Oracle语句优化规则详解(转)
Oracle sql 性能优化调整 1. 选用适合的ORACLE优化器 ORACLE的优化器共有3种:a. RULE (基于规则) b. COST (基于成本) c. CHOOSE ...
- 【转载】 Eclipse注释模板设置详解
Eclipse注释模板设置详解 网站推荐: 金丝燕网(主要内容是 Java 相关) 木秀林网(主要内容是消息队列)
- ESLint 规则详解(二)
接上篇 ESLint 规则详解(一) 前端界大神 Nicholas C. Zakas 在 2013 年开发的 ESLint,极大地方便了大家对 Javascript 代码进行代码规范检查.这个工具包含 ...
- QuantLib 金融计算——基本组件之天数计算规则详解
目录 天数计算规则详解 定义 30 / 360 法 30/360 US 30/360 Bond Basis 30E/360 30E/360 ISDA Actual 法 Actual/Actual IC ...
随机推荐
- 基于 Rainbond 的混合云管理解决方案
内容概要:文章探讨了混合云场景中的难点.要点,以及Rainbond平台在跨云平台的混合云管理方面的解决方案.包括通过通过统一控制台对多集群中的容器进行编排和管理,实现了对混合云中应用的一致性管理.文章 ...
- 2022-04-27:Alice 有一个下标从 0 开始的数组 arr ,由 n 个正整数组成。她会选择一个任意的 正整数 k 并按下述方式创建两个下标从 0 开始的新整数数组 lower 和 hig
2022-04-27:Alice 有一个下标从 0 开始的数组 arr ,由 n 个正整数组成.她会选择一个任意的 正整数 k 并按下述方式创建两个下标从 0 开始的新整数数组 lower 和 hig ...
- 2021-04-03:给定两个字符串str1和str2,想把str2整体插入到str1中的某个位置,形成最大的字典序,返回字典序最大的结果。
2021-04-03:给定两个字符串str1和str2,想把str2整体插入到str1中的某个位置,形成最大的字典序,返回字典序最大的结果. 福大大 答案2021-04-03: 1.暴力法. 2.DC ...
- 500行代码代码手写docker-将rootfs设置为只读镜像
(3)500行代码代码手写docker-将rootfs设置为只读镜像 本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现 ...
- js代理(Proxy) 和 反射(Reflection)
在实际开发中经常会遇到js抛出的错误,但是我们有没有想过自己去接管js异常验证,根据自己的需求抛出异常呢?原本也许不行,但是在es6出来后就可以做到了 一.代理(Proxy) 什么是'代理' 呢?代理 ...
- 代码随想录算法训练营Day12 栈与队列
代码随想录算法训练营 代码随想录算法训练营Day12 栈与队列| 239. 滑动窗口最大值 347.前 K 个高频元素 总结 239. 滑动窗口最大值 给定一个数组 nums,有一个大小为 k 的 ...
- 一篇文章带你详细了解axios的封装
axios 封装 对请求的封装在实际项目中是十分必要的,它可以让我们统一处理 http 请求.比如做一些拦截,处理一些错误等.本篇文章将详细介绍如何封装 axios 请求,具体实现的功能如下 基本配置 ...
- Scalpel:解构API复杂参数Fuzz的「手术刀」
Scalpel简介 Scalpel是一款自动化Web/API漏洞Fuzz引擎,该工具采用被动扫描的方式,通过流量中解析Web/API参数结构,对参数编码进行自动识别与解码,并基于树结构灵活控制注入位点 ...
- 开源 API 网关的访问策略(一)
许多企业和组织面临着网关访问控制的挑战,因为传统的访问控制方法往往过于笨重和繁琐.这些方法可能涉及复杂的规则集.繁琐的手动配置过程.缺乏灵活性和可扩展性等问题.此外,随着云计算和移动设备的广泛应用,访 ...
- c++函数重载 c/c++混合编程
C++语言支持函数重载实现原理: 名字改编(name mangling)具体步骤: 当函数名称相同时,会根据函数参数的类型.个数.顺序进行改编 对源码直接用C++编译器进行编译时,会按C++方式进行调 ...