Initial version 0.1 master
authorW. Trevor King <wking@drexel.edu>
Thu, 4 Mar 2010 22:33:08 +0000 (17:33 -0500)
committerW. Trevor King <wking@drexel.edu>
Thu, 4 Mar 2010 22:33:08 +0000 (17:33 -0500)
Makefile [new file with mode: 0644]
pytex.dtx [new file with mode: 0644]
pytex.ins [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..4b53159
--- /dev/null
+++ b/pytex.dtx
@@ -0,0 +1,438 @@
+% \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
diff --git a/pytex.ins b/pytex.ins
new file mode 100644 (file)
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