使用sphinx写项目文档
Sphinx是一个开源的python文档生成器,这里有一个中文的使用手册,它采用reStructuredText
作为默认的源文件格式,也可以配置使用markdown
作为源文件,可以输出为html文件或者pdf文件(需要latex)等多种格式.
由于它自带解析python docstring的功能,因此非常适合用于给python项目做接口文档.通过其他第三方插件的支持,sphinx也可以为javascript,c/c++,golang自动生成接口文档.
本文会结合例子介绍,会按以下顺序演示在不同的场景下使用sphinx写文档
- 基本设置,使用markdown写文档
- 多语言支持
- 借助工具自动生成对应编程语言的api文档
- python
- C/C++
安装与设置
Sphinx需要python作为运行环境,安装只要pip install sphinx
就可以.
sphinx项目的基本构建过程是:
-
使用快速构建工具构建文档项目
-
修改
conf.py
做全局设置 -
编写文档源文件
-
编译文档源文件构造为目标输出格式
构建文档项目
Sphinx提供了两个快速构建工具用于构造一个文档项目.
sphinx-quickstart
用于从0开始构造一个文档项目.sphinx-apidoc
用于在已有python模块的情况下快速构建一个文档项目
介于本文并不是专门针对python项目的,这部分我们介绍第一种快速构建工具.sphinx-apidoc
则会在[为Python项目创建api文档]部分介绍.
使用sphinx-quickstart
创建文档项目.会一步一步提示你填写好项目的基本信息,如果懒得进入交互填写,可以用如下命令
sphinx-quickstart --no-sep -v <项目版本> -r <项目发行版本> -p <项目名> -a <作者名> -l <使用的语言> [ --ext-todo --ext-mathjax --ext-viewcode [文档源文件所在目录]]
使用sphinx-quickstart
创建文档项目有以下几点需要注意
- 如果
项目版本
和项目发行版本
一致,则只有项目版本,否则形式为<项目版本>rc<项目发行版本>
--no-sep -v <项目版本> -r 0.0.0 -p <项目发行版本> -a <作者名> -l <使用的语言>
这几个中只要有一个没填就需要进交互去填写-
使用的语言
只支持如下:缩写 语言 ar
阿拉伯语 bg
保加利亚语 bn
孟加拉 ca
加泰罗尼亚语 cak
喀克其奎语 cs
捷克语 cy
威尔士语 da
丹麦语 de
德语 el
希腊语 en
英语 eo
世界语 es
西班牙语 et
爱沙尼亚语 eu
巴斯克语 fa
伊朗语 fi
芬兰语 fr
法语 he
希伯来语 hi
印地语 hi_IN
印度印地语 hr
克罗地亚语 hu
匈牙利语 id
印度尼西亚语 it
意大利语 ja
日语 ko
韩语 lt
立陶宛语 lv
拉脱维亚语 mk
马其顿语 nb_NO
书面挪威语 ne
尼泊尔语 nl
荷兰语 pl
波兰语 pt
葡萄牙语 pt_BR
巴西葡萄牙语 pt_PT
欧洲葡萄牙语 ro
罗马尼亚语 ru
俄语 si
僧伽罗语 sk
斯洛伐克语 sl
斯洛文尼亚语 sq
阿尔巴尼亚语 sr
塞尔维亚语 sr@latin
塞尔维亚语(拉丁文) sr_RS
塞尔维亚语(西里尔文) sv
瑞典语 ta
泰米尔语 te
泰卢固语 tr
土耳其语 uk_UA
乌克兰语 ur
乌尔都语 vi
越南语 zh_CN
简体中文 zh_TW
繁体中文 文档源文件所在目录
可以缺省,缺省就是在执行目录生成.建议在项目的document
目录下创建.--ext-todo --ext-mathjax --ext-viewcode
是推荐的通用插件,建议都安上.todo
一个TODO list插件mathjax
可以显示latex写成的公式viewcode
可以查看到项目的源码
一路按照提示选择,创建好后目录结构大约是这样:
|-_build # 用于存放编译好的文档结果
|-_static # 用于存放模板的静态资源文件
|-_templates # 用于存放模板文件
|-conf.py # 文档项目的配置项
|-index.rst # 首页源文件
|-Makefile # 非windows平台用于编译文档项目的makefile文件
|-make.bat # windows平台用于编译文档项目的makefile文件
修改conf.py
conf.py
是sphinx文档项目的配置文件,它就是一个python脚本,使用上面的方式创建出来的conf.py
大致如下:
project = 'sample'
copyright = '2020, hsz'
author = 'hsz'
# 确定项目版本
version = '0.0.0'
release = '0.0.0'
# 使用的插件
extensions = [
'sphinx.ext.todo',
'sphinx.ext.mathjax',
'sphinx.ext.viewcode',
]
# 指定模板所在文件夹位置
templates_path = ['_templates']
# 文档语言
language = 'en'
# 不进行编译的文件/文件夹
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# 设置不同后缀的文件使用不同解析器(这个需要后加)
source_suffix = {
'.rst': 'restructuredtext'
}
# 指定编译成html时使用的主题
html_theme = 'alabaster'
# 指定编译成html时使用的静态文件所在位置
html_static_path = ['_static']
# todo插件的设置
todo_include_todos = True
我们可以直接在conf.py
中通过python代码进行设置,设置的基本模式是:
如果上面已经有了但需要修改,就直接修改
-
修改主题
html_theme = "alabaster"
自带的主题可以在这里查找.
个人比较推荐的自带主题是
alabaster
,其设置可以参考下面html_theme = "sphinx_rtd_theme" html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', 'searchbox.html', 'donate.html', ] }
都不喜欢的话可以去这里查找第三方主题,第三方主题需要先安装.
个人比较推荐的第三方主题是
sphinx_rtd_theme
,其设置为import sphinx_rtd_theme extensions.append('sphinx_rtd_theme') html_theme = "sphinx_rtd_theme" html_theme_options = { 'logo_only': True, 'navigation_depth': 5, }
如果是原本支持功能,就直接设置对应的参数,比较常用的设置包括:
-
设置latex的编译配置:
latex_engine = 'xelatex' latex_elements = { 'fontpkg': r''' \setmainfont{DejaVu Serif} \setsansfont{DejaVu Sans} \setmonofont{DejaVu Sans Mono} ''', 'preamble': r''' \usepackage[titles]{tocloft} \cftsetpnumwidth {1.25cm}\cftsetrmarg{1.5cm} \setlength{\cftchapnumwidth}{0.75cm} \setlength{\cftsecindent}{\cftchapnumwidth} \setlength{\cftsecnumwidth}{1.25cm} ''', 'fncychap': r'\usepackage[Bjornstrup]{fncychap}', 'printindex': r'\footnotesize\raggedright\printindex', } latex_show_urls = 'footnote'
如果是通过插件支持的功能,则需要先在``中添加插件,然后写入插件的配置.比如
-
配置对markdown的支持(需要先安装依赖
pip install --upgrade recommonmark
)from recommonmark.transform import AutoStructify from recommonmark.parser import CommonMarkParser # 使用插件支持markdowm # 使用插件支持markdowm extensions.append('recommonmark') # 针对`.md`为后缀的文件做markdown渲染 source_suffix[".md"] = 'markdown' # 设置markdown渲染器的自定义项 def setup(app): github_doc_root = 'https://localhost:5000' app.add_config_value('recommonmark_config', { #'url_resolver': lambda url: github_doc_root + url, # 如果不是本地调试,可以将自己项目的url根目录写上 "enable_auto_toc_tree": True, "auto_toc_tree_section": "目录", 'auto_toc_maxdepth': 2, # 设置toc_tree深度 "enable_math": True, # 支持```math,注意目前inline_math会有问题因此不建议使用. 'enable_eval_rst': True, # 支持嵌入rst }, True)
使用markdown编写文档
我们的第一个例子是纯粹使用sphinx渲染markdown.这个例子中我们的入口是index.md
,它有个目录项会将page1.md
和page2.md
引入文件.
编译源文档
编译源文档可以有两种方法:
使用
make
命令
make <目标文件类型>
命令可以在_build
目录下简单快捷地构建文档.最常用的就是make html
.我们可以看下make
的源码:
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
可以看出它实际就是执行sphinx-build
命令,我们如果追求简单可以直接修改其中的参数固化每次的编译行为,比如修改BUILDDIR
为../docs
这样方便直接给Github Page
使用
使用
sphinx-build [options] sourcedir builddir [filenames]
来构建.
上面就可以看出sphinx-build
是make
的底层,用它可以更灵活的构建.其中sourcedir
是源文件目录,builddir
是生成文件目录, filenames
用于指定要编译的文件,一般不需要写
默认情况下sphinx-build
只会编译有变化的文件
脚本 sphinx-build
的可选参数有:
-
-b
生成器名字生成器,决定了生成文档的类型,是最重要的选项.通用的生成器有:
html
生成HTML
文档. 默认的生成器.dirhtml
生成HTML
文档,但是每个文档都有单一的目录,在用浏览器访问时有漂亮的URLs(没有后缀.html
)singlehtml
所有内容生成单一的HTML
htmlhelp
,qthelp
,devhelp
,epub
生成HTML
文档,建立文档集时包含这些类型之一的额外信息.latex
生成LaTeX
源,可使用指定的latex编译器将其编译成PDF
文档.默认使用的是xelatex
man
生成UNIX系统的groff
格式手册.texinfo
生成Texinfo
文件,可以使用makeinfo
产生Info
文件.text
生成纯文本文件.gettext
生成gettext-style
分类信息(.pot
文件).linkcheck
检查所有外部链接的可信度.查看Available builders
,列出了Sphinx
支持的所有生成器及其可添加的扩展.
-
-a
给出时重写全部文档,默认则仅重新生成有新的源文件或源文件被修改的文档.(不适用于所有生成器) -
-E
不使用保存的environment
(环境,缓存了所有的参考索引),而是完全重建.默认仅读取和解析最近新添加及改动的源文件. -
-t <tag>
定义标签<tag>
.与only
指令相关,标签是一个目录集合,仅处理标签目录中的内容. -
-
d <path>
目前Sphinx生成输出前会读取和解析所有的源文件,解析过的源文件被缓存成”doctree pickles”.通常,这些文件被放在生成目录的.doctrees
文件夹中;这个选项可以选择不同的缓存目录(doctrees
可以被所有的生存器共享). -
-c <path>
不使用源目录下的conf.py
而是使用指定的配置文件.注意在配置文件中提及的路径都是相对配置文件所在目录的相对路径,因此路径必须一致. -
-C
不查找配置文件,仅使用选项-D
的配置. -
-D setting=<value>
覆盖conf.py
里的配置值.value
是一个字符串或字典.例如:-D latex_elements.docclass=scrartcl
.布尔值使用0
或1
代替. -
-A name=<value>
模板里的name
变量使用value
值代替. -
-n
采用nit-picky
模式.该模式下所有错误都会产生警告信息. -
-N
不产生彩色输出.(在Windows下,彩色输出一直是不可用的) -
-q
不产生标准输出,仅使用标准错误输出输出警告和错误信息. -
-Q
不产生标准输出,也不产生警告信息,仅使用标准错误输出输出错误信息. -
<-w file>
除标准错误输出外,将警告(错误)输出到指定文件. -
-W
将警告视为错误.产生第一个警告就停止文档生成活动,sphinx-build
在状态1
退出. -
-P
发生未绑定的异常时运行Python调试器pdb
.(仅在调试时使用)
多语言支持
上面的例子中我们已经使用language = 'zh_CN'
设定了文档使用的语言.但如果一个项目希望国际化,不可避免的会需要多语言支持,Sphinx自然也有对应的方案.
本部分的例子在分支sphinx-example-locale上
要支持多语言我们会用到sphinx-intl:
-
pip install sphinx-intl
. -
config.py
中添加设置
locale_dirs = ['locale/'] #
gettext_compact = False # optional.
-
将文档内容转化为
pot
文件,放入document/_build/gettext
文件夹下sphinx-build -b gettext document document/_build/gettext
-
使用
sphinx-intl
工具将pot
文件都转化成po
文件用于翻译sphinx-intl update -p document/_build/gettext -d document/locale -l 语言1 -l 语言2 ...
这一步的
-d document/locale
和上面配置的locale_dirs
对应 -
翻译
上一步以后我们可以在文档源文件文件夹下获得一个
locale
文件夹.其结构如下:locale\ |- en\ |- index.po |- xxx.po |-... |- fr\ |- index.po |- xxx.po |... |- ....
一种语言会对应一个文件夹,其中的文件和源文件是一一对应的关系.每个
.po
文件类似如下:# SOME DESCRIPTIVE TITLE. # Copyright (C) 2020, hsz # This file is distributed under the same license as the sample package. # FIRST AUTHOR <EMAIL@ADDRESS>, 2020. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: sample 0.0.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-11-20 14:21+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.9.0\n" #: ../../index.md:5 msgid "Page1" msgstr "" #: ../../index.md:5 msgid "Page2" msgstr "" #: ../../index.md:1 msgid "Welcome to sample's documentation!" msgstr "" #: ../../index.md:3 msgid "Example" msgstr "" #: ../../index.md:5 msgid "目录" msgstr "Index"
其中
#
为注释,msgid
为源文件中的内容,msgstr
为待翻译内容.这一步的任务就是把msgstr
都填完. -
编译
还是使用
sphinx-build
,只是需要加上-D language='<语言>'
并且注意目标文件夹,建议将目标文件夹放在主文件夹下的子文件夹下.sphinx-build -D language='fr' -b html document docs/fr
单一静态站的多语言支持
上面就是多语言支持的完整步骤了,不过要注意,不同语言的静态页面本质上都是不相关独立静态网站,要将他们联系起来我们要借助sphinx的扩展能力.
sphinx支持使用jinja2语法做扩展,我们可以在_template
文件夹下写一个versions.html
(注意由于css使用的是sphinx_rtd_theme
提供的.因此这个方法只能在这个主题中使用)
\{\% if READTHEDOCS or display_lower_left \%\}
\{\# Add rst-badge after rst-versions for small badge style. \#}
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
<span class="rst-current-version" data-toggle="rst-current-version">
<span class="fa fa-book"> Read the Docs</span>
v: \{\{ current_version \}\}
<span class="fa fa-caret-down"></span>
</span>
<div class="rst-other-versions">
\{\% if languages|length >= 1 \%\}
<dl>
<dt>\{\{ _('Languages') \}\}</dt>
\{\% for slug, url in languages \%\}
\{\% if slug == current_language \%\} <strong> \{\% endif \%\}
<dd><a href="\{\{ url \}\}">\{\{ slug \}\}</a></dd>
\{\% if slug == current_language \%\} </strong> \{\% endif \%\}
\{\% endfor \%\}
</dl>
\{\% endif \%\}
</div>
</div>
{\{\% endif \%\}
这个模板可以看出,我们会用到变量display_lower_left
,version
,current_language
,和languages
,因此我们需要在config.py
中配置这4个变量
...
# 多语言支持
try:
html_context
except:
html_context = dict()
html_context["current_version"] = version
html_context['display_lower_left'] = True
# 从环境变量`CURRENT_LANGUAGE`获取当前语言,默认为zh_CN
current_language = os.environ.get('CURRENT_LANGUAGE') if os.environ.get('CURRENT_LANGUAGE') else 'zh_CN'
html_context['current_language'] = current_language
if current_language == 'zh_CN':
# # POPULATE LINKS TO OTHER LANGUAGES
html_context['languages'] = [('zh_CN', 'index.html')]
languages = [lang.name for lang in Path(__file__).parent.joinpath("locale").iterdir() if lang.is_dir()]
for lang in languages:
html_context['languages'].append((lang, f'{lang}/index.html'))
else:
# # POPULATE LINKS TO OTHER LANGUAGES
html_context['languages'] = [('zh_CN', '../index.html')]
languages = [lang.name for lang in Path(__file__).parent.joinpath("locale").iterdir() if lang.is_dir()]
for lang in languages:
html_context['languages'].append((lang, f'../{lang}/index.html'))
html_context
是在使用模板时变量的保存位置,它是一个字典结构,我们把需要设置的值都放入其中即可.需要注意的是languages
中的元素是一个展示和路径组成的元组,在编译不同语言的静态页面时,路径往往是不同的(主要是主语言和locale中的语言会有不同),为了解决这个问题,我们要在外部借助环境变量CURRENT_LANGUAGE
在外部告知编译时的程序.因此编译程序为:
-
linux/macos
export CURRENT_LANGUAGE=fr && sphinx-build -D language='fr' -b html document docs/fr
-
windows
$env:CURRENT_LANGUAGE="fr"; sphinx-build -D language='fr' -b html document docs/fr
利用这种方式我们还可以扩展出其他用法,比如将历史版本也带上什么的,不过这就要借助CI/CD
工具了.具体可以参考这个项目.
借助工具自动生成对应编程语言的api文档
sphinx在设计之初主要目的就是给python做项目文档,因此内置了插件sphinx.ext.autodoc
来自动从python项目的源码中获取docstring
生成api文档.这也是它能流行的主要原因.
在它越来越流行的同时,用户开始希望它可以将这一特性扩展至其他编程语言.这一功能目前并没有官方实现,但比较接近的有几个第三方项目,比较可惜的是目前并没有相对成熟的方案
-
这个项目由
readthedocs
这个项目组在维护,目前支持对Python
的配置化部署.也提供了Javascript
,Go
,C#
的支持,但还在alpha版本,基本不可用因此建议只将它用在python项目中.这个项目有意构造为一个专注于python并为其他语言的支持提供扩展接口的底层库.我觉得是个蛮好的思路.这个项目使用
pip install sphinx-autoapi
安装. -
sphinx-js 这个项目由
mozilla
在维护.它和上面用法类似,支持Javascript
和Typescript
.但很遗憾比较手动挡,而且不支持markdown.因此本文不做介绍
接下来的部分我们会介绍上面几种编程语言的自动化api编译.
本部分的例子演示的是单一语言环境下的api文档自动生成.
自动生成Python项目的API
本部分的例子在标签sphinx-example-python下.
在安装好sphinx-autoapi
后,我们只需要写好配置,指定好要解析的文件夹位置即可.
conf.py
...
# autoapi-python
extensions.append('autoapi.extension')
extensions.append("sphinx.ext.napoleon")
autoapi_type = 'python'
autoapi_dirs = ['../pythonsrc']
# autoapi_options = ['members', 'undoc-members', 'show-inheritance',
# 'show-module-summary', 'special-members', 'show-inheritance-diagram', 'imported-members']
# autoapi_add_toctree_entry = False
我们需要在配置中导入插件sphinx.ext.napoleon
来支持解析google风格的docstring.指定autoapi_type
为python
,并使用autoapi_dirs
指名python脚本所在目录.
注意autoapi
会扫描并解析autoapi_dirs
指定目录下的所有.py
和.pyi
文件,因此要注意下项目结构,不要将虚拟环境文件夹包含进去.
autoapi_options
可以指定解析的范围:
枚举值 | 说明 |
---|---|
members |
展示对象的子对象 |
inherited-members |
展示对象的继承对象 |
undoc-members |
展示没有docstring 的对象 |
private-members |
展示私有对象(以_ 开头命名的对象) |
special-members |
展示特殊对象(以__ 开头命名的对象) |
show-inheritance |
展示继承关系 |
show-inheritance-diagram |
以图的形式展示继承关系,需要使用插件sphinx.ext.inheritance_diagram 并安装Graphviz |
show-module-summary |
展示模块的综述 |
imported-members |
展示import进来的模块 |
默认的设置是['members', 'undoc-members', 'private-members', 'show-inheritance', 'show-module-summary', 'special-members', 'imported-members']
.
我们在src
文件夹下放入我们的演示python模块sample.py
"""牛顿法扩展都n次方根
:math:`\\sqrt[k] a` .的表达式为:
:math:`x_{n+1} = x_n - \\frac {x_n^k-a} {kx_n^{k-1}} = \\frac {k-1}{k} x_n + \\frac {a}{kx_n^{k-1}}`
"""
from typing import Union
def sqrt_nt(n: Union[int, float], m: Union[int, float], *, round_: int = 5) -> float:
"""牛顿法求n次方根.
用法: :math:`sqrt\\_nt(n,m,round) = \\sqrt[m] n`
Parameters:
n (int,float): 被开方的数
m (int,float) : 开多少次方
round(int): 精度
Returns:
int,float: 开方结果
Raises:
TypeError: 参数类型不对时报错
ValueError:被开方参数为复数时报错
>>> sqrt_nt(2,2,round_=2)
1.41
"""
if all(map(lambda x: isinstance(x, (int, float)), [n, m])):
if n < 0:
raise ValueError(u"必须是整数")
elif n == 0:
return 0
elif n == 1:
return 1
else:
deviation = 0.1**(round_ + 1)
seed = n / m
counter = 0
max_count = 100000
now_value = seed
last_value: Union[int, float] = 0
while abs(now_value - last_value) > deviation:
if counter > max_count:
raise ValueError(u"在{count}次循环内未能得到精度为{round}的解".format(
count=max_count, round=round_))
counter += 1
last_value = now_value
now_value = (1 - 1.0 / m) * last_value + \
n / (m * last_value**(m - 1))
return round(now_value, round_)
else:
raise TypeError(u"必须是数")
class A:
"""一个测试类.
Attributes:
a (str): 测试元素
"""
def __init__(self, a: str) -> None:
self.a = a
def echo(self, n: int) -> int:
"""测试原样返回.
Args:
n (int): n
Returns:
int: n
"""
return n
之后执行sphinx-build document docs
即可.
自动生成C/Cpp项目的API
本部分的例子在sphinx-example-c
我们需要先安装doxygen
,这是一个为多种编程语言构造api文档的命令行工具,可以在linux,macos,windows上执行,不过需要注意的是在windows上只支持cmd
命令行.因此我们编译文档的时候需要在cmd命令行中执行.
接下来是安装插件breathe
和exhale
.这两个包是python包,pip安装即可.
接着是该配置:
-
conf.py
# C/CPP 语言支持 extensions.append('breathe') extensions.append('exhale') # 指定doxyout生成的xml文件的位置,相对于我们的文档源文件所在文件夹的位置 breathe_projects = { "Sample": "./doxyoutput/xml" } # 项目名 breathe_default_project = "Sample" exhale_args = { # 必填配置(不建议更改) "containmentFolder": "./api",# api文档的输出位置,相对于 "rootFileName": "index.rst", # api文档的入口文件,建议写成这样 "rootFileTitle": "Library API", # api文档的入口标题 "doxygenStripFromPath": "..", # doxygen的执行位置 # 推荐填的配置 "createTreeView": True, # 是否创建树状视图 "exhaleExecutesDoxygen": True, # 由exhale执行Doxygen而非手工执行 "exhaleDoxygenStdin": "INPUT = ../include" # c/cpp的头文件位置 } # Tell sphinx what the primary language being documented is. primary_domain = 'cpp' # Tell sphinx what the pygments highlight language should be. highlight_language = 'cpp'
然后我们在待编译的项目入口文件index.md
中添加对api入口文件的引用
* [api文档](api/index.rst)
最后就是执行sphinx-build document docs
就可以了.