LaTeX 文学编程的不同方法

LaTeX 文学编程的不同方法

我花了几个小时重新思考我们为 提供的文字样式工具LaTeX,它们本质上是docdocstripltxdoc。虽然使用好的编辑器的传统工作流程可能不那么麻烦,但将代码限制在保护和百分比标记中可能会破坏编写代码的流程,尤其是对于新手来说。考虑到这一点,也为了尝试改进代码的排版和功能,我想出了一个替代方案。

核心思想是能够使用任何类作为基类(在 MWE 中我使用了 tufte-book),并且能够拥有漂亮的打印使用诸如 之类的包listings。该方法是仅使用一个.tex文件,该文件在编译时会生成文档以及类、包或文件。

本质上,它在定义时逐字保存代码。MWE 示例仅输出到一个.sty文件,但可以根据需要保存到任意数量的文件(模拟保护)。如果还想模拟在不同目录中安装文件的能力docstrip,可以合并并自行生成一个小的安装脚本。LuaLaTeXdocstrip

在开发这样的代码时应采取哪些预防措施,该doc/docstrip方法中是否存在一些无法纳入我的方法的基本特征?

\documentclass{tufte-book}
\usepackage{microtype,soul}
\makeatletter
\usepackage[charter]{mathdesign}
 \def\rmdefault{bch} % not scaled
 \def\ttdefault{blg}
\usepackage{xcolor,filecontents,ragged2e}
\usepackage[listings, theorems]{tcolorbox}
\definecolor{theblue} {rgb}{0.02,0.04,0.48}
\definecolor{thered}  {rgb}{0.65,0.04,0.07}
\definecolor{thegreen}{rgb}{0.06,0.44,0.08}
\definecolor{thegrey} {gray}{0.5}
\definecolor{theshade}{gray}{0.94}
\definecolor{theframe}{gray}{0.75}

\lstloadlanguages{[LaTeX]TeX, [primitive]TeX}

% Emphasis
\newcommand\emphasis[2][thered]{\lstset{emph={newcommand,def,gdef,#2},
   emphstyle={\ttfamily\textcolor{#1}}}}%

\lstset{language={[LaTeX]TeX},
      escapeinside={{(*@}{@*)}}, 
       numbers=left, gobble=2,
       stepnumber=1,numbersep=5pt, 
       numberstyle={\footnotesize\color{gray}},firstnumber=last,
       breaklines=true,
       framesep=5pt,
       basicstyle=\small\ttfamily,
       showstringspaces=false,
     % keywordstyle=\ttfamily\textcolor{thegreen},
      stringstyle=\color{orange},
      commentstyle=\color{black},
      rulecolor=\color{theshade},
      breakatwhitespace=true,
     showspaces=false,  % shows spacing symbol
      xleftmargin=0pt,
      xrightmargin=5pt,
      aboveskip=3pt plus1pt minus1pt, % compact the code looks ugly in type
      belowskip=7pt plus1pt minus1pt,  % user responsible to insert any skips
      backgroundcolor=\color{theshade}
}
\lst@RequireAspects{writefile}

\lstnewenvironment{Macro}[1][Test]
    {\emphasis{#1}\marginpar{\vskip0.5\baselineskip\footnotesize\color{thered}\texttt{\string#1}}
   \lst@BeginAlsoWriteFile{textsamples.sty}}
   {\endgroup}

\newcommand\lorem{Fusce adipiscing justo nec ante. Nullam in enim.
 Pellentesque felis orci, sagittis ac, malesuada et, facilisis in,
 ligula. Nunc non magna sit amet mi aliquam dictum. In mi. Curabitur
 sollicitudin justo sed quam et quadd. \par}


\DeclareRobustCommand{\todo}[1]{\sidenote{\hl{#1}}}
\author{Y Lazarides}\publisher{Camel Press}
\title{\parindent0pt A New Approach in\\ Documenting Macros}
\begin{document}

\backmatter
\maketitle
\parindent0pt


\mainmatter

\tableofcontents

\chapter{The Approach}
\section{User Documentation}
The user documentation can be styled a bit better. The user should be able to 
write the package or class in a more natural style.\todo{Write a few more notes here.}
\medskip

\begin{tcblisting}{colframe=thegreen,boxrule=1pt,colback=thered!5,listing options={style=tcblatex}}
The macro  \verb+\LaTeXe+, typesets the \LaTeXe logo.
\end{tcblisting}

\section{Implementation}

\lorem

\Macro[\ProvidesPackage]
  \NeedsTeXFormat{LaTeX2e}
  \ProvidesPackage{textsamples}[2012/02/13 v1.0 sample texts]
\endMacro

\lorem


\begin{Macro}[\lorem]
  \newcommand\lorem{\leavevmode 
    Fusce adipiscing justo nec ante. Nullam in enim.
    Pellentesque felis orci, sagittis ac, malesuada et, facilisis in,
    ligula. Nunc non magna sit amet mi aliquam dictum. 
    In mi. Curabitur sollicitudin justo sed quam et quadd.
    \par
  }
\end{Macro}

\lorem

\emphasis{\tex}
\begin{Macro}[\tex]
  \gdef{\tex}{%
      \TeX\xspace (*@\sidenote{You can even place sidenotes.% 
      These notes have no impact on the %
      code being written to the file.} @*)
  }
\end{Macro}
 % closes the open file
\begingroup\lst@EndWriteFile

\section{Full listing of {textsamples.sty}}
\lstinputlisting{textsamples.sty}
\end{document}

注意,我已将其listings用于逐字写入和tcolorbox示例自运行框。它还省略了doc's许多仍需调整的有用宏。

答案1

正如前面评论中讨论的那样,用这种方法解决定义类的问题完全有可能。当然,正如你所说,这需要两次运行,但可以制作一个文档,在第一次运行时处理书写(和一些最小的打印),在第二次运行时只处理类定义的命令(当然,如果标记正确的话)。

为了执行这个检查,我依赖于文件的存在(或不存在).aux,因为这很容易 - 大多数 IDE 可以一键删除它,允许覆盖等 - 并且在出现问题时对用户很友好 - 因为我预计大多数用户如果出现问题会首先尝试删除此文件。

唯一的要求是我们将代码存储在类文件中,而不是包中。以下 MWE 实现了此解决方案,以及针对多个文件问题的基于宏的解决方案(您应该一个接一个地编写文件,而不是随意混合它们)。由于我使用逗号分隔的宏列表,因此它可能使索引更容易,例如为创建的事物类型(宏、环境、计数器等)添加可选参数。

文件.cls

\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{literate}

\newif\if@firstrun
    \@firstruntrue
\newif\if@iscsname
    \@iscsnamefalse
\newcounter{c@copyright}

\IfFileExists{\jobname.aux}
    {\IfFileExists{\jobname.cls}
        {\LoadClass{\jobname}}{}
      \IfFileExists{\jobname.sty}
        {\RequirePackage{\jobname}}{}
      \@firstrunfalse}
    {\LoadClass{book}}

\newcommand{\filetype}[1]{%
    \if@firstrun
        \begingroup\lst@EndWriteFile
    \fi
    \def\usefiletype{#1}
    \listadd{\file@list}{\listingshead\lstinputlisting[style=plain]{\jobname.#1}\addvspace{\baselineskip}}
    \setcounter{c@copyright}{0}
}

\newcommand{\listingshead}{%
    \section{Listings for \jobname.\usefiletype}%
    The file contains the following code:
}

\RequirePackage{etoolbox,xstring,xcolor,listings,verbatim,docmute,makeidx}
\lst@RequireAspects{writefile}
\makeindex

\newcommand{\numbercolor}{\color[HTML]{655643}}% Ash
\newcommand{\macrocolor}{\color[HTML]{78948D}}% Teal
\newcommand{\commentcolor}{\color[HTML]{BF4D28}}% Saffron
\newcommand{\mathcolor}{\textcolor[HTML]{CF872E}}% Ochre
\newcommand{\emphcolor}{\color{cyan}}
\newcommand{\identifiercolor}{\color{black}}
\newcommand{\bracketcolor}{\color{black}}

\lstset{
    language=[AlLaTeX]TeX,
    breaklines=true,
    columns=spaceflexible,
    emptylines=0,
    breakindent=1em,
    tabsize=4,
    basicstyle=\ttfamily,
    texcsstyle=*\macrocolor,
    commentstyle=\commentcolor\itshape,
    identifierstyle=\identifiercolor,
    morestring=[b]$,
    stringstyle=\mathcolor,
}

\lstdefinestyle{blockcode}{
    frame=leftline,
    framerule=.2ex,
    rulecolor=\numbercolor,
    numbers=left,
    numberstyle=\footnotesize\numbercolor,
    firstnumber=auto,
}

\lstdefinestyle{samplecode}{
    numbers=left,
    numberfirstline=true,
    stepnumber=10000,
    firstnumber=1,
    numberstyle=\numbercolor\makebox[1em][c]{\Large$\star$}\@gobble,
}

\lstdefinestyle{macrocode}{
    numbers=left,
    numberfirstline=true,
    stepnumber=10000,
    firstnumber=1,
    numberstyle=\numbercolor\makebox[1em][c]{\P}\@gobble,
    identifierstyle=\identifiercolor\itshape,
    literate=   {\{}{{\bracketcolor\{}{\identifiercolor$\langle$}}{2}
            {\}}{{\identifiercolor$\,\rangle$}{\bracketcolor\}}}{2}
            {[}{{\bracketcolor[}{\identifiercolor$\langle$}}{2}
            {]}{{\identifiercolor$\,\rangle$}{\bracketcolor]}}{2}
            {<}{{\identifiercolor$\langle$}}{1}
            {>}{{\identifiercolor$\,\rangle$}}{1},
}

\lstdefinestyle{inlinecode}{
    literate=   {\{}{{\bracketcolor\{}}{1}
            {\}}{{\bracketcolor\}}}{1}
            {[}{{\bracketcolor[}}{1}
            {]}{{\bracketcolor]}}{1},
}

\lstdefinestyle{plain}{
    style=blockcode,
    texcsstyle=*\color{black},
    emphstyle=\color{black},
    commentstyle={\color{black}\itshape},
    stringstyle=\color{black},  
}

\newcommand{\addcs}[1]{\lstset{moretexcs={#1},}}

% Create commands for globally adding indexation patterns. By default, it is assumed to be a control sequence
\newcommand{\defaultindexdef}{}
\newcommand{\addindexdef}{}

\newcommand{\setdefaultindex}[3]{%
    \def\defaultindexdef
        {\@iscsnametrue
          \expandafter\ifstrempty{#1}
            {\def\macro@type{}}
            {\def\macro@type{#1}}
          \expandafter\ifstrempty{#2}{}{\def\macro@format{#2}}
          \expandafter\ifstrempty{#3}{}{\def\macro@index{#3}}}
}
\newcommand{\addtoindex}{\@ifstar{\s@addtoindex}{\@addtoindex}}
\newcommand{\@addtoindex}[4]{%
    \apptocmd{\addindexdef}
        {\expandafter\ifstrequal\expandafter{\macrotype}{#1}
            {\@iscsnamefalse
              \expandafter\ifstrempty{#2}
                {\def\macro@type{}}
                {\def\macro@type{#2}}
              \expandafter\ifstrempty{#3}{}{\def\macro@format{#3}}
              \expandafter\ifstrempty{#4}{}{\def\macro@index{#4}}}
            {}}
        {}{}
}
\newcommand{\s@addtoindex}[4]{%
    \apptocmd{\addindexdef}
        {\expandafter\ifstrequal\expandafter{\macrotype}{#1}
            {\@iscsnametrue
              \expandafter\ifstrempty{#2}{}{\def\macro@type{#2}}
              \expandafter\ifstrempty{#3}{}{\def\macro@format{#3}}
              \expandafter\ifstrempty{#4}{}{\def\macro@index{#4}}}
            {}}
        {}{}
}

% Set default index
\setdefaultindex
    {\macrotype}
    {\macroname}
    {\entryname @\string\texttt{{\string\textbackslash}\entryname}\space(\entrytype)}

% Create a command for setting the macro type based on the optional argument to code
\newcommand{\set@macro@type}[2]{
    % Create helper macros for text
    \def\macrotype{#1}
    \def\macroname{\if@iscsname\textbackslash\fi#2}
    % Create helper macros for indexes
    \StrSubstitute{#2}{@}{"@}[\entryname]
    \def\entrytype{\macro@type}
    % Default definitions
    \defaultindexdef
    % Specific type definitions
    \ifstrequal{#1}{m}
        {\@iscsnametrue
          \def\macro@type{}
          \def\macro@index{%
            \entryname @\string\texttt{{\string\textbackslash}\entryname}}}{}
    \ifstrequal{#1}{l}
        {\@iscsnametrue
          \def\macro@type{length}}{}
    \ifstrequal{#1}{d}
        {\@iscsnametrue
          \def\macro@type{dimension}}{}
    \ifstrequal{#1}{e}
        {\@iscsnametrue
          \def\macro@type{environment}
          \def\macro@format{\textbackslash#2\par\textbackslash end#2}}{}
    \ifstrequal{#1}{f}
        {\@iscsnametrue
          \def\macro@type{float}
          \def\macro@format{\textbackslash#2\par\textbackslash end#2}}{}
    \ifstrequal{#1}{p}
        {\@iscsnamefalse
          \def\macro@type{package}
          \def\macro@index{%
            \entryname @\string\texttt{\entryname}\entrytype}}{}
    \ifstrequal{#1}{o}
        {\@iscsnamefalse
          \def\macro@type{option}
          \def\macro@index{%
            \entryname @\string\texttt{\entryname}\entrytype}}{}
    \ifstrequal{#1}{c}
        {\@iscsnamefalse
          \def\macro@type{counter}
          \def\macro@index{%
            \entryname @\string\texttt{\string\textit{\entryname}}\space(\entrytype)}}{}
    % Append user created macros
    \addindexdef
}


\newcommand{\docindex}[2][]{%
    \ifstrempty{#1}
        {\@ifundefined{index@#2}
            {\ClassError{literate}{Wrong item to index}{Control sequences must be indexed directly (no optional argument)\MessageBreak For parameters, write their type as an optional argument}}
            {\index{\csname index@#2\endcsname}}}
        {\@ifundefined{index@#1@#2}
            {\ClassError{literate}{Wrong item to index}{Control sequences must be indexed directly (no optional argument)\MessageBreak For parameters, write their type as an optional argument}}
            {\index{\csname index@#1@#2\endcsname}}}}

\newcommand{\processtexcs}[2][]{%
    \set@macro@type{#1}{#2}
    \if@iscsname
        \def\@index@name{index@#2}
    \else
        \def\@index@name{index@#1@#2}
    \fi
    \def\@index@content{\macro@index}
    \expandafter\xdef\csname\@index@name\endcsname
        {\macro@index}
    \texttt{\macro@format}\index{\@nameuse{\@index@name}}\par%
}

\lstnewenvironment{code}[2][m]
    {\lstset{name=code@\usefiletype,style=blockcode,moretexcs={#2}}%
      \AfterEndEnvironment{code}{\lstset{moretexcs={#2},}}%
          \@ifundefined{usefiletype}{\ClassError{literate}{Missing filetype definition}{You must specify which file to write to with the command \string\filetype}}{}%
      \addvspace{\baselineskip}%
      \marginpar{%
        \hskip0pt\vskip.3\baselineskip%
        \forcsvlist{\processtexcs[#1]}{#2}}%
      \csname\@lst @SetFirstNumber\endcsname%
      \if@firstrun%
        \lst@BeginAlsoWriteFile{\jobname.\usefiletype}%
        \ifnum\value{c@copyright}=0\lstinputlisting{\jobname.copyright}\fi%
      \fi}
    {\if@firstrun\endgroup\fi%
      \csname\@lst @SaveFirstNumber\endcsname%
      \stepcounter{c@copyright}%
      \addvspace{\baselineskip}}

\lstMakeShortInline[style=inlinecode]{|}

\lstnewenvironment{macro}
    {\lstset{name=macro@\usefiletype,style=macrocode,}%
      \addvspace{\baselineskip}%
      \csname\@lst @SetFirstNumber\endcsname}
    {\csname\@lst @SaveFirstNumber\endcsname
      \addvspace{\baselineskip}}

\if@firstrun
    \lstnewenvironment{copywrite}
        {\if@firstrun\lst@BeginWriteFile{\jobname.copyright}\fi}
        {\if@firstrun\lst@EndWriteFile\fi}
    \newenvironment{example}
        {\expandafter\comment}
        {\expandafter\endcomment}
\else
    \newenvironment{copywrite}
        {\expandafter\comment}
        {\expandafter\endcomment}
    \lstnewenvironment{example}
        {\lstset{name=example@\usefiletype,style=samplecode,}%
          \addvspace{\baselineskip}%
          \lst@BeginAlsoWriteFile{\jobname.tmp}}
        {\lst@EndWriteFile%
          \noindent\llap{\numbercolor\makebox[1em][c]{$\rightarrow$}\hspace{\lst@numbersep}}%
          \input{\jobname.tmp}%
          \addvspace{\baselineskip}}
\fi

\newcommand{\printlistings}{%
    \if@firstrun\else
        \forlistloop{}{\file@list}
    \fi
}

\AtEndDocument{\if@firstrun\begingroup\lst@EndWriteFile\fi}

以下是.tex自文档类 + 包的示例:

\documentclass{literate}

\addcs{ProvidesClass, definecolor, textcolor}

\addtoindex{col}{colour}{\macroname}{\entrytype!\entryname @\string\texttt{\entryname}}

\begin{document}

\begin{copywrite}
%
% This is a sample copyright notice that will be printed verbatim at the beginning of all files.
%
\end{copywrite}

\section{The first section}

\filetype{cls}

The class (|.cls|) begins thus:

\begin{code}{}
\NeedsTeXFormat{LaTeX2e}
\ProvidesClass{MWE}
    [2011/03/20 v1 Some sample class]
\LoadClass{tufte-book}
\end{code}

Then we create a new macro:

\begin{code}{mymacro,my@macro}
% Some comment
\newcommand{\mymacro}[2][]{#1{#2}}
\newcommand{\my@macro}[1]{$#1$}
\end{code}

It can be used this way:

\begin{macro}
\mymacro[format]{text}
\end{macro}

And outputs this:

\begin{example}
\mymacro[\emph]{some text}
\end{example}

Then we create a counter:

\begin{code}[c]{something}
% Some comment
\newcounter{something}
\end{code}

\section{The second section}

\filetype{sty}

We also create an |.sty| file:

\begin{code}{}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{MWE}
    [2011/03/20 v1 Some sample package]
\end{code}

\pagebreak A new environment:

\begin{code}[e]{myenv}
\newenvironment{myenv}{}{}
\end{code}

A new color:

\begin{code}[col]{ochre}
\usepackage{xcolor}
\definecolor{ochre}{HTML}{CF872E}
\end{code}

\begin{example}
\textcolor{ochre}{Some ochre text!}
\end{example}

But perhaps should we return to talking about |\mymacro|\docindex{mymacro}, and later we'll switch back to colours\docindex[col]{ochre}.

\printlistings

\printindex

\end{document}

需要运行 2 次 LaTeX 来打印文档 + 运行一次 MakeIndex 并最终运行一次 LaTeX。

编辑(4 月 3 日):添加了一个AfterEndEnvironment钩子,以便在第一个环境之后全局设置新的宏名称code(修复语法高亮中的一个错误)。

编辑(4月10日):添加了一些新内容:

  • listings一个用于将一些控制序列添加到“着色但不对它们进行索引”的宏: \addcs{},它以 CSV 列表作为其参数。

  • 写入版权文件并在每个新文件开头打印它的环境:\begin{copywrite}

  • 一个输入所有已创建文件列表的命令,带有一个可通过其自定义的“标题” \renewcommand{\listingshead} ——默认情况下,我已将其设置为打印一个章节标题和一个短句。(我想可以使其更具可定制性……)

  • 半自动索引(见下文)。

  • 一些错误消息,因为很容易产生错误的索引命令,而 LaTeX 错误消息确实没有帮助。

code几个宏可根据其类型自动索引每个环境的标记内容。它还适用@于宏名称中的字符。默认情况下,假定字符串是宏,但您可以使用可选参数(例如,\begin{code}[e]{myenv}对于环境)更改它。

我已经定义了宏(m,默认)、环境(e)、浮点数(f)、长度(l)、尺寸(d)、计数器(c)、包(p)和选项(o)。可以隐式定义新类型:

\begin{code}[great-macro]{mygreatmacro}

在这种情况下,它们将被视为宏。它们也可以在序言中明确定义:

\addtoindex{<short name>}{<name to index>}{<format>}{<indexing code>}
% The starred version is for control sequences, the normal version for everything else
% Helper macros are `\macroname`, `\entryname` and `\entrytype`

然后,您需要在主文件中编写\docindex{mymacro}控制序列或\docindex[c]{mycounter}其他所有内容。这是必要的,因为尽管您只能有一个具有给定名称的控制序列,但您可以有多个具有相同名称的其他类型的字符串(例如\chapter命令对比计数器chapter,也许还有chapter颜色)。


我将尝试建立一个包含完整文件(其中包括类别选择选项)的存储库,并尝试处理一些文档,因为现在事情变得有点复杂了。

我确实非常高兴收到有关错误和可能的功能或改进的任何反馈 - 希望它可以在经过测试和清理后成为一个软件包。

答案2

一旦一项技术根深蒂固,就很难被取代,并且doc/docstrip多年来一直运行良好,为社会服务。

上面列出的方法有一定的局限性:

使用定义的类/包。如果用于定义类,则类本身不能用于排版代码和手册。这可能在两次运行中实现。另一种可能性是先排版代码及其注释,然后排版“用户手册”。请注意,所用方法中没有“驱动程序”。因此,命令按输入和保存在文件中的顺序可用。

多个文件。其优点之一doc/docstrip是代码可以保存在多个文件中。上面的示例可以扩展以满足这一点。

          \lstnewenvironment{Macro}[1][Test]
          ...
          \lst@BeginAlsoWriteFile{textsamples.sty}}

filename在这种情况下,无需对 进行硬编码textsamples.sty,而是可以将其传递给Macro。还可以扩展代码以接受文件列表,或者可能有类似于 的特殊谓词doc/docstrip

自动索引。这将很难编程,因为宏的内容没有完全解析。但是,可以轻松添加索引命令,以便在添加宏时对其进行索引。这也将消除过度索引和必须从索引中排除命令的问题。相反,它让用户根据需要添加命令。

我采用了对上述方法稍加修改的方法来记录 JavaScript 代码,到目前为止,事实证明它非常有用。

答案3

还有其他文学编程工具可用于准备 TeX 宏。我有使用noweb(Norman Ramsey 的网站)的经验,但听说过另一种选择muwebnoweb和都muweb提供了与语言无关的cweb和的实现ctangle

由于noweb语言无关它提供了一种扩展机制,以便可以使用外部工具来生成macros某些代码片段中使用的列表。(我认为有一些可用的,但我不确定我是否使用过它)。

如果您使用它noweb来生成 LaTeX 宏,它支持:

  • 使用定义的类来准备文档。
  • 多个文件(查找在文档中)。
  • 自动索引(使用适当的筛选)。
  • 生成 HTML 文档,而不是 LaTeX,这对于在线文档非常有用。
  • 使用代码片段,这样\input如果您想提供易于使用的文件(例如,如果您将文件发送到期刊或 arXiv),您就可以避免使用语句。

如果你有兴趣看看输出是什么样子的,我写了一个完整的 TeX 格式使用此软件,它被称为 Bhrìd TeX,是免费软件。示例输出位于下载部分,该文件名为bhridman.pdf(法语)。

我还有一些有用的Makefile宏来简化 NOWEB 的使用,它们被称为 BSD Make Pallàs 脚本并且您正在寻找的文件是noweb/noweb.latex.mk

相关内容