% \iffalse meta-comment
% SPDX-FileCopyrightText: Copyright (c) 2021-2026 Yegor Bugayenko
% SPDX-License-Identifier: MIT
% \fi

% \CheckSum{0}
%
% \CharacterTable
%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%   Digits        \0\1\2\3\4\5\6\7\8\9
%   Exclamation   \!     Double quote  \"     Hash (number) \#
%   Dollar        \$     Percent       \%     Ampersand     \&
%   Acute accent  \'     Left paren    \(     Right paren   \)
%   Asterisk      \*     Plus          \+     Comma         \,
%   Minus         \-     Point         \.     Solidus       \/
%   Colon         \:     Semicolon     \;     Less than     \<
%   Equals        \=     Greater than  \>     Question mark \?
%   Commercial at \@     Left bracket  \[     Backslash     \\
%   Right bracket \]     Circumflex    \^     Underscore    \_
%   Grave accent  \`     Left brace    \{     Vertical bar  \|
%   Right brace   \}     Tilde         \~}

% \GetFileInfo{iexec.dtx}
% \DoNotIndex{\endgroup,\begingroup,\let,\else,\fi,\newcommand,\newenvironment}

% \iffalse
%<*driver>
\ProvidesFile{iexec.dtx}
%</driver>
%<package>\NeedsTeXFormat{LaTeX2e}
%<package>\ProvidesPackage{iexec}
%<*package>
[2026-07-10 0.16.0 Inputable Shell Executions]
%</package>
%<*driver>
\documentclass{ltxdoc}
\usepackage{lmodern}
\usepackage{microtype}
\AddToHook{env/verbatim/begin}{\microtypesetup{protrusion=false}}
\usepackage{xcolor}
\usepackage[dtx]{docshots}
\usepackage{iexec}
\usepackage{href-ul}
\PageIndex
\EnableCrossrefs
\CodelineIndex
\RecordChanges
\begin{document}
  \DocInput{iexec.dtx}
  \PrintChanges
  \PrintIndex
\end{document}
%</driver>
% \fi

% \title{|iexec|: \LaTeX{} Package \\ for Inputable Shell Executions\thanks{The sources are in GitHub at \href{https://github.com/yegor256/iexec}{yegor256/iexec}}}
% \author{Yegor Bugayenko \\ \texttt{yegor256@gmail.com}}
% \date{\filedate, \fileversion}
%
% \maketitle
%
% \textbf{\color{red}NB!}
% This package does not operate on Windows!
%
% \section{Introduction}
%
% This package permits the execution of shell commands directly from within
% the document and the inclusion of their output in the resulting text:
%
% \begin{docshot}
% \documentclass{article}
% \usepackage{iexec}
% \usepackage[paperwidth=3in]{geometry}
% \pagestyle{empty}
% \begin{document}
% Today is \textbf{%
%   \iexec{date +\%e-\%b-\%Y}}\unskip!
% \end{document}
% \end{docshot}

% \DescribeMacro{\iexec} The sole command supplied by this package is
% |\iexec| \oarg{options} \marg{cmd}.
% Its only mandatory argument, \meta{cmd}, denotes the command to be executed
% through the terminal shell (|bash|, or whichever shell is configured as
% the default in the host console).

% Compilation must be performed with |pdflatex| (or simply |latex|)
% under the |--shell-escape| flag, so that
% \href{https://ctan.org/pkg/shellesc}{shellesc}
% may execute the supplied shell command.

% It is worth noting that \LaTeX{} invariably employs the ``|/bin/sh|'' shell.
% This behaviour cannot be altered, as
% \href{https://tex.stackexchange.com/questions/698312}{explained here}.

% \section{Options}

% \DescribeMacro{quiet}
% Should the output be required to remain invisible,
% |\phantom{\iexec{...}}| may be employed.
% Alternatively, the ``|quiet|'' option is available:
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
I just want to delete some file:
\iexec[quiet]{rm -f foo.txt}
\end{verbatim}
%\iffalse
%</verb>
%\fi
% Under this option, whatever the shell command produces is excluded
% from the document.

% \DescribeMacro{stdout}
% The output of the executed code is written to the file specified as an
% optional argument of |\iexec| (the default being ``|iexec.tmp|''):
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
Today is \iexec[stdout=date.txt]{date +\%e-\%b-\%Y | tr -d '\\n'}.
\end{verbatim}
%\iffalse
%</verb>
%\fi
% The trailing portion of the command above removes all line terminators.

% \DescribeMacro{stderr}
% The error output of the executed code is written to the file specified
% as an optional argument of |\iexec| (by default the error output
% is redirected into ``|stdout|''):
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
Today is \iexec[stderr=my.txt]{broken-command}.
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \DescribeMacro{exit}
% The exit code of the command is written to a file. The name of this file
% may be altered by means of the ``|exit|'' option:
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
Today is \iexec[exit=code.txt]{./broken-command.sh}.
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \DescribeMacro{trace}
% The file thus produced is deleted immediately after its use. To prevent
% this deletion, the ``|trace|'' package option may be employed: all files
% will then remain in the directory where they were created.
% Tracing may also be enabled globally, for the entire document, by means
% of the ``|trace|'' option of the package:
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
\documentclass{article}
\usepackage[trace]{iexec}
\begin{document}
This file is preserved: \iexec[stdout=me.txt]{whoami}.
\end{document}
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \DescribeMacro{append}
% The ``stdout'' thus produced is appended to the file specified:
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
\documentclass{article}
\usepackage[trace]{iexec}
\begin{document}
\iexec[append,stdout=foo.txt,quiet]{echo 'Hello, '}
\iexec[append,stdout=foo.txt,quiet]{echo 'Jeffrey!'}
\input{foo.txt}
\end{document}
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \DescribeMacro{unskip}
% To remove the trailing space following the content, the |unskip| package option may be employed,
% which appends an |\unskip| command to every |\iexec|:
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
\documentclass{article}
\usepackage[unskip]{iexec}
\begin{document}
Today is \iexec{date +\%Y}!
\end{document}
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \DescribeMacro{log}
% The ``stdout'' thus produced is printed to the \TeX{} log:
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
\iexec[log]{echo 'Hello, \\LaTeX!'}
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \DescribeMacro{null}
% The ``stdout'' of the command is redirected to ``|/dev/null|''
% (or to ``|NUL|'' on Windows):
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
\iexec[null]{rm some-file.txt}
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \DescribeMacro{ignore}
% By default, an error is reported whenever the exit code differs from zero. This behaviour
% may be suppressed by means of the ``|ignore|'' option:
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
\iexec[ignore]{broken-command}
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \DescribeMacro{maybe}
% In the absence of the |--shell-escape| flag, the |\iexec| command would normally produce a
% compilation failure. Such failure may be averted by means of the |maybe| option, whereby the
% execution of |\iexec| is silently skipped whenever |--shell-escape| is not in force:
%\iffalse
%<*verb>
%\fi
\begin{verbatim}
\iexec[maybe]{echo 'Hello, world!'}
\end{verbatim}
%\iffalse
%</verb>
%\fi

% \StopEventually{}

% \section{Implementation}

% First, we include the \href{https://ctan.org/pkg/shellesc}{shellesc} package, which
% we use in order to execute shell commands:
%    \begin{macrocode}
\RequirePackage{shellesc}
%    \end{macrocode}

% Then, we include the \href{https://ctan.org/pkg/pdftexcmds}{pdftexcmds} package, which
% provides |\pdf@filesize|, working uniformly across \texttt{pdftex},
% \texttt{luatex}, and \texttt{xetex} (the |\pdffilesize| primitive exists in
% \texttt{pdftex} only):
% \changes{0.16.1}{2026/07/10}{The file size is now printed to the log under \texttt{luatex} and \texttt{xetex} too, not only \texttt{pdftex}, because we use \texttt{\char`\\pdf@filesize} from \texttt{pdftexcmds} instead of the \texttt{\char`\\pdffilesize} primitive.}
%    \begin{macrocode}
\RequirePackage{pdftexcmds}
%    \end{macrocode}

% Then, we parse package options, with the help
% of \href{https://ctan.org/pkg/pgfopts}{pgfopts}:
% \changes{0.14.0}{2024/01/14}{The \texttt{xkeyval} package is not used anymore. Instead, we use \texttt{pgfopts} in order to parse package options.}
%    \begin{macrocode}
\RequirePackage{pgfopts}
\pgfkeys{
  /iexec/.cd,
  trace/.store in=\iexec@trace,
}
\ProcessPgfPackageOptions{/iexec}
%    \end{macrocode}

% Then, we prepare to parse the options of the |\iexec| command, with the help
% of \href{https://ctan.org/pkg/pgfkeys}{pgfkeys}:
%    \begin{macrocode}
\RequirePackage{pgfkeys}
\makeatletter\pgfkeys{
  /iexec/.is family,
  /iexec,
  exit/.store in = \iexec@exit,
  exit/.default = iexec.ret,
  stdout/.store in = \iexec@stdout,
  stdout/.default = iexec.tmp,
  stderr/.store in = \iexec@stderr,
  trace/.store in = \iexec@traceit,
  append/.store in = \iexec@append,
  log/.store in = \iexec@log,
  null/.store in = \iexec@null,
  unskip/.store in = \iexec@unskip,
  quiet/.store in = \iexec@quiet,
  ignore/.store in = \iexec@ignore,
  maybe/.store in = \iexec@maybe,
  stdout,exit
}\makeatother
%    \end{macrocode}

% \begin{macro}{\iexec@typeout}
% Then, we define an internal command |\iexec@typeout| for printing the content of a file,
% as suggested \href{https://tex.stackexchange.com/questions/660808}{here}:
%    \begin{macrocode}
\RequirePackage{expl3}
\makeatletter\ExplSyntaxOn
\NewDocumentCommand{\iexec@typeout}{m}{
  \iexec_typeout_file:n { #1 }}
\ior_new:N \g_iexec_typeout_ior
\cs_new_protected:Nn \iexec_typeout_file:n
{
  \ior_open:Nn \g_iexec_typeout_ior { #1 }
  \ior_str_map_inline:Nn \g_iexec_typeout_ior
    {\iow_term:n { ##1 }}
  \ior_close:N \g_iexec_typeout_ior
}
\ExplSyntaxOff\makeatother
%    \end{macrocode}
% \end{macro}

% Then, we detect the operating system, because a few shell fragments
% differ between Windows and Unix: the null device (|/dev/null| versus
% |NUL|), the file removal command (|rm| versus |del|), and the way the
% exit code of the command is captured.
% \changes{0.16.0}{2026/07/10}{The package now works on Windows too, not only on Unix, because the shell fragments are chosen depending on the operating system.}
%    \begin{macrocode}
\makeatletter
\newif\ifiexec@windows
\ExplSyntaxOn
\sys_if_platform_windows:TF{\iexec@windowstrue}{\iexec@windowsfalse}
\ExplSyntaxOff
\ifiexec@windows
  \def\iexec@devnull{NUL}
  \def\iexec@rm{del}
\else
  \def\iexec@devnull{/dev/null}
  \def\iexec@rm{rm}
\fi
\makeatother
%    \end{macrocode}

% \begin{macro}{\iexec}
% Then, we define |\iexec| command.
% It is implemented with the help of |\ShellEscape| from |shellesc| package:
% \changes{0.10.0}{2022/10/19}{The file "iexec.ret" is reused for all scripts.}
% \changes{0.10.0}{2022/10/19}{The option ``\texttt{ignore}'' suppresses the checking of ``\texttt{iexec.ret}'' value.}
% \changes{0.7.0}{2022/09/25}{The option "append" was introduced --- if it's turned on, stdout will be appended to the file, instead of rewriting it (this is how it was before).}
% \changes{0.7.0}{2022/09/25}{The option ``\texttt{log}'' was introduced, to turn on log/debug messages in TeX log (they were all visible always, which was sometimes annoying). Also, this option enables printing of the entire content of stdout to the log too (this may be pretty convenient for debugging).}
% \changes{0.11.0}{2022/10/22}{The option ``\texttt{exit}'' allows to change the name of the file with exit code.}
% \changes{0.12.0}{2023/10/12}{The option ``\texttt{unskip}'' adds \texttt{\char`\\unskip} after each \texttt{\char`\\iexec}, in order to strip the trailing end-of-line space.}
%    \begin{macrocode}
\makeatletter
\newread\iexec@exitfile
\newcommand\iexec[2][]{%
  \begingroup%
    \pgfqkeys{/iexec}{#1}%
%    \end{macrocode}
% First, we verify that |latex| is running with the |--shell-escape| option, since without
% it nothing will work; so, it's better to throw an error earlier than later:
%    \begin{macrocode}
    \ifnum\ShellEscapeStatus=1%
      \begingroup%
%    \end{macrocode}
% Then, we start the log from a clean line:
%    \begin{macrocode}
        \ifdefined\iexec@log%
          \message{^^J}%
        \fi%
%    \end{macrocode}
% Then, we define a few special chars in order to escape them in the shell
% (the full
% list of them is in \href{https://ctan.mirror.norbert-ruehl.de/info/macros2e/macros2e.pdf}{macros2e}):
%    \begin{macrocode}
        \let\%\@percentchar%
        \let\\\@backslashchar%
        \let\{\@charlb%
        \let\}\@charrb%
%    \end{macrocode}
% Then, we execute it and save exit code into a file (where we also add \texttt{\%} in order to trim the content to exactly one number, as suggested \href{https://tex.stackexchange.com/questions/662756}{here}):
% \changes{0.10.0}{2022/10/19}{The ability to track exit code was added. Now, the code is saved into ``\texttt{iexec.ret}'' file, which is then read and checked for zero value.}
% \changes{0.8.0}{2022/10/05}{The option "null" was introduced, allowing redirection of stdout to ``\texttt{/dev/null}''. Doesn't work on Windows, though.}
% \changes{0.9.0}{2022/10/15}{The option ``\texttt{stderr}'' was introduced, allowing redirection of stderr to a file. Without this option specified, stderr will go to stdout.}
% \changes{0.11.0}{2022/10/22}{The file with exit code now contains just numbers, without end of line.}
% \changes{0.11.1}{2022/10/23}{When exit code is printed to the file, we add percentchar at the end of line in order to avoid extra space when reading it back.}
% \changes{0.11.3}{2022/10/29}{Bug fixed, because of which we had an extra leading space.}
% \changes{0.11.4}{2022/11/01}{In this version we escape dollar sign with \texttt{\char`\\string} command.}
%    \begin{macrocode}
        \def\iexec@cmd{(#2)
          \ifdefined\iexec@append>\fi>
          \ifdefined\iexec@null\iexec@devnull\else\iexec@stdout\fi
          \space\ifdefined\iexec@stderr2>\iexec@stderr\else2>&1\fi%
          \ifiexec@windows%
            \space&&\space(echo 0)>\iexec@exit\space||\space(echo 1)>\iexec@exit%
          \else%
            ;\space/bin/echo -n \string$?\% >\iexec@exit%
          \fi}%
        \ShellEscape{\iexec@cmd}%
%    \end{macrocode}
% Then, a message is printed to \TeX{} log:
%    \begin{macrocode}
        \ifdefined\iexec@log%
          \message{iexec: [\iexec@cmd]^^J}%
        \fi%
      \endgroup%
%    \end{macrocode}
% Then, we read back the exit code, from the file:
%    \begin{macrocode}
      \immediate\openin\iexec@exitfile=\iexec@exit%
      \read\iexec@exitfile to \iexec@code%
      \immediate\closein\iexec@exitfile%
%    \end{macrocode}
% Then, if required, we print the content of the stdout file to \TeX{} log:
% \changes{0.11.2}{2022/10/25}{If execution fails, we print the content of ``\texttt{stdout}'' anyway, even if the ``\texttt{log}'' is not turned on.}
%    \begin{macrocode}
      \ifdefined\iexec@null\else%
        \IfFileExists
          {\iexec@stdout}
          {}
          {\PackageError{iexec}{The "\iexec@stdout" file is absent
            after processing, looks like some internal error}{}}%
        \ifdefined\iexec@log%
          \message{iexec: This is the content of '\iexec@stdout'%
            \space(\pdf@filesize{\iexec@stdout} bytes):^^J}%
          \IfFileExists
            {\iexec@stdout}
            {\iexec@typeout{\iexec@stdout}}
            {\PackageError{iexec}{The "\iexec@stdout" file is absent
              after processing, looks like some internal error}{}}%
          \message{<EOF>^^J}%
        \else%
          \ifnum\iexec@code=0\else%
            \ifdefined\iexec@ignore\else%
              \message{iexec: See the content of '\iexec@stdout'%
                \space(\pdf@filesize{\iexec@stdout} bytes)
                after failure:^^J}%
              \iexec@typeout{\iexec@stdout}%
              \message{<EOF>^^J}%
            \fi%
          \fi%
        \fi%
      \fi%
%    \end{macrocode}
% Then, we check whether it's zero or not (if not zero, we either print a message or fail the build, depending on the presence of |ignore| option):
%    \begin{macrocode}
      \ifnum\iexec@code=0\else%
        \ifdefined\iexec@ignore%
          \ifdefined\iexec@log%
            \message{iexec: Execution failure ignored,
              the exit code was \iexec@code^^J}%
          \fi%
        \else%
          \PackageError{iexec}{Execution failure,
            the exit code was \iexec@code}{}%
        \fi%
      \fi%
%    \end{macrocode}
% Then, we include the produced output into the current document:
%    \begin{macrocode}
      \ifdefined\iexec@null\else%
      \ifdefined\iexec@quiet%
        \ifdefined\iexec@log%
          \message{iexec: Due to 'quiet' option we didn't read
          the content of '\iexec@stdout'
          (\pdf@filesize{\iexec@stdout} bytes)^^J}%
        \fi%
      \else%
        \ifdefined\iexec@log%
          \message{iexec: We are going to include the content of
          '\iexec@stdout'(\pdf@filesize{\iexec@stdout} bytes)...^^J}%
        \fi%
        \input{\iexec@stdout}%
        \ifdefined\iexec@unskip\unskip\fi%
        \message{iexec: The content of '\iexec@stdout'
        was included into the document^^J}%
      \fi\fi%
%    \end{macrocode}
% Then, we delete the file or leave it untouched:
%    \begin{macrocode}
      \ifdefined\iexec@null\else%
      \ifdefined\iexec@trace%
        \ifdefined\iexec@log%
          \message{iexec: Due to package option 'trace',
          the files '\iexec@stdout' and '\iexec@exit'
          were not deleted^^J}%
        \fi%
      \else%
        \ifdefined\iexec@traceit%
          \ifdefined\iexec@log%
            \message{iexec: Due to 'trace' package option,
            the files '\iexec@stdout' and '\iexec@exit'
            were not deleted^^J}%
          \fi%
        \else%
          \ShellEscape{\iexec@rm\space\iexec@stdout}%
          \ifdefined\iexec@log%
            \message{iexec: The file '\iexec@stdout' was deleted^^J}%
          \fi%
          \ShellEscape{\iexec@rm\space\iexec@exit}%
          \ifdefined\iexec@log%
            \message{iexec: The file '\iexec@exit' was deleted^^J}%
          \fi%
        \fi%
      \fi\fi%
%    \end{macrocode}
% \changes{0.14.0}{2024/01/14}{The \texttt{maybe} option introduced, allowing the user to skip the entire execution of the \texttt{\char`\\iexec} command, when \texttt{--shell-escape} option is off.}
% Finally, we ignore the whole story if the |maybe| option is provided
% and the |--shell-escape| is not set:
%    \begin{macrocode}
    \else%
      \ifdefined\iexec@maybe%
        \message{iexec: The execution skipped because --shell-escape
          is not set and 'maybe' option is provided^^J}%
      \else%
        \PackageError{iexec}{You must run TeX processor with
        --shell-escape option}{}%
      \fi%
    \fi%
  \endgroup%
}\makeatother
%    \end{macrocode}
% \end{macro}

%    \begin{macrocode}
\endinput
%    \end{macrocode}

% \Finale

%\clearpage
%\PrintChanges

%\clearpage
%\PrintIndex
