% \iffalse %% Source File: shellesc.dtx %% Copyright (C) 2015-2023 %% %% 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} % %\ifx\ProvidesPackage\undefined %\def\next#1#2[#3]{\wlog{#2 #3}} %\ifx\PackageInfo\undefined\def\PackageInfo#1#2{\wlog{#1: #2}}\fi %\ifx\PackageWarning\undefined\def\PackageWarning#1#2{\wlog{#1: #2}}\fi %\expandafter\next\fi %\ProvidesPackage{shellesc} % \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} % % \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} % % \end{macrocode} % % \Finale %