%<*driver> \def\nameofplainTeX{plain} \ifx\fmtname\nameofplainTeX \input l3docstrip.tex \keepsilent \askforoverwritefalse \preamble ======================================================================== osglistings - Code-listing front end for osglecture, built on minted. (C) 2026 Matthias Werner E-mail: matthias.werner@informatik.tu-chemnitz.de Released under the LaTeX Project Public License v1.3c or later See http://www.latex-project.org/lppl.txt ======================================================================== \endpreamble \postamble This work is "maintained" (as per LPPL maintenance status) by Matthias Werner. \endpostamble \generate{\file{osglistings.sty}{\from{osglistings.dtx}{package}}} \expandafter\endbatchfile \fi \iffalse % %<*package> \NeedsTeXFormat{LaTeX2e}[2022-06-01] \ProvidesExplPackage{osglistings}{2026-08-28}{v0.1.0} {Code-listing front end for osglecture, built on minted} \ExplSyntaxOff % %<*driver> \fi \documentclass[add-bib=false]{osgdoc} \OnlyDescription \usepackage{enumitem} \usepackage{microtype} \usepackage[ languages={de,en}, map={de=german,en=english}, targetlang={job=2}, load babel ]{langselect} \newpackagename{\osglistings}{osglistings} \makeindex \setcnltx{ package = osglistings, version = \UseVersionOf{osglistings.sty}, date = \UseDateOf{osglistings.sty}, title = {\ldeen{Das \osglistings\ Paket}{The \osglistings\ Package}}, info = {\ldeen{Ein auf \pkg*{minted} aufbauendes Frontend für Code-Listings}{ A \pkg*{minted}-based front end for code listings}}, authors = {Matthias Werner[matthias.werner@informatik.tu-chemnitz.de]}, abstract={ \ldeen*{ Das Paket \osglistings\ stellt ein Frontend für \pkg*{minted} bereit, welches Farben und Rahmenstile leicht konfigurierbar macht und Markierungen, externe Editoren und Codequellen (gists) unterstützt. Außerdem wird der Quelltext als @2 getaggt, falls @1 gesetzt ist. Das Paket ist Teil des \bnd*{osglecture}-Bundles, aber von den anderen Paketen weitgehend unabhängig. }{ The \osglistings\ package provides a frontend for \pkg*{minted} that makes colors and border styles easily configurable and supports highlights, external editors, and code sources (gists). In addition, the source code is tagged as @2 if @1 is set. The package is part of the \bnd*{osglecture} bundle, but is largely independent of the other packages. }{\cs{DocumentMetadata}\Marg{tagging=on}}{\code{Code}} }, url =https://github.com/werner-matthias/osglecture, build-title } \begin{document} % -------------------------------------------------------------------------- % Documentation % -------------------------------------------------------------------------- \section{\ldeen{Nutzung}{Usage}} \ldeen*{Das Paket wird auf dem üblichen Weg mit @1 geladen und lädt seinerseits (u.a.) @2.}{ The package is loaded in the usual way using @1 and, in turn, loads (among other things) @2. }{\cs{usepackage}[\oarg{\ldeen{Optionen}{options}}\Marg{osglistings}]}{\pkg*{minted}} \ldeen*{@1 kennt vier Paketoptionen: @2, @3, @4 und @5, Diese werden unverändert an @6s eigene, gleichnamige Ladezeit-Optionen weitergereicht und steuern dessen Highlighting-Cache. Ohne Angabe verhält sich @6 wie mit seinen eigenen Voreinstellungen. Andere @6-Paketoptionen lassen sich derzeit nicht über @1 setzen.}{@1 knows four package options: @2, @3, @4, and @5. They are forwarded unchanged to @6's own, identically named load-time options, controlling its highlighting cache. Without them, @6 behaves as with its own defaults. Other @6 package options currently cannot be set through @1.} {\pkg*{osglistings}}{\code{cache}}{\code{cachedir}}{\code{frozencache}} {\code{cacheignoresfilecontents}}{\pkg*{minted}} \begin{example}[code-only] \usepackage[cachedir=build/minted-cache]{osglistings} \end{example} \ldeen*{Ein Quelltext, der direkt im Dokument steht, wird mit der Umgebung @1 gesetzt; ein Quelltext aus einer externen Datei mit @2. Beide akzeptieren dieselben Schlüssel, dazu jede von \pkg*{minted} selbst verstandene Option (@3, @4, \ldots), die unverändert durchgereicht wird.}{Source that lives directly in the document is typeset with the @1 environment; source from an external file with @2. Both accept the same keys, plus any option \pkg*{minted} itself understands (@3, @4, \ldots), forwarded unchanged.} {\code{osglisting}}{\cs{osglistinginput}}{\code{firstline}}{\code{breaklines}} \begin{example}[compile,program=lualatex,exe-with=--shell-escape, graphics={trim=30mm 200mm 30mm 30mm,clip,scale=1.2}] \documentclass{article} \usepackage{osglistings} \begin{document} \begin{osglisting}[style=editor, title=main.rs]{rust} fn main() { println!("hi"); } \end{osglisting} \end{document} \end{example} \subsection{\ldeen{Rahmenstile}{Box Styles}} \ldeen*{Der Schlüssel @1 wählt zwischen drei Darstellungen: @2 (ein schlichter Rahmen), @3 (\glqq Druckerpapier\grqq{} mit gerissener Kante, ohne Kopfzeile) und @4 (ein \glqq Editorfenster\grqq{} mit Titelzeile, Sprachsymbol und, falls @5 gesetzt ist, einem Bearbeiten-Symbol). Der Stil @3 unterstützt keinen Seitenumbruch innerhalb der Box.}{The @1 key chooses between three looks: @2 (a plain frame), @3 (\glqq printer paper\grqq{} with a torn edge, no title bar), and @4 (an \glqq editor window\grqq{} with a title bar, a language badge, and, when @5 is set, an edit-link icon). The @3 style does not support breaking across a page.} {\code{style}}{\code{plain}}{\code{paper}}{\code{editor}}{\code{link}} \subsection{\ldeen{Farbpalette}{Color Palette}} \ldeen*{Die Syntaxfarben kommen aus einer benannten Palette, aktiviert mit @1 bzw.\ für einen einzelnen Aufruf mit dem Schlüssel @2. Eine eigene Palette wird mit @3 deklariert; nicht angegebene Slots übernehmen den Wert der eingebauten Standardpalette @4.}{Syntax colors come from a named palette, activated with @1, or for a single call via the @2 key. A custom palette is declared with @3; slots left unspecified inherit their value from the built-in default palette @4.} {\cs{UseOsgListingsPalette}}{\code{palette}}{\cs{DeclareOsgListingsPalette}} {\code{osglistings-default}} \begin{example}[code-only] \DeclareOsgListingsPalette{my-theme}{ keyword = {model=HTML, value=8A2BE2}, string = {alias=keyword}, } \UseOsgListingsPalette{my-theme} \end{example} \ldeen*{Jeder Slot ist entweder eine \emph{Ressource} (@1+@2, in jedem von \pkg*{xcolor} unterstützten Modell) oder ein \emph{Alias} (@3, höchstens zwei Ebenen tief aufgelöst).}{Each slot is either a \emph{resource} (@1+@2, in any model \pkg*{xcolor} supports) or an \emph{alias} (@3, resolved at most two levels deep).} {\code{model}}{\code{value}}{\code{alias=}\meta{other slot}} \subsection{\ldeen{Links in Online-Editoren}{Links to Online Editors}} \ldeen*{Der Schlüssel @1 hinterlegt eine URL; @2 legt fest, wo sie erscheint: @3 (ein Symbol in der Titelzeile, nur für @4), @5 (ein klickbarer Text unterhalb der Box), @6 (ein QR-Code unterhalb der Box) oder @7 (kein sichtbarer Hinweis).} {The @1 key attaches a URL; @2 controls where it shows up: @3 (an icon in the title bar, @4 style only), @5 (clickable text below the box), @6 (a QR code below the box), or @7 (no visible indicator).} {\code{link}}{\code{link-display}}{\code{titlebar}}{\code{editor}} {\code{label}}{\code{qrcode}}{\code{none}} \subsection{\ldeen{Remote-Quellen}{Remote Sources}} \ldeen*{Das dritte Argument von @1 akzeptiert statt eines lokalen Pfads auch einen @2- oder @3-Lokator, z.\,B.\ @4 bzw.\ @5. Der Quelltext wird beim ersten Kompilieren geladen und unter @6 gecacht; @7 steuert die Wiederverwendung: @8 (Voreinstellung) nutzt einen vorhandenen Cache- Eintrag, @9 lädt immer neu und aktualisiert ihn.} {The third argument of @1 accepts, besides a local path, a @2 or @3 locator as well, e.g.\ @4 or @5. The source is fetched on first compile and cached under @6; @7 controls reuse: @8 (the default) uses an existing cache entry, @9 always re-fetches and updates it.} {\cs{osglistinginput}}{\code{gist:}}{\code{github:}} {\code{gist:}\meta{owner}\code{/}\meta{gist-id}\code{/}\meta{filename}} {\code{github:}\meta{owner}\code{/}\meta{repo}\code{@}\meta{ref}\code{/}\meta{path}} {\File{\cs{jobname}.osglistings-cache/}}{\code{cache}}{\code{cache=true}} {\code{cache=refresh}} \begin{example}[code-only] \osglistinginput[style=editor, title={fizzbuzz.rs}] {rust}{gist:werner-matthias/3af1e0.../fizzbuzz.rs} \end{example} \ldeen*{Fehlt der Schlüssel @1, leitet das Paket automatisch einen Editor-Link aus dem Sprachregister ab: aus @2, falls für die Sprache hinterlegt und die Quelle ein Gist ist (Vorlage typischerweise mit @3 statt @4), sonst aus @5. Der Platzhalter @4 wird durch den URL-codierten Quelltext ersetzt, @3 durch die Gist-ID.} {When @1 is not set, the package automatically derives an editor link from the language registry: from @2, if declared for the language and the source is a gist (template typically using @3 instead of @4), else from @5. The @4 placeholder is replaced with the URL-encoded source, @3 with the gist id.} {\code{link}}{\code{editor-url-gist}}{\code{\{GIST\_ID\}}} {\code{\{CODE\}}}{\code{editor-url}} \ldeen*{Ein einmal gecachter Eintrag wird beim Kompilieren nicht automatisch gegen die Quelle validiert -- @1 (Voreinstellung) prüft gar nicht; @2 löst am Dokumentende genau eine Anfrage pro \emph{unterschiedlichem} Repository/Gist aus (nicht pro Datei) und warnt bei veralteten Einträgen; @3 aktualisiert veraltete Einträge dabei zusätzlich sofort. Außerhalb von \LaTeX{} übernimmt das Kommandozeilenskript @4 dieselbe Prüfung für einen oder mehrere Cache-Ordner.}{An entry, once cached, is not automatically validated against its source on compile -- @1 (the default) checks nothing; @2 fires exactly one request per \emph{distinct} repository/gist at the end of the document (not per file) and warns about stale entries; @3 additionally refreshes stale entries immediately. Outside \LaTeX, the command-line script @4 runs the same check for one or more cache directories.} {\code{remote-check=manual}}{\code{remote-check=compile}} {\code{remote-refresh=true}}{\File{osglistings-check-updates.lua}} \begin{example}[code-only] texlua osglistings-check-updates.lua --refresh myfile.osglistings-cache \end{example} \begin{warnbox}{\ldeen{Achtung, Sicherheitsrisiko!}{Attention, Security Risk!}} \ldeen*{Ein @1- oder @2-Lokator benötigt zur Übersetzung @3 und ruft dabei intern @4 mit den Rechten des \TeX-Prozesses auf. Verwenden Sie diese Lokatoren nur in Dokumenten, deren Inhalt (und deren Lokatoren) Sie vertrauen -- ein böswillig gewählter Lokator könnte sonst beliebige Netzwerkanfragen unter Ihrer Identität auslösen.} {A @1 or @2 locator requires compiling with @3 and internally invokes @4 with the \TeX{} process's own permissions. Only use these locators with documents (and locators) you trust -- a maliciously chosen locator could otherwise trigger arbitrary network requests under your identity.} {\code{gist:}}{\code{github:}}{\texttt{--shell-escape}}{\texttt{curl}} \end{warnbox} \subsection{\ldeen{Zeichengenaue Markierungen}{Character-level Marks}} \ldeen*{@1 legt an der Stelle, an der es im Quelltext steht, einen unsichtbaren, benannten \pkg*{tikz}-Knoten ab; ein späteres @2 kann von oder zu ihm zeichnen. Für Inline-Quelltext (Umgebung @3) muss dazu @4 selbst gesetzt werden (z.\,B.\ @5), damit @1 überhaupt als \LaTeX-Code statt als Verbatim-Text erkannt wird.} {@1 drops an invisible, named \pkg*{tikz} node exactly where it appears in the source; a later @2 can then draw to or from it. For inline source (the @3 environment) @4 itself must be set (e.g.\ @5) for @1 to be recognized as \LaTeX{} code at all, rather than verbatim text.} {\cs{osglistingsmark}\Marg{name}}{\code{tikzpicture}} {\code{osglisting}}{\code{escapeinside}}{\code{escapeinside=§§}} \begin{example}[compile,program=lualatex,exe-with=--shell-escape, graphics={trim=30mm 200mm 30mm 30mm,clip,scale=1.2}] \documentclass{article} \usepackage{tikz} \usepackage{osglistings} \begin{document} \begin{osglisting}[style=plain, escapeinside=§§]{rust} let code = ExitCode::from(42§\osglistingsmark{here}§); \end{osglisting} \begin{tikzpicture}[remember picture, overlay] \draw[->, red, thick] (osgm-here) ++(0,1) node[above]{Exit-Code} -- (osgm-here); \end{tikzpicture} \end{document} \end{example} \ldeen*{Für externe Quellen übernimmt der Schlüssel @1 von @2 dieselbe Aufgabe, ohne dass @3 manuell gesetzt werden muss -- ist @1 nicht leer, schaltet das Paket @3 selbst ein (Trennzeichen @4, änderbar über @5). Jeder durch Komma getrennte Eintrag adressiert entweder zeichengenau über @6 (@7 zählt in \emph{Zeichen}, nicht Bytes) oder über eine wörtliche Textsuche mit @8 bzw.\ @9, optional gefolgt von \code{occurrence=}\meta{n} (Voreinstellung~1).} {For external sources, the @1 key of @2 does the same job without needing @3 set manually -- when @1 is non-empty, the package switches on @3 itself (delimiter @4, overridable via @5). Each comma-separated entry addresses a position either exactly by @6 (@7 counts in \emph{characters}, not bytes) or via a literal text search using @8 or @9, optionally followed by \code{occurrence=}\meta{n} (default~1).} {\code{marks}}{\cs{osglistinginput}}{\code{escapeinside}}{\code{§}} {\code{marks-escape}}{\meta{name}\code{ at }\meta{line}\code{:}\meta{col}} {\meta{col}}{\meta{name}\code{ after "}\meta{text}\code{"}} {\meta{name}\code{ before "}\meta{text}\code{"}} \begin{example}[code-only] \osglistinginput[ marks = { exitc after "ExitCode", semi before ";" occurrence=2, } ]{rust}{src/example.rs} \end{example} \ldeen*{@1 verhält sich wie @2/@3, markiert aber keine Einzelstelle, sondern grenzt den gefundenen Text mit einem Knotenpaar @4/@5 ein. @6 verbindet ein solches Paar anschließend zu einer Markierungsbox -- dank der in @7 mitgemessenen Zeichenhöhe/-tiefe ganz ohne manuelle \code{xshift}/\code{yshift}-Korrekturen. Für Inline-Quelltext lässt sich derselbe Effekt auch ohne @8 erzielen, indem @7 einfach zweimal (mit den Namen @4/@5) direkt im Quelltext gesetzt wird.} {@1 behaves like @2/@3, but instead of marking a single spot it brackets the found text with a node pair @4/@5. @6 then connects such a pair into a highlight box -- thanks to the character height/depth captured by @7, with no manual \code{xshift}/\code{yshift} corrections needed. For inline source the same effect is available without @8 too, by simply placing @7 twice (named @4/@5) directly in the source.} {\code{around}}{\code{after}}{\code{before}} {\meta{name}\code{-begin}}{\meta{name}\code{-end}} {\cs{osglistingshighlight}}{\cs{osglistingsmark}}{\code{marks}} \begin{example}[compile,program=lualatex,exe-with=--shell-escape, graphics={trim=30mm 200mm 30mm 30mm,clip,scale=1.2}] \documentclass{article} \usepackage{tikz} \usepackage{osglistings} \begin{document} \begin{osglisting}[style=plain, escapeinside=§§]{rust} let code = §\osglistingsmark{kw-begin}§ExitCode§\osglistingsmark{kw-end}§::from(42); \end{osglisting} \osglistingshighlight{kw} \end{document} \end{example} \ldeen*{Für externe Quellen genügt dagegen @1 im @2-Eintrag, gefolgt von @3.} {For external sources, @1 in the @2 entry is enough instead, followed by @3.} {\code{around}}{\code{marks}}{\cs{osglistingshighlight}} \begin{example}[code-only] \osglistinginput[ marks = { exitc around "ExitCode" } ]{rust}{src/example.rs} \osglistingshighlight{exitc} \end{example} \ldeen*{@1 (wie jede Nutzung von @2) benötigt Lua\LaTeX.} {@1 (like any use of @2) requires Lua\LaTeX.} {\code{marks}}{\cs{osglistingsmark}} \subsection{\ldeen{Barrierefreiheit}{Accessibility}} \ldeen*{Trägt ein @1-Eintrag zusätzlich @2, öffnet die Ersetzung statt eines bloßen Punktpaars eine getaggte @3-Struktur mit diesem Text als \code{alt}-Attribut (Voraussetzung: @4, sonst bleibt es beim Punktpaar) -- assistive Technologien lesen dann @2 statt des markierten Quelltexts selbst. Für Inline-Quelltext leisten @5/@6 dasselbe von Hand. @7 selbst kapselt sein Overlay dabei automatisch als \code{Artifact}; assistive Technologien überspringen es, weil die eigentliche Information ja schon über @2 am Quelltext hängt, nicht am Pfeil oder der Box drumherum.} {When an @1 entry also carries @2, the substitution opens a tagged @3 structure instead of a bare point pair, with that text as its \code{alt} attribute (requires @4; without it, a plain point pair as usual) -- assistive technology then reads @2 instead of the marked-up source itself. For inline source, @5/@6 do the same by hand. @7 itself wraps its overlay as an \code{Artifact} automatically; assistive technology skips it, because the real information already hangs off the source via @2, not off the arrow or box around it.} {\code{around}}{\code{desc="}\meta{text}\code{"}}{\code{Span}} {\cs{DocumentMetadata}\Marg{tagging=on}} {\cs{osglistingsmarkspanbegin}}{\cs{osglistingsmarkspanend}} {\cs{osglistingshighlight}} \begin{example}[code-only] \osglistinginput[ marks = { exitc around "ExitCode" desc="the process exit status", } ]{rust}{src/example.rs} \osglistingshighlight{exitc} \end{example} \begin{warnbox}{\ldeen{Nur getaggte Spannen tragen Text}{Only Tagged Spans Carry Text}} \ldeen*{Der übrige Quelltext landet zwar in einer getaggten @1-Struktur, aber (Stand dieser Implementierung) ohne für Screenreader auslesbaren Text -- @2 ist für eine markierte Spanne also meist nicht Zusatz, sondern die einzige tatsächlich vorgelesene Information.} {The rest of the source does land inside a tagged @1 structure, but (as currently implemented) without screen-reader-readable text -- for a marked span, @2 is therefore usually not an addition but the only information actually read aloud.}{\code{Code}}{\code{desc}} \end{warnbox} \begin{warnbox}{\ldeen{Zweiter Kompilierlauf nötig}{Second Compile Run Needed}} \ldeen*{@1 löst sich, wie jedes seitenübergreifende \pkg*{tikz}-Overlay, erst nach einem zweiten Kompilierlauf korrekt auf -- vor dem ersten Lauf (oder nach Änderungen an den markierten Positionen) zeigen Pfeile/Markierungen noch auf ihre vorherige Position oder gar nicht.} {@1 only resolves correctly, like any cross-page \pkg*{tikz} overlay, after a second compile run -- before the first run (or after changing the marked positions) arrows/markers still point at their previous position or not at all.}{\code{remember~picture}} \end{warnbox} \subsection{\ldeen{Tagging}{Tagging}} \ldeen*{Ist \LaTeX{} mit @1 gestartet, wird der Quelltext als Struktur @2 getaggt (Slotname @3, umsetzbar mit @4/@5). Tagging wird pro Aufruf mit @6 abgeschaltet.}{When \LaTeX{} is started with @1, the source is tagged as structure @2 (structure name @3, remappable with @4/@5). Tagging is switched off per call with @6.} {\cs{DocumentMetadata}\Marg{tagging=on}}{\code{Code}} {\code{osglistings/code}}{\cs{NewStructureName}}{\cs{AssignStructureRole}} {\code{tag=false}} \section{Implementation} \MaybeStop{\ldeen*{Um die Implementationsbeschreibung zu erhalten, kommentieren Sie bitte \cs{OnlyDescription} in @1 aus.}{To view the implementation description, please uncomment @1 in \code{osgdoc.dtx}.}{\File{osglistings.dtx}}} \subsection{\emph{osglistings.sty}} \AutoDocInput{osglistings.dtx}{package} \end{document} % %<*package> \RequirePackage{xcolor} \RequirePackage{tikz} \usetikzlibrary{positioning,calc,shapes.geometric,decorations.pathreplacing,decorations.pathmorphing,fit} \RequirePackage{tcolorbox} \tcbuselibrary{skins,breakable} \RequirePackage{fontawesome5} \RequirePackage{hyperref} \RequirePackage{qrcode} % \ldeen{@1/@2/\ldots{} sind Ladezeit-Paketoptionen von @3, keine % \cs{setminted}-Schlüssel -- sie steuern, ob/wohin @3 seinen % Highlighting-Cache schreibt, nicht das Aussehen eines einzelnen % Listings. Nur diese vier werden durchgereicht: @4/@5 (der Nummerierungs- % Parent von @3s eigenem, hier ungenutzten Listing-Float) blieben % bewusst außen vor, weil ein falsch gesetzter Wert dort das % Kapitel-/Abschnitts-Zählsystem des ganzen Dokuments durcheinanderbringen % kann, nicht nur die Darstellung eines Listings.}{@1/@2/\ldots{} are % load-time package options of @3, not \cs{setminted} keys -- they % control whether/where @3 writes its highlighting cache, not how a % single listing looks. Only these four are forwarded: @4/@5 (the % numbering parent of @3's own, here-unused listing float) were % deliberately left out, because a wrong value there can derail the % whole document's chapter/section counters, not just one listing's % appearance.}{\code{cache}}{\code{cachedir}}{\pkg*{minted}} % {\code{chapter}}{\code{section}} \RequirePackage{l3keys2e} \ExplSyntaxOn \tl_new:N \l__osglistings_mintedopts_tl \tl_set:Nn \l__osglistings_mintedopts_tl { newfloat, highlightmode=immediate } \bool_new:N \l__osglistings_cacheignoresfilecontents_given_bool \tl_new:N \l__osglistings_cacheignoresfilecontents_tl \keys_define:nn { osglistings/options } { cache .choices:nn = { true, false } { \tl_put_right:Nx \l__osglistings_mintedopts_tl { , cache = \l_keys_choice_tl } }, cache .default:n = true, cachedir .code:n = { \tl_put_right:Nx \l__osglistings_mintedopts_tl { , cachedir = {#1} } }, frozencache .choices:nn = { true, false } { \tl_put_right:Nx \l__osglistings_mintedopts_tl { , frozencache = \l_keys_choice_tl } }, frozencache .default:n = true, % \ldeen{@1 wird nicht mehr an @2s Ladezeit-Optionen angehängt, sondern % erst danach über dessen eigenen etoolbox-Bool gesetzt (siehe unten) -- % @1 gibt es erst seit einer neueren @2-Version, und eine unbekannte % Ladezeit-Option würde auf älteren Installationen sonst kommentarlos % verworfen, ohne dass @3 dies dem Nutzer mitteilen könnte.}{@1 is no % longer appended to @2's load-time options but set afterwards through % its own etoolbox bool instead (see below) -- @1 only exists from a % certain @2 version onward, and an unknown load-time option would % otherwise be silently dropped on older installations, leaving @3 with % no way to tell the user.}{\code{cacheignoresfilecontents}}{\pkg*{minted}} % {\pkg*{osglistings}} cacheignoresfilecontents .choices:nn = { true, false } { \bool_set_true:N \l__osglistings_cacheignoresfilecontents_given_bool \tl_set:Nx \l__osglistings_cacheignoresfilecontents_tl { \l_keys_choice_tl } }, cacheignoresfilecontents .default:n = true, } \ProcessKeysPackageOptions { osglistings/options } % \ldeen{@1 kann @2 nicht direkt als eckige Klammer füttern -- anders als % bei einem regulären \pkg*{expl3}-Argument nimmt @3 seine optionale % Klammer über eigene, klassische Scan-Logik entgegen, nicht über ein % \pkg*{expl3}-Argumentschema, das \cs{exp\_args:No} umleiten könnte. % Ein \cs{tl\_set:Nx} baut den kompletten Aufruf inklusive Klammer % stattdessen als expandierten Text zusammen.}{@1 cannot feed @2 % directly into a square-bracket argument -- unlike a regular % \pkg*{expl3} argument, @3 takes its optional bracket via its own, % classic scanning logic, not an \pkg*{expl3} argument scheme that % \cs{exp\_args:No} could redirect. A \cs{tl\_set:Nx} instead builds % the whole call, brackets included, as expanded text.} % {\cs{exp\_args:No}}{\cs{l\_\_osglistings\_mintedopts\_tl}}{\cs{RequirePackage}} \tl_new:N \l__osglistings_mintedload_tl \tl_set:Nx \l__osglistings_mintedload_tl { \exp_not:N \RequirePackage [ \l__osglistings_mintedopts_tl ] { minted } } \l__osglistings_mintedload_tl % \ldeen{@1 prüft mit @2 die Existenz von @3s eigenem Bool, statt dessen % Version zu vergleichen -- der Bool entsteht nur, wenn @3 die Option % selbst kennt, ist also unabhängig von der genauen Versionsnummer, ab % der sie eingeführt wurde.}{@1 checks whether @3's own bool exists via % @2 rather than comparing version numbers -- the bool only comes into % being if @3 knows the option itself, so this is independent of the % exact version number at which it was introduced.} % {\pkg*{osglistings}}{\cs{ifcsname}}{\pkg*{minted}} \bool_if:NT \l__osglistings_cacheignoresfilecontents_given_bool { \ifcsname ifminted@cacheignoresfilecontents\endcsname \str_if_eq:eeTF { \l__osglistings_cacheignoresfilecontents_tl } { true } { \booltrue{minted@cacheignoresfilecontents} } { \boolfalse{minted@cacheignoresfilecontents} } \else \PackageWarningNoLine{osglistings} { The~option~'cacheignoresfilecontents'~requires~a~newer~version~ of~minted~than~the~one~currently~loaded;~ignoring~it } \fi } \ExplSyntaxOff \setminted{style=default} % \ldeen{Latin Modern Mono (die Standardschrift von \pkg*{minted}/\pkg*{fvextra}) % deckt weder Kyrillisch noch die meisten Symbolblöcke ab -- fehlende % Zeichen werden von \LuaTeX{} stillschweigend ausgelassen, nicht durch % Kästchen ersetzt. @1 hat spürbar breitere Abdeckung und ist Teil jeder % vollständigen \TeX~Live-Installation; ohne sie fällt die Schrift auf % \cs{ttfamily} zurück.}{Latin Modern Mono (the default font of % \pkg*{minted}/\pkg*{fvextra}) covers neither Cyrillic nor most symbol % blocks -- missing glyphs are silently dropped by \LuaTeX, not replaced % with a box. @1 has noticeably wider coverage and ships with any full % \TeX~Live install; without it, the font falls back to \cs{ttfamily}.} % {DejaVu~Sans~Mono} \RequirePackage{fontspec} \IfFontExistsTF{DejaVu Sans Mono} { \newfontfamily\osglistingsmonofont{DejaVu Sans Mono} } { \PackageWarning{osglistings} {Font~"DejaVu~Sans~Mono"~not~found;~falling~back~to~ttfamily.\MessageBreak Multi-byte~UTF-8~source~(Cyrillic,~symbols,~...)~may~lose~glyphs} \let\osglistingsmonofont\ttfamily } % \ldeen{\pkg*{fvextra} wählt seine Verbatim-Schrift beim Öffnen jeder % Umgebung selbst neu (Default @1); ein umgebendes \cs{fontupper} wird % dabei überschrieben. @2 ist der von \pkg*{fvextra} vorgesehene Haken, % um stattdessen ein beliebiges Font-Makro zu benutzen.}{\pkg*{fvextra} % reselects its own verbatim font every time an environment opens % (default @1); a surrounding \cs{fontupper} gets overwritten in the % process. @2 is \pkg*{fvextra}'s own hook for using an arbitrary font % macro instead.}{\cs{ttfamily}}{\code{fontfamily=myFont}} \let\myFont\osglistingsmonofont \setminted{fontfamily=myFont} \makeatletter \ExplSyntaxOn % \subsection{\ldeen{Farbpalette}{Color palette}} % \ldeen*{Eine Palette bildet portable Slotnamen auf konkrete % \pkg*{xcolor}-Farben ab; die Rohangaben (Ressource oder Alias) landen in % dieser Prop unter dem Schlüssel "\meta{Palette}/\meta{Slot}". Erst % @1 löst sie zu tatsächlichen \cs{definecolor}-Aufrufen auf.}{A palette % maps portable slot names onto concrete \pkg*{xcolor} colors; the raw % specification (resource or alias) lands in this prop keyed by % "\meta{palette}/\meta{slot}". Only @1 resolves it to actual % \cs{definecolor} calls.}{\cs{UseOsgListingsPalette}} \prop_new:N \g__osglistings_palette_data_prop \clist_const:Nn \c__osglistings_palette_slots_clist { background, frame, titlebar-bg, titlebar-fg, keyword, string, comment, number, operator, directive, emph, linenumber, fail-icon } \tl_new:N \g_osglistings_active_palette_tl \tl_new:N \l__osglistings_slot_model_tl \tl_new:N \l__osglistings_slot_value_tl \tl_new:N \l__osglistings_slot_alias_tl \tl_new:N \l__osglistings_tmp_tl \tl_new:N \l__osglistings_tmp_two_tl \tl_new:N \l__osglistings_active_lookup_tl \keys_define:nn { osglistings/paletteslot } { model .tl_set:N = \l__osglistings_slot_model_tl, model .initial:n = { }, value .tl_set:N = \l__osglistings_slot_value_tl, value .initial:n = { }, alias .tl_set:N = \l__osglistings_slot_alias_tl, alias .initial:n = { }, } \tl_new:N \l__osglistings_palette_name_tl % \ldeen{Ein Key pro erlaubtem Slotnamen speichert die Rohangabe unter % "\meta{Palette}/\meta{Slot}", ohne sie schon aufzulösen -- Aliase % dürfen auf Slots verweisen, die erst später in derselben Deklaration % folgen.}{One key per allowed slot name stores the raw spec under % "\meta{palette}/\meta{slot}", without resolving it yet -- aliases may % point at slots that only follow later in the same declaration.} % \ldeen{Schlüssel und Wert werden mit \code{.tl\_set:Nx} eingefroren, bevor % sie in die Prop wandern -- sonst landete dort nur ein Verweis auf die % (wiederverwendeten) Slot-Variablen, und jeder Eintrag zeigte am Ende % den zuletzt verarbeiteten Slot.}{Key and value are frozen with % \code{.tl\_set:Nx} before going into the prop -- otherwise it would only % store a reference to the (reused) slot variables, and every entry % would end up showing whichever slot was processed last.} \cs_new_protected:Npn \__osglistings_palette_slot:nn #1 #2 { \__osglistings_slot_spec_set:n {#2} \tl_set:Nx \l__osglistings_tmp_tl { \l__osglistings_palette_name_tl / #1 } \tl_set:Nx \l__osglistings_tmp_two_tl { model = \l__osglistings_slot_model_tl, value = \l__osglistings_slot_value_tl, alias = \l__osglistings_slot_alias_tl, } \exp_args:NNVV \prop_gput:Nnn \g__osglistings_palette_data_prop \l__osglistings_tmp_tl \l__osglistings_tmp_two_tl } \clist_map_inline:Nn \c__osglistings_palette_slots_clist { \keys_define:nn { osglistings/palette } { #1 .code:n = \__osglistings_palette_slot:nn {#1} {##1} } } \msg_new:nnn { osglistings } { unknown-palette } { Palette~"#1"~is~not~declared.~Use~\token_to_str:N\DeclareOsgListingsPalette~before~using~it. } \msg_new:nnn { osglistings } { alias-too-deep } { Slot~"#2"~of~palette~"#1"~is~an~alias~chain~longer~than~two~levels;~falling~back~to~black. } % \ldeen{Neue Paletten übernehmen zunächst jeden Slot der eingebauten % Standardpalette; die übergebenen Keys überschreiben danach nur die % ausdrücklich genannten Slots.}{New palettes start by inheriting every % slot from the built-in default palette; the keys passed in then % override only the slots named explicitly.} \cs_new_protected:Npn \DeclareOsgListingsPalette #1 #2 { \tl_set:Nn \l__osglistings_palette_name_tl {#1} \str_if_eq:eeF {#1} { osglistings-default } { \clist_map_inline:Nn \c__osglistings_palette_slots_clist { \prop_get:NnNTF \g__osglistings_palette_data_prop { osglistings-default / ##1 } \l__osglistings_tmp_tl { \prop_gput:NnV \g__osglistings_palette_data_prop { #1 / ##1 } \l__osglistings_tmp_tl } { } } } \keys_set:nn { osglistings/palette } {#2} } % \ldeen{Setzt die drei Slot-Spec-Variablen aus einem gespeicherten % Eintrag; sie werden vorher geleert, weil @1 nicht erwähnte Keys nicht % auf ihren @2 zurücksetzt.}{Sets the three slot-spec variables from a % stored entry; they are cleared first because @1 does not reset keys % it isn't given back to their @2.}{\cs{keys\_set:nn}}{\code{.initial:n}} \cs_new_protected:Npn \__osglistings_slot_spec_set:n #1 { \tl_clear:N \l__osglistings_slot_model_tl \tl_clear:N \l__osglistings_slot_value_tl \tl_clear:N \l__osglistings_slot_alias_tl \exp_args:Nnx \keys_set:nn { osglistings/paletteslot } {#1} } % \ldeen{Löst einen Slot höchstens zweistufig zu einer Ressource auf und % definiert die zugehörige Farbe \code{osglistings@color@}\meta{Slot}.} % {Resolves a slot at most two levels deep to a resource and defines the % corresponding color \code{osglistings@color@}\meta{slot}.} \cs_new_protected:Npn \__osglistings_palette_resolve:nn #1 #2 { \tl_set:Nx \l__osglistings_tmp_two_tl { #1 / #2 } \exp_args:NNV \prop_get:NnNTF \g__osglistings_palette_data_prop \l__osglistings_tmp_two_tl \l__osglistings_tmp_tl { \__osglistings_slot_spec_set:n { \l__osglistings_tmp_tl } \tl_if_empty:NTF \l__osglistings_slot_alias_tl { \definecolor { osglistings@color@#2 } { \l__osglistings_slot_model_tl } { \l__osglistings_slot_value_tl } } { \tl_set:Nx \l__osglistings_tmp_two_tl { #1 / \l__osglistings_slot_alias_tl } \exp_args:NNV \prop_get:NnNTF \g__osglistings_palette_data_prop \l__osglistings_tmp_two_tl \l__osglistings_tmp_tl { \__osglistings_slot_spec_set:n { \l__osglistings_tmp_tl } \tl_if_empty:NTF \l__osglistings_slot_alias_tl { \definecolor { osglistings@color@#2 } { \l__osglistings_slot_model_tl } { \l__osglistings_slot_value_tl } } { \msg_warning:nnnn { osglistings } { alias-too-deep } {#1} {#2} \definecolor { osglistings@color@#2 } { rgb } { 0,0,0 } } } { \definecolor { osglistings@color@#2 } { rgb } { 0,0,0 } } } } { \definecolor { osglistings@color@#2 } { rgb } { 0,0,0 } } } % \ldeen{@1 kann eine wörtliche Palette oder eine Variable sein, die einen % Palettennamen enthält (z.\,B.\ der @2-Schlüssel); deshalb wird zuerst % einmal expandiert und das Ergebnis danach überall wiederverwendet.} % {@1 may be a literal palette name or a variable holding one (e.g.\ the % @2 key); it is therefore expanded once up front and the result reused % throughout.}{\cs{UseOsgListingsPalette}}{\code{palette=}} \cs_new_protected:Npn \UseOsgListingsPalette #1 { \tl_set:Nx \l__osglistings_active_lookup_tl {#1} \tl_set:Nx \l__osglistings_tmp_tl { \l__osglistings_active_lookup_tl / background } \exp_args:NNV \prop_get:NnNTF \g__osglistings_palette_data_prop \l__osglistings_tmp_tl \l__osglistings_tmp_two_tl { \clist_map_inline:Nn \c__osglistings_palette_slots_clist { \exp_args:NV \__osglistings_palette_resolve:nn \l__osglistings_active_lookup_tl {##1} } \tl_gset:Nx \g_osglistings_active_palette_tl { \l__osglistings_active_lookup_tl } } { \msg_error:nnx { osglistings } { unknown-palette } { \l__osglistings_active_lookup_tl } } } % \ldeen{Rein expandierbar: liefert den Farbnamen eines Slots der % aktuell aufgelösten Palette, verwendbar überall dort, wo \pkg*{xcolor} % einen Farbnamen erwartet.}{Purely expandable: returns the color name % of a slot in the currently resolved palette, usable anywhere % \pkg*{xcolor} expects a color name.} \cs_new:Npn \OsgListingsColor #1 { osglistings@color@#1 } % \ldeen{Hausfarben, portiert aus den historischen \cs{clst...}-Makros.} % {House colors, ported from the historical \cs{clst...} macros.} \DeclareOsgListingsPalette { osglistings-default } { background = { model=gray, value=1 }, frame = { model=gray, value=0.6 }, titlebar-bg = { model=gray, value=0.9 }, titlebar-fg = { model=gray, value=0 }, keyword = { model=HTML, value=1B5E20 }, string = { model=HTML, value=0000CD }, comment = { model=HTML, value=00CDCD }, number = { model=gray, value=0.5 }, operator = { model=gray, value=0.3 }, directive = { model=HTML, value=8B008B }, emph = { model=HTML, value=CC6600 }, linenumber = { model=gray, value=0.5 }, fail-icon = { model=HTML, value=CC0000 }, } \UseOsgListingsPalette { osglistings-default } % \subsection{\ldeen{Pygments-Farbkopplung}{Pygments color hook}} % \ldeen*{\pkg*{minted} legt seine Token-Farben als % \cs{PYG@tok@}\meta{Kürzel}-Makros neu an, jedes Mal wenn ein Stil % (wieder-)geladen wird -- lokal zum jeweiligen Aufruf, siehe % \cs{minted@patch@PygmentsStyledef} in \File{minted.sty}. Das ist der % einzige Punkt, an dem sich diese Makros zuverlässig umbiegen lassen, % bevor \pkg*{minted} sie zum Setzen des Quelltexts benutzt.}{@1 % redefines its token colors as \cs{PYG@tok@}\meta{code} macros every % time a style is (re)loaded -- locally to that call, see % \cs{minted@patch@PygmentsStyledef} in \File{minted.sty}. That is the % only place these macros can reliably be redirected before @1 uses them % to typeset the source.}{\pkg*{minted}} % \ldeen{\cs{cs\_gset:cn}/\cs{cs\_gset:Nn} lehnen Zielnamen ohne \code{:} als % Kodierfehler ab; die Pygments-Tokenmakros folgen aber \pkg*{minted}s % \LaTeXe-Namensschema, nicht dem von \pkg*{expl3}, deshalb der Umweg % über die nackten Primitive.}{\cs{cs\_gset:cn}/\cs{cs\_gset:Nn} reject % target names without a \code{:} as a coding error; the Pygments token % macros follow \pkg*{minted}'s \LaTeXe{} naming scheme, not \pkg*{expl3}'s, % hence the detour through the bare primitives.} % \ldeen{Die eigentliche Parametermarke \code{#1} steckt allein in @1s % eigener, einstufiger Definition; \cs{PYG@tok@}\meta{Kürzel}s Rumpf % enthält dadurch selbst kein \code{\#}~mehr und muss nicht durch % mehrere ineinander verschachtelte \cs{tex\_def:D}-Aufrufe hindurch mit % der richtigen Anzahl Doppelraute überleben.}{The real parameter marker % \code{\#1} lives entirely inside @1's own, single-level definition; % \cs{PYG@tok@}\meta{code}'s body therefore contains no \code{\#} of its % own and doesn't have to survive the right number of doubled hashes % through several nested \cs{tex\_def:D} calls.} % {\cs{\_\_osglistings\_pyg\_settc:n}} \cs_new_protected:Npn \__osglistings_pyg_settc:n #1 { \tex_def:D \PYG@tc ##1 { \textcolor { \OsgListingsColor{#1} } {##1} } } \cs_new_protected:Npn \__osglistings_pygments_tok:nn #1 #2 { \tex_global:D \exp_args:Nc \tex_def:D { PYG@tok@#1 } { \__osglistings_pyg_settc:n {#2} } } \cs_new_protected:Npn \__osglistings_pyg_seterr: { \tex_def:D \PYG@bc ##1 { { \fboxsep=-\fboxrule \fcolorbox { \OsgListingsColor{fail-icon} } { osglistings@color@background } { \strut ##1 } } } } \cs_new_protected:Npn \__osglistings_patch_pygments_colors: { \clist_map_inline:nn { k,kd,kn,kt,kp,kc,kr,bp } { \__osglistings_pygments_tok:nn {##1} { keyword } } \clist_map_inline:nn { s,sd,sh,s1,s2,sx,ss,si,se,sr } { \__osglistings_pygments_tok:nn {##1} { string } } \clist_map_inline:nn { c,c1,cm,cs,cp } { \__osglistings_pygments_tok:nn {##1} { comment } } \clist_map_inline:nn { m,mi,mf,mh,mo,il } { \__osglistings_pygments_tok:nn {##1} { number } } \clist_map_inline:nn { o,ow } { \__osglistings_pygments_tok:nn {##1} { operator } } \clist_map_inline:nn { nd,na } { \__osglistings_pygments_tok:nn {##1} { directive } } \tex_global:D \tex_def:D \PYG@tok@err { \__osglistings_pyg_seterr: } } \cs_if_exist:NT \minted@patch@PygmentsStyledef { \g@addto@macro \minted@patch@PygmentsStyledef \__osglistings_patch_pygments_colors: } % \subsection{\ldeen{Sprachregister}{Language registry}} \prop_new:N \g__osglistings_lang_icon_prop \prop_new:N \g__osglistings_lang_url_prop \prop_new:N \g__osglistings_lang_gisturl_prop \keys_define:nn { osglistings/language } { % \ldeen{\code{.code:n} liefert den Slotnamen unexpandiert -- ohne % \cs{exp\_args:NNx} wäre der gespeicherte Schlüssel das % \emph{Makro} \cs{l\_\_osglistings\_lang\_name\_tl} selbst, nicht % sein Wert, sodass alle Sprachen denselben (falschen) Eintrag % überschreiben würden.}{\code{.code:n} leaves the slot name % unexpanded -- without \cs{exp\_args:NNx} the stored key would be % the \emph{macro} \cs{l\_\_osglistings\_lang\_name\_tl} itself, not % its value, so every language would overwrite the same (wrong) % entry.} icon .code:n = \exp_args:NNx \prop_gput:Nnn \g__osglistings_lang_icon_prop { \l__osglistings_lang_name_tl } {#1}, editor-url .code:n = \exp_args:NNx \prop_gput:Nnn \g__osglistings_lang_url_prop { \l__osglistings_lang_name_tl } {#1}, % \ldeen{Eigene, bevorzugte Vorlage für Gist-Quellen (typischerweise % mit \code{\{GIST\_ID\}} statt \code{\{CODE\}}, z.\,B.\ Rust % Playgrounds \code{gist=}-Parameter) -- \code{editor-url} bleibt % der Fallback für alle anderen Quellen.}{Own, preferred template for % gist sources (typically using \code{\{GIST\_ID\}} instead of % \code{\{CODE\}}, e.g.\ Rust Playground's \code{gist=} parameter) -- % \code{editor-url} remains the fallback for every other source.} editor-url-gist .code:n = \exp_args:NNx \prop_gput:Nnn \g__osglistings_lang_gisturl_prop { \l__osglistings_lang_name_tl } {#1}, } \tl_new:N \l__osglistings_lang_name_tl \cs_new_protected:Npn \DeclareOsgListingsLanguage #1 #2 { \tl_set:Nn \l__osglistings_lang_name_tl {#1} \keys_set:nn { osglistings/language } {#2} } \NewDocumentCommand \osglistingshextile { m } { \tikz[baseline=-0.5ex]{ \node[regular~polygon, draw, regular~polygon~sides=6, shape~border~rotate=30, inner~sep=.5pt, fill=black, text=white, font=\tiny\bfseries\sffamily] {#1}; } } % \ldeen{@1 in den URL-Vorlagen ist ein Platzhalter für den (in Phase~2 % hinzukommenden) codierten Quelltext -- in Phase~1 unbenutzt, aber schon % hinterlegt, damit sich das Sprachregister später nicht ändert.}{@1 in % the URL templates is a placeholder for the (Phase~2) encoded source -- % unused in Phase~1, but already stored so the language registry doesn't % need to change later.}{\{CODE\}} \DeclareOsgListingsLanguage { rust } { icon = \faRust, editor-url = { https://play.rust-lang.org/?version=stable\&mode=debug\&edition=2021\&code={CODE} }, editor-url-gist = { https://play.rust-lang.org/?version=stable\&mode=debug\&edition=2021\&gist={GIST_ID} } } \DeclareOsgListingsLanguage { python } { icon = \faPython, editor-url = { https://trinket.io/python3?code={CODE} } } \DeclareOsgListingsLanguage { c } { icon = \osglistingshextile{C}, editor-url = { https://code.livegap.com/?l=c\&code={CODE} } } \DeclareOsgListingsLanguage { cpp } { icon = \osglistingshextile{\smash{\raisebox{1pt}{\scalebox{.7}{C++}}}}, editor-url = { https://code.livegap.com/?l=cpp\&code={CODE} } } \cs_new:Npn \__osglistings_lang_icon:n #1 { \prop_item:Nn \g__osglistings_lang_icon_prop {#1} } % \subsection{\ldeen{Tagging}{Tagging}} % \ldeen{Tagging darf nicht erfolgen, wenn das Dokument nicht mit @1 % geladen wurde.}{Tagging must not happen when the document was not % loaded with @1.}{\cs{DocumentMetadata}\Marg{tagging=on}} \cs_new_protected:Npn \__osglistings_tag_struct_begin:n #1 { \cs_if_exist:NT \tagstructbegin { \tagstructbegin{ tag=#1 } } } \cs_new_protected:Npn \__osglistings_tag_struct_end: { \cs_if_exist:NT \tagstructend { \tagstructend } } \cs_if_exist:NT \NewStructureName { \NewStructureName{ osglistings/code } \AssignStructureRole{ osglistings/code }{ Code } } % \subsection{\ldeen{Schlüssel}{Keys}} \tl_new:N \l__osglistings_style_tl \tl_new:N \l__osglistings_title_tl \tl_new:N \l__osglistings_langicon_tl \tl_new:N \l__osglistings_link_tl \tl_new:N \l__osglistings_linkdisplay_tl \tl_new:N \l__osglistings_palette_tl \bool_new:N \l__osglistings_tag_bool \tl_new:N \l__osglistings_tagname_tl \tl_new:N \l__osglistings_extra_tl \tl_new:N \l__osglistings_cache_tl \tl_new:N \l__osglistings_remotecheck_tl \bool_new:N \l__osglistings_remoterefresh_bool \tl_new:N \l__osglistings_marks_tl \tl_new:N \l__osglistings_marksescape_tl \keys_define:nn { osglistings } { style .choices:nn = { plain, paper, editor } { \tl_set:Nx \l__osglistings_style_tl { \l_keys_choice_tl } }, title .tl_set:N = \l__osglistings_title_tl, lang-icon .tl_set:N = \l__osglistings_langicon_tl, link .tl_set:N = \l__osglistings_link_tl, link-display .choices:nn = { titlebar, label, none, qrcode } { \tl_set:Nx \l__osglistings_linkdisplay_tl { \l_keys_choice_tl } }, palette .tl_set:N = \l__osglistings_palette_tl, % \ldeen{Nur für \cs{osglistinginput} relevant; siehe Abschnitt % \glqq Zeichengenaue Markierungen\grqq.}{Only relevant for % \cs{osglistinginput}; see the \glqq Character-level marks\grqq{} % section.} marks .tl_set:N = \l__osglistings_marks_tl, marks-escape .tl_set:N = \l__osglistings_marksescape_tl, tag .bool_set:N = \l__osglistings_tag_bool, tag .default:n = true, tag-name .tl_set:N = \l__osglistings_tagname_tl, % \ldeen{Nur für \code{gist:}/\code{github:}-Quellen relevant; steuert, % ob eine vorhandene, gecachte Fassung wiederverwendet wird.}{Only % relevant for \code{gist:}/\code{github:} sources; controls whether % an existing cached copy is reused.} cache .choices:nn = { true, false, refresh } { \tl_set:Nx \l__osglistings_cache_tl { \l_keys_choice_tl } }, % \ldeen{Paketweite Option (nicht pro Aufruf): automatischer % Aktualisierungscheck beim Kompilieren -- bewusst standardmäßig aus, % siehe Sicherheitshinweis im Abschnitt \glqq Remote-Quellen\grqq.} % {Package-wide option (not per call): automatic update check on % compile -- deliberately off by default, see the security note in % the \glqq Remote sources\grqq{} section.} remote-check .choices:nn = { manual, compile } { \tl_set:Nx \l__osglistings_remotecheck_tl { \l_keys_choice_tl } }, remote-refresh .bool_set:N = \l__osglistings_remoterefresh_bool, % \ldeen{@1 statt @2: \cs{l_keys_key_str} ist eine % gemeinsam genutzte l3keys-Variable, die \pkg*{minted}s eigener % \cs{keys_set:nn}-Aufruf (beim späteren Verarbeiten von @3) % selbst wieder überschreibt -- ohne sofortige Expansion würde % hier nur eine \emph{Referenz} gespeichert, die zum % Verwendungszeitpunkt längst einen anderen Schlüsselnamen % trägt. @4 schützt dagegen den Wert @5 selbst vor dieser Expansion: % Ein Wert wie \cs{tiny} expandiert unter \cs{edef}/@1 zu % \cs{let}\cs{@currsize}\cs{tiny} -- das erneut lesbare \cs{tiny} % am Ende expandiert die \cs{edef}-Maschinerie sofort weiter, und % das wiederholt sich ohne Ende, bis der Eingabestack überläuft.} % {@1 instead of @2: \cs{l_keys_key_str} is a shared l3keys % variable that \pkg*{minted}'s own \cs{keys\_set:nn} call (when % later processing @3) overwrites again itself -- without % immediate expansion this would only store a \emph{reference}, % which by the time it's used already holds some other key's % name. @4 in turn protects the value @5 itself from that same % expansion: a value such as \cs{tiny} expands under \cs{edef}/@1 % to \cs{let}\cs{@currsize}\cs{tiny} -- the freshly re-exposed % \cs{tiny} at the end gets expanded again immediately by the same % \cs{edef} machinery, and that repeats without end until the input % stack overflows.} % {\cs{tl\_set:Nx}}{\cs{tl\_set:Nn}}{\cs{l\_\_osglistings\_extra\_tl}} % {\cs{exp\_not:n}}{\code{#1}} unknown .code:n = { \tl_if_empty:NTF \l__osglistings_extra_tl { \tl_set:Nx \l__osglistings_extra_tl { \l_keys_key_str = { \exp_not:n {#1} } } } { \tl_put_right:Nx \l__osglistings_extra_tl { , \l_keys_key_str = { \exp_not:n {#1} } } } }, } \keys_set:nn { osglistings } { style = plain, title = {}, lang-icon = auto, link = {}, link-display = titlebar, palette = {}, tag = true, tag-name = osglistings/code, cache = true, remote-check = manual, remote-refresh = false, marks = {}, marks-escape = §, } % \subsection{\ldeen{Rahmenstile}{Box styles}} % \ldeen{Sprachsymbol oben links, in allen drei Stilen gleich % positioniert.}{Language badge top-left, positioned the same way in % all three styles.} \cs_new_protected:Npn \__osglistings_overlay_langicon:n #1 { \tl_if_eq:NnF \l__osglistings_langicon_tl { none } { \node [anchor=north~west, inner~sep=2pt, font=\scriptsize] at (frame.north~west) { \tl_if_eq:NnTF \l__osglistings_langicon_tl { auto } { \__osglistings_lang_icon:n {#1} } { \l__osglistings_langicon_tl } }; } } % \ldeen{Bearbeiten-Symbol oben rechts, nur wenn ein Link gesetzt und % @1 gewählt ist.}{Edit-link icon top-right, only when a link is set % and @1 is selected.}{\code{link-display=titlebar}} \cs_new_protected:Npn \__osglistings_overlay_link: { \tl_if_empty:NF \l__osglistings_link_tl { \tl_if_eq:NnT \l__osglistings_linkdisplay_tl { titlebar } { \node [anchor=north~east, inner~sep=2pt, font=\scriptsize] at (frame.north~east) { \href { \l__osglistings_link_tl } { \faEdit[regular] } }; } } } \cs_new_protected:Npn \__osglistings_below_link: { \tl_if_empty:NF \l__osglistings_link_tl { \tl_if_eq:NnT \l__osglistings_linkdisplay_tl { label } { \par\noindent\hfill {\scriptsize\href{\l__osglistings_link_tl}{\faLink~\ttfamily\l__osglistings_link_tl}}\par } % \ldeen{Anders als \code{link-display=label} steht der Link hier % nirgends als sichtbarer Text -- der QR-Code ist der einzige Weg % dorthin. Kein lokales \cs{qrset}\Marg{tag=...} nötig: der % zentrale \pkg{qrcode}-Tagging-Hook aus \File{osglecture.dtx} % taggt ihn per Default als \code{Figure} mit der URL als % Alt-Text.}{Unlike \code{link-display=label}, the link appears % nowhere as visible text here -- the QR code is the only way to % it. No local \cs{qrset}\Marg{tag=...} needed: the central % \pkg{qrcode} tagging hook in \File{osglecture.dtx} tags it as a % \code{Figure} by default, with the URL as alt text.} \tl_if_eq:NnT \l__osglistings_linkdisplay_tl { qrcode } { \par\noindent\hfill \qrcode*[height=1.5cm]{\l__osglistings_link_tl}\par } } } \tcbset { osglistings/plain/.style = { enhanced, breakable, colframe = osglistings@color@frame, colback = osglistings@color@background, boxrule = 0.4pt, arc = 0pt, left=2mm, right=2mm, top=1mm, bottom=1mm, boxsep = .5ex, fontupper = \osglistingsmonofont, }, osglistings/paper/.style = { enhanced, breakable = false, colframe = osglistings@color@background, colback = osglistings@color@background, boxrule = 0pt, left=4mm, right=3mm, top=3mm, bottom=3mm, boxsep = .5ex, fontupper = \osglistingsmonofont, frame~code = { \pgfmathsetseed{13} \fill[very~thin, osglistings@color@background, draw=black!30!white, decoration={random~steps,segment~length=3pt,amplitude=1pt,raise=2pt, pre=lineto,post=lineto,pre~length=2pt,post~length=2pt},decorate] (interior.north~west) -- ++(0,2pt) -| (interior.north~east); \fill[very~thin, osglistings@color@background, draw=black!30!white, decoration={random~steps,segment~length=3pt,amplitude=1pt,raise=-2pt, pre=lineto,post=lineto,pre~length=2pt,post~length=2pt},decorate] (interior.south~west) -- ++(0,-2pt) -| (interior.south~east); }, }, osglistings/editor/.style = { enhanced, breakable, colframe = osglistings@color@frame, colback = osglistings@color@background, coltitle = osglistings@color@titlebar-fg, colbacktitle = osglistings@color@titlebar-bg, title = \l__osglistings_title_tl, center~title, fonttitle = \scriptsize\ttfamily, boxrule = 0.4pt, left=2mm, right=2mm, top=1mm, bottom=1mm, boxsep = .5ex, fontupper = \osglistingsmonofont, }, } \cs_new_protected:Npn \__osglistings_box_begin:n #1 { \tl_if_eq:NnTF \l__osglistings_style_tl { paper } { \begin{tcolorbox}[ osglistings/paper, overlay = { \__osglistings_overlay_langicon:n {#1} \__osglistings_overlay_link: } ] } { \tl_if_eq:NnTF \l__osglistings_style_tl { editor } { \begin{tcolorbox}[ osglistings/editor, overlay~first = { \__osglistings_overlay_langicon:n {#1} \__osglistings_overlay_link: }, overlay~unbroken = { \__osglistings_overlay_langicon:n {#1} \__osglistings_overlay_link: } ] } { \begin{tcolorbox}[ osglistings/plain, overlay~first = { \__osglistings_overlay_langicon:n {#1} \__osglistings_overlay_link: }, overlay~unbroken = { \__osglistings_overlay_langicon:n {#1} \__osglistings_overlay_link: } ] } } } \cs_new_protected:Npn \__osglistings_box_end: { \end{tcolorbox} \__osglistings_below_link: } % \subsection{\ldeen{Remote-Quellen und Lua-Anbindung}{Remote sources and Lua bridge}} \ExplSyntaxOff \newcommand\osglistingsluainit{% % A missing osglistings.lua (e.g.\ an unfinished local install) must % not turn ordinary package loading into a fatal error -- only actually % using a gist:/github: locator should fail, and only then. \ifdefined\directlua \directlua{local found; found, osglistings = pcall(require, "osglistings"); if not found then osglistings = nil end}% \fi } \newcommand\osglistingsluafetch[2]{% % \#1 = locator, \#2 = cache mode (true/false/refresh) \directlua{ local bs = string.char(92) local locator = "\luaescapestring{#1}" if not osglistings then tex.error("osglistings: the Lua module osglistings.lua was not found; remote sources are unavailable") else local ok, result = pcall(osglistings.fetch, { locator = locator, cache_dir = "\luaescapestring{\jobname}.osglistings-cache", cache = "\luaescapestring{#2}", }) if ok then local loc = osglistings.parse_locator(locator) tex.print(bs .. "gdef" .. bs .. "gosglistingsresolvedpath{" .. result .. "}") tex.print(bs .. "gdef" .. bs .. "gosglistingsresolvedkind{" .. loc.kind .. "}") tex.print(bs .. "gdef" .. bs .. "gosglistingsresolvedgistid{" .. (loc.id or "") .. "}") else tex.error("osglistings: could not fetch '" .. locator .. "': " .. tostring(result)) end end }% } \newcommand\osglistingsluabuildlink[2]{% % \#1 = editor-url template (may contain \{CODE\}/\{GIST\_ID\}), \#2 = resolved content path \directlua{ local bs = string.char(92) local url = "\luaescapestring{#1}" url = osglistings.replace_once(url, "{GIST_ID}", "\luaescapestring{\gosglistingsresolvedgistid}") if url:find("{CODE}", 1, true) then local f = io.open("\luaescapestring{#2}", "rb") local content = f and f:read("*a") or "" if f then f:close() end url = osglistings.replace_once(url, "{CODE}", osglistings.percent_encode(content)) end tex.print(bs .. "gdef" .. bs .. "gosglistingsbuiltlink{" .. url .. "}") }% } \newcommand\osglistingsluacheckupdates[1]{% % \#1 = "true"/"false" -- refresh stale entries immediately? \directlua{ local bs = string.char(92) if osglistings then local ok, report = pcall(osglistings.check_updates, "\luaescapestring{\jobname}.osglistings-cache", { refresh = ("\luaescapestring{#1}" == "true") }) if ok then for _, e in ipairs(report) do local refreshed = {} for _, r in ipairs(e.refreshed) do refreshed[r] = true end for _, loc in ipairs(e.stale_files) do if not refreshed[loc] then tex.print(bs .. "osglistingsremotestalewarning{" .. loc .. "}{" .. e.type .. " " .. e.key .. "}") end end end end end }% } \newcommand\osglistingsluaapplymarks[3]{% % \#1 = source path, \#2 = marks spec, \#3 = escape char (used for both % the escapeinside open and close delimiter) \directlua{ local bs = string.char(92) if not osglistings then tex.error("osglistings: the Lua module osglistings.lua was not found; marks are unavailable") else local ok, result = pcall(osglistings.inject_marks, { path = "\luaescapestring{#1}", marks = "\luaescapestring{#2}", escape_open = "\luaescapestring{#3}", escape_close = "\luaescapestring{#3}", cache_dir = "\luaescapestring{\jobname}.osglistings-cache", }) if ok then tex.print(bs .. "gdef" .. bs .. "gosglistingsmarkedpath{" .. result .. "}") else tex.error("osglistings: could not apply marks to '" .. "\luaescapestring{#1}" .. "': " .. tostring(result)) end end }% } \ExplSyntaxOn \osglistingsluainit \msg_new:nnn { osglistings } { luatex-required-remote } { Remote~sources~(gist:/github:)~require~LuaLaTeX. } \msg_new:nnn { osglistings } { luatex-required-marks } { Character-level~marks~(the~marks~key)~require~LuaLaTeX. } \msg_new:nnn { osglistings } { remote-stale } { Cached~source~'#1'~may~be~out~of~date~(#2).~Run~texlua~osglistings-check-updates.lua,~or~set~cache=refresh. } \cs_new_protected:Npn \osglistingsremotestalewarning #1 #2 { \msg_warning:nnnn { osglistings } { remote-stale } {#1} {#2} } \bool_new:N \g__osglistings_was_remote_bool \tl_new:N \g__osglistings_remote_kind_tl \tl_new:N \g__osglistings_remote_gistid_tl \cs_new_protected:Npn \__osglistings_resolve_remote:nN #1 #2 { \sys_if_engine_luatex:TF { \osglistingsluafetch{#1}{\l__osglistings_cache_tl} \tl_set:Nx #2 { \gosglistingsresolvedpath } \bool_gset_true:N \g__osglistings_was_remote_bool \tl_gset:Nx \g__osglistings_remote_kind_tl { \gosglistingsresolvedkind } \tl_gset:Nx \g__osglistings_remote_gistid_tl { \gosglistingsresolvedgistid } } { \msg_fatal:nn { osglistings } { luatex-required-remote } } } \cs_new_protected:Npn \__osglistings_input_path:nN #1 #2 { \bool_gset_false:N \g__osglistings_was_remote_bool \regex_match:nnTF { \A (?: gist | github ) : } {#1} { \__osglistings_resolve_remote:nN {#1} #2 } { \tl_set:Nn #2 {#1} } } % \subsection{\ldeen{Zeichengenaue Markierungen}{Character-level marks}} % \ldeen{@1 legt an dieser Stelle einen namentlich adressierbaren, % unsichtbaren \pkg*{tikz}-Knoten ab (@2); ein späteres @3 kann von % oder zu ihm zeichnen. Die Auflösung passiert erst im zweiten % Kompilierlauf, wie bei jedem seitenübergreifenden \pkg*{tikz}-Overlay.} % {@1 drops an addressable, invisible \pkg*{tikz} node at this exact % spot (@2); a later @3 can then draw to or from it. Resolution only % happens on the second compile pass, like any cross-element % \pkg*{tikz} overlay.}{\cs{osglistingsmark}}{\code{remember~picture}} % {\code{tikzpicture}} % \ldeen{@1 im Knoteninhalt gibt dem sonst punktförmigen Knoten die % Höhe/Tiefe eines Zeichens der aktuell aktiven (Mono-)Schrift, ohne % seine Breite zu verändern -- @2 verankert den Knoten trotzdem exakt % an der Einfügestelle (der Textbasislinie), so dass bestehende % Verweise auf \code{osgm-}\meta{name} als Punkt unverändert bleiben; % erst @3 nutzt die neu gewonnene Ausdehnung, um zwei solcher Knoten % zu einer Box zu verbinden.}{@1 in the node content gives the % otherwise point-sized node the height/depth of a character in the % currently active (mono) font, without changing its width -- @2 % still pins the node exactly at the insertion point (the text % baseline), so that existing references to \code{osgm-}\meta{name} % as a point are unaffected; only @3 makes use of the newly gained % extent to connect two such nodes into a box.} % {\cs{vphantom\{Xg\}}}{\code{anchor=base}}{\cs{osglistingshighlight}} \NewDocumentCommand \osglistingsmark { m } { \tikz[remember~picture,~overlay,~baseline=-0.5ex] \node[inner~sep=0pt,~anchor=base](osgm-#1){\vphantom{Xg}}; } % \ldeen{Verbindet zwei per @1 (oder per \code{marks}-Schlüssel mit % \code{around}) gesetzte Knoten \code{osgm-}\meta{name}\code{-begin} % und \code{osgm-}\meta{name}\code{-end} zu einer Markierungsbox -- % dank der in @1 gemessenen Höhe/Tiefe ohne manuelle % \code{xshift}/\code{yshift}-Korrekturen. @2 reicht beliebige % weitere \pkg*{tikz}-Optionen durch, z.\,B. um Farbe oder % Eckenradius zu ändern; die Voreinstellung zeichnet eine % halbtransparente, gelbe, abgerundete Box hinter (dank Deckkraft % durchscheinendem) Text.}{Connects two nodes % \code{osgm-}\meta{name}\code{-begin} and % \code{osgm-}\meta{name}\code{-end} placed via @1 (or via the % \code{marks} key's \code{around}) into a highlight box -- thanks to % the height/depth measured in @1, with no manual % \code{xshift}/\code{yshift} corrections needed. @2 passes through % arbitrary further \pkg*{tikz} options, e.g.\ to change color or % corner radius; the default draws a semi-transparent, yellow, % rounded box behind (translucent, so the) text (shows through).} % {\cs{osglistingsmark}}{\Oarg{tikz~options}} \NewDocumentCommand \osglistingshighlight { O{} m } { \osglistingsartifact { \tikz[remember~picture,~overlay] \node[fill=yellow,~fill~opacity=0.4,~draw=none,~rounded~corners=1pt, inner~sep=1pt,~fit=(osgm-#2-begin)(osgm-#2-end),~#1]{}; } } % \ldeen{Markiert #1 als \code{Artifact} (ist Tagging aktiv) -- für rein % dekoratives Beiwerk wie Pfeile oder Beschriftungen eines Overlays, % das assistive Technologien überspringen sollen, weil die eigentliche % Information bereits über einen \code{alt}-Text am Quelltext selbst % hängt (siehe \code{desc=} bei \code{around}). Ohne Tagging (oder % ohne @1) ein reines Durchreichen.}{Marks #1 as an \code{Artifact} % (when tagging is active) -- for purely decorative overlay chrome % like arrows or labels that assistive technology should skip, % because the real information already hangs off the source itself % via an \code{alt} text (see \code{desc=} on \code{around}). Without % tagging (or without @1) a plain passthrough.} % {\cs{DocumentMetadata}\Marg{tagging=on}} \NewDocumentCommand \osglistingsartifact { m } { \cs_if_exist:NT \tagstructbegin { \tagstructbegin{ tag=Artifact } } #1 \cs_if_exist:NT \tagstructend { \tagstructend } } % \ldeen{Das Gegenstück zu @1 für den \code{desc=}-Fall von % \code{around}: statt eines bloßen Punktpaars @2/@3 öffnet @4 (bzw. % schließt @5) zusätzlich eine getaggte \code{Span}-Struktur mit dem % angegebenen Text als \code{alt}-Attribut -- assistive Technologien % lesen dann diesen Text statt (oder zusätzlich zu) dem markierten % Quelltext selbst. Ohne Tagging ein reines @2/@3-Punktpaar wie % sonst auch.}{The counterpart to @1 for \code{around}'s \code{desc=} % case: instead of a bare point pair @2/@3, @4 (resp.\ @5) also opens % (resp.\ closes) a tagged \code{Span} structure carrying the given % text as its \code{alt} attribute -- assistive technology then reads % that text instead of (or alongside) the marked-up source itself. % Without tagging, a plain @2/@3 point pair like any other.} % {\cs{osglistingsmark}}{\code{osgm-}\meta{name}\code{-begin}} % {\code{osgm-}\meta{name}\code{-end}} % {\cs{osglistingsmarkspanbegin}}{\cs{osglistingsmarkspanend}} \NewDocumentCommand \osglistingsmarkspanbegin { m m } { \osglistingsmark{#1-begin} \cs_if_exist:NT \tagstructbegin { % \tagstructbegin alone only pushes an (empty) structure element; % \tagmcbegin is what actually attaches the following glyphs to % it -- the \alt attribute has to sit on the former, since that's % the one \tagmcbegin's own `alt` key would only stamp onto the % raw MC dictionary, not onto a real child structure element. \tagstructbegin{ tag=Span, alt={#2} } \tagmcbegin{ tag=Span } } } \NewDocumentCommand \osglistingsmarkspanend { m } { \cs_if_exist:NT \tagstructbegin { \tagmcend \tagstructend } \osglistingsmark{#1-end} } \cs_new_protected:Npn \__osglistings_apply_marks:N #1 { \tl_if_empty:NF \l__osglistings_marks_tl { \sys_if_engine_luatex:TF { \osglistingsluaapplymarks { #1 } { \l__osglistings_marks_tl } { \l__osglistings_marksescape_tl } \tl_set:Nx #1 { \gosglistingsmarkedpath } } { \msg_fatal:nn { osglistings } { luatex-required-marks } } } } \cs_new_protected:Npn \__osglistings_maybe_derive_link:n #1 % #1 = language { \bool_if:NT \g__osglistings_was_remote_bool { \tl_if_empty:NT \l__osglistings_link_tl { \tl_clear:N \l__osglistings_tmp_tl \tl_if_eq:NnT \g__osglistings_remote_kind_tl { gist } { \prop_get:NnNTF \g__osglistings_lang_gisturl_prop {#1} \l__osglistings_tmp_tl { } { } } \tl_if_empty:NT \l__osglistings_tmp_tl { \prop_get:NnNTF \g__osglistings_lang_url_prop {#1} \l__osglistings_tmp_tl { } { } } \tl_if_empty:NF \l__osglistings_tmp_tl { \sys_if_engine_luatex:T { \osglistingsluabuildlink { \l__osglistings_tmp_tl } { \l__osglistings_input_path_tl } \tl_set:Nx \l__osglistings_link_tl { \gosglistingsbuiltlink } } } } } } \cs_new_protected:Npn \__osglistings_check_updates_now: { \sys_if_engine_luatex:T { \bool_if:NTF \l__osglistings_remoterefresh_bool { \osglistingsluacheckupdates{true} } { \osglistingsluacheckupdates{false} } } } \AtEndDocument{ \tl_if_eq:NnT \l__osglistings_remotecheck_tl { compile } { \__osglistings_check_updates_now: } } % \subsection{\ldeen{Öffentliche Schnittstelle}{Public interface}} \cs_new_protected:Npn \__osglistings_setup:n #1 { \tl_clear:N \l__osglistings_extra_tl \keys_set:nn { osglistings } {#1} \tl_if_empty:NF \l__osglistings_palette_tl { \UseOsgListingsPalette { \l__osglistings_palette_tl } } \tl_if_empty:NF \l__osglistings_marks_tl { \tl_if_empty:NTF \l__osglistings_extra_tl { \tl_set:Nx \l__osglistings_extra_tl { escapeinside = \l__osglistings_marksescape_tl \l__osglistings_marksescape_tl } } { \tl_put_right:Nx \l__osglistings_extra_tl { , escapeinside = \l__osglistings_marksescape_tl \l__osglistings_marksescape_tl } } } } % \ldeen{@1 muss ausgeführt werden, \emph{bevor} die umschließende % @2-Box geöffnet wird -- @1 merkt sich die gerade aktive Umgebung % (\code{osglisting}) als Endemarke für die spätere Verbatim-Suche; % würde @2 zuerst geöffnet, würde stattdessen \emph{diese} zur (im % Quelltext nie erscheinenden) Endemarke.}{@1 has to run \emph{before} % the enclosing @2 box opens -- @1 records the currently active % environment (\code{osglisting}) as the end marker for the later % verbatim search; opening @2 first would make \emph{that} the end % marker instead, which never appears in the source.} % {\cs{VerbatimEnvironment}}{\pkg*{tcolorbox}} \NewDocumentEnvironment { osglisting } { O{} m } { \group_begin: \VerbatimEnvironment \__osglistings_setup:n {#1} \__osglistings_box_begin:n {#2} \bool_if:NT \l__osglistings_tag_bool { \__osglistings_tag_struct_begin:n { \UseStructureName{ \l__osglistings_tagname_tl } } } % \ldeen{@1 statt @2 -- @3 nimmt sein Argument als Kontrollsequenz % entgegen und würde diese sonst unexpandiert (also den % Variablennamen statt ihres Inhalts) an \pkg*{pgfkeys} % weiterreichen.}{@1 instead of @2 -- @3 takes its argument as a % bare control sequence and would otherwise forward it % unexpanded (i.e.\ the variable's name rather than its content) % to \pkg*{pgfkeys}.}{\cs{exp\_args:No}}{a~plain~brace~group} % {\cs{setminted}} \exp_args:No \setminted { \l__osglistings_extra_tl } \begin{minted}{#2} } { \end{minted} \bool_if:NT \l__osglistings_tag_bool { \__osglistings_tag_struct_end: } \__osglistings_box_end: \group_end: } \tl_new:N \l__osglistings_input_path_tl \NewDocumentCommand \osglistinginput { O{} m m } { \group_begin: \__osglistings_setup:n {#1} \__osglistings_input_path:nN {#3} \l__osglistings_input_path_tl \__osglistings_maybe_derive_link:n {#2} \__osglistings_apply_marks:N \l__osglistings_input_path_tl \__osglistings_box_begin:n {#2} \bool_if:NT \l__osglistings_tag_bool { \__osglistings_tag_struct_begin:n { \UseStructureName{ \l__osglistings_tagname_tl } } } % \ldeen{@1 statt einer eckigen Klammer mit @2 darin -- @2 wäre % dort nur ein unexpandiertes Token; @3 setzt dieselben % Optionen global (innerhalb der \cs{group\_begin:}/\cs{group\_end:} % dieses Aufrufs also effektiv lokal) und wirkt genauso auf das % folgende, klammerlose @4.}{@1 instead of a bracket option % holding @2 -- @2 there would be just an unexpanded token; @3 % sets the same options globally (effectively local within this % call's \cs{group\_begin:}/\cs{group\_end:}) and applies just as % well to the following, bracket-less @4.} % {\cs{exp\_args:No}\cs{setminted}}{\cs{l\_\_osglistings\_extra\_tl}} % {\cs{setminted}}{\cs{inputminted}} \exp_args:No \setminted { \l__osglistings_extra_tl } \inputminted {#2} { \l__osglistings_input_path_tl } \bool_if:NT \l__osglistings_tag_bool { \__osglistings_tag_struct_end: } \__osglistings_box_end: \group_end: } \NewDocumentCommand \OsgListingsSetup { m } { \keys_set:nn { osglistings } {#1} } \ExplSyntaxOff \makeatother %