% 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/02/28}%
\newcommand\version@bashful{V 0.91}%
\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}%

% Use listing to display bash scripts. 
\RequirePackage{listings}%
  % listings style for the script, can be redefined by client
  \lstdefinestyle{bashfulList}{
    basicstyle=\ttfamily,
    showstringspaces=false}%
  % listings style for the stdoutFile, can be redefined by client
  \lstdefinestyle{bashfulStdout}{
    basicstyle=\sl\ttfamily,
    showstringspaces=false
  }%

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

  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  % Keys in alphabetical order:
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  % 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

  % scriptFile: String = Where should the listed script be saved?
  \def\BL@scriptFile{\jobname.lsh}%
  \define@cmdkey{bashful}[BL@]{scriptFile}{}%
  
  % list: Boolean = \ifBL@list: Should we list the script we generate?
  \define@boolkey{bashful}[BL@]{list}[true]{}%

  % 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}{}%
  
  % scriptFile: String = \BL@scriptFile: Where should the script be saved?
  \def\BL@scriptFile{\jobname.sh}%
  \define@cmdkey{bashful}[BL@]{scriptFile}{}%

  % stderrFile: String = \BL@stderrFile: Where should the standard error be saved?
  \def\BL@stderrFile{\jobname.stderr}%
  \define@cmdkey{bashful}[BL@]{stderrFile}{}%
  
  % stdout: Boolean = \ifBL@stdout: Should we list the standard output?
  \define@boolkey{bashful}[BL@]{stdout}[true]{}%

  % stdoutFile: String = \BL@stdoutFile: Where should the standard output be saved?
  \def\BL@stdoutFile{\jobname.stdout}%
  \define@cmdkey{bashful}[BL@]{stdoutFile}{}%
  
  % verbose: Boolean = \ifBL@verbose: Log every step we do 
  \define@boolkey{bashful}[BL@]{verbose}[true]{}%        

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

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

% \bashII: Set category codes of all to special, and proceed.  
\begingroup
  \catcode`\^^M\active%
  \gdef\bashII{%
  	\log@BL{bashII: Making ^^M a true new line}% 
    \catcode`\^^M\active%
    \def^^M{^^J}%
    \log@BL{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}}@relax%
@endgroup

% \bashIV: Process the tokens by storing them in a script file, executing this
% file, listing it if required, and presenting the standard output if required.
\newcommand\bashIV[1]{%
	\generateScriptFile@BL{#1}\relax
  \executeScriptFile@BL
  \checkStderr@BL
  \listScript@BL{#1}\relax%
  \listStdout@BL
  \log@BL{Ending group so that all cat codes changes disappear}\relax%
  \endgroup
}%

\def\setKeys@BL#1{%
  \log@BL{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
\newcommand\storeToFile@BL[2]{%   
   \log@BL{Creating file #2 :=^^J#1^^J}%
  \immediate\openout\writer@BL#2
  \immediate\write\writer@BL{#1}%
  \immediate\closeout\writer@BL
}%

% Execute the content of our script file.
\newcommand\executeScriptFile@BL{%
  \def\command@BL{\BL@shell\space\BL@scriptFile}%
  \edef\command@BL{%
    \BL@shell\space -c 
    "\command@BL \space > \BL@stdoutFile \space2> \BL@stderrFile \space 
			|| echo '\command@BL' exit code was $? 2>>\BL@stderrFile"%
  }%
	\log@BL{Executing:^^J \command@BL}%
	\immediate\write18{\command@BL}%
}%

\newread\reader@BL
% Issue an error if stderr is not empty 
\newcommand\checkStderr@BL{%
  \log@BL{Examining \BL@stderrFile}%
  \begingroup
  \newif\ifErrorsFound@\ErrorsFound@false
  \log@BL{Opening \BL@stderrFile}%
  \openin\reader@BL=\BL@stderrFile\relax
  \ifeof\reader@BL
    \BL@verbosetrue
    \log@BL{Strange... \BL@stderrFile{} does not exist (probably bashful bug)}%
    \log@BL{Switching to verbose mode}%
  \else
    \log@BL{Reading first line of \BL@stderrFile}%
    \catcode`\^^M=5
    \read\reader@BL to \firstErrorLine
    \ifeof\reader@BL
      \def\eoln{\par}
      \ifx\firstErrorLine\eoln
         \log@BL{File \BL@stderrFile\space is empty}%
         \closein\reader@BL
      \else
         \log@BL{File \BL@stderrFile\space has one line [\firstErrorLine]}%
         \ErrorsFound@true
         \closein\reader@BL
      \fi
    \else
      \log@BL{File \BL@stderrFile\space has two lines or more}%
      \ErrorsFound@true
      \closein\reader@BL
    \fi
  \fi
  \ifErrorsFound@
    \log@BL{Issuing an error message since \BL@stderrFile\space was not empty}%
    \let\please=\relax\let\examine=\relax\let\stderr=\relax
    \errmessage{^^JError(s) executing \BL@scriptFile. Here is how 
     ^^Jfile \BL@stderrFile\space begins:
     ^^J\firstErrorLine
		 ^^Jbut, you really ought to examine this file yourself}
	  \please
	  \examine
	  \stderr
  \else
    \log@BL{Proceeding as usual since \BL@stderrFile{} is empty}%
  \fi
	\endgroup
}%

% List the content of the script file.
\newcommand\listScript@BL[1]{%
  \ifBL@list{%
    \log@BL{Prepending prefix [[\BL@prefix]] to generate \BL@scriptFile}%
    \storeToFile@BL{\BL@prefix#1}{\BL@scriptFile}%
      \forceLTR@BL
      \fixPolyglossiaBug@BL
      \log@BL{Invoking \noexpand\lstinputlisting to list \BL@scriptFile}%
      \ifBL@stdout  
         % trim bottom
          \beginWrappingEnvironment@BL
  	     \lstinputlisting[style=bashfulList,belowskip=0pt]{\BL@scriptFile}%
  	  \else
  	     \beginWrappingEnvironment@BL
  	  	 \lstinputlisting[style=bashfulList]{\BL@scriptFile}%
  	  	 \endWrappingEnvironment@BL
  	  \fi
	}\else\relax\fi
}%

\def\beginWrappingEnvironment@BL{%
  \expandafter\csname\BL@environment\endcsname
}%

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

% Display the standard output of the execution 
\newcommand\listStdout@BL{%
  \ifBL@stdout{%
    \log@BL{Listing stdout file \BL@stdoutFile}%
      \forceLTR@BL
      \fixPolyglossiaBug@BL
      \log@BL{Invoking \noexpand\lstinputlisting to list \BL@stdoutFile}%
    \ifBL@list % trim top
  	   \lstinputlisting[style=bashfulStdout,aboveskip=0pt]{\BL@stdoutFile}%
  	   \endWrappingEnvironment@BL
  	\else
  	    \beginWrappingEnvironment@BL
  	  	\lstinputlisting[style=bashfulStdout]{\BL@stdoutFile}%
  	  	\endWrappingEnvironment@BL
  	\fi
	}\else\relax\fi
}%

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

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

\newcommand\log@BL[1]{%
  \ifBL@verbose
    \typeout{L\the\inputlineno: #1}%
   \else
			\relax
    \fi
}%

