% \def\fileversion{0.1} % \def\filedate{2010/03/03} % \iffalse meta-comment %\def\fileversion{0.1} %\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} % %\NeedsTeXFormat{LaTeX2e} %\ProvidesClass{pytex} % [\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} % % Include some useful commands from |xkeyval|'s ||. %<*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}}{} % % Define a quick and dirty version of |xkeyval|'s |\DescribeOptions|. %<*driver> \newenvironment{option}[1]{\begin{macro}{#1}} {\end{macro}} % % 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> \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> \EnableCrossrefs \RecordChanges \EnableCrossrefs \begin{document} \DocInput{pytex.dtx} \end{document} % % \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} % % \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() % % \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} % % \end{macrocode} % % % \Finale \endinput