-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcomment2tex.dtx
More file actions
688 lines (688 loc) · 29.3 KB
/
Copy pathcomment2tex.dtx
File metadata and controls
688 lines (688 loc) · 29.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
% \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