From ec5240b52d1b4447c571e172e6818d86c055efb9 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sun, 17 Nov 2013 02:47:53 -0300 Subject: [PATCH 1/8] The python code in each environment is only run again if it has changed - Refactored to simplify code - Changed to VerbatimOutput to simplify writing the environment content to a file. - A helper python module is used to run the user code in a cross plataform and encoding safe way (runpy.py). - The output file is now named @output.tex, it is included as utf-8 - After each run the used code is copied to a cache file. If the tex is compiled again and the code has not changed, the previous output is used. - Package takes options - Key: Value option `bin` used to specify the python binary to use. - Option subfolder used to indicate that python related files should be saved in a subfolder named `py` Still to do: - Make it cross platform (copy, delete and compare files) - Check if the path separator works in windows - Fix encoding in Python 3 --- python.sty | 151 ++++++++++++++++++++++++++++++----------------------- runpy.py | 26 +++++++++ 2 files changed, 111 insertions(+), 66 deletions(-) create mode 100644 runpy.py diff --git a/python.sty b/python.sty index 346c89b..d14284f 100644 --- a/python.sty +++ b/python.sty @@ -39,79 +39,98 @@ \RequirePackage{verbatim} \RequirePackage{color} \RequirePackage{ifplatform} - -\newwrite\@out -\newwrite\@module -\newread\@retcode +\RequirePackage{fancyvrb} +\RequirePackage{inputenc} +\RequirePackage{kvoptions} % Put the resulting Python code in separate files. +\newcommand{\@outname}{\jobname\arabic{@PythonEnvironmentCounter}} \newcounter{@PythonEnvironmentCounter} \setcounter{@PythonEnvironmentCounter}{1} -\newcommand{\@outname}{\jobname\arabic{@PythonEnvironmentCounter}} -\begingroup \catcode `|=0 \catcode `[=1 -\catcode`]=2 \catcode `\{=12 \catcode `\}=12 -\catcode`\\=12 |gdef|@xpython#1\end{python}[|immediate|write|@out[#1]|end[python]] -|endgroup - -\def\python{\kernel@ifnextchar [{\@python}{\@python[]}} - -\def\@python[#1]{% -\gdef\@pythoninclude{#1} -\immediate\openout\@out=\@outname.py -\newlinechar='15 -\begingroup \catcode`\^^M=12 % -\let\do\@makeother\dospecials\obeyspaces% -\@xpython} - -\def\endpython{% -\endgroup -\immediate\closeout\@out -\@writemodule -\ifwindows -\immediate\write18{type \@pythoninclude\space\@outname.py 2> nul: | python > \@outname.py.out 2> \@outname.py.err & call echo ^\@percentchar ERRORLEVEL^\@percentchar > \@outname.rc} -\else -\immediate\write18{cat \@pythoninclude\space\@outname.py | /usr/bin/env python > \@outname.py.out 2> \@outname.py.err; echo -n $? > \@outname.rc} -\fi -\immediate\input\@outname.py.out - -% Read the return code of the executed Python script. -\immediate\openin\@retcode=\@outname.rc -\immediate\read\@retcode to \rc -\immediate\closein\@retcode - -% If the return code isn't zero then include -% the traceback. -\ifnumequal{\rc}{0}{}{% - \begingroup - \color{red} - \verbatiminput{\@outname.py.err} - \endgroup +% Package Options +\SetupKeyvalOptions{ + family=PYT, + prefix=PYT@ } -\immediate\stepcounter{@PythonEnvironmentCounter} +\DeclareVoidOption{subfolder}{ + \renewcommand{\@outname}{py/\jobname\arabic{@PythonEnvironmentCounter}} +} + +\DeclareStringOption[python]{bin}[python] + +\ProcessKeyvalOptions* + +\newcommand{\@pythonbin}{\PYT@bin \space runpy.py \space} + +\typeout{PYTHON: Binary \@pythonbin} + +\newread\@retcode +\newread\@diff + +\def\executecode{ + \typeout{PYTHON: Running file \@outname.py} + \immediate\write18{\@pythonbin \space\@outname.py} + + \inputencoding{utf8} + \input{\@outname} + + % Read the return code of the executed Python script. + \immediate\openin\@retcode=\@outname.py.rc + \immediate\read\@retcode to \rc + \immediate\closein\@retcode + + % If the return code isn't zero then include + % the traceback. + \ifnumequal{\rc}{0}{ + \typeout{PYTHON: Run was successful} + }{% + \typeout{PYTHON: Run failed} + \begingroup + \color{red} + \verbatiminput{\@outname.py.err} + \endgroup + } + + % Copy to the current script to keep a cache. + \immediate\write18{cp \@outname.py \@outname.py.cache} + \immediate\write18{rm \@outname.py} + +} + +\newenvironment{python} +{ + \typeout{PYTHON: Writing file \@outname.py} + \deactivateeightbit\VerbatimOut{\@outname.py}} +{ + \endVerbatimOut + + \immediate\write18{cmp \@outname.py \@outname.py.cache; echo $? > \@outname.cmp.rc} + \immediate\openin\@diff=\@outname.cmp.rc + \immediate\read\@diff to \rc + \immediate\closein\@diff + + \ifnumequal{\rc}{0}{ + % Old file and new file are identical + \typeout{PYTHON: No need to run the script again} + \inputencoding{utf8} + \input{\@outname} + }{ + % Old file and new file are different or old is not present + \executecode + } + + \immediate\stepcounter{@PythonEnvironmentCounter} +} + +\newcommand{\deactivateeightbit}{% + \count@=127 + \loop + \catcode\count@=12 + \ifnum\count@<255 + \advance\count@\@ne + \repeat } -\def\@writemodule{% -\immediate\openout\@module=latex.py -\immediate\write\@module{jobname="\@outname"} -\immediate\closeout\@module} -% BUGS: -% -% \immediate\write18{cat \@pythoninclude\space\jobname.py | python > \jobname.py.out 2>\jobname.py.err} -% -% 1. Watch out for indentation done by aucTeX in Emacs -% -% 2. Let the package accept a "final version" option, such -% that the output of each python run is saved such that it can be -% inserted into the document by hand -% (conference, journals are not likely to compile with -% shell_escape or have python). -% -% \gdef\@prepython{} -% \def\prepython#1{% -% \gdef\@prepython{#1} -% } -% sed -e 's/^ //g' cluster.py -% \immediate\write18{\@prepython\space\jobname.py > \ diff --git a/runpy.py b/runpy.py new file mode 100644 index 0000000..07b525a --- /dev/null +++ b/runpy.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +import sys + +try: + reload(sys).setdefaultencoding("UTF-8") +except: + pass + + +filename = sys.argv[1] + +with open(filename[:-3] + '.tex', "w") as fout: + sys.stdout = fout + with open(filename + '.err', "w") as ferr: + with open(filename + '.rc', 'w') as frc: + with open(filename) as f: + try: + code = compile(f.read(), filename, 'exec') + exec(code, {'print': fout.write}) + frc.write('0') + except Exception as e: + frc.write('1') + import traceback + ferr.write(traceback.format_exc()) + raise e From 147c4c7626823db35d46ef70c9f2b3d9d379455d Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sun, 17 Nov 2013 20:53:30 -0300 Subject: [PATCH 2/8] Removed build.sh and note in README --- README.rst | 5 ----- build.sh | 3 --- 2 files changed, 8 deletions(-) delete mode 100755 build.sh diff --git a/README.rst b/README.rst index a3408df..85017b1 100644 --- a/README.rst +++ b/README.rst @@ -7,11 +7,6 @@ python.sty and associated scripts Discussion and examples at http://pycurious.org/2011/12/the-ultimate-python-latex-environment/ -build.sh - Compiles example.tex into example.pdf using rubber. Requires the - shell-escape branch of rubber - (https://code.launchpad.net/~brotchie/rubber/shell-escape) - example.tex Example usage of python.sty. diff --git a/build.sh b/build.sh deleted file mode 100755 index eba2773..0000000 --- a/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -mkdir -p .build -rubber -d --shell-escape --into=.build example.tex && cp .build/example.pdf . From 4e86d8ea1875ce7923552a723c9f21deaef04931 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sun, 17 Nov 2013 21:57:13 -0300 Subject: [PATCH 3/8] Added documentation --- README.rst | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- python.sty | 41 ++++++++++++++++++++++++++++++++--------- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 85017b1..19d219b 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,53 @@ +Python in Latex +=============== + +This package enables you to embed Python code (www.python.org) in LaTeX and insert the output into your LaTeX document. + +Installing +---------- + +Copy `python.sty` and `runpy.py` side by side with your document. + + +Using +----- + +In you LaTeX document insert: + + \usepackage{python} + ... + \begin{python} + print("42") + \end{python} + +Compile the document with the shell escape option and of course Python installed: + +% latex -shell-escape document.tex + +or insert/change "shell_escape = t" in your texmf.cnf + **Note**: By default LaTeX and its variants disallowing calling of arbitrary shell commands. python.sty requires unrestricted to the shell in order to execute embedded Python scripts. When running python.sty you must execute ``latex`` or ``pdftex`` with either the ``-enable-write18`` or ``-shell-escape`` command line argument to enabled access to the Python executable. -python.sty and associated scripts -================================= -Discussion and examples at http://pycurious.org/2011/12/the-ultimate-python-latex-environment/ +Package options +--------------- + +The package takes an optional key-value option (`bin`) that can be used to specify the python binary to use. (If not present `python` is used) + + \usepackage[bin=python2.6]{python} + + +Python scripts, output, error and return files are saved by default in the same folder as the documents and the folder gets crowded quite easily, If the word `subfolder` is added to the options, theses files will be served to a subfolder named `py`: + + \usepackage[subfolder]{python} + + +Package content +--------------- + +Original discussion and examples at http://web.archive.org/web/20120423051102/http://pycurious.org/2011/12/the-ultimate-python-latex-environment/ example.tex Example usage of python.sty. @@ -17,3 +59,6 @@ pytex.vim python.sty A LaTeX package that allows direct embedding of python scripts in LaTeX documents. + +runpy.py + A helper python script. diff --git a/python.sty b/python.sty index d14284f..681bbde 100644 --- a/python.sty +++ b/python.sty @@ -12,10 +12,15 @@ %% along with this program; if not, write to the Free Software %% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. %% + +%% 0.4 Version by HernĂ¡n Grecco +%% 0.3 Version by James Brotchie +%% %% Author: Martin R. Ehmsen, ehmsen@imada.sdu.dk. %% Department of Mathematics and Computer Science, %% University of Southern Denmark, DK %% + %% You can find an online copy of the GPL at %% http://www.gnu.org/copyleft/gpl.html . %% @@ -23,18 +28,36 @@ %% This can either be done by passing -shell-escape as an option to %% latex or by adding/changing "shell_escape = t" in your texmf.cnf . -% 0.21 -> 0.3: Intermediate files are now stored with different filenames. -% The return code of each Python shell execution is stored in an .rc file. -% Any stderr output of a failed Python shell execution is inserted as -% a red verbatim in the resulting document. -% -- James Brotchie + +% Python-sty Changelog +% ==================== +% +% +% 0.4 (unreleased) +% ---------------- +% - +% +% 0.3 (2011-12-22) +% ---------------- +% +% - Intermediate files are now stored with different filenames. +% - The return code of each Python shell execution is stored +% in an .rc file. +% - Any stderr output of a failed Python shell execution is +% inserted as a red verbatim in the resulting document. +% -- James Brotchie +% +% 0.21 +% ---- +% +% - Moved \newwrite\@module from \@writemodule and out, since +% no more than 15 \newwrites are allowed (and the previous +% version created a new every time \@writemodule was called. +% % -% 0.2 -> 0.21: Moved \newwrite\@module from \@writemodule and out, since -% no more than 15 \newwrites are allowed (and the previous version created a new -% every time \@writemodule was called. \NeedsTeXFormat{LaTeX2e}[1994/12/01] -\ProvidesPackage{python}[2011/12/22 v0.3 Python in LaTeX] +\ProvidesPackage{python}[2013/11/17 v0.4 Python in LaTeX] \RequirePackage{etoolbox} \RequirePackage{verbatim} \RequirePackage{color} From 96921a0ef52dad52539fb405e07e46d75a35256c Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sun, 17 Nov 2013 21:59:56 -0300 Subject: [PATCH 4/8] Fixed rst --- README.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 19d219b..6e01556 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ Copy `python.sty` and `runpy.py` side by side with your document. Using ----- -In you LaTeX document insert: +In you LaTeX document insert:: \usepackage{python} ... @@ -20,9 +20,9 @@ In you LaTeX document insert: print("42") \end{python} -Compile the document with the shell escape option and of course Python installed: +Compile the document with the shell escape option and of course Python installed:: -% latex -shell-escape document.tex + $ latex -shell-escape document.tex or insert/change "shell_escape = t" in your texmf.cnf @@ -34,12 +34,14 @@ either the ``-enable-write18`` or ``-shell-escape`` command line argument to ena Package options --------------- -The package takes an optional key-value option (`bin`) that can be used to specify the python binary to use. (If not present `python` is used) +The package takes an optional key-value option (`bin`) that can be used to specify the +python binary to use. (If not present `python` is used):: \usepackage[bin=python2.6]{python} -Python scripts, output, error and return files are saved by default in the same folder as the documents and the folder gets crowded quite easily, If the word `subfolder` is added to the options, theses files will be served to a subfolder named `py`: +Python scripts, output, error and return files are saved by default in the same folder as the documents and the folder gets crowded quite easily, If the word `subfolder` is +added to the options, these files will be served to a subfolder named `py`:: \usepackage[subfolder]{python} From 500344651139941dd3389f001f79b1137dda5ad1 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Wed, 27 Nov 2013 21:36:49 -0300 Subject: [PATCH 5/8] Check for error and show it if necessary even if the script has not changed. --- python.sty | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/python.sty b/python.sty index 681bbde..0c60218 100644 --- a/python.sty +++ b/python.sty @@ -96,6 +96,13 @@ \typeout{PYTHON: Running file \@outname.py} \immediate\write18{\@pythonbin \space\@outname.py} + % Copy to the current script to keep a cache. + \immediate\write18{cp \@outname.py \@outname.py.cache} + \immediate\write18{rm \@outname.py} + +} + +\def\usecoderesult{ \inputencoding{utf8} \input{\@outname} @@ -115,11 +122,6 @@ \verbatiminput{\@outname.py.err} \endgroup } - - % Copy to the current script to keep a cache. - \immediate\write18{cp \@outname.py \@outname.py.cache} - \immediate\write18{rm \@outname.py} - } \newenvironment{python} @@ -137,12 +139,11 @@ \ifnumequal{\rc}{0}{ % Old file and new file are identical \typeout{PYTHON: No need to run the script again} - \inputencoding{utf8} - \input{\@outname} }{ % Old file and new file are different or old is not present \executecode } + \usecoderesult \immediate\stepcounter{@PythonEnvironmentCounter} } From 9c00732a09aa703aae80c1925e22d972beec4b06 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Thu, 28 Nov 2013 08:15:40 -0300 Subject: [PATCH 6/8] Restoree windows compatibility --- python.sty | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/python.sty b/python.sty index 0c60218..8aa880c 100644 --- a/python.sty +++ b/python.sty @@ -92,17 +92,41 @@ \newread\@retcode \newread\@diff -\def\executecode{ +\def\copytocache{ + \ifwindows + \immediate\write18{copy \@outname.py \@outname.py.cache} + \else + \immediate\write18{cp \@outname.py \@outname.py.cache} + \fi +} + +\def\delscript{ + \ifwindows + \immediate\write18{del \@outname.py} + \else + \immediate\write18{rm \@outname.py} + \fi +} + +\def\comparefiles{ + \ifwindows + \immediate\write18{fc \@outname.py \@outname.py.cache & call echo ^\@percentchar ERRORLEVEL ^\@percentchar > \@outname.cmp.rc} + \else + \immediate\write18{cmp \@outname.py \@outname.py.cache; echo $? > \@outname.cmp.rc} + \fi +} + +\def\executescript{ \typeout{PYTHON: Running file \@outname.py} \immediate\write18{\@pythonbin \space\@outname.py} % Copy to the current script to keep a cache. - \immediate\write18{cp \@outname.py \@outname.py.cache} - \immediate\write18{rm \@outname.py} + \copytocache + \delscript } -\def\usecoderesult{ +\def\usescriptresult{ \inputencoding{utf8} \input{\@outname} @@ -131,7 +155,7 @@ { \endVerbatimOut - \immediate\write18{cmp \@outname.py \@outname.py.cache; echo $? > \@outname.cmp.rc} + \comparefiles \immediate\openin\@diff=\@outname.cmp.rc \immediate\read\@diff to \rc \immediate\closein\@diff @@ -141,9 +165,9 @@ \typeout{PYTHON: No need to run the script again} }{ % Old file and new file are different or old is not present - \executecode + \executescript } - \usecoderesult + \usescriptresult \immediate\stepcounter{@PythonEnvironmentCounter} } From 76e2818890a9a5dfb988c3819ac5240c0e5c625d Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Thu, 28 Nov 2013 08:28:17 -0300 Subject: [PATCH 7/8] Changed names to avoid clashing with other packages --- python.sty | 66 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/python.sty b/python.sty index 8aa880c..8711a5d 100644 --- a/python.sty +++ b/python.sty @@ -67,7 +67,7 @@ \RequirePackage{kvoptions} % Put the resulting Python code in separate files. -\newcommand{\@outname}{\jobname\arabic{@PythonEnvironmentCounter}} +\newcommand{\@PYT@outname}{\jobname\arabic{@PythonEnvironmentCounter}} \newcounter{@PythonEnvironmentCounter} \setcounter{@PythonEnvironmentCounter}{1} @@ -78,7 +78,7 @@ } \DeclareVoidOption{subfolder}{ - \renewcommand{\@outname}{py/\jobname\arabic{@PythonEnvironmentCounter}} + \renewcommand{\@PYT@outname}{py/\jobname\arabic{@PythonEnvironmentCounter}} } \DeclareStringOption[python]{bin}[python] @@ -89,51 +89,51 @@ \typeout{PYTHON: Binary \@pythonbin} -\newread\@retcode -\newread\@diff +\newread\@PYT@retcode +\newread\@PYT@diff -\def\copytocache{ +\newcommand{\@PYT@copytocache}{ \ifwindows - \immediate\write18{copy \@outname.py \@outname.py.cache} + \immediate\write18{copy \@PYT@outname.py \@PYT@outname.py.cache} \else - \immediate\write18{cp \@outname.py \@outname.py.cache} + \immediate\write18{cp \@PYT@outname.py \@PYT@outname.py.cache} \fi } -\def\delscript{ +\newcommand{\@PYT@deletescript}{ \ifwindows - \immediate\write18{del \@outname.py} + \immediate\write18{del \@PYT@outname.py} \else - \immediate\write18{rm \@outname.py} + \immediate\write18{rm \@PYT@outname.py} \fi } -\def\comparefiles{ +\newcommand{\@PYT@comparefiles}{ \ifwindows - \immediate\write18{fc \@outname.py \@outname.py.cache & call echo ^\@percentchar ERRORLEVEL ^\@percentchar > \@outname.cmp.rc} + \immediate\write18{fc \@PYT@outname.py \@PYT@outname.py.cache & call echo ^\@percentchar ERRORLEVEL ^\@percentchar > \@PYT@outname.cmp.rc} \else - \immediate\write18{cmp \@outname.py \@outname.py.cache; echo $? > \@outname.cmp.rc} + \immediate\write18{cmp \@PYT@outname.py \@PYT@outname.py.cache; echo $? > \@PYT@outname.cmp.rc} \fi } -\def\executescript{ - \typeout{PYTHON: Running file \@outname.py} - \immediate\write18{\@pythonbin \space\@outname.py} +\newcommand{\@PYT@executescript}{ + \typeout{PYTHON: Running file \@PYT@outname.py} + \immediate\write18{\@pythonbin \space\@PYT@outname.py} % Copy to the current script to keep a cache. - \copytocache - \delscript + \@PYT@copytocache + \@PYT@deletescript } -\def\usescriptresult{ +\newcommand{\@PYT@returncode}{ \inputencoding{utf8} - \input{\@outname} + \input{\@PYT@outname} % Read the return code of the executed Python script. - \immediate\openin\@retcode=\@outname.py.rc - \immediate\read\@retcode to \rc - \immediate\closein\@retcode + \immediate\openin\@PYT@retcode=\@PYT@outname.py.rc + \immediate\read\@PYT@retcode to \rc + \immediate\closein\@PYT@retcode % If the return code isn't zero then include % the traceback. @@ -143,36 +143,36 @@ \typeout{PYTHON: Run failed} \begingroup \color{red} - \verbatiminput{\@outname.py.err} + \verbatiminput{\@PYT@outname.py.err} \endgroup } } \newenvironment{python} { - \typeout{PYTHON: Writing file \@outname.py} - \deactivateeightbit\VerbatimOut{\@outname.py}} + \typeout{PYTHON: Writing file \@PYT@outname.py} + \@PYT@deactivateeightbit\VerbatimOut{\@PYT@outname.py}} { \endVerbatimOut - \comparefiles - \immediate\openin\@diff=\@outname.cmp.rc - \immediate\read\@diff to \rc - \immediate\closein\@diff + \@PYT@comparefiles + \immediate\openin\@PYT@diff=\@PYT@outname.cmp.rc + \immediate\read\@PYT@diff to \rc + \immediate\closein\@PYT@diff \ifnumequal{\rc}{0}{ % Old file and new file are identical \typeout{PYTHON: No need to run the script again} }{ % Old file and new file are different or old is not present - \executescript + \@PYT@executescript } - \usescriptresult + \@PYT@returncode \immediate\stepcounter{@PythonEnvironmentCounter} } -\newcommand{\deactivateeightbit}{% +\newcommand{\@PYT@deactivateeightbit}{% \count@=127 \loop \catcode\count@=12 From 3641d16180eac183291a334229db66adc233d8ef Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Thu, 28 Nov 2013 21:12:57 -0300 Subject: [PATCH 8/8] Added version information. --- python.sty | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python.sty b/python.sty index 8711a5d..6b68184 100644 --- a/python.sty +++ b/python.sty @@ -33,9 +33,14 @@ % ==================== % % -% 0.4 (unreleased) +% 0.4 (2013-11-28) % ---------------- -% - +% - Python code is only executed if it has changed. +% - Option to save auxiliary files in a subfolder. +% - Option to choose the python binary. +% - Code refactoring. +% - UTF-8 support. +% % % 0.3 (2011-12-22) % ----------------