% Copyright (C) 2011 by Yossi Gil  yogi@cs.technion.ac.il
% ---------------------------------------------------------------------------
% This work may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3 of this license or
% (at your option) any later version.  The latest version of this license is in
% http://www.latex-project.org/lppl.txt and version 1.3 or later is part of all
% distributions of LaTeX version 2005/12/01 or later.
%
% This work has the LPPL maintenance status `maintained'.
%
% The Current Maintainer of this work is Yossi Gil. 
%
% This work consists of the files bashful.tex and bashful.sty and the derived
% bashful.pdf

\NeedsTeXFormat{LaTeX2e}%

% Auxiliary identification information
\newcommand\date@bashful{2011/03/12}%
\newcommand\version@bashful{V 0.92}%
\newcommand\author@bashful{Yossi Gil}%
\newcommand\mail@bashful{yogi@cs.technion.ac.il}%
\newcommand\signature@bashful{%
  bashful \version@bashful{} by 
  \author@bashful{} \mail@bashful
}%
  
% Identify this package
\ProvidesPackage{bashful}[\date@bashful{} \signature@bashful:
  Write and execute a bash script within LaTeX, with, or 
  without displaying the script and/or its output. 
]
\PackageInfo{bashful}{This is bashful, \signature@bashful}%

\RequirePackage{catchfile}
% Use xkeyval for retrieving parameters
\RequirePackage{xkeyval}%

% If true, all activities take place in a designated directory.
\newif\if@hide@BL@\@hide@BL@false

% This is the default name for a directory in which processing should
% take place if \@hide@BL@true.
\def\directory@BL{_00}

% Use listing to display bash scripts. 
\RequirePackage{listings}%

  % listings style for the script, can be redefined by client
  \lstdefinestyle{bashfulScript}{
    basicstyle=\ttfamily,
    keywords={},
    showstringspaces=false}%
  % listings style for the stdoutFile, can be redefined by client
  \lstdefinestyle{bashfulStdout}{
    basicstyle=\sl\ttfamily,
    keywords={},
    showstringspaces=false
  }%
  % listings style for the stderrFile, can be redefined by client
  \lstdefinestyle{bashfulStderr}{
    basicstyle=\sl\ttfamily\color{red},
    keywords={},
    showstringspaces=false
  }%


  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % File Name keys in alphabetical order:
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
   % scriptFile: String = \BL@exitCodeFile: Where should the exit code be stored
   % if it is not zero.
  \edef\BL@exitCodeFile{\jobname.exitCode}%
  \define@cmdkey{bashful}[BL@]{exitCodeFile}{}%
  
  % scriptFile: String = \BL@scriptFile: Where should the script be saved?
  \edef\BL@scriptFile{\jobname.sh}%
  \define@cmdkey{bashful}[BL@]{scriptFile}{}%
  
   % stderrFile: String = \BL@stderrFile: Where should the standard error 
   % sream be saved?
  \edef\BL@stderrFile{\jobname.stderr}%
  \define@cmdkey{bashful}[BL@]{stderrFile}{}%
  
   % stdoutFile: String = \BL@stdoutFile: Where should the standard output stream 
   % be saved?
  \edef\BL@stdoutFile{\jobname.stdout}%
  \define@cmdkey{bashful}[BL@]{stdoutFile}{}%
  
   % dir: String = \directory@BL: name of directory in which execution is going to take
   % place
  \define@cmdkey{bashful}[BL@]{dir}{\def\directory@BL{#1}}%
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % List configuration boolean keys
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  
   % list: Boolean = \ifBL@script: Should we list the script we generate?
  \define@boolkey{bashful}[BL@]{script}[true]{}%
  
   % stdout: Boolean = \ifBL@stderr: Should we list the standard error? 
   \define@boolkey{bashful}[BL@]{stderr}[true]{}%
   
  % stdout: Boolean = \ifBL@stdout: Should we list the standard output?
  \define@boolkey{bashful}[BL@]{stdout}[true]{}
  
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % Error checking Boolean keys.
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     
  % stdout: Boolean = \ifBL@ignoreExitCode: Should we ignore the exit 
  % code?
  \define@boolkey{bashful}[BL@]{ignoreExitCode}[true]{}
  
  % stdout: Boolean = \ifBL@ignoreStderr: Should we ignore the exit 
  % code?
  \define@boolkey{bashful}[BL@]{ignoreStderr}[true]{}

  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % Miscelaneous keys
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  % environment: String = \BL@environment: Which environment should we wrap 
  % the listings
  \def\BL@environment{none@BL}%
  \define@cmdkey{bashful}[BL@]{environment}{}%
  \newenvironment{none@BL}{}{} % Default, empty environment for wrapping the listings

  % prefix: String = \BL@prefix: What prefix should be printed before a listing.
  \def\BL@prefix{\@percentchar\space}%
  \define@cmdkey{bashful}[BL@]{prefix}{}%
  
   % shell: String = \BL@shell: Which shell should be used for execution?
  \def\BL@shell{bash}%
  \define@cmdkey{bashful}[BL@]{shell}{}%

  % verbose: Boolean = \ifBL@verbose: Log every step we do 
  \define@boolkey{bashful}[BL@]{verbose}[true]{}%   
  
  
\DeclareOptionX{hide}{\@hide@BL@true}
\DeclareOptionX{dir}{\@hide@BL@true\def\directory@BL{#1}}
\DeclareOptionX{verbose}{\BL@verbosetrue}
\ExecuteOptionsX{}
\ProcessOptionsX\relax     

% \bash: the main command we define. It chains to \bashI which chains to
% \bashII, etc.
\begingroup
  %\where@BL
  \catcode`\^^M\active%
  \gdef\bash{% 
    \logBL{Beginning a group so that all cat code changes are local}%
    \begingroup%
    \logBL{Making \^\^M a true newline}%
    \catcode`\^^M\active%
    \def^^M{^^J}%
    \logBL{Checking for optional arguments}%
    \@ifnextchar[\bashI{\bashI[]}%
  }%
\endgroup

% \bashI: Process the optional arguments and continue
\def\bashI[#1]{\setKeys@BL{#1}\bashII}

% \bashII: Set category codes of all characters to special, and proceed.
\begingroup
  \catcode`\^^M\active%
  \gdef\bashII{%
    \logBL{bashII: Making \^\^M a true new line}% 
    \catcode`\^^M\active%
    \def^^M{^^J}%
    \logBL{bashII: Making all characters other}% 
    \let\do\@makeother%
    \dospecials%
    \bashIII}%
\endgroup

% \bashIII: Consume all tokens until \END (but ignoring the preceding and 
% terminating newline), and proceed. 
\begingroup
  \catcode`\@=0\relax
  \catcode`\^^M\active
  @catcode`@\=12@relax%
  @gdef@bashIII^^M#1^^M\END{@bashIV{#1}@bashV{#1}@logBL{bashV: Done!}@endgroup}@endgroup
%  

\newcommand\logBL[1]{\ifBL@verbose\typeout{L\the\inputlineno: #1}\fi}

% \bashIV: Process the tokens by storing them in a script file, and executing 
% this file, 
\newcommand\bashIV[1]{%
  \logBL{BashIV: begin}%
  \makeDirectory@BL
  \generateScriptFile@BL{#1}\relax
  \executeScriptFile@BL
  \logBL{BashIV: done}%
}%
\def\makeDirectory@BL{%
  \if@hide@BL@
    \logBL{Making directory \directory@BL}%
    \immediate\write18{mkdir -p \directory@BL}%
  \else
    \logBL{Using current directory}%
  \fi
}

\newcommand\splice[1]{%
  \bashIV{#1}%
  \expandFileName@BL{\BL@stdoutFile}%
  \CatchFileDef{\BL@file@contents}{\BL@stdoutFile}{\relax}%
  \ignorespaces\BL@file@contents\unskip
}

% listing the script file if required, and presenting the standard output and
% standar error if required.
\newcommand\bashV[1]{%
  \logBL{Wrapping up after execution}%
  \storeToFile@BL{\BL@prefix#1}{\BL@scriptFile}%
  \expandFileName@BL\BL@scriptFile
  \expandFileName@BL\BL@stdoutFile
  \expandFileName@BL\BL@stderrFile
  \logBL{Files are: \BL@scriptFile, \BL@stdoutFile, and \BL@stderrFile}%
  %\checkScriptErrors@BL
  \listEverything@BL
  \logBL{Wrap up done}}

\def\expandFileName@BL#1{%
  \logBL{expandFileName}%
  \logBL{Setting, if necessary, correct path of \noexpand#1 }%
  \if@hide@BL@
    \logBL{Prepending path (\directory@BL) to #1}%
    \edef#1{\directory@BL/#1}%
    \logBL{Obtained #1}%
  \fi
}

\def\setKeys@BL#1{%
  \logBL{Processing key=val pairs in options string [#1]}\relax
  \setkeys{bashful}{#1}%
}%

% Store the list of tokens in the first argument into our script file
\newcommand\generateScriptFile@BL[1]{%
  \storeToFile@BL{#1}{\BL@scriptFile}%
}%

\newwrite\writer@BL  
% Store the list of tokens in the first argument into the file given
% in the second argument; prepend directory if necessary
\newcommand\storeToFile@BL[2]{% 
  \logBL{  #2 :=^^J#1^^J}%
  \if@hide@BL@
     \logBL{File #2 will be created in \directory@BL}%
     \storeToFileI@BL{#1}{\directory@BL/#2}
  \else
     \logBL{File #2 will be created in current directory}%
     \storeToFileI@BL{#1}{#2}%
  \fi
  \logBL{Writing done!}%
}%

% Store the list of tokens in the first argument into the file given
% in the second argument; the second argument could be qualified with 
% a directory name.
\newcommand\storeToFileI@BL[2]{% 
  \logBL{Writing to file #2...}%
  \immediate\openout\writer@BL#2%
  \immediate\write\writer@BL{#1}%
  \immediate\closeout\writer@BL
}%

% Execute the content of our script file.
\newcommand\executeScriptFile@BL{%
  \edef\command@BL{\BL@shell \space \BL@scriptFile}%
  \if@hide@BL@
    \logBL{Adding a "cd command"}%
    \edef\command@BL{cd \directory@BL;\command@BL}
  \fi%
  \edef\command@BL{\command@BL \space >\BL@stdoutFile \space 2>\BL@stderrFile}%
  \edef\command@BL{\command@BL \space || echo $? >\BL@exitCodeFile}%
  \edef\command@BL{\BL@shell\space -c "\command@BL"}%
  \logBL{Executing:^^J \command@BL}%
  \immediate\write18{\command@BL}%
}%

\newread\reader@BL

% Issue an error message if errors found during execution
\newcommand\checkScriptErrors@BL{%
  \logBL{Checking for script errors}%
  \begingroup
  \newif\ifErrorsFound@\ErrorsFound@false
  \checkExitCodeFile@BL
  \ifdefined\exitCode@BL
    \logBL{Non zero exit code found (\exitCode@BL), and I was not instructed to ignore it}
    \ErrorsFound@true
  \fi
  \def\eoln{\par}
  \def\firstErrorLine{\par}
  \checkStderrFile@BL
  \logBL{I will print content of \BL@stderrFile\space (if found)}
  \ifx\firstErrorLine\eoln
    \relax
  \else
    \logBL{Standard error was not empty, and I was not instructed to ignore it}
    \message{Standard error not empty. Here is how 
     ^^Jfile \BL@stderrFile\space begins:
     ^^J>>>>\space 
     ^^Jbut, you really ought to examine this file yourself!}
    \ErrorsFound@true
  \fi
  \ifErrorsFound@
    \logBL{Issuing an error message since \BL@stderrFile\space was not empty}%
    \errmessage{Your shell script failed....}
  \else
    \logBL{Proceeding as usual}%
  \fi
  \endgroup
}%

\newcommand\checkExitCodeFile@BL{%
  \ifBL@ignoreExitCode
     \logBL{Ignoring \BL@exitCodeFile, as per command flag}%
  \else
    \logBL{Opening \BL@exitCodeFile}%
    \openin\reader@BL=\BL@exitCodeFile
    \ifeof\reader@BL
      \logBL{File \BL@exitCodeFile\space missing, exit code was probably 0}
    \else
      \logBL{File \BL@exitCodeFile\space exists, let's get the exit code}% 
      \logBL{Reading first line of \BL@exitCodeFile}%
      \catcode`\^^M=5
      \read\reader@BL to \exitCode@BL
      \closein\reader@BL
    \fi
  \fi
}

\newcommand\checkStderrFile@BL{%
  \ifBL@stderr
    \logBL{Will be listing \BL@stderrFile, so erroneous content is ignored}%
  \else
    \ifBL@ignoreStderr
      \logBL{Ignoring \BL@stderrFile, as per command flag}%
    \else
      \checkStderrFileI@BL
    \fi
  \fi
}

\newcommand\checkStderrFileI@BL{%
  \logBL{Opening \BL@stderrFile}%
  \openin\reader@BL=\BL@stderrFile\relax
  \ifeof\reader@BL
    \BL@verbosetrue
    \logBL{Hmm... \BL@stderrFile\space does not exist (probably a package bug)}%
    \logBL{Switching to verbose mode}%
  \else
    \logBL{Reading first line of \BL@stderrFile}%
    \catcode`\^^M=5
    \read\reader@BL to \firstErrorLine
    \ifeof\reader@BL
      \ifx\firstErrorLine\eoln
        \logBL{File \BL@stderrFile\space is empty}
      \else
        \logBL{File \BL@stderrFile\space has one line [\firstErrorLine]}%
        \ErrorsFound@true
      \fi
    \else
     \logBL{File \BL@stderrFile\space has two lines or more}%
     \ErrorsFound@true
   \fi
   \closein\reader@BL
}
    
% List the contents of the script, stdout and stderr, as per the flags.
\newcommand\listEverything@BL{%
  \logBL{Checking whether any listings are required}%
  \newif\if@listSomething@BL@
  \ifBL@script\@listSomething@BL@true\fi
  \ifBL@stdout\@listSomething@BL@true\fi
  \ifBL@stderr\@listSomething@BL@true\fi
  \if@listSomething@BL@
    \beginWrappingEnvironment@BL
    \listEverythingI@BL
    \endWrappingEnvironment@BL
  \else
    \logBL{Nothing has to be listed}%
  \fi
}

% Auxiliary macro to list the contents of the script, stdout and stderr, as per 
% the flags.
\newcommand\listEverythingI@BL{%
  \logBL{Laying out the correct \noexpand\lstinputlisting commands}%1
  \ifBL@script\listScript@BL\BL@scriptFile\fi
  \ifBL@stdout\listStdout@BL\BL@stdoutFile\fi
  \ifBL@stderr\listStderr@BL\BL@stderrFile\fi
}%

\newcommand\listScript@BL[1]{%
  \logBL{Listing script: #1}
  \def\flags@BL{style=bashfulScript}
  \logBL{Initial flags for listing #1 are \flags@BL}
  \ifBL@stdout\edef\flags@BL{\flags@BL, belowskip=0pt}\fi
  \ifBL@stderr\edef\flags@BL{\flags@BL, belowskip=0pt}\fi
  \doList@BL#1\flags@BL
}

\newcommand\listStdout@BL[1]{%
  \logBL{Listing stdout: #1}
  \edef\flags@BL{style=bashfulStdout}
  \logBL{Initial flags for listing stdout file are \flags@BL}
  \ifBL@script\edef\flags@BL{\flags@BL, aboveskip=0pt}\fi
  \ifBL@stderr\edef\flags@BL{\flags@BL, belowskip=0pt}\fi
  \doList@BL#1\flags@BL
}%

\newcommand\listStderr@BL[1]{%
  \logBL{Listing stderr: #1}%
  \def\flags@BL{style=bashfulStderr}%
  \logBL{Initial flags for listing stderr file are \flags@BL}
  \ifBL@script\edef\flags@BL{\flags@BL, aboveskip=0pt}\fi
  \ifBL@stdout\edef\flags@BL{\flags@BL, aboveskip=0pt}\fi
  \doList@BL#1\flags@BL
}%

\newcommand\doList@BL[2]{%
    \logBL{Flags for listing #1 are #2}%
    \expandafter\lstset\expandafter{#2}%
    \lstinputlisting{#1}%
  }%
 
\def\beginWrappingEnvironment@BL{%
  \logBL{Beginning environment \BL@environment}%
  \expandafter\csname\BL@environment\endcsname
  \forceLTR@BL
  \fixPolyglossiaBug@BL
}%

\def\endWrappingEnvironment@BL{%
  \expandafter\csname end\BL@environment\endcsname
}%

\newcommand\fixPolyglossiaBug@BL{%
  \logBL{Trying to fix a Polyglossia package bug}%
  \ifdefined\ttfamilylatin
    \logBL{Replacing \noexpand\ttfamily with \noexpand\ttfamilylatin}%
    \let\ttfamily=\ttfamilylatin
    \logBL{Replacing \noexpand\rmfamily with \noexpand\rmfamilylatin}%
    \let\rmfamily=\rmfamilylatin
    \logBL{Replacing \noexpand\sffamily with \noexpand\sffamilylatin}%
    \let\sffamily=\sffamilylatin
    \logBL{Replacing \noexpand\normalfont with \noexpand\normalfontlatin}%
    \let\normalfont=\normalfontlatin
  \else
    \logBL{Polyglossia package probably not loaded}%
    \relax
 \fi
}%

\newcommand\forceLTR@BL{%
  \logBL{Making sure we are not in right-to-left mode}%
  \ifdefined\setLTR
    \logBL{Command \noexpand\setLTR is defined, invoking it}%
    \setLTR
  \else
    \logBL{Command \noexpand\setLTR is not defined, we are probably LTR}%
    \relax
  \fi
}%




