% 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 }%