缘起:近期有看到提问 “doc-as-code 方案下如何做内容复用?”,想想使用 Sphinx + reStructuredtext 已经有段时间了,也有一些个人认为还是比较实用和提升效率的做法,故趁机重新梳理、复盘一下,希望对有同样需求和问题的小伙伴有些启发和帮助。
P.S.:其实在之前的两篇文档里,也已经分别介绍过了复用实现方式的变量和条件文本的使用,可分别查看:
Sphinx+reStructuredText:变量的使用 - 简书 (jianshu.com)
Sphinx+reStructuredText:条件文本的使用 - 简书 (jianshu.com)
什么是内容复用
DITA Best Practice 一书中有这么一句话讲到内容复用,即 In the world of DITA, reuse is about writing content once and reusing that content wherever it is needed. 内容复用,确实具有诸多好处,比如:
-
提升效率。相同的内容写一次就可以了,省去了重复写的麻烦。如果有翻译的需求,还省去了重复翻译的成本。另外,内容需要维护和审阅时,也省去了重复维护和审阅的成本。
-
提升准确性和一致性。一次更新可保证全局修改。
那么,是不是如果没用DITA,是不是就没法实现内容复用呢?doc-as-code 方案中是否内容复用呢?本文想重点讨论这个问题,并分享一些笔者在实际工作过程中的经验。
根据内容模块的颗粒度,内容复用设计可以包括以下的情况:
- 词汇和短语类(Phrase level)
- 段落类(Paragraph level)
- 主题类(Topic level)
- 目录/多个主题集合(Map level)
不同颗粒度内容模块的复用设计与方法
针对词汇和短语类的信息片段
对于词汇、短语类的信息片段,可以考虑使用变量的方式处理。例如:
- 产品名称
- 模块名称
- 同一个参数在不同产品/方案上的不同设定值
步骤:
Step 1 在 conf.py
文件中定义全局 substitution。
rst_epilog = """
.. |product name| replace:: Product A
"""
Step 2 在 .rst
文件中使用已定义的 substitution。
|product name| provides a solution to xxxxxxxxxxxxxxxxxxxxxxxxxx.
如遇需更新的情况,直接调整 conf.py
的表述即可。当然,如只需达到比如某些 rst (reStructuredText)文件中的统一,可以在 rst 文件中使用局部 substitution 声明变量值,而不在 conf.py
定义(相当于全局变量)。
当然,substitution 不仅仅能用于文本类变量的应用,也能用于图片或者可执行功能块类的变量应用。以图片为例,reStructuredText Markup Specification (sourceforge.io) 官方提供的用法示例如下:
West led the |H| 3, covered by dummy's |H| Q, East's |H| K,
and trumped in hand with the |S| 2.
.. |H| image:: /images/heart.png
:height: 11
:width: 11
.. |S| image:: /images/spade.png
:height: 11
:width: 11
* |Red light| means stop.
* |Green light| means go.
* |Yellow light| means go really fast.
.. |Red light| image:: red_light.png
.. |Green light| image:: green_light.png
.. |Yellow light| image:: yellow_light.png
|-><-| is the official symbol of POEE_.
.. |-><-| image:: discord.png
.. _POEE: http://www.poee.org/
此种用法下,再提供一种升级的用法,即结合 only directive,可以进一步实现相同参数名在不同产品/方案上不同的设定值。具体做法示例如下:
.. only:: tag_A
.. |parameter_value| replace:: Value_A
.. only:: tag_B
.. |parameter_value| replace:: Value_B
The Parameter value is |parameter|
需要注意的一点是,如果使用 only directive,需保证引入的 tag 必须在 conf.py
文件中进行过声明。声明命令如下:
# In conf.py file
tags.has ('tag_a')
后续结合不同场景需求,可过滤得到已定义 tag 后的内容。
针对段落类的信息块
在词汇或短语的基础上,如待重用的是内容较多的内容块,如段落、注意信息等,可以将待重用的内容保存为单独的文件,并使用 include directive 在需要重用的地方直接插入。
具体操作及代码可参考如下:
Step 1: 如需重用如下所示的 note 信息,可以将该 note 信息单独存储成一个文件,例如 note_a.txt
文件。
.. note:: This is a piece of note information, which can be shared for different topics.
Step 2: 在需要重用的位置,使用 include directive 插入。
Paragraph example xxxxxxxxxxx.
.. include:: note_a.txt
针对主题类的模块化文档
对于内容规模更大些的,即差异较大的内容模块,更建议提供单独的主题文件,及独立的 rst 文件存放。在不同需求场景下,使用 toctree directive 调用不同的主题即可。
例如假定有如下的文档目录:
trunk
|-- product_A/
| |-- introduction_product_A.rst
|-- product_B/
| |-- introduction_product_B.rst
|-- shared/
| |-- topic_1.rst
| |-- topic_2.rst
那么针对产品A、产品B,在文档输出时,可以提供不同主题文件的引用,如:
# In index_A.rst file
.. toctree::
Introduction <product_A/introduction_product_A.rst>
# In index_B.rst file
.. toctree::
Introduction <product_B/introduction_product_B.rst>
# OR: In a same toc or subtoc file, use *only* directive to filter
.. only:: tag_A
.. toctree::
Introduction <product_A/introduction_product_A.rst>
.. only:: tag_B
.. toctree::
Introduction <product_B/introduction_product_B.rst>
针对目录/多个主题集合
进一步地,除了单个的 Topic,待复用的是多个主题文件的集合,方法其实与主题文档的复用手段一样,区别在于可以定义整个 Sphinx project 的根目录下的 index.rst
文件,也即 root_doc
(version 4.0 以前也叫master_doc
)文件。
此时,可以在项目根目录下准备多个 index.rst
文件,并在不同发布场景的配置文件 conf.py
文件中声明具体使用哪个目录结构文件。
# In conf.py file
root_doc = index_product_A.rst
条件发布
在以上的内容复用实现示例中,不难发现已经引入了 conf.py
, reStructuredText directives (substitution, only 等),那么在最后文档发布过程如何进行 tag 以及 root_doc
的过滤呢?此时,我们可以考虑灵活应用和扩展下 Makefile 文件。
在改 Makefile 文件之前,我们先来回顾下 Sphinx build 文档的基本命令。以 HTML 的输出为例,基本的 sphinx-build 命令如下:
sphinx-build -b html source build -t tag_option -c conf_file_path/
在上述命令中:
-
-b
代表要输出的文件格式,此处以 HTML 文件输出为例。如需 PDF输出,则相应为-M latexpdf
。 -
source
代表源文件的文件夹。 -
build
代表输出文件的文件夹。此时,文档生成结束后,可以在build/html
找到所有的输出文件。 -
-t
代表此次文档生成时要去抓取的 tag 标签,即标记为指定 tag_option 下的所有内容。默认为空。 -
-c
代表此次文档生成时要应用的 configuration 文件。默认为source
目录下的conf.py
文件。
一旦引入了不同的 tag_option
和 conf.py
的组合,以达到不同发布物不同模版配置及内容过滤的需求。进一步地,可在 Makefile 中区分不同的发布命令,以便于其他协作者直接使用简单的 make 命令即可获得相应的发布物。
In Makefile file
# Set up the target for the HTML output of product A
PA-HMTL:
@$(SPHINXBUILD) -b html "$(SOURCEDIR)" "(BUILDDIR)/product_a" -t tag_a -c ../conf_a/
如是,使用 make PA-HTML
命令即可得到产品 A 的 HMTL 文档。在输出文件中,对于打了 tag 的内容,仅抓取标记为 tag_a
的内容。同时,应用了指定的 conf_a/conf.py
模版文件。在模版文件中,就可以自定义不同的样式设计、全局变量定义等等。
与 DITA 重用方式的比较
从上述内容不难发现,可以针对不同的内容颗粒度,灵活使用和组合不同的方式,以达到复用的目的。结合以前使用 DITA 的经验,将不同颗粒度的内容模块复用实现方式比较如下:
待重用的内容模块 | DITA | Sphinx + reStructuredText |
---|---|---|
词汇或短语 | conref / keyref / conkeyref | substitution directive |
段落 | conref | include directive + only directive |
主题 | topicref | toctree directive + only directive |
目录结构 | mapref |
root_doc + conf.py |
写在最后
实际工作中,可能也会有小伙伴疑问 “既然已经是 doc-as-code 了,为啥要这么大费周章地考虑复用,最简单的复用方式,不是拉取分支(branch)吗?” 拉取分支固然是简单、直接的方式,但一旦分支(branch)分出后,分支管理、与主 trunk 的关系管理、冲突解决其实会是个比较难处理的问题,特别是对于非技术背景的 TW,一看到稍复杂的冲突可能就得头皮发麻、两手无措了。
另外,并不是提起内容复用,就一定只能有 DITA 的解决方案,在轻量级的 doc-as-code 模式下,也有相应的复用实现方法。但是,不论何种解决方案下,就内容复用本身,其也不应该是想起来复用就复用的。在决定复用内容之前,要经过全局的内容分析与设计,与团队内约定什么内容应该有限复用,何种场景下选择何种恰当的方式进行复用,以最小化的内容管理代价实现最大化复用效率。无论什么内容都想复用解决、或者一味为了复用而复用,都是不太可取的。
网友评论