documented latex sources
when we download a latex package from ctan (eg: xcolor
), we would
often find it does not contain .sty
but .dtx
and .ins
files; however, if
we follow instructions in its readme, then we can generate .sty
files from
.dtx
and .ins
files; this is irrelevant for users who install latex packages
via tds archives or os package managers; but latex packages shipped with os may
be outdated, and not every package offers tds archives (eg: xcolor
), so in
that case we need some knowledge about .dtx
and .ins
files;
before reading sections below, i suggest downloading the source of
xcolor
to experiment with; xcolor
has a very nice readme to try it out;
what is dtx
dtx stands for documented latex source; since it is latex source, we can run
it through the latex
program (or its derivations like pdflatex
, etc.):
pdflatex xcolor.dtx
this gives us xcolor.pdf
, which is the documentation of the xcolor
package;
so, processing a .dtx
file with latex gives a document; reasonable, but then
what would be its difference from a regular .tex
file?
why use dtx
a package needs code, code documentation and user documentation; dtx makes it possible to combine all three in one file, with additional benefits:
-
the documentation can be formatted with latex commands;
-
there is no need to update code separately in documentation;
-
the code can be extracted from the combined file;
writing typeset code documentation mixed with code itself is commonly known as literate programming;
a simple dtx
we can either write a .dtx
file from scratch, or convert from an existing
.sty
file; i think the latter is more illustrative; so now we construct a
simple example.dtx
file from the following example.sty
file:
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{example}[2020/02/02 Example package]
\RequirePackage{lmodern}
\newcommand{\myname}{foo}
\DeclareOption{bar}{
\renewcommand{\myname}{bar}
}
\ProcessOptions\relax
\newcommand{\showname}{\myname}
\endinput
while there are tools to do this job, here we want to handcraft it to make it as
simple as possible; the crafted example.dtx
is pasted below; dont be afraid of
its size and complexity, since we will be analyzing it shortly:
% \iffalse meta-comment
% Copyright (C) 2020 Author
% \fi
% \iffalse
%<*driver>
\ProvidesFile{example.dtx}
\documentclass{ltxdoc}
\usepackage{example}
\EnableCrossrefs
\CodelineIndex
\RecordChanges
\begin{document}
\DocInput{example.dtx}
\end{document}
%</driver>
% \fi
% \title{The \textsf{example} package}
% \author{Author}
% \date{}
%
% \maketitle
%
% \section{User Documentation}
%
% \StopEventually{
% \PrintChanges
% \PrintIndex
% }
%
% \section{Code Documentation}
%
% \iffalse
%<*package>
% \fi
% \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{example}[2020/02/02 Example package]
\RequirePackage{lmodern}
\newcommand{\myname}{foo}
\DeclareOption{bar}{
\renewcommand{\myname}{bar}
}
\ProcessOptions\relax
\newcommand{\showname}{\myname}
\endinput
% \end{macrocode}
% \iffalse
%</package>
% \fi
%
% \Finale
\endinput
this is the accompanying example.ins
:
%% Copyright (C) 2020 Author
\input docstrip.tex
\keepsilent
\askforoverwritefalse
\preamble
Copyright (C) 2020 Author
\endpreamble
\usedir{tex/latex/example}
\generate{\file{example.sty}{\from{example.dtx}{package}}}
\obeyspaces
\Msg{*************************************************************}
\Msg{* Done! *}
\Msg{*************************************************************}
\endbatchfile
now we can play with these files:
-
to extract
example.sty
fromexample.dtx
:pdflatex example.ins
it should give an identical
example.sty
except for comments and empty lines; -
generate package documentation
example.pdf
:pdflatex example.dtx pdflatex example.dtx makeindex -s gind.ist example.idx pdflatex example.dtx pdflatex example.dtx
it needs multiple commands because of the index; if you dont want the index, then just run the first command;
tools behind dtx
there are 2 important tools behind dtx:
-
strip comments and extract code blocks from dtx;
-
format latex user and code documentations;
the complexity of dtx mainly comes from the need to be readable by both tools,
while remaining valid latex source at the same time; by the way, if you are
really interested, docstrip
and doc
were written by the same person, so
there is little surprise that they work together despite the intricacies;
the dtx trinity
now we delve further into dtx grammar; when analyzing dtx, it is very helpful to understand the dtx trinity:
the dtx is one file, but three views;
the first view
the first view of example.dtx
:
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{example}[2020/02/02 Example package]
\RequirePackage{lmodern}
\newcommand{\myname}{foo}
\DeclareOption{bar}{
\renewcommand{\myname}{bar}
}
\ProcessOptions\relax
\newcommand{\showname}{\myname}
\endinput
the first view is about docstrip
and .ins
file;
first of all, we use an .ins
file to extract code from a .dtx
file; looking
at the code, it is obvious that example.ins
runs docstrip
on example.dtx
;
then, it is noticeable that example.dtx
contains both commented and
uncommented lines; when we run example.ins
through latex, the commented lines
in example.dtx
are removed by docstrip
;
but there are some strange comments: <*package>
and </package>
, and similar
ones for driver
; these are markers that mark a code block; docstrip
understands these markers; in fact, when we ask docstrip
to extract code from
example.dtx
into example.sty
using marker package
:
\generate{\file{example.sty}{\from{example.dtx}{package}}}
it extracts all uncommented lines between <*package>
and </package>
(and all
uncommented lines outside of any markers) in example.dtx
and writes them into
example.sty
(multiple empty lines are merged into one); actually, docstrip
also recognizes single-line markers like <package>
, but those are omitted here
for brevity; additionally, docstrip
stops reading if it hits \endinput
;
the second view
the second view of example.dtx
:
\ProvidesFile{example.dtx}
\documentclass{ltxdoc}
\usepackage{example}
\EnableCrossrefs
\CodelineIndex
\RecordChanges
\begin{document}
\DocInput{example.dtx}
\end{document}
the second view is about latex source;
as we have said, a .dtx
file is a valid latex source; this means, from a latex
point of view, only uncommented lines matter (not even docstrip
markers);
looking at the code, the uncommented lines include a small piece of driver code
followed by package source; however, the driver code ends with a line:
\end{document}
this means all lines thereafter are ignored;
so, from a latex point of view, the .dtx
file is merely a driver code;
but how is the body of the generated .pdf
file filled with contents?
the third view
the third view of example.dtx
:
\title{The \textsf{example} package}
\author{Author}
\date{}
\maketitle
\section{User Documentation}
\StopEventually{
\PrintChanges
\PrintIndex
}
\section{Code Documentation}
\begin{macrocode}
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{example}[2020/02/02 Example package]
\RequirePackage{lmodern}
\newcommand{\myname}{foo}
\DeclareOption{bar}{
\renewcommand{\myname}{bar}
}
\ProcessOptions\relax
\newcommand{\showname}{\myname}
\endinput
\end{macrocode}
\Finale
\endinput
the third view is about doc
;
the body of the driver code includes a single line:
\DocInput{example.dtx}
here the entire example.dtx
file is sourced and typeset by doc
; the magic
(and source of confusion) of the typeseting here is:
- commented lines (lines beginning with
%
) are treated as uncommented;
this explains why we need many \iffalse
guards: contents between \iffalse
and \fi
are filtered because of the \iffalse
, not the leading %
; if we
ignore contents between these guards, we will see what typeset into the body are
texts and some macrocode
environments; the macrocode
environment typesets
code verbatim; for the macrocode
environment to work correctly, we must have
exactly 4 spaces between the leading %
and \end{macrocode}
in the .dtx
file, and we usually do the same for \begin{macrocode}
;
from sty to dtx
now we have understood what dtx is and how to view a .dtx
file, we come up
with the next question:
how to convert an existing
.sty
file into a.dtx
file?
first of all, there is good news: the accompanying .ins
file does not have to
change (except for metadata such as package name); so we can take the above
example.ins
file for another package;
in fact, we can take the above example.dtx
file as well; what we need to do
here is to replace contents between \begin{macrocode}
and \end{macrocode}
with contents of the new .sty
file;
this should work except when the new .sty
file has commented lines (lines
beginning with %
); if so, these lines are typeset verbatim as code, because
they are within the macrocode
environment; this is usually not what we want;
to fix this problem, 2 things need to be done:
-
we need to end
macrocode
before commented lines and begin anothermacrocode
after commented lines; -
we need to change
%
to% ^^A
, if we want to keep comments as comments;%
is ignored bydoc
, and^^A
is provided as a comment character;
tools
there are some useful tools to make working with dtx easier:
-
this tool creates a new
.dtx
file using a builtin template; use this tool if you want to start writing a new.dtx
file; -
this tool converts a
.sty
file into a.dtx
file; it can track macro definitions and put them in special environments in the generated.dtx
file; it can also generate a.ins
file; empty lines are removed, which could be a source of bug; so you probably want to double check the.sty
file extracted from the generated.dtx
file matches the original; other than this, it is a great tool if you need to create a.dtx
file from an existing.sty
file;
summary
this is really the nutshell of dtx; we analyzed its basic structure but didnt
talk much about specific commands; on the one hand, users can do much more with
user documentation and code documentation, using various commands provided by
package doc
and ltxdoc
; on the other hand, the docstrip
package has
commands to control code extraction which werent covered here; interested
readers should consult their package documentation and references linked below;
latex3 users may want to check l3doc;