% \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}
%</driver>
% \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
%</package>
% \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
%</plain>
% \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
