DocStripプログラムとは?
DocStripプログラムとは,ソースファイルから特定の部分だけを抜き出す機能を実装したプログラムのことです.この機能により1つのソースファイルからソースコードとそのドキュメントを作成することができます.ドキュメント作成にはTeX/LaTeXを使用します.
言葉で説明するよりも,実際の例を見た方が早いでしょう.次の節に実行例を載せておきます.
なお,既にDocStripプログラムを使ったことがある方にお伝えしたい事として,ここで紹介するコードでは**.ins
ファイルが不要になります**1.
DocStripプログラムの実行例
実行環境
- Ubuntu 20.04
- TeX Live 2019
サンプルコード
まず,次のsample-code.dtx
を保存してください.
`sample-code.dtx`を表示する
% \iffalse
%<*driver>
\begingroup
\input l3docstrip
\askforoverwritefalse
\keepsilent
\nopreamble
\nopostamble
\generate{\file{trapezoidal-rule.c}{\from{\jobname.dtx}{code}}}
\endgroup
\documentclass{ltjltxdoc}
\usepackage{amsmath,minted,tcolorbox,tikz,hyperref}
\newminted{c}{bgcolor=gray!10, firstnumber=last, linenos}
\renewcommand{\figurename}{図}
\begin{document}
\DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{数値積分}
% \maketitle
% \tableofcontents
% \newpage
%
% \section{台形公式}
%
% \subsection{導出}
% ^^A defining f(x)
% \def\myfunction{{0.3*(\x^3 - 7.6*\x^2 + 18.3*\x) - 2.5}}
%
% \begin{figure}[h]
% \centering
% \begin{tikzpicture}[domain=1:4, >=stealth]
% \coordinate (a) at (1,0);
% \coordinate (b) at (4,0);
% \coordinate (fa) at (1,1);
% \coordinate (fb) at (4,2.2);
% \filldraw[fill=blue!10!white] (a) -- plot (\x, \myfunction) -- (b);
% \draw[blue, very thick] plot (\x, \myfunction);
% \draw[->, thick] (-0.3,0) -- (5,0) node[below] {$x$};
% \draw[->, thick] (0,-0.3) -- (0,3) node[left] {$y$};
% \draw (0,0) node[below left] {$O$};
% \draw (a) node[below] {$a$};
% \draw (b) node[below] {$b$};
% \draw[<-] (1.7,1.8) -- (1.5,2.3) node[above] {$y=f(x)$};
% \draw (2.5,0.7) node {$\displaystyle \int_a^b f(x)\,dx$};
% \end{tikzpicture}
% \caption{$\displaystyle \int_a^b f(x)\,dx$の図形的意味}
% \label{fig:integration}
% \end{figure}
%
% $\forall x\in[a,b]: f(x)\geq0$である関数$f$に対し,
% 定積分$\displaystyle\int_a^b f(x)\,dx$は,曲線$y=f(x)$と$x$軸,
% および2直線$x=a, x=b$で囲まれた図形の面積に等しい(図\ref{fig:integration}).
%
% \begin{figure}[h]
% \centering
% \begin{tikzpicture}[domain=1:4, >=stealth]
% \filldraw[fill=red!10!white] (a) -- (fa) -- (fb) -- (b);
% \draw[blue, very thick] plot (\x, \myfunction);
% \draw[red, thick] (fa) -- (fb);
% \draw[->, thick] (-0.3,0) -- (5,0) node[below] {$x$};
% \draw[->, thick] (0,-0.3) -- (0,3) node[left] {$y$};
% \draw (0,0) node[below left] {$O$};
% \draw (a) node[below] {$a$};
% \draw (b) node[below] {$b$};
% \draw[<-] (1.7,1.8) -- (1.5,2.3) node[above] {$y=f(x)$};
% \draw (2.5,0.6) node {$\displaystyle (b-a) \frac{f(a)+f(b)}{2}$};
% \end{tikzpicture}
% \caption{一次近似}
% \label{fig:linear-approximation}
% \end{figure}
%
% $f(x)$を一次関数で近似すると,この図形は台形となる(図\ref{fig:linear-approximation}).
% したがって,この場合,$\displaystyle \int_a^b f(x)\,dx$は次のように近似される.
% \begin{equation}
% \int_a^b f(x)\,dx \approx (b-a) \frac{f(a)+f(b)}{2}
% \end{equation}
%
% \begin{figure}[h]
% \centering
% \begin{tikzpicture}[domain=1:4, >=stealth]
% \coordinate (a1) at (1.4,0);
% \coordinate (fa1) at (1.4,1.53);
% \coordinate (a2) at (2.2,0);
% \coordinate (fa2) at (2.2,1.75);
% \coordinate (an-1) at (3.7,0);
% \coordinate (fan-1) at (3.7,1.8);
% \filldraw[fill=red!10!white] (a) -- (fa) -- (fa1) -- (a1);
% \filldraw[fill=red!10!white] (a1) -- (fa1) -- (fa2) -- (a2);
% \filldraw[fill=red!10!white] (an-1) -- (fan-1) -- (fb) -- (b);
% \draw[blue, very thick] plot (\x, \myfunction);
% \draw[red, thick] (fa) -- (fa1);
% \draw[red, thick] (fa1) -- (fa2);
% \draw[red, thick] (fan-1) -- (fb);
% \draw[->, thick] (-0.3,0) -- (5,0) node[below] {$x$};
% \draw[->, thick] (0,-0.3) -- (0,3) node[left] {$y$};
% \draw (0,0) node[below left] {$O$};
% \draw (a) node[below] {$a$};
% \draw (b) node[below right] {$b$};
% \draw (a1) node[below] {$a_1$};
% \draw (a2) node[below] {$a_2$};
% \draw (an-1) node[below] {$a_{n-1}$};
% \draw[<-] (1.7,1.8) -- (1.5,2.3) node[above] {$y=f(x)$};
% \draw (3,0.7) node {$\dots\dots$};
% \end{tikzpicture}
% \caption{台形公式}
% \label{fig:trapezoidal-rule}
% \end{figure}
%
% ここで,積分値の精度を上げるために,
% \[
% a=a_0 < a_1 < \dots < a_n = b
% \]
% として積分区間$[a, b]$を$n$個の閉区間$[a_0,a_1], [a_1,a_2], \dots, [a_{n−1},a_n]$に分け,
% それぞれの区間を同様に一次近似する(図\ref{fig:trapezoidal-rule}).
%
% このとき,$\displaystyle \int_a^b f(x)\,dx$は次のように近似される.
% \begin{align*}
% \int_a^b f(x)\,dx
% = & \int_{a_0}^{a_1} f(x)\,dx
% +\int_{a_1}^{a_2} f(x)\,dx
% +\dots
% +\int_{a_{n-1}}^{a_n} f(x)\,dx \\
% = & \sum_{k=0}^{n-1} \int_{a_k}^{a_{k+1}} f(x)\,dx \\
% \approx & \sum_{k=0}^{n-1} (a_{k+1} - a_k) \frac{f(a_k)+f(a_{k+1})}{2}
% \end{align*}
%
% ここで,$n$個に分割された区間の幅が等間隔の場合,
% すなわち,任意の$k=0,1,\dots,n-1$に対し,
% \begin{equation}
% a_{k+1}-a_k=\frac{b-a}{n}
% \end{equation}
% となる場合を考える.このとき,
% \begin{equation}
% a_k=a+k\frac{b-a}{n} \quad (k=0,1,2,\dots,n)
% \end{equation}
% であるから,$\displaystyle \int_a^b f(x)\,dx$は次のようになる.
% \begin{align*}
% \int_a^b f(x)\,d{x}
% \approx & \sum_{k=0}^{n-1} (a_{k+1} - a_k) \frac{f(a_k)+f(a_{k+1})}{2} \\
% = & \frac{b-a}{2n}\sum_{k=0}^{n-1}(f(a_k)+f(a_{k+1})) \\
% = & \frac{b-a}{2n}\left(f(a_0) + 2\sum_{k=1}^{n-1}f(a_k) + f(a_n)\right) \\
% = & \frac{b-a}{n}\left(\frac{f(a)+f(b)}{2} + \sum_{k=1}^{n-1}f\left(a+k\frac{b-a}{n}\right)\right)
% \end{align*}
%
% 以上をまとめると,次のようになる.
% \begin{tcolorbox}
% \begin{equation}
% \label{eq:trapezoidal-rule}
% \int_a^b f(x)\,dx \approx \frac{b-a}{n}
% \left(\frac{f(a)+f(b)}{2} + \sum_{k=1}^{n-1}f\left(a+k\frac{b-a}{n}\right)\right)
% \end{equation}
% \end{tcolorbox}
%
% \subsection{\texttt{trapezoidal-rule.c}のソースコード}
% \iffalse
%<*code>
% \fi
%
% \subsubsection{ヘッダファイルのインクルード}
% \begin{ccode}
# include <math.h>
# include <stdio.h>
% \end{ccode}
%
% \subsubsection{関数プロトタイプ}
% \begin{ccode}
double integrand(double x);
double trapezoidal_rule(double (*f)(double x), double a, double b, int n);
% \end{ccode}
%
% \subsubsection{main関数}
% 次のコードは,積分区間の分割数$n=1000$のときの$\displaystyle \int_1^3f(x)\,dx$の値を表示する.
% \begin{ccode}
int main(void)
{
printf("Result: %f\n", trapezoidal_rule(integrand, 1, 3, 1000));
return 0;
}
% \end{ccode}
%
% \subsubsection{被積分関数の定義}
% \begin{ccode}
double integrand(double x)
{
return x * sin(x);
}
% \end{ccode}
%
% \subsubsection{台形公式の実装}
% 式(\ref{eq:trapezoidal-rule})を実装する.
% \begin{ccode}
double trapezoidal_rule(double (*f)(double x), double a, double b, int n)
{
int k;
double sum = 0.0;
for (k = 1; k < n; k++)
sum += f(a + k*(b-a)/n);
return (b-a)/n * ((f(a) + f(b))/2 + sum);
}
% \end{ccode}
%
% \iffalse
%</code>
% \fi
%
% \section{Simpson公式}
%
% \subsection{導出}
% ...
%
% ^^A End of file `sample-code.dtx'.
上記のsample-code.dtx
に対して,次のコマンドを実行してみてください2.
$ lualatex -shell-escape sample-code.dtx
すると次のtrapezoidal-rule.c
とsample-code.pdf
の2ファイルが生成されるはずです3.
`trapezoidal-rule.c`を表示する
# include <math.h>
# include <stdio.h>
double integrand(double x);
double trapezoidal_rule(double (*f)(double x), double a, double b, int n);
int main(void)
{
printf("Result: %f\n", trapezoidal_rule(integrand, 1, 3, 1000));
return 0;
}
double integrand(double x)
{
return x * sin(x);
}
double trapezoidal_rule(double (*f)(double x), double a, double b, int n)
{
int k;
double sum = 0.0;
for (k = 1; k < n; k++)
sum += f(a + k*(b-a)/n);
return (b-a)/n * ((f(a) + f(b))/2 + sum);
}
以下がsample-code.pdf
の内容です.元ファイルは ここ でも見れます.
このサンプルコードでは.dtx
ファイルから.c
ファイルを生成しましたが,任意のプログラミング言語のファイルを生成できます.
DocStripプログラムを使うメリット
- ソースコードとそのドキュメント・マニュアルを1つのファイルで管理できる
- 任意のプログラミング言語ファイルを生成できる
- ドキュメントに数式や図・表を記載できる
1.について,例えばあるソースコードのドキュメントを作成していたとします.もしそのコードとドキュメントを別ファイルで管理していたら,コードを変更する度にドキュメント側に記載しているコードも変更する必要があります.いちいち変更するのは手間ですし,コードの転記ミスも発生し得ます.しかし,DocStripプログラムではそういったマイナスポイントがありません.
2.について,上記の例で.dtx
ファイルから.c
ファイルを生成したように,.dtx
ファイルからあらゆる言語ファイルを生成できます.さらに言うと,DocStripプログラムは1つの.dtx
ファイルから複数の異なる言語ファイルを生成できます.その方法は後で説明します.
3.について,上記例のドキュメント(.pdf
ファイル)のように,数式や図・表を記載できます.すなわち,LaTeX
ができることは何でもできます.したがって,索引の自動作成やマクロを組むこともできます.コードについての簡単なメモ書きであれば,コメントとして直接ソースファイルに記述できますが,さすがに上記例のような数式や図は記述できないでしょう4.
テンプレート
.dtx
ファイルのテンプレートを記載します.各コマンドの役割は次節で説明します.
基本的な編集箇所は以下になるかと思います.
-
\file{foo}{\from{\jobname.dtx}{bar}}
のfoo
とbar
-
foo
は生成ファイル名 -
bar
はガード
-
-
\fi
以降にドキュメント化するコードを記述 - 必要に応じて
\usepackage{baz}
を追加
% \iffalse
%<*driver>
\begingroup
\input l3docstrip
\askforoverwritefalse
\keepsilent
\nopreamble
\nopostamble
\generate{\file{foo}{\from{\jobname.dtx}{bar}}}
\endgroup
\documentclass{ltjltxdoc}
\begin{document}
\DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{Template}
% \maketitle
% \tableofcontents
%
% \section{***}
% ...
% ...
% ...
%
% ^^A End of file `template.dtx'.
各コマンドの役割
ここでは上記template.dtx
内の各コマンドの役割を説明していきます.より詳しい説明は docstrip.pdf を参照してください.
\iffalse
,\fi
\DocInput
から読み込まれるとき,\iffalse
と\fi
で囲まれた部分は読み飛ばされます.\iffalse
,\fi
で囲まないとエラーが発生します.
<*driver>
,</driver>
driver
ガードを設定します.「ガード」という用語については後述します.
実は,<*driver> ... </driver>
で囲まなくてもエラーが発生することなくコンパイルできますが,生成ファイルに余計な記述が追加されてしまいます.
\begingroup
,\endgroup
\begingroup
と\endgroup
で囲まれた部分をグループ化します.これによりDocStripプログラムの影響がグループ内にしか及ばないようにします.
\input l3docstrip
DocStripプログラムを利用するための記述です.これによりl3docstrip.tex
内に定義されているコマンドが使えるようになります.
\askforoverwritefalse
生成したいファイル名のファイルが既に存在する場合,上書きしてよいか確認を求められます.ただ,\askforoverwritefalse
を書いておくことにより,いちいち確認されずに済みます.
逆に上書きの確認をしたいときは\askforoverwritetrue
を記述しておくとよいでしょう.
\keepsilent
コンパイル時にDocStripプログラムの処理状況がターミナルに表示されますが,\keepsilent
を記述しておくことでそれが表示されません.
\nopreamble
,\nopostamble
\nopreamble
は生成ファイルにプリアンブルを記述しないようにするためのコマンドです.同様に,\nopostamble
はポストアンブルを記述しないようにするためのコマンドがです.
\generate{ ... }
\generate
コマンドでファイルを生成します.例えば,次のコードではbar
ガードで囲まれた部分をfoo
ファイルに書き込みます.
\generate{\file{foo}{\from{\jobname.dtx}{bar}}}
ここでガードとは%<...>
のことで,ガードの始まりは%<*...>
,終わりは%</...>
で記述します.
例えば,次のguard-example.dtx
に対して,$ lualatex guard-example.dtx
を実行します.
`guard-example.dtx`を表示する
% \iffalse
%<*driver>
\begingroup
\input l3docstrip
\askforoverwritefalse
\keepsilent
\nopreamble
\nopostamble
\generate{\file{output-file.txt}{\from{\jobname.dtx}{myguard}}}
\endgroup
\documentclass{ltjltxdoc}
\begin{document}
\DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
%<*myguard>
text1
% comment
text2
%</myguard>
%
% ^^A End of file `guard-example.dtx'.
すると,次のようなoutput-file.txt
が生成されます.
text1
text2
また,複数個ファイルを生成することもできます.例えば,次のコードではqiita.md
,sample.py
,test.cpp
というファイルを生成できます.
\generate{
\file{qiita.md} {\from{\jobname.dtx}{qiita}}
\file{sample.py}{\from{\jobname.dtx}{sample}}
\file{test.cpp} {\from{\jobname.dtx}{test}}
}
\jobname
\jobname
は.dtx
ファイル名に置き換わります.例えば,my-source-code.dtx
であれば\jobname
はmy-source-code
に置き換わります.
\DocInput{ ... }
\DocInput{ ... }
の引数で指定されたファイルを読み込みます.その際,%
をコメントと解釈せずに処理していきます.
つまり,\DocInput{\jobname.dtx}
とすることにより,自分自身のファイルにあるコメント行もLaTeXのコードとして解釈してドキュメント化してくれます.