前言
apiDoc是我们在日常开发中经常会使用到的一款开源的api文档解析框架,使用apiDoc可以轻松的将我们代码中的api解析成为可以方便阅读的api文档,只需轻松的按照格式进行编辑即可,使用起来非常方便。但是,apiDoc也存在一定的问题,即apiDoc解析生成的文档,通常格式上都不是特别严谨,这样它就无法存放在一些NoSQL的框架如Elasticsearch中,因此,本文后面提供了一种解决思路,将apiDoc生成的文档,进行标准化成为统一的格式,可存放到Elasticsearch中,方便我们后期再做管理。
入门
首先是apiDoc的入门教程。
安装&运行
安装:
npm install apidoc -g
运行:
apidoc -i myapp/ -o apidoc/ -t mytemplate/
-i
指定输入的源代码文件,-o
指定输出的文件夹,-t
指定模板文件的位置。
如何编写
apiDoc支持多种语言,包括但不限于:C#, Go, Dart, Java, JavaScript, PHP, TypeScript, CoffeeScript, Elixir, Erlang, Perl, Python, Ruby, Lua等。
而如何编写呢,就是在不同语言的多行注释内,比如Java:
/**
* @api {get} /user/:id Request User information
* @apiName GetUser
* @apiGroup User
*
* @apiParam {Number} id Users unique ID.
*
* @apiSuccess {String} firstname Firstname of the User.
* @apiSuccess {String} lastname Lastname of the User.
*/
还有python:
"""
@api {get} /user/:id Request User information
@apiName GetUser
@apiGroup User
@apiParam {Number} id Users unique ID.
@apiSuccess {String} firstname Firstname of the User.
@apiSuccess {String} lastname Lastname of the User.
"""
怎么样,看上面的例子,是不是非常简单呢。
具体如何使用,查查官方文档即知:apiDoc官方
解析
apiDoc可以读取我们编写的代码,并将其转化为apiDoc的文档,当我们打开文件夹时,会发现下面有两个文件,一个是api_project.js,另一个是api_data.js,没错,这就是apiDoc的数据了。其中api_project是项目的数据,这部分的数据主要描述的是我们项目文档的整体介绍,而api_data.js则是具体的api数据了。因此,当我们看到用apiDoc生成的文档时,我们也可以通过curl或其他方式,下载这两个文件,轻松获得api文档的所有数据。
转换
具体查看apiDoc生成的数据,我们就会发现,这两个文件生成的,里面的数据并非完全一致的,有些字段并不是每个项都有,这就非常不好了,不适合Elasticsearch内数据的存放使用,因此我们需要对这些数据进行一定的转化,转化为我们需要的标准Json数据。
以下使用python构建,python内的json不是特别符合我们的需求,因此我们专门定制了一个Json类,对这个类有如下要求:
1. 自动处理str类型、dict类型、list类型、None(null)、布尔类型等
2. 格式化输出json,且保证不浪费任何一个字节
3. 处理str类型时需要注意换行符,双引号等问题
为了解决第1个和第3个问题,我们编写了如下代码:
其中按照不同的类型进行了相应的处理,并利用了递归函数的技巧,来使得程序代码更加简化。
def _json_string(self, value):
if value != None:
if isinstance(value, str):
return "\"" + value.replace("\"", "\\\"").replace("\n", "\\n") + "\""
elif isinstance(value, dict):
dict_json = "{"
isFirst = True
for key in iter(value):
if not isFirst:
dict_json += ","
isFirst = False
dict_json += "\"" + key + "\":" + self._json_string(value[key])
return dict_json + "}"
elif isinstance(value, list):
sub_list = "["
isFirst = True
for d in value:
if not isFirst:
sub_list += ","
isFirst = False
sub_list += self._json_string(d)
return sub_list + "]"
elif isinstance(value, bool):
if value:
return "true"
return "false"
else:
return str(value)
return "null"
接下来我们只需要简单的输出即可:
def __str__(self):
string = ""
isFirst = True
for key in iter(self._data):
if not isFirst:
string += ","
isFirst = False
string += "\"" + str(key) + "\":"
string += self._data[key]
return "{" + string + "}"
上面的这个Json类是可以复用的。
下面来针对apiDoc编写专门的转换代码:
首先是解决对象内是否含有该键的问题:
def _valid_key(api, key):
if api != None and key != None \
and api.keys().__contains__(key):
return api[key]
return None
然后是解决共有的数据问题,即api_project.js的数据:
因为我们的数据是直接从网络上下载的,所有需要去除前面的无用信息,然后根据api_project的格式,我们提取出几个有效信息即name、title、description、version、header下的content字段,生成了Json对象返回。
def _parse_header(header_data):
header_data = header_data[7:len(header_data)-3]
json_data = json.loads(header_data.decode('utf-8'))
keys = ['name', 'title', 'description', 'version']
data = Json()
for key in keys:
data.append(key, _valid_key(json_data, key))
data.append("content", _valid_key(_valid_key(json_data, 'header'), 'content'))
return data
接下来解析api_data.js内的数据:
def _parse_api(data):
data = data[7:len(data)-3]
json_data = json.loads(data.decode('utf-8'))
apis = json_data['api']
simple_keys = ["type", "url", "title", "group", "groupTitle",
"description", "examples", "version"]
dup_keys = ["parameter", "error", "success"]
api_data_list = []
for api in apis:
data = Json()
for key in simple_keys:
data.append(key, _valid_key(api, key))
for key in dup_keys:
if _valid_key(api, key) == None:
data.append(key, None)
else:
sub_json = Json()
sub_json.append('examples', _valid_key(api[key], 'examples'))
info = []
if _valid_key(api[key], 'fields') != None:
for v in _valid_key(api[key], 'fields').values():
info.append(v)
sub_json.append('info', info)
data.append(key, sub_json)
api_data_list.append(data)
return api_data_list
这个函数相对而言比较复杂,但也就是针对数据进行定制化罢了。
附录
完整代码贴在下方,以供参考。
my_json.py 代码
#!/usr/bin/env python
# coding=utf-8
class Json:
def __init__(self):
self._data = dict()
def _json_string(self, value):
if value != None:
if isinstance(value, str):
return "\"" + value.replace("\"", "\\\"").replace("\n", "\\n") + "\""
elif isinstance(value, dict):
dict_json = "{"
isFirst = True
for key in iter(value):
if not isFirst:
dict_json += ","
isFirst = False
dict_json += "\"" + key + "\":" + self._json_string(value[key])
return dict_json + "}"
elif isinstance(value, list):
sub_list = "["
isFirst = True
for d in value:
if not isFirst:
sub_list += ","
isFirst = False
sub_list += self._json_string(d)
return sub_list + "]"
elif isinstance(value, bool):
if value:
return "true"
return "false"
else:
return str(value)
return "null"
def append(self, key, value=None):
if key == None:
return
self._data[key] = self._json_string(value)
def __str__(self):
string = ""
isFirst = True
for key in iter(self._data):
if not isFirst:
string += ","
isFirst = False
string += "\"" + str(key) + "\":"
string += self._data[key]
return "{" + string + "}"
apidoc_parse.py
#!/usr/bin/env python
# coding=utf-8
import urllib.request
import json
from my_json import Json
def _valid_key(api, key):
if api != None and key != None \
and api.keys().__contains__(key):
return api[key]
return None
def _parse_api(data):
data = data[7:len(data)-3]
json_data = json.loads(data.decode('utf-8'))
apis = json_data['api']
simple_keys = ["type", "url", "title", "group", "groupTitle",
"description", "examples", "version"]
dup_keys = ["parameter", "error", "success"]
api_data_list = []
for api in apis:
data = Json()
for key in simple_keys:
data.append(key, _valid_key(api, key))
for key in dup_keys:
if _valid_key(api, key) == None:
data.append(key, None)
else:
sub_json = Json()
sub_json.append('examples', _valid_key(api[key], 'examples'))
info = []
if _valid_key(api[key], 'fields') != None:
for v in _valid_key(api[key], 'fields').values():
info.append(v)
sub_json.append('info', info)
data.append(key, sub_json)
api_data_list.append(data)
return api_data_list
def _parse_header(header_data):
header_data = header_data[7:len(header_data)-3]
json_data = json.loads(header_data.decode('utf-8'))
keys = ['name', 'title', 'description', 'version']
data = Json()
for key in keys:
data.append(key, _valid_key(json_data, key))
data.append("content", _valid_key(_valid_key(json_data, 'header'), 'content'))
return data
# 输入apidoc生成的模板数据,生成统一的模板数据
def model_data(header_data, data):
api_data = _parse_header(header_data)
api_data.append('api', _parse_api(data))
return str(api_data)
if __name__ == '__main__':
header_data = urllib.request.urlopen('http://localhost:5000/static/api_project.js')
data = urllib.request.urlopen('http://localhost:5000/static/api_data.js')
model = model_data(header_data.read(), data.read())
print(model)