使用dtx文档类编写源码

在学习和工作中,需要编写程序,但是注释过多的话会影响速度,特别是对于科研工作而言 是一个很重要的参考! 对于一个脚本语言而言,其方便的地方是编程快速,但是其运行速度不如c等编译型语言,但是从整体来看,编写python之类的程序会更加方便。不写注释虽然可以提高运行速度,但是时间久了可能自己都读不懂程序在干什么及当时设计思路都无法回忆起来,更别谈及程序维护了。为了调和注释与可维护的矛盾,决定借用latex宏包的编写思路,使用dtx文件编写源码与说明文档二合一的文件,这样可以生成纯粹的无任何注释的脚本程序,同时也可以生成PDF格式的详细说明文档,不仅可以记录设计思路,还可以详细说明技术细节。

完成第一个 Python 脚本示例,实现了源码与注释的分离,使用 xetex 编译 timecristal.dtx 将获得 timecristal.py 无注释脚本程序,使用 xelatex 编译 timecristal.dtx 将会获得脚本的说明文档,详细的记录了各段代码的作用。

项目地址: https://gitlab.com/fengzhenhua/doc-python

分别生成说明文档和程序

这种类型的源码借用了skeleton.dtx文件,下面说明其应用:

skeleton.dtx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
% \iffalse meta-comment
%<*internal>
\iffalse
%</internal>
%<*readme>
Readme
%</readme>
%<*internal>
\fi
\begingroup
\def\nameoflatexe{LaTeX2e}
\expandafter\endgroup\ifx\nameoflatexe\fmtname\else
\csname fi\endcsname
%</internal>
%<*install>
\input docstrip.tex
\keepsilent
\generate{
\usedir{tex/latex/\jobname}
\file{\jobname.cls}{\from{\jobname.dtx}{class}}
\nopreamble\nopostamble
\file{README}{\from{\jobname.dtx}{readme}}}
\Msg{* Happy TeXing!}
\endbatchfile
%</install>
%<*internal>
\fi
%</internal>
%<*driver>
\ProvidesFile{skeleton.dtx}[2015/01/20 v1.0 A Skeleton File]
\documentclass{ltxdoc}
\begin{document}
\DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
% \CheckSum{0}
% \GetFileInfo{\jobname.dtx}
% \DoNotIndex{\test}
% \StopEventually{}
% \section{Implementation}
%<*class>
% \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}[1999/12/01]
\ProvidesClass{skeleton}[2015/01/20 v1.0 A Skeleton File]
% \end{macrocode}
%</class>
%<class>\endinput
% \Finale
\endinput

理解这文件的核心在于第 10 行–第 13 行的代码.第 10 行定义了一个临时命令 \nameoflatexe 由于它是临时起作用, 所以可以使用任何你想要的名称都可以. 由于它是临时的, 所以需要将它限定于 \begingroup\endgroup 之间. 但是根据不同的的功能需求在这时作了特定的设置. 命令 \fmtname 包含了当前执行此文件的程序, 如 (pdf/Xe)LaTeX 编译时, \fmtname 就是 LaTeX2e , 当使用 TeX 编译时, 它就不是LaTeX2e

获得宏包文件

使用 TeX 编译二合一的 .dtx 文件. TeX 读入文件,其中第1行 \iffalse 应当于第36行的 \fi 配对,第3行的 \iffalse 和 第9行的 \fi配对.第12行的 \ifx 和 第13行及第27行的\fi 配对.文件前面包含 % 的行被忽略,第3行和第9行 之间的部分也不会被执行,这样文件就从第10行开始执行,第11行定义了 \nameoflatexe 第11行使用\expandafter使\ifx 先于 \endgroup 执行.这样就来到了 \ifx,由于此时编译程序为 TeX ,所以程序就到了 \else 部分,于是 \csname fi\endcsname 执行,则\ifx 执行完毕,再执行 \endgroup 限定住了 \nameoflatexe 的作用范围,实际上此命令也就没有用了.紧接着调入 docstrip.tex 程序,执行分离程序,一直到第24行 \endbatchfile 则程序终止.由于一直到程序运行终止都还没有运行到第27行的 \fi,所以这不会出现任何错误.

获得说明文件

使用 XeLaTeX 编译二合一的 .dtx 文件. XeLaTeX 读入文件,其中第1行 \iffalse 应当于第36行的 \fi 配对,第3行的 \iffalse 和 第9行的 \fi配对.第12行的 \ifx 和 第13行及第27行的\fi 配对.文件前面包含 % 的行被忽略,第3行和第9行 之间的部分也不会被执行,这样文件就从第10行开始执行,第11行定义了 \nameoflatexe 第11行使用\expandafter 使\ifx 先于 \endgroup 执行.这样就来到了 \ifx,由于此时编译程序为 XeLaTeX 所以 \ifx 判断的结果为真,则 \else 到第13行 \endcsname 将失效所以 \ifx 还没有执行完成,直到第27行的 \fi,然后再执行 \endgroup. 所以第10行到第27行之间的部分将不被执行,也就是跨过 docstrip 程序分离宏包的部分.这样程序忽略其间被 % 注释掉的部分.运行到了第30行,一直到第33行使用 \DocInput 重新调入 .dtx 文件, % 将失去注释符的作用, 在调入文件后再恢复注释作用(这是命令\DocInput的作用)同时由于第1行的 \iffalse 和第36行的 \fi 配对, 所以这一部分将不被执行.则程序运行到第37行,而第37行一直到第50行是说明文档的内容,则一直遇到第50行的 \endinput 则输入 .dtx 文件执行完毕,回到第34行,遇到 \end{document} 则过程结束.我们将获得和宏包对应的说明文档.

同时生成说明文档和程序

这是第二种方式来编写源文件和注释,并且能够完成源码无注释分离。这里借助了l3doc.dtx文件,其结构为

l3doc.dtx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
\iffalse meta-comment
Your copy@right
\def\nameofplainTeX{plain}
\ifx\fmtname\nameofplainTeX\else
\expandafter\begingroup
\fi
\input l3docstrip.tex
\askforoverwritefalse
\preamble
the preamble information
\endpreamble
\% stop docstripadding \endinput

\postamble
the postamble information
\endpostamble

\generate {\file{package.sty}{\from{\jboname.dtx}{package}}}

\ifx\fmtname\nameofplainTeX
\expandafter\endbatchfile
\else
\expandafter\endgroup
\fi
</driver>
<driver|class>\RequirePackage{expl3,calc}
<*driver>
\PassOptionsToPackage{fontset = windows}{ctex}
\documentclass{ctxdoc}
\usepackage{framed}
\begin{document}
\DocInput{\jobname.dtx}
\end{document}
</driver>
\fi

\title{\jobname}
\author{<++>}
\date{<++>}
\maketitle
\tableofcontents

\begin{documentation}
\section{Introduction}
Code and documentation for this class have been written prior to the
\end{documentation}

\begin{implementation}
\section{\pkg{l3doc} implementation}
\end{implementation}

此文件也是二合一文件,但是实现代码和 ctex.dtx 不同,所以放在此处作为第二种方法.这代码的的解释为:第1行的 \iffalse 和第 32行的 \fi 配对,第3-6行和 第17-21行配对. 在第3行定义了 \nameofplainTeXplain , 然后对比编译程序的名字是否为 plain .如果是, 则略去 \else 后到 第6行的 \fi 部分,即不执行 \begingroup 接下来就是调入 l3docstrip.tex 文件,即开始进行宏包和类文件的分离工作.直到第17行,由于文件名 \nameofplainTeX 就是 plain ,则执行 \endbatchfile 则程序结束.反之,如果在第4行的判断为否,则执行第5行的 \begingroup 同时第 17-21行,执行 第20行的 \endgroup.则介于 \begingroup\endgroup 的部分一直到最后所有的部分都被执行.

  • ctex.dtx 执行xetex 只生成pkg.styclass.cls文件,执行xelatex只生成说明文档ctex.pdf
  • l3doc.dtx 执行tex 只生成 l3doc.cls或其他宏包,执行latex l3doc.clsl3doc.pdf

参考文献