% \iffalse
%% Source File: shellesc.dtx
%% Copyright (C) 2015-2025
%%
%% The LaTeX Project and any individual authors listed elsewhere
%% in this file.
%%
%% This file may be distributed under the terms of the LPPL.
%% See README for details.
%
%<*dtx>
          \ProvidesFile{shellesc.dtx}
%</dtx>
%<package>\ifx\ProvidesPackage\undefined
%<package>\def\next#1#2[#3]{\wlog{#2 #3}}
%<package>\ifx\PackageInfo\undefined\def\PackageInfo#1#2{\wlog{#1: #2}}\fi
%<package>\ifx\PackageWarning\undefined\def\PackageWarning#1#2{\wlog{#1: #2}}\fi
%<package>\expandafter\next\fi
%<package>\ProvidesPackage{shellesc}
%<driver> \ProvidesFile{shellesc.drv}
% \fi
%         \ProvidesFile{shellesc.dtx}
       [2023/07/08 v1.0d unified shell escape interface for LaTeX]
%
% \iffalse
%<*driver>
\documentclass{ltxdoc}
\begin{document}
\DocInput{shellesc.dtx}
\end{document}
%</driver>
% \fi
%
% \GetFileInfo{shellesc.dtx}
%
% \title{The \textsf{shellesc} Package\thanks{This file
%        has version number \fileversion, last
%        revised \filedate.}}
% \author{\LaTeX\ project}
% \date{\filedate}
%
%
% \maketitle
%
% \vspace*{-\baselineskip}
% \enlargethispage{\baselineskip}
%
% \changes{v0.1b}{2016/02/02}{Doc typo fixes (JB)}
% \changes{v0.2a}{2016/06/07}{Improve use with plain TeX}
% \section{Introduction}
%
%
% For many years web2c-based \TeX\ implementations have used the syntax
% of the \verb|\write| command to access  system commands by using a
% special stream 18 (streams above 15 can not be allocated to files in
% classical \TeX\ so stream 18 would otherwise just print to the
% terminal).
%
% This is a useful extension that did not break the strict rules on
% extensions in classical \TeX.  This package provides a simple
% macro level interface hiding the \verb|write18| implementation
% so a command to remove a file on a Unix-like system could be
% specified  using \verb|\ShellEscape{rm file.txt}| (or \verb|del| in
% Windows). Note that by default system access is not allowed and
% \LaTeX\ will typically need to be called with the \verb|--shell-escape|
% command line option.
%
% The package may be used with standard \texttt{latex} or
% \texttt{pdflatex} or \texttt{xetex}, however it is mostly motivated by
% \texttt{lualatex} as from Lua\TeX~0.87 onwards Lua\TeX\ does \emph{not}
% support the \verb|\write18| syntax to access system commands: it has
% 256 write streams and stream 18 can be associated to a file and
% (without this package) has no special significance. This packge
% defines the same \verb|\ShellEscape| syntax in Lua\LaTeX, but the
% implementation is via Lua and the \verb|os.execute| function.
%
% \verb|\ShellEscape| in fact corresponds to \verb|\immediate\write18|
% (or \verb|\directlua|). Very rarely you may need to delay a system
% command until the current page is output (when page numbers are
% known), for this you could classically use \verb|\write18| (or
% (\verb|\latelua|). This package provides \verb|\DelayedShellEscape|
% as a common syntax for this use.
%
% The shell escape status may be queried by checking the integer (chardef)
% command \verb|\ShellEscapeStatus|, 0 (disabled) 1 (enabled) 2 (restricted).
%
% To aid porting existing documents to Lua\TeX~0.87 this package does
% overload the \verb|\write| command so that
% \verb|\write18{rm file.txt}|
% will work with Lua\TeX. Note that the redefinition of \verb|\write|
% can not detect whether \verb|\immediate| has been used,
% \verb|\immediate| will work as normal when writing to file streams
% or the terminal but the special case of stream 18 which is defined to
% use \verb|os.execute| always uses \verb|\directlua| (so corresponds
% to \verb|\immediate\write18|). In the rare situations that you need
% non-immediate \verb|\write18| in a document being ported to current
% Lua\TeX, you will need to change to use the
% \verb|\DelayedShellEscape| command.
%
% \section{Implementation}
%
%    \begin{macrocode}
%<*package>
%    \end{macrocode}
%
% \changes{v1.0b}{2019/10/17}{Catcode protection}
%    \begin{macrocode}
\chardef\shellesc@quotecat\catcode`\"
\chardef\shellesc@underscorecat\catcode`\_
\@makeother\"
\@makeother\_
%    \end{macrocode}
%
% \subsection{Status Check}
%
%
% \subsection{The shellesc package interface}
%
% \begin{macro}{\ShellEscapeStatus}
% \changes{v1.0a}{2019/10/13}{Command Introduced}
% Integer value with meanings 0 (shell escape disabled), 1 (shell escape allowed), 2 (restricted shell escape).
%
%    \begin{macrocode}
\chardef\ShellEscapeStatus
  \ifx\pdfshellescape\@undefined
    \ifx\shellescape\@undefined
      \ifx\directlua\@undefined
        \z@
      \else
        \directlua{%
          tex.sprint((status.shell_escape or os.execute()) .. " ")}
      \fi
    \else
      \shellescape
    \fi
  \else
    \pdfshellescape
  \fi
%    \end{macrocode}
% \end{macro}
%
% \changes{v0.2a}{2016/06/07}{spelling in messages}
%    \begin{macrocode}
\ifcase\ShellEscapeStatus
  \PackageWarning{shellesc}{Shell escape disabled}
\or
  \PackageInfo   {shellesc}{Unrestricted shell escape enabled}
\else
  \PackageInfo   {shellesc}{Restricted shell escape enabled}
\fi
%    \end{macrocode}
%
%
% \begin{macro}{\ShellEscape}
% \changes{v1.0a}{2019/10/13}{Lua logging for gh/195}
% Execute the supplied tokens as a system dependent command, assuming
% such execution is allowed.
%    \begin{macrocode}
\ifx\lastsavedimageresourcepages\@undefined
  \protected\def\ShellEscape{\immediate\write18 }
%    \end{macrocode}
%
%    \begin{macrocode}
\else
  \protected\def\ShellEscape{\directlua\ShellEscape@Lua}
\fi
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\DelayedShellEscape}
% \changes{v0.1c}{2016/04/29}{Define \cs{DelayedShellEscape} not \cs{ShellEscape}(UF)}
% \changes{v1.0a}{2019/10/13}{Lua logging for gh/195}
% Execute the supplied tokens as a system dependent command, when this
% node is shipped out with the completed page, assuming
% such execution is allowed.
%    \begin{macrocode}
\ifx\lastsavedimageresourcepages\@undefined
  \protected\def\DelayedShellEscape{\relax\write18 }
%    \end{macrocode}
%
%    \begin{macrocode}
\else
  \protected\def\DelayedShellEscape{\latelua\ShellEscape@Lua}
\fi
%    \end{macrocode}
% \end{macro}
%
%
%
% \begin{macro}{\ShellEscape@Lua}
% \changes{v1.0a}{2019/10/13}{loging for gh/195}
% \changes{v1.0d}{2023/04/15}{Add dots in messages gh/1008}
% Shared Lua code for \verb|\DelayedShellEscape| and \verb|\ShellEscape|.
%    \begin{macrocode}
\ifx\directlua\@undefined\else
\protected\def\ShellEscape@Lua#1{{%
local status, msg = os.execute("\luaescapestring{#1}")%
if status == nil then
    texio.write_nl("log",%
      "runsystem(" .. "\luaescapestring{#1}"%
       .. ")...(" .. msg .. ").\string\n")
  elseif status == 0 then
    texio.write_nl("log",%
      "runsystem(" .. "\luaescapestring{#1}"%
      .. ")...executed.\string\n")
  else
    texio.write_nl("log",%
      "runsystem(" .. "\luaescapestring{#1}"%
       .. ")...failed. " .. (msg or "") .. "\string\n")
  end
}}
\fi
%    \end{macrocode}
% \end{macro}
%
% \subsection{The write18 package interface}
%
% In web2c-based engines other than Lua\TeX, |\write18| may be used
% directly.  The same was true in older Lua\TeX, but from version 0.85
% onwards that is not available.
%
% The above |shellesc| package interface is recommended for new code,
% however for ease of porting existing documents and packages to newer
% Lua\TeX\ releases, a |\write18| interface is provided here via a
% call to Lua's |os.execute|.
%
% Note that as currently written this always does an \emph{immediate}
% call to the system.
%
% |\immediate| is supported but ignored, |\immediate\write18| and
% |\write18| both execute immediately. To use a delayed execution at
% the next shipout, use the |\DelayedShellEscape| command defined
% above.
%
% Note that it would be easy to make |\wriete18| defined here use
% delayed execution, just use |\DelayedShellEscape| instead of
% |ShellEscape| in the definition below. However detecting
% |\immediate| is tricky so the choice here is to always use the
% immediate form, which is overwhelmingly more commonly used with
% |\write18|.
%
% Stop at this point if not a recent Lua\TeX.
%    \begin{macrocode}
\ifx\lastsavedimageresourcepages\@undefined
 \catcode`\"\shellesc@quotecat
 \catcode`\_\shellesc@underscorecat
 \expandafter\endinput
\fi
%    \end{macrocode}
%
%    \begin{macrocode}
\directlua{%
%    \end{macrocode}
%
%    \begin{macrocode}
shellesc = shellesc or {}
%    \end{macrocode}
%
% Lua function to use the token scanner to grab the following \TeX\
% number, and then test if stream 18 is being used, and then insert an
% appropriate \TeX\ command to handle the following brace group in
% each case.
%    \begin{macrocode}
local function write_or_execute()
  local s = token.scan_int()
  if (s==18) then
     tex.sprint(\the\numexpr\catcodetable@atletter\relax,
                "\string\\ShellEscape ")
  else
     tex.sprint(\the\numexpr\catcodetable@atletter\relax,
                "\string\\shellesc@write " .. s)
  end
end
%    \end{macrocode}
%
%    \begin{macrocode}
shellesc.write_or_execute=write_or_execute
%    \end{macrocode}
%
%    \begin{macrocode}
}
%    \end{macrocode}
%
%    \begin{macrocode}
\let\shellesc@write\write
%    \end{macrocode}
%
%    \begin{macrocode}
\protected\def\write{\directlua{shellesc.write_or_execute()}}
%    \end{macrocode}
%
%    \begin{macrocode}
\catcode`\"\shellesc@quotecat
\catcode`\_\shellesc@underscorecat
%    \end{macrocode}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \Finale
%
