Background

寫程式除了測試之外還有另外一個我覺得還蠻重要的就是寫文件, 怎麼把程式的文件寫好我覺得是一件蠻難的事. 那剛好最近發現了 Sphinx 這個package來幫助寫有關python的文件. 試用了一下感覺不錯, 就想說寫個blog來紀錄一下, 希望之後也能導入進公司.

Prerequisites

首先我們先來建立一個python的virtual environment吧:

$ mkdir demo
$ cd demo
# create a virtual environment
$ virtualenv .venv
$ ls -a
.     ..    .venv
$ pip install sphinx

我個人是喜歡把文件放在docs這個子資料夾下.

Quickstart

安裝完成之後可以透過 sphinx-quickstart 來自動生成一些基礎的設定檔.

$ sphinx-quickstart

然後按照指示設定走就可以了, 以下是我自己的設定

> Root path for the documentation [.]: docs
> Separate source and build directories (y/N) [n]: n
> Name prefix for templates and static dir [_]: <ENTER>
> Project name: Demo
> Author name(s): Paul
> Project version: 0.0.1
> Project release [0.0.1]: <ENTER>
> Project language [en]: <ENTER>
# also support Chinese.
> Source file suffix [.rst]: <ENTER>
> Name of your master document (without suffix) [index]: <ENTER>
> Do you want to use the epub builder (y/n) [n]: n
> autodoc: automatically insert docstrings from modules (y/n) [n]: y
> doctest: automatically test code snippets in doctest blocks (y/n) [n]: y
> intersphinx: link between Sphinx documentation of different projects (y/N) [n]: n
> todo: write “todo” entries that can be shown or hidden on build (y/N) [n]: y
> coverage: checks for documentation coverage (y/N) [n]: y
> imgmath: include math, rendered as PNG or SVG images (y/n) [n]: n
> mathjax: include math, rendered in the browser by MathJax (y/n) [n]: n
> ifconfig: conditional inclusion of content based on config values (y/n) [n]: n
> viewcode: include links to the source code of documented Python objects (y/n) [n]: y
> githubpages: create .nojekyll file to publish the document on GitHub pages (y/n) [n]: n
> Create Makefile? (y/n) [y]: y
> Create Windows command file? (y/n) [y]: n

設定完之後整個目錄結構會長這樣

› tree
.
├── ....
├── docs
│   ├── _build
│   ├── conf.py
│   ├── index.rst
│   ├── Makefile
│   ├── _static
│   └── _templates
└── ....

接著對 docs/conf.py 做一些設定上的修改, 因為我習慣把code放在一個子資料夾中. 所以必須讓 sphinx 能找到 project 的 source code.

14:06:07 › tree
.
├── code
│   ├── demo.py
│   ├── __init__.py
├── docs
│   ├── _build
│   ├── conf.py
│   ├── index.rst
│   ├── Makefile
│   ├── _static
│   └── _templates
└── ....
$ vim docs/conf.py

conf.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# demo documentation build configuration file, created by
# sphinx-quickstart on Tue Feb 28 14:06:07 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#

...
 
import os
import sys
sys.path.insert(0, os.path.abspath('..'))

...

Writing Docstring

接下來我們要在程式碼中加上 docstring(reStructuredText), 這些加上去的內容會自動被 sphinx 抓出來轉成document. 下面這張圖(src: [2])很好的解釋 sphinx & reStructuredText 在整個文件化的過程中所扮演的角色.

ok, 接下來再code資料夾下面新增以下的程式, 記住 docstring 是用 reStructuredText 規則所組成的. 所以你不一定要使用我的寫法, 可以參考(Here)

$ touch code/__init__
$ vim code/demo.py

code/__init __

# -*- coding: utf-8 -*-

__version__ = '0.0.1'
__author__ = 'Paul'
__email__ = '[email protected]'
__license__ = 'BSD'
__copyright__ = 'Copyright (c) 2017, Paul Liang.'

from code import demo

code/demo.py

# -*- coding: utf-8 -*-
"""
.. module:: demo
   :platform: Ubuntu 16.04, linux
   :synopsis: demo code

.. moduleauthor:: Paul Liang <[email protected]>
"""

def capital_case(x):
    """This function change the string to capitalization

    Args:
        x (str):  The string that will be translate.

    Returns:
        str. The capital_case

    >>> from code import demo
    >>> demo.capital_case('foo')
    'Foo'
    """
    return x.capitalize()


if __name__ == '__main__': # pragma: no cover
    print(capital_case('semaphore'))

P.S. 以>>>開頭的句子是為了之後測試docstring裡的examples用的.

接下來打以下的指令, 然後看到這個命令在 docs/api 目錄下自動為每個 sub-module 產生 .rst 檔, 然後我們就可以用 Makefile 來產生 HTML document.

$ sphinx-apidoc -f -o docs/api code
Creating file docs/api/code.rst.
Creating file docs/api/modules.rst.
$ cd docs
$ make html
Running Sphinx v1.5.3
loading pickled environment... done
building [mo]: targets for 0 po files that are out of date
building [html]: targets for 0 source files that are out of date
updating environment: 0 added, 1 changed, 0 removed
reading sources... [100%] demo
looking for now-outdated files... none found
pickling environment... done
checking consistency... /opt/paul/demo/docs/api/modules.rst:: WARNING: document isn't included in any toctree
done
preparing documents... done
writing output... [100%] index
generating indices... genindex py-modindex
highlighting module code... [100%] codeop
writing additional pages... search
copying static files... done
copying extra files... done
dumping search index in English (code: en) ... done
dumping object inventory... done
build succeeded, 1 warning.

Build finished. The HTML pages are in _build/html.

接下來如果你想增加Index在主頁的話. 你必須修改 docs/index.rst

docs/index.rst

...

Welcome to code's documentation!
================================

.. toctree::
   :maxdepth: 2
   :caption: Contents:

   demo

...

接著新增一個 demo.rst檔案在docs資料夾下, 這樣index.rst會自動去讀取對應的檔案.

docs/demo.rst

documentation for "demo"
=========================

This is something I want to say that is not in the docstring.

.. automodule:: demo
   :members: capital_case
   :noindex:

完成後再重新 make html 一次, 匯出成 HTML 後會顯示如下圖:

Doctest

前面有提到我們可以測試 docstring 裡頭的 examples(>>> 開頭的那幾行). 如果要用到這項功能請先確定docs/conf.pyextensions = [...]裡面含有'sphinx.ext.doctest'. 不管是成功&錯誤都會有相關的訊息出來. 大家再自行去修正.

$ cd docs
$ make doctest
Running Sphinx v1.5.3
loading pickled environment... done
building [mo]: targets for 0 po files that are out of date
building [doctest]: targets for 4 source files that are out of date
updating environment: 0 added, 0 changed, 0 removed
looking for now-outdated files... none found
running tests...

Document: demo
--------------
1 items passed all tests:
   2 tests in default
2 tests in 1 items.
2 passed and 0 failed.
Test passed.

Document: api/code
------------------
1 items passed all tests:
   2 tests in default
2 tests in 1 items.
2 passed and 0 failed.
Test passed.

Doctest summary
===============
    4 tests
    0 failures in tests
    0 failures in setup code
    0 failures in cleanup code
build succeeded.
Testing of doctests in the sources finished, look at the results in _build/doctest/output.txt.

Reference