% \iffalse meta-comment % % comment2tex.dtx % Copyright (C) 2026 Erik Nijenhuis. % % This work may be distributed and/or modified under the conditions of % the LaTeX Project Public License, either version 1.3c of this license % or (at your option) any later version. % %<*driver> \documentclass{ltxdoc} \usepackage{listings}% optional companion; loaded here for the listings demo \usepackage{comment2tex} \usepackage{hyperref}% \nameref for the section cross-references, plus PDF navigation \GetFileInfo{comment2tex.sty} \begin{filecontents*}[overwrite]{c2t-demo.sh} ## A short annotated Bash source. Double-hash lines become prose, ## everything else is code. echo "building" for f in *.tex; do lualatex --interaction=nonstopmode "$f" done \end{filecontents*} \EnableCrossrefs \CodelineIndex \AtBeginDocument{\hypersetup{hidelinks}}% ltxdoc colours links at begin-document \begin{document} \DocInput{comment2tex.dtx} \end{document} % % \fi % % \title{The \textsf{comment2tex} package} % \author{Erik Nijenhuis} % \date{\filedate \quad \fileversion} % \maketitle % % \begin{abstract} % \textsf{comment2tex} typesets a source file that carries its documentation in % special comments: the comments become ordinary \LaTeX, the rest becomes a % listing. It ships a Lua converter and two \TeX\ wrappers --- one for % \LaTeX/Lua\LaTeX\ and one for plain \TeX\ --- both providing \cs{includebash}, % \cs{includelua}, \cs{includeyaml} and \cs{includemake}. % \end{abstract} % % \tableofcontents % % \section{Introduction} % % A source file may document itself: lines beginning with a chosen % \emph{doc-comment} prefix are prose, everything else is code. For Bash, YAML % and Make the prefix is a double hash (\texttt{\#\#}, itself a comment in all % three); for Lua it is a triple dash (\texttt{-{}-{}-}). The converter % \texttt{comment2tex.lua} turns such a file into \LaTeX: doc lines are emitted % verbatim (minus the prefix) and runs of code are wrapped in a listing. % % This package is the \TeX\ front end. \cs{includebash}, \cs{includelua}, % \cs{includeyaml} and \cs{includemake} take a source file, run it through the % converter, and input the result, so a document can pull in annotated sources % without a manual build step. % % \subsection{Is it literate programming?} % % Loosely. \textsf{comment2tex} borrows literate programming's vocabulary --- it % \emph{weaves} an annotated source into typeset documentation and can \emph{tangle} % it back to runnable code --- but it is a deliberately lightweight take, not an % implementation of Knuth's \textsc{web}. % % Two things set it apart. First, order: classic literate programming writes the % source for the reader --- code is split into named chunks presented in whatever % sequence best explains it, and \emph{tangle} reassembles them into the order the % machine needs. \textsf{comment2tex} does no reordering; the source \emph{is} the % program, in its natural order. Second, \emph{tangle} is off the critical path: in % \textsc{web} you must tangle a \texttt{.web} before you can compile or run it, % whereas here the doc-comments are ordinary language comments, so the annotated file % runs unmodified and \emph{tangling} --- just dropping the doc lines --- is an % optional convenience, never a build step. The prose follows the code, not the % other way round. % % That fits its origin. \textsf{comment2tex} was written to document Xerdi's % production-critical sources: Bash scripts, and the Ansible playbooks and % inventories woven into a complete infrastructure handbook. Such sources are mostly % linear and functional, so \textsc{web}'s reordering would buy little --- and since % they are deployed and run as-is, needing no tangle step between the documented file % and the running one was a hard requirement, not a nicety. Each stays a short, % standalone file, documented in place and pulled into the book by the include macros % rather than dissolved into one monolithic source, so the file on disk stays the % single source of truth. % % What is left is the low-friction core --- one source that reads as documentation % and runs as code, with no separate web language and no chance of the two drifting % apart. In spirit it sits beside a self-documenting \texttt{.dtx} (this manual is % one) rather than \textsc{web} or \textsc{cweb}. % % \section{Usage}\label{sec:usage} % % \DescribeMacro{\includebash} % \DescribeMacro{\includelua} % \DescribeMacro{\includeyaml} % \DescribeMacro{\includemake} % \cs{includebash}\marg{file} typesets a Bash source (doc prefix \texttt{\#\#}); % \cs{includelua}\marg{file} a Lua source (doc prefix \texttt{-{}-{}-}); % \cs{includeyaml}\marg{file} a YAML source and \cs{includemake}\marg{file} a % Makefile (both doc prefix \texttt{\#\#}). All four derive an output name by % replacing the file's extension with \texttt{.c2t.tex}, generate that file with % the appropriate \emph{style} and \emph{wrapper}, and \cs{input} it. The % \meta{file} carries at most one extension; an extensionless name (such as a % \texttt{Makefile}) is fine and keeps its whole self as the base. % % Because \texttt{\#\#} is an ordinary YAML comment, the double-hash doc lines % stay valid YAML: the annotated file remains loadable as-is, so \cs{includeyaml} % typesets the very file that ships --- no stripped copy, no risk of drift. % % \DescribeMacro{\ctxuselistings} % \DescribeMacro{\ctxuseverbatim} % \paragraph{Verbatim or listings.} Under \LaTeX\ the include macros render in % one of two modes, switchable anywhere in the document. The default is a % built-in \emph{numbered verbatim} --- no dependency, no setup, the same plain % look as this manual's own listings. \cs{ctxuselistings} switches subsequent % includes to \textsf{listings} output (syntax highlighting, line breaking, the % full \cs{lstset} vocabulary); \cs{ctxuseverbatim} switches back. Both obey % \TeX\ grouping, so a single block can opt in locally. Requesting % \textsf{listings} without having loaded it warns once and stays on verbatim. % Like \textsf{ltxdoc}'s own \texttt{macrocode}, the verbatim renderer does not % break long lines; switch to \textsf{listings} mode (\emph{\nameref{sec:listings}}, % with \texttt{breaklines}) where wrapping matters. % % In \textsf{listings} mode the package applies a plain default \cs{lstset} for % you (\cs{AtBeginDocument}, only if \textsf{listings} is loaded). The Bash, Lua % and Make styles name languages \textsf{listings} ships itself (\texttt{make} is % loaded on demand), so they need no help. Only \texttt{yaml} is missing from % \textsf{listings}; \cs{includeyaml} handles that gracefully by installing an % empty \texttt{yaml} language if the document has not defined its own (so % \texttt{language=yaml} resolves to the default style rather than faulting). It % does \emph{not} do the same for Make: since \texttt{make} exists but loads % lazily, pre-installing an empty one would shadow the real highlighter. The plain % \TeX\ wrapper always uses a verbatim listing, since \textsf{listings} is % \LaTeX-only. % % \subsection{Examples in this manual}\label{sec:examples} % % This manual is built with the very macros it documents, so each one appears as a % full worked example on a real file: % \begin{itemize} % \item \cs{includemake} on a \texttt{Makefile} --- see \emph{\nameref{sec:build}}. % \item \cs{includelua} on the converter itself --- see % \emph{\nameref{sec:converter}}. % \item \cs{includebash} on a short script, both verbatim and through % \textsf{listings} --- see \emph{\nameref{sec:listings}}. % \item \cs{includebash} on the cross-engine test suite --- see % \emph{\nameref{sec:testing}}. % \item \cs{includeyaml} on the GitHub Actions workflows --- see % \emph{\nameref{sec:cicd}}. % \end{itemize} % Together they cover all four styles and both render modes. Even the code in % \emph{\nameref{sec:impl}} is typeset automatically --- there by \textsf{ltxdoc}'s % \texttt{macrocode} rather than \textsf{comment2tex} --- so no listing in this % manual is typed by hand. % % \section{Engines and use cases}\label{sec:engines} % % How the conversion happens depends on the engine. % % \begin{description} % \item[Lua\LaTeX\ (recommended).] Lua\TeX\ embeds Lua, so the wrapper loads % \texttt{comment2tex.lua} as a library and converts \emph{in process} through % \cs{directlua}. No shell escape, no separate run: just % \texttt{lualatex document}. % \item[pdf\LaTeX\ with shell escape.] pdf\TeX\ cannot run Lua, so with % \texttt{-{}-shell-escape} the wrapper calls \texttt{texlua comment2tex.lua} % through \cs{write18} to generate the fragment, then inputs it. Run % \texttt{pdflatex -shell-escape document}. % \item[pdf\LaTeX, separate run (no shell escape).] Generate the fragments ahead % of time and let a normal run merely \cs{input} them. For each source run % \texttt{texlua comment2tex.lua -{}-style \meta{style} -o \meta{base}.c2t.tex % \meta{file}} (or \texttt{make}), then \texttt{pdflatex document}. If a % required fragment is missing the package raises an error telling you which % command to run. % \item[plain \TeX.] \texttt{comment2tex.tex} provides the same two macros for % plain \TeX. Under \texttt{luatex} it converts in process; under % \texttt{pdftex}/\texttt{tex} it uses the same shell-escape or separate-run % fallback. % \end{description} % % \begin{center} % \begin{tabular}{lll} % \hline % Engine & In process & Otherwise \\ % \hline % Lua\LaTeX & yes (\cs{directlua}) & --- \\ % pdf\LaTeX & no & \texttt{-shell-escape} or separate run \\ % plain \texttt{luatex} & yes & --- \\ % plain \texttt{pdftex}/\texttt{tex} & no & \texttt{-shell-escape} or separate run \\ % \hline % \end{tabular} % \end{center} % % In every case the generated file is \meta{base}\texttt{.c2t.tex}, so the % in-process, shell-escape and separate-run routes are interchangeable. % % \section{The build}\label{sec:build} % % The package's own build is a \texttt{Makefile}, and --- like the converter and % the test suite --- it is an annotated source, typeset here by \cs{includemake} % (the Make style, doc prefix \texttt{\#\#}). It drives the converter under % \texttt{texlua}, builds this manual, and assembles the CTAN upload; its % \texttt{\#\#} lines are the prose you read, the rest is the Make code that runs. % % Like the test suite, the \texttt{Makefile} is a development artefact and is not % part of the distribution, so the listing appears only when it is present. % \IfFileExists{Makefile}{% % \includemake{Makefile}% % }{\par\emph{(the \texttt{Makefile} is not included in this distribution; see the % package's development repository.)}\par} % % \section{The converter, by example}\label{sec:converter} % % Because the converter is itself a literate Lua file, the cleanest % demonstration is to let it document itself. The remainder of this section is % produced by \cs{includelua}\marg{comment2tex.lua} --- the generated % documentation demonstrates the converter's functionality under Lua\LaTeX. It % renders through the default verbatim path (numbered, dependency-free), the same % look as the code listings elsewhere in this manual. % % \includelua{comment2tex.lua} % % \section{The listings variant}\label{sec:listings} % % Everything above --- \emph{\nameref{sec:build}} and \emph{\nameref{sec:converter}} % --- used the default verbatim renderer. When a document loads % \textsf{listings}, \cs{ctxuselistings} switches the blocks that follow to it % (and \cs{ctxuseverbatim} switches back). Below is one annotated Bash source % both ways: first the verbatim default, then the same file through % \textsf{listings}, which adds syntax highlighting. Each renderer numbers from % one; only the presentation differs. % % \medskip\noindent\emph{Verbatim (default):} % \includebash{c2t-demo.sh} % % \medskip\noindent\emph{Listings (\cs{ctxuselistings}):} % \ctxuselistings % \includebash{c2t-demo.sh} % \ctxuseverbatim % % \subsection{Styling} % % The two modes differ in capability, not just appearance. The verbatim default is % self-contained: numbered (a \cs{reset@font}\cs{scriptsize} gutter auto-sized to % the file's line count), code in \cs{small}\cs{ttfamily}, tabs expanded --- but no % syntax highlighting and, like \textsf{ltxdoc}'s \texttt{macrocode}, no long-line % breaking. \textsf{listings} mode gives that self-containment up for the full % \textsf{listings} feature set: per-language highlighting, line breaking, and the % whole \cs{lstset} vocabulary. % % The converter bakes only the essentials into each \texttt{lstlisting} block --- % \texttt{language=}\meta{lang}, \texttt{numbers=left}, and (on every block after % the first) \texttt{firstnumber=last}, so a file's line numbers run continuously. % Everything else is left to a document-wide \cs{lstset}. So that listings mode % looks sensible with no configuration, \textsf{comment2tex} applies exactly one % default, \cs{AtBeginDocument} and only when \textsf{listings} is actually loaded: % \begin{itemize} % \item \texttt{basicstyle} is \cs{ttfamily}\cs{small}, the monospace body font; % \item \texttt{columns=fullflexible}, natural glyph widths rather than a rigid grid; % \item \texttt{breaklines=true}, wrapping long lines (the verbatim renderer cannot); % \item \texttt{showstringspaces=false}, no visible-space markers inside strings. % \end{itemize} % It sets nothing else --- no colours, no keyword or comment styles --- so the % default is deliberately plain, close to the verbatim look. % % To change it, add your own \cs{lstset} \emph{after} \cs{begin}\marg{document} (or % in a begin-document hook registered after \textsf{comment2tex}); the four keys % above are applied at begin-document, so a preamble \cs{lstset} of the same keys is % overridden. Keys \textsf{comment2tex} never sets --- keyword, comment and number % styles, colours, and the like --- may go anywhere and survive. \textsf{listings} % has no \texttt{yaml} language; to highlight YAML, define one before the first % \cs{includeyaml} (\emph{\nameref{sec:usage}}); otherwise an empty one is installed % and YAML falls back to the plain default. % % \section{Implementation}\label{sec:impl} % % \subsection{The \LaTeX\ wrapper} % % \changes{v1.0}{2026/06/08}{Initial version} % \changes{v1.1}{2026/07/22}{Add YAML style and \cs{includeyaml}} % \changes{v1.1}{2026/07/22}{Add a numbered verbatim fallback under \LaTeX, made % the default; \cs{ctxuselistings} and \cs{ctxuseverbatim} switch modes} % \changes{v1.1}{2026/07/22}{Auto-size the verbatim line-number gutter to the % file's line count} % \changes{v1.1}{2026/07/22}{Add Make style and \cs{includemake}, and expand tabs % when weaving} % \changes{v1.1}{2026/07/22}{Add the \texttt{-{}-tangle} command-line mode} % \changes{v1.1}{2026/07/22}{Accept extensionless and directory-qualified file % names} % \changes{v1.1}{2026/07/22}{Fix a spurious \% on the first line of a plain-wrapper % listing} % % Announce the package. \textsf{listings} is an \emph{optional} companion, not % a hard dependency: if the document has loaded it, the include macros emit % \texttt{lstlisting} blocks and a neutral default \cs{lstset} is applied; % otherwise they fall back to a built-in numbered verbatim (below). The % \cs{lstset} is deferred to \cs{AtBeginDocument} so it runs regardless of the % order in which the document loads \textsf{listings}, and only when it does. % \iffalse %<*package> % \fi % \setcounter{CodelineNo}{0} % \begin{macrocode} \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{comment2tex} [2026/07/22 v1.1 Include annotated source files as LaTeX listings] \AtBeginDocument{% \@ifpackageloaded{listings}{% \lstset{basicstyle=\ttfamily\small,columns=fullflexible,% breaklines=true,showstringspaces=false}% }{}} % \end{macrocode} % % The name of the converter, overridable before loading if it is not on the % current directory or \TeX\ tree. % \begin{macrocode} \providecommand\ctxscript{comment2tex.lua} % \end{macrocode} % % Under LuaTeX, load the converter once into the global \texttt{comment2tex}, % locating it through \texttt{kpse}. % \begin{macrocode} \ifdefined\directlua \directlua{ if not comment2tex then local f = kpse.find_file("comment2tex.lua","lua") or kpse.find_file("comment2tex.lua") or "comment2tex.lua" comment2tex = assert(loadfile(f))("comment2tex") end } \fi % \end{macrocode} % % \begin{macro}{\ctxlisting} % \begin{macro}{\ctx@lineno} % \begin{macro}{\ctx@num} % The verbatim fallback, used when \textsf{listings} is absent. It reuses the % converter's \texttt{plain} wrapper (which brackets each code block with % \cs{ctxlisting}\texttt{\%} and \cs{endctxlisting}) and mirrors the plain \TeX\ % wrapper's line-by-line reader --- the one hook \textsf{listings} and % \textsf{fancyvrb} rely on and the kernel \texttt{verbatim} environment lacks % --- so every line can be numbered. \cs{ctx@lineno} is a count reset once per % included file (by \cs{ctx@include}) and advanced \emph{globally} per line, so % it survives the group boundary between consecutive blocks: that reproduces % \textsf{listings}' \texttt{firstnumber=last}. \cs{ctx@num} styles the number % like \textsf{ltxdoc}'s \texttt{macrocode} --- \cs{reset@font}\cs{scriptsize}, % right-aligned with \cs{llap} into a \cs{leftskip} gutter. \cs{ctxlistingtotal} % sizes that gutter: the converter emits it once before the first block with a run % of zeros matching the file's code-line count, so the width fits the largest % number exactly (a three-digit default, set in \cs{ctx@include}, covers % hand-made fragments). \cs{ctx@uncatcode} switches to % the verbatim regime (\cs{dospecials} makes the specials \emph{and} the space % category~12, preserving indentation); \cs{ctx@gobble} discards the remainder of % the \cs{ctxlisting} line (its trailing \texttt{\%}) before \cs{ctx@grab} % collects real lines, exactly as the plain wrapper does. % \begin{macrocode} \newcount\ctx@lineno \newdimen\ctx@numindent \providecommand\ctx@numstyle{\reset@font\scriptsize} \def\ctxlistingtotal#1{\settowidth\ctx@numindent{\ctx@numstyle #1\ }} \def\ctx@num{% \global\advance\ctx@lineno\@ne \llap{\ctx@numstyle\the\ctx@lineno\ }} \def\ctx@uncatcode{\let\do\@makeother\dospecials} \begingroup \catcode`\^^M=\active% \gdef\ctxlisting{% \par\begingroup \ctx@uncatcode \parindent=\z@ \leftskip=\ctx@numindent \small\ttfamily \catcode`\^^M=\active% \ctx@gobble}% \gdef\ctx@gobble#1^^M{\ctx@grab}% \gdef\ctx@grab#1^^M{\ctx@checkend{#1}}% \endgroup \def\ctx@checkend#1{% \def\ctx@tmp{#1}% \ifx\ctx@tmp\ctx@endmark \let\ctx@next\ctx@finish \else \leavevmode\ctx@num#1\par \let\ctx@next\ctx@grab \fi \ctx@next} \def\ctx@finish{\par\endgroup} \begingroup \catcode`\|=0 \catcode`\\=12 |gdef|ctx@endmark{\endctxlisting}% |endgroup % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\ctxuselistings} % \begin{macro}{\ctxuseverbatim} % The rendering mode is switchable anywhere in the document and defaults to the % numbered-verbatim fallback, so a document needs neither \textsf{listings} nor % any configuration to typeset a source. \cs{ctxuselistings} routes subsequent % includes through \textsf{listings}; \cs{ctxuseverbatim} routes them back to the % verbatim fallback. Both are ordinary declarations --- honour \TeX\ grouping, % so a group or environment can switch mode locally. Requesting \textsf{listings} % while the package is not loaded warns once and stays on verbatim. % \begin{macrocode} \newif\ifctx@lst \newif\ifctx@warned \newcommand\ctxuselistings{\ctx@lsttrue} \newcommand\ctxuseverbatim{\ctx@lstfalse} % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\ctx@include} % Strip the extension, build the output name, pick the wrapper from the current % mode --- \texttt{lstlisting} in \textsf{listings} mode (falling back to the % numbered-verbatim \texttt{plain} wrapper, with a one-time warning, when % \textsf{listings} is not loaded), otherwise \texttt{plain} --- generate it the % best way the engine allows, then input it. The verbatim path resets the line % counter once per file. \cs{ctx@stripdir} drops any directory part and % \cs{ctx@strip} then keeps everything before the first dot, so a path such as % \texttt{.github/workflows/build.yml} yields the base \texttt{build}; the sentinel % dot still lets an extensionless name (a \texttt{Makefile}) keep its whole self. % \begin{macrocode} \def\ctx@strip#1.#2\ctx@stop{#1} \def\ctx@stripdir#1{\ctx@sd#1/\ctx@sdstop} \def\ctx@sd#1/#2\ctx@sdstop{\ifx\relax#2\relax#1\else\ctx@sd#2\ctx@sdstop\fi} \newcommand\ctx@include[2]{% \edef\ctx@in{#2}% \edef\ctx@base{\ctx@stripdir{#2}}% \edef\ctx@base{\expandafter\ctx@strip\ctx@base.\ctx@stop}% \edef\ctx@out{\ctx@base.c2t.tex}% \ifctx@lst \@ifpackageloaded{listings}% {\def\ctx@wrap{lstlisting}}% {\ifctx@warned\else \ctx@warnedtrue \PackageWarning{comment2tex}{\string\ctxuselistings\space requested but listings is not loaded; using the verbatim fallback}% \fi \def\ctx@wrap{plain}\global\ctx@lineno\z@\ctxlistingtotal{000}}% \else \def\ctx@wrap{plain}\global\ctx@lineno\z@\ctxlistingtotal{000}% \fi \ifdefined\directlua \directlua{comment2tex.write("#1","\ctx@wrap", "\luaescapestring{\ctx@in}","\luaescapestring{\ctx@out}")}% \else \ifdefined\pdfshellescape \ifnum\pdfshellescape>0\relax \immediate\write18{texlua \ctxscript\space --style #1 --wrapper \ctx@wrap\space -o \ctx@out\space \ctx@in}% \fi \fi \fi \IfFileExists{\ctx@out}{\input{\ctx@out}}{% \PackageError{comment2tex}{Converted file \ctx@out\space not found}% {Use LuaLaTeX, run pdflatex with -shell-escape, or pre-build it:^^J% texlua \ctxscript\space --style #1 --wrapper \ctx@wrap\space -o \ctx@out\space \ctx@in}}% } % \end{macrocode} % \end{macro} % % \begin{macro}{\includebash} % \begin{macro}{\includelua} % \begin{macro}{\includeyaml} % \begin{macro}{\includemake} % The four public entry points fix the style. \textsf{listings} provides the % \texttt{bash}, \texttt{Lua} and \texttt{make} languages itself, but ships no % \texttt{yaml} one, so in \textsf{listings} mode \cs{includeyaml} first ensures a % \texttt{yaml} language exists: if the document has loaded \textsf{listings} but % not defined one, \cs{ctx@ensureyaml} installs an \emph{empty} language so the % emitted \texttt{language=yaml} resolves to the default listing style (no % YAML-specific highlighting) instead of faulting. The test peeks at % \textsf{listings}' own language store (\cs{lstlang@yaml\$}); a document that % defines its own \texttt{yaml} language is left untouched. In verbatim mode (the % default) there is no language, so the guard is a no-op and never touches the % \textsf{listings} internals. % \begin{macrocode} \newcommand\ctx@ensureyaml{% \ifctx@lst \@ifpackageloaded{listings}{% \@ifundefined{\@lst lang@yaml$}{\lstdefinelanguage{yaml}{}}{}% }{}% \fi} \newcommand\includebash[1]{\ctx@include{bash}{#1}} \newcommand\includelua[1]{\ctx@include{lua}{#1}} \newcommand\includeyaml[1]{\ctx@ensureyaml\ctx@include{yaml}{#1}} \newcommand\includemake[1]{\ctx@include{make}{#1}} % \end{macrocode} % \iffalse % % \fi % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \subsection{The plain \TeX\ wrapper} % % Plain \TeX\ has neither \textsf{listings} nor the \LaTeX\ file helpers, so the % wrapper carries its own verbatim listing and uses primitive file tests. % Control-sequence names are letters only (no digits, no \texttt{@}). % % Locate and load the converter under Lua\TeX, exactly as the package does. % \iffalse %<*plain> % \fi % \setcounter{CodelineNo}{0} % \begin{macrocode} \def\ctxscript{comment2tex.lua} \expandafter\ifx\csname directlua\endcsname\relax\else \directlua{ if not comment2tex then local f = kpse.find_file("comment2tex.lua","lua") or kpse.find_file("comment2tex.lua") or "comment2tex.lua" comment2tex = assert(loadfile(f))("comment2tex") end } \fi % \end{macrocode} % % \begin{macro}{\ctxendmark} % The end-of-listing sentinel, stored as a string of category-12 characters so % it can be compared against a verbatim line. A temporary escape character % (\verb+|+) lets us define it while the backslash is category~12 (``other''). % \begin{macrocode} \begingroup \catcode`\|=0 \catcode`\\=12 |gdef|ctxendmark{\endctxlisting}% |endgroup % \end{macrocode} % \end{macro} % % \begin{macro}{\ctxuncatcode} % Make the usual specials (and the space) category 12, the verbatim regime. % \begin{macrocode} \def\ctxuncatcode{\def\do##1{\catcode`##1=12 }\dospecials} % \end{macrocode} % \end{macro} % % \begin{macro}{\ctxlistingtotal} % The plain \TeX\ listing carries no line numbers, so the converter's % gutter-sizing hint (emitted once before the first block) is a no-op here. % \begin{macrocode} \def\ctxlistingtotal#1{} % \end{macrocode} % \end{macro} % % \begin{macro}{\ctxlisting} % \begin{macro}{\ctxgobble} % \begin{macro}{\ctxgrab} % \cs{ctxlisting} (emitted by the converter as \verb+\ctxlisting%+) opens a % group, switches to the verbatim regime with an active end-of-line, and starts % grabbing lines. The trailing \verb+%+ on the begin line \emph{cannot} be % relied on to swallow that line end: \cs{ctxuncatcode} has already made \verb+%+ % category~12 by the time the scanner reaches it, so it would be grabbed as a % spurious first line. \cs{ctxgobble} therefore discards everything up to and % including the first active \verb+^^M+ (the remainder of the begin line) before % \cs{ctxgrab} collects the first real line. \cs{ctxgrab} collects one line up % to the active \verb+^^M+ and hands it on. % \begin{macrocode} \begingroup \catcode`\^^M=\active% \gdef\ctxlisting{% \par\begingroup% \ctxuncatcode% \parindent=0pt \tt% \catcode`\^^M=\active% \ctxgobble}% \gdef\ctxgobble#1^^M{\ctxgrab}% \gdef\ctxgrab#1^^M{\ctxcheckend{#1}}% \endgroup % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\ctxcheckend} % Compare the grabbed line with the sentinel: if equal, finish; otherwise % typeset it and loop. \cs{ctxnext} defers the recursion until after \cs{fi}. % \begin{macrocode} \def\ctxcheckend#1{% \def\ctxtmp{#1}% \ifx\ctxtmp\ctxendmark \let\ctxnext\ctxfinish \else \leavevmode#1\par \let\ctxnext\ctxgrab \fi \ctxnext} \def\ctxfinish{\par\endgroup} % \end{macrocode} % \end{macro} % % \begin{macro}{\ctxinclude} % Mirror of the \LaTeX\ \cs{ctx@include}: derive the output name, generate it in % process (Lua\TeX) or via shell escape (pdf\TeX), then input it, erroring if it % is still missing. % \begin{macrocode} \def\ctxstrip#1.#2\ctxstop{#1} \def\ctxstripdir#1{\ctxsd#1/\ctxsdstop} \def\ctxsd#1/#2\ctxsdstop{\ifx\relax#2\relax#1\else\ctxsd#2\ctxsdstop\fi} \def\ctxinclude#1#2{% \edef\ctxin{#2}% \edef\ctxbase{\ctxstripdir{#2}}% \edef\ctxbase{\expandafter\ctxstrip\ctxbase.\ctxstop}% \edef\ctxout{\ctxbase.c2t.tex}% \expandafter\ifx\csname directlua\endcsname\relax \expandafter\ifx\csname pdfshellescape\endcsname\relax\else \ifnum\pdfshellescape>0 \immediate\write18{texlua \ctxscript\space --style #1 --wrapper plain -o \ctxout\space \ctxin}% \fi \fi \else \directlua{comment2tex.write("#1","plain", "\luaescapestring{\ctxin}","\luaescapestring{\ctxout}")}% \fi \openin0=\ctxout\relax \ifeof0 \closein0 \errmessage{comment2tex: \ctxout\space not found (use luatex, -shell-escape, or pre-run texlua)}% \else \closein0 \input \ctxout\relax \fi} % \end{macrocode} % \end{macro} % % \begin{macro}{\includebash} % \begin{macro}{\includelua} % \begin{macro}{\includeyaml} % \begin{macro}{\includemake} % The public entry points. % \begin{macrocode} \def\includebash{\ctxinclude{bash}} \def\includelua{\ctxinclude{lua}} \def\includeyaml{\ctxinclude{yaml}} \def\includemake{\ctxinclude{make}} % \end{macrocode} % \iffalse % % \fi % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \section{Testing}\label{sec:testing} % % \textsf{comment2tex} carries a cross-engine test suite, \texttt{comment2tex-test.sh} % --- and, true to the package, that script is itself an annotated Bash source. % What follows is the script, typeset by \cs{includebash} in the default verbatim % mode: its \texttt{\#\#} lines are the prose you read, everything else is the code % that runs. % % The suite is a development artefact and is not part of the distribution, so the % listing appears only when the script is present next to the \texttt{.dtx}. % \IfFileExists{comment2tex-test.sh}{% % \includebash{comment2tex-test.sh}% % }{\par\emph{(\texttt{comment2tex-test.sh} is not included in this % distribution; see the package's development repository.)}\par} % % \section{CI/CD}\label{sec:cicd} % % Two GitHub Actions workflows under \texttt{.github/workflows/} keep the package % honest, and --- like the \texttt{Makefile} and the test suite --- they are % annotated YAML, typeset here by \cs{includeyaml} (the fourth include macro, shown % at last, on the package's own continuous-integration setup). \emph{build} tests % and typesets on every push and pull request; \emph{publish} bundles for CTAN and % drafts a release on every tag. Both are development artefacts, not shipped, so each % listing appears only when its file is present. % % \IfFileExists{.github/workflows/build.yml}{% % \includeyaml{.github/workflows/build.yml}% % }{\par\emph{(the workflows are not included in this distribution; see the package's % development repository.)}\par} % \IfFileExists{.github/workflows/publish.yml}{% % \includeyaml{.github/workflows/publish.yml}% % }{} % % \Finale \endinput