From: W. Trevor King Date: Thu, 4 Mar 2010 22:33:08 +0000 (-0500) Subject: Initial version 0.1 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;ds=sidebyside;p=pytex.git Initial version 0.1 --- 7075fc0788cb48a8bd9ea8013d80f096b5129caf diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..41ec4c6 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +all : pytex.pdf pytex.sty example.pdf + +pytex.pdf : pytex.dtx + pdflatex $< + makeindex pytex.glo -s gglo.ist -o pytex.gls + pdflatex $< + +pytex.sty example.tex : pytex.ins pytex.dtx + pdflatex $< + +example.pdf : example.tex pytex.sty + #pdflatex -interaction=batchmode $< + pdflatex -shell-escape $< + +temp-clean : + rm -f *.aux *.log *.out *.lof *.lot *.toc \ + *.ilg *.glo *.gls *.idx *.ind *.dvi \ + *.pyc *.pkl + +semi-clean : temp-clean + rm -f *.tex + +clean : semi-clean + rm -f pytex.pdf example.pdf pytex.sty pytex.py pytex.tar.gz + +dist : pytex.tar.gz + +CLASS_FILES = Makefile README pytex.dtx pytex.ins \ + pytex.sty pytex.pdf +EXAMPLE_FILES = example.tex example.pdf + +pytex.tar.gz : $(CLASS_FILES) $(EXAMPLE_FILES) + rm -f $@ + mkdir pytex + cp -p $(CLASS_FILES) pytex/ + mkdir pytex/examples + cp -p $(EXAMPLE_FILES) pytex/examples/ + tar -chozf $@ pytex + rm -rf pytex diff --git a/pytex.dtx b/pytex.dtx new file mode 100644 index 0000000..4b53159 --- /dev/null +++ b/pytex.dtx @@ -0,0 +1,438 @@ +% \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 diff --git a/pytex.ins b/pytex.ins new file mode 100644 index 0000000..5199efa --- /dev/null +++ b/pytex.ins @@ -0,0 +1,100 @@ +%% +%% 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. +%% +%% Based on Scott Pakin's dtxtut/cskeleton.ins +%% http://www.ctan.org/tex-archive/info/dtxtut/ +%% + +\input docstrip.tex + +\keepsilent +\askforoverwritefalse + +\usedir{tex/latex/pytex} + +\def\mypreamble{^^J% +\MetaPrefix\space This is a generated file.^^J% +\MetaPrefix ^^J% +\MetaPrefix\space Copyright (C) 2010 W. Trevor King^^J% +\MetaPrefix ^^J% +\MetaPrefix\space This file may be distributed and/or modified under the conditions of^^J% +\MetaPrefix\space the LaTeX Project Public License, either version 1.3 of this license^^J% +\MetaPrefix\space or (at your option) any later version. The latest version of this^^J% +\MetaPrefix\space license is in:^^J% +\MetaPrefix ^^J% +\MetaPrefix\space\space\space\space\space http://www.latex-project.org/lppl.txt^^J% +\MetaPrefix ^^J% +\MetaPrefix\space and version 1.3 or later is part of all distributions of LaTeX version^^J% +\MetaPrefix\space 2003/06/01 or later.^^J% +\MetaPrefix ^^J% +} + +%%%%%%%%%%%%%%% +% LaTeX files % +%%%%%%%%%%%%%%% + +\preamble +\mypreamble +\endpreamble + +\generate{\file{pytex.sty}{\from{pytex.dtx}{package}} + \file{example.tex}{\from{pytex.dtx}{example}}} + +%%%%%%%%%%%%%%%% +% Python files % +%%%%%%%%%%%%%%%% + +% Switch to using the # character for comments +\catcode`\#=11 +\edef\MetaPrefix{#} +\catcode`\#=6 + +% Refresh the pre/post-ambles with the new \MetaPrefix. +\preamble +\mypreamble +\endpreamble +\postamble +\endpostamble +% From the docstrip manual: +% You can change the prefix used for putting meta comments to output +% files by redefining \MetaPrefix. Its default value +% is \DoubleperCent. The preamble uses value of \MetaPrefix current +% at time of \declarepreamble while meta comments in the source file +% use value current at time of \generate. Note that this means that +% you cannot produce concurrently two files using +% different \MetaPrefixes. +% I don't know why docstrip.dtx/.tex uses \edef rather than \def +% for \org@preamble and \org@postamble, but it does. + +\generate{\file{pytex.py}{\from{pytex.dtx}{module}}} + +%%%%%%%%%%%%%%% +% Conclusions % +%%%%%%%%%%%%%%% + +\obeyspaces +\Msg{*************************************************************} +\Msg{* *} +\Msg{* To finish the installation you have to move the following *} +\Msg{* file into a directory searched by TeX: *} +\Msg{* *} +\Msg{* pytex.sty *} +\Msg{* *} +\Msg{* To produce the documentation run the file pytex.dtx *} +\Msg{* through LaTeX. *} +\Msg{* *} +\Msg{* Happy TeXing! *} +\Msg{* *} +\Msg{*************************************************************} + +\endbatchfile