+% \def\fileversion{0.1}
+% \def\filedate{2010/03/03}
+% \iffalse meta-comment
+%<package>\def\fileversion{0.1}
+%<package>\def\filedate{2010/03/03}
+%
+% Copyright (C) 2010 W. Trevor King
+% -------------------------------------------------------
+%
+% This file may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, 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 2003/12/01 or later.
+%
+% Docstrip formatting initially based on Scott Pakin's
+% dtxtut/cskeleton.dtx
+% http://www.ctan.org/tex-archive/info/dtxtut/
+% Shellout calls initially based on Will Robertson's asyfig.sty
+% http://github.com/wspr/asyfig/
+%
+% \fi
+%
+% \iffalse meta-comment
+%<*driver>
+\ProvidesFile{pytex.dtx}
+%</driver>
+%<package>\NeedsTeXFormat{LaTeX2e}
+%<package>\ProvidesClass{pytex}
+%<package> [\filedate\ \fileversion\ embed Python in LaTeX files]
+%
+% Code for the .ini driver, see section "2.1 The driver file" of doc.dtx.
+%<*driver>
+\documentclass{ltxdoc}
+\usepackage[colorlinks=true]{hyperref}
+%</driver>
+% Include some useful commands from |xkeyval|'s |<preamble>|.
+%<*driver>
+\usepackage{xcolor}
+\usepackage{listings}
+\lstnewenvironment{command}{%
+ \lstset{columns=flexible,frame=single,backgroundcolor=\color{blue!20},%
+ xleftmargin=\fboxsep,xrightmargin=\fboxsep,escapeinside=`',gobble=1}}{}
+\lstnewenvironment{example}{%
+ \lstset{basicstyle=\footnotesize\ttfamily,columns=flexible,frame=single,%
+ backgroundcolor=\color{yellow!20},xleftmargin=\fboxsep,%
+ xrightmargin=\fboxsep,gobble=1}}{}
+%</driver>
+% Define a quick and dirty version of |xkeyval|'s |\DescribeOptions|.
+%<*driver>
+\newenvironment{option}[1]{\begin{macro}{#1}}
+ {\end{macro}}
+%</driver>
+% Some commonly used abbreviations from |classes.dtx|.
+%<*driver>
+\newcommand*{\Lopt}[1]{\textsf {#1}}
+\newcommand*{\file}[1]{\texttt {#1}}
+\newcommand*{\Lcount}[1]{\textsl {\small#1}}
+\newcommand*{\pstyle}[1]{\textsl {#1}}
+%</driver>
+%<*driver>
+\makeatletter
+\def\DescribeOption#1{\leavevmode\@bsphack
+ \marginpar{\raggedleft\PrintDescribeOption{#1}}%
+ \SpecialOptionIndex{#1}\@esphack\ignorespaces}
+\def\PrintDescribeOption#1{\strut\emph{option}\\\MacroFont #1\ }
+\def\SpecialOptionIndex#1{\@bsphack
+ \index{#1\actualchar{\protect\ttfamily#1}
+ (option)\encapchar usage}%
+ \index{options:\levelchar#1\actualchar{\protect\ttfamily#1}\encapchar
+ usage}\@esphack}
+\def\DescribeOptions#1{\leavevmode\@bsphack
+ \marginpar{\raggedleft\strut\emph{options}%
+ \@for\@tempa:=#1\do{%
+ \\\strut\MacroFont\@tempa\SpecialOptionIndex\@tempa
+ }}\@esphack\ignorespaces}
+\makeatother
+%</driver>
+%<*driver>
+\EnableCrossrefs
+\RecordChanges
+\EnableCrossrefs
+\begin{document}
+ \DocInput{pytex.dtx}
+\end{document}
+%</driver>
+% \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 \~}
+%
+%
+% \changes{0.1}{2006/01/18}{Initial package}
+%
+% \MakeShortVerb{\|}
+% \newcommand{\pkg}[1]{\textsf{#1}}
+% \newcommand{\cls}[1]{\textsf{#1}}
+% \newcommand{\fixme}[1]{\emph{FIXME: #1}}
+% \newcommand{\python}{Python}
+% \newcommand{\firstPython}{\href{http://www.python.org/}{\python}}
+%
+% \title{The \textsf{pytex} class\thanks{This document
+% corresponds to \textsf{pytex}~\fileversion, dated \filedate.}}
+% \author{W.~Trevor King \\\texttt{wking@drexel.edu}}
+% \date{\filedate}
+%
+% \maketitle
+%
+%
+% \section{Introduction}
+%
+% This package defines a \LaTeX\ package for embedding \firstPython\ in
+% \LaTeX\ files. This is useful, for example, if you want to automate
+% physics problem generation. This is intended for easy transitions
+% from static \LaTeX sources, or for those to whom \LaTeX seems more
+% natural. An alternative approach would be to write \python\ code
+% that built static \LaTeX\ files on the fly.
+%
+%
+% \section{Usage}
+%
+% Include \pkg{pytex} in your preamble (after |\documentclass{}| and
+% before |\begin{document}|).
+% \begin{example}
+% \usepackage{pytex}
+% \end{example}
+%
+% This will give you the |\pytex{}| and |\pytexfile{}| macros.
+% The long names are chosen to reduce the risk of collision with
+% other packages, but feel free to abbreviate.
+% \begin{example}
+% \newcommand{\py}[1]{\pytex{#1}}
+% \end{example}
+%
+% Remember to use |-shell-escape| option or its equivalent for your
+% flavor of \LaTeX, since \pkg{pytex} works by shelling out to
+% \python. Obviously, you'll have to trust the user writing whatever
+% goes into the |\pytex| macro because (1) no shell escaping is done
+% on the argument and (2) even if it were, you can do whatever you
+% want through \python's |os.system| etc.
+%
+%
+% \subsection{Macros}
+%
+% \begin{command}
+% `\cs{pytexfile}\marg{path}'
+% \end{command}
+% \DescribeMacro{\pytexfile}
+% Set the working pickle file to \meta{path}. \pkg{pytex} keeps track
+% of your environment by
+% \href{http://docs.python.org/library/pickle.html}{pickling}
+% everything in a pickle file. By default, this will be
+% |./\jobname.pkl|, but feel free to use whatever name you want, or to
+% switch back an forth between names.
+% \begin{example}
+% \pytexfile{problem1}
+% \end{example}
+% The pickle file is \emph{not} cleared at the start of each
+% \LaTeX\ run (but it could be, perhaps via a |\newpytexfile{}|).
+%
+% \begin{command}
+% `\cs{pytex}\marg{python-code}'
+% \end{command}
+% \DescribeMacro{\pytex}
+% Here's where the fun begins.
+% \begin{example}
+% If $a=\pytex{p.a=1; print p.a}$ and $b=\pytex{p.b=2; print p.b}$,
+% then $a\cdot b = \pytex{print p.a*p.b}$.
+% \end{example}
+% |p| is a |PyTeX_Storage| instance that handles saving to and loading
+% from the current pytex file.
+%
+%
+% \section{Implementation}
+%
+% \begin{macrocode}
+%<*package>
+% \end{macrocode}
+%
+% Use \pkg{ifplatform} to detect Linux / Mac / MS Windows \ldots.
+% \begin{macrocode}
+\RequirePackage{ifplatform}
+% \end{macrocode}
+%
+% Use \pkg{catchfile} for reading in files.
+% \begin{macrocode}
+\RequirePackage{catchfile}
+% \end{macrocode}
+%
+% \begin{macro}{\pytex@currentfile}
+% Keep track of the current pytex file, making |\jobname| the default.
+% \begin{macrocode}
+\newcommand\pytex@currentfile{\jobname}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pytexfile}
+% Give the user a way to change the current pytex file.
+% \begin{macrocode}
+\newcommand\pytexfile[1]{\renewcommand\pytex@currentfile{#1}}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pytex@stdout}
+% \begin{macro}{\pytex@stderr}
+% Temporary files for capturing stdout and stderr from a |\pytex|
+% call.
+% \begin{macrocode}
+\newcommand\pytex@stdout{pytex.stdout}
+\newcommand\pytex@stderr{pytex.stderr}
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\pytex@par}
+% We will be using |^^J| for line breaks on our input stderr to
+% preserve line wrapping. Here we give |^^J| a name to make
+% the stderr comparison more clear.
+% \begin{macrocode}
+\newcommand\pytex@par{^^J}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pytex@qt}
+% Surround an argument with escaped quotes.
+% For wrapping strings in the python shellout.
+% \fixme{doesn't actually work.}
+% ^^A echo '\write16{"hi \"there"}\bye' | tex
+% ^^A echo '\write16{"hi \ "there"}\bye' | tex
+% \begin{macrocode}
+\newcommand{\pytex@qt}[1]{\char"22 #1\char"22}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pytex}
+% Here's the meat. Use the pytex file to provide a persitent
+% environment for \python\ execution.
+% \begin{macrocode}
+\newcommand\pytex[1]{%
+ \immediate\write18{%
+ python -c "import pytex;
+ p = pytex.PyTeX_Storage('\pytex@currentfile');
+ p.load();
+ #1;
+ p.save();"
+ 1> \pytex@stdout\space 2> \pytex@stderr}%
+% \end{macrocode}
+% Read stdout and stderr files into |\@tempa| and |\@tempb|
+% respectively. \fixme{How do we know the command is complete? wait
+% call?}. We preserve linebreaks while reading stderr so that
+% they will show up if we have to |\typeout| them later.
+% \begin{macrocode}
+ \CatchFileEdef{\@tempa}{\pytex@stdout}{}%
+ \CatchFileEdef{\@tempb}{\pytex@stderr}{\let\par\pytex@par\obeylines}%
+% \end{macrocode}
+% Remove the temporary files.
+% \begin{macrocode}
+ \immediate\write18{\ifwindows del \else rm \fi \pytex@stdout}
+ \immediate\write18{\ifwindows del \else rm \fi \pytex@stderr}
+% \end{macrocode}
+% If stderr is blank (equivalent to a single endline?)\ldots
+% \begin{macrocode}
+ \if\@tempb\pytex@par
+% \end{macrocode}
+% \ldots the \python\ code ran cleanly. Put stdout in the token
+% stream.
+% \begin{macrocode}
+ \@tempa
+ \else
+% \end{macrocode}
+% \ldots otherwise, the \python\ code failed. Put stderr in the token
+% stream and raise an error.
+% \begin{macrocode}
+ \errorstopmode
+ \typeout{%
+ ------------------------------------^^J%
+ ----------- PYTHON ERROR -----------^^J}
+ %\typeout{\expandafter\strip@prefix\meaning\@tempb}
+ \typeout{\@tempb}
+ \typeout{^^J%
+ ----------- PYTHON ERROR -----------^^J%
+ ------------------------------------}%
+ \batchmode
+ \end{document}
+ \fi
+}
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</package>
+% \end{macrocode}
+%
+%
+% \section{Module}
+% \label{sec:module}
+%
+% \begin{macrocode}
+%<*module>
+# See pytex.pdf for details.
+
+import os
+import pickle
+
+class PyTeX_Storage (object):
+ """
+ >>> test_file = 'test.pkl'
+ >>> p = PyTeX_Storage(test_file)
+ >>> p.a = 1
+ >>> p.b = 2
+ >>> p.save()
+ >>> del p
+ >>> p = PyTeX_Storage(test_file)
+ >>> p.a
+ Traceback (most recent call last):
+ ...
+ AttributeError: 'PyTeX_Storage' object has no attribute 'a'
+ >>> p.load()
+ >>> p.a
+ 1
+ >>> p.b
+ 2
+ >>> p.cleanup()
+ """
+ def __init__(self, pytexfile):
+ self._pytexfile = self._pytexfile_path(pytexfile)
+ self._hidden = dir(self)
+ self._hidden.append('_hidden')
+
+ def _pytexfile_path(self, pytexfile):
+ """
+ >>> p = PyTeX_Storage('dummy')
+ >>> p._pytexfile_path('test')
+ 'test.pkl'
+ >>> p._pytexfile_path('test.pkl')
+ 'test.pkl'
+ """
+ if not pytexfile.endswith('.pkl'):
+ pytexfile += '.pkl'
+ return pytexfile
+
+ def load(self):
+ try:
+ f = open(self._pytexfile, 'r')
+ except IOError: # [Errno 2] No such file or directory
+ return
+ saved = pickle.load(f)
+ for key,value in saved.items():
+ setattr(self, key, value)
+
+ def save(self, protocol=-1):
+ saved = {}
+ for key in dir(self):
+ if key in self._hidden:
+ continue
+ saved[key] = getattr(self, key)
+ f = open(self._pytexfile, 'w')
+ pickle.dump(saved, f, protocol=protocol)
+ f.close()
+
+ def cleanup(self):
+ os.remove(self._pytexfile)
+
+def test() :
+ import doctest
+ doctest.testmod()
+
+if __name__ == '__main__':
+ test()
+%</module>
+% \end{macrocode}
+%
+%
+% \section{Examples}
+% \label{sec:example}
+%
+% \begin{macrocode}
+%<*example>
+%% See pytex.pdf for details.
+\documentclass{article}
+\usepackage{pytex}
+\begin{document}
+
+\section{Problem 1}
+\pytexfile{problem1}
+
+If $a=\pytex{p.a=1; print p.a}$ and $b=\pytex{p.b=2; print p.b}$, then
+what is $a\cdot b$? [Answer: $\pytex{p.ans=p.a*p.b; print p.ans}$]
+
+\section{Problem 2}
+\pytexfile{problem2}
+
+If $a=\pytex{p.a=1; print p.a}$ and $b=\pytex{p.b=3; print p.b}$, then
+what is $a\cdot b$? [Answer: $\pytex{p.ans=p.a*p.b; print p.ans}$]
+
+\section{Discussion}
+\pytexfile{problem3}
+
+Comparing our answers for Problems 1 and 2, we see that the answer to
+1 (\pytexfile{problem1}$\pytex{print p.ans}$) is smaller than the answer
+to 2 (\pytexfile{problem2}$\pytex{print p.ans}$). This is to be expected,
+since $a$ is the same in both problems, and $b$ is larger in Problem 2
+($\pytexfile{problem1}\pytex{print p.b} <
+ \pytexfile{problem2}\pytex{print p.b}$).
+% \end{macrocode}
+% We can explicitly check our statement. \LaTeX\ compilation will
+% fail if the |\pytex| call returns a non-zero status code, as it
+% does for Exceptions.
+% \begin{macrocode}
+\pytex{p1 = pytex.PyTeX_Storage('problem1'); p1.load();
+ assert p.b > p1.b, ('b2 = '+str(p.b)+' > '+str(p1.b)+' = b1')}
+
+\end{document}
+%</example>
+% \end{macrocode}
+%
+%
+% \Finale
+\endinput