summaryrefslogtreecommitdiff
path: root/macros/luatex/latex
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/latex')
-rw-r--r--macros/luatex/latex/pyluatex/README.md10
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex-interpreter.py37
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex.lua73
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex.pdfbin79524 -> 82190 bytes
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex.sty29
-rw-r--r--macros/luatex/latex/pyluatex/pyluatex.tex47
6 files changed, 153 insertions, 43 deletions
diff --git a/macros/luatex/latex/pyluatex/README.md b/macros/luatex/latex/pyluatex/README.md
index 3165301bd8..feb77a04bc 100644
--- a/macros/luatex/latex/pyluatex/README.md
+++ b/macros/luatex/latex/pyluatex/README.md
@@ -8,10 +8,6 @@ LaTeX documents must be compiled with LuaLaTeX for this to work.
## Example
1\. LaTeX document `example.tex`
-**Note:** PyLuaTeX starts Python 3 using the command `python3` by default.
-If `python3` does not start Python 3 on your system, find the correct command
-and replace `\usepackage{pyluatex}` with `\usepackage[executable={your python command}]{pyluatex}`.
-For example, `\usepackage[executable=python.exe]{pyluatex}`.
```latex
\documentclass{article}
@@ -36,11 +32,15 @@ $\sqrt{371} = \py{math.sqrt(371)}$
\randint{2}{5}
\end{document}
```
+**Note:** PyLuaTeX starts Python 3 using the command `python3` by default.
+If `python3` does not start Python 3 on your system, find the correct command
+and replace `\usepackage{pyluatex}` with `\usepackage[executable={your python command}]{pyluatex}`.
+For example, `\usepackage[executable=python.exe]{pyluatex}`.
+
2\. Compile using LuaLaTeX (shell escape is required)
```
lualatex -shell-escape example.tex
```
-
**Note:** Running LaTeX with the shell escape option enabled allows arbitrary code to be
executed. For this reason, it is recommended to compile trusted documents only.
diff --git a/macros/luatex/latex/pyluatex/pyluatex-interpreter.py b/macros/luatex/latex/pyluatex/pyluatex-interpreter.py
index 4a17b5f5b0..0e8b32035f 100644
--- a/macros/luatex/latex/pyluatex/pyluatex-interpreter.py
+++ b/macros/luatex/latex/pyluatex/pyluatex-interpreter.py
@@ -30,8 +30,36 @@ import socketserver
import json
import textwrap
from collections import defaultdict
+import re
class Interpreter(InteractiveInterpreter):
+ def execute_repl(self, code, ignore_errors):
+ self.success = True
+ output = ''
+ incomplete = False
+ for line in re.split('\r?\n', code):
+ output += ('... ' if incomplete else '>>> ') + line + '\n'
+ if incomplete:
+ buffer += '\n' + line
+ else:
+ buffer = line
+ with StringIO() as out, redirect_stdout(out), redirect_stderr(out):
+ try:
+ code_obj = compile_command(buffer)
+ if code_obj is not None:
+ incomplete = False
+ self.runcode(code_obj)
+ else:
+ incomplete = True
+ except:
+ incomplete = False
+ traceback.print_exc(limit=0)
+ self.success = False
+ output += out.getvalue()
+ if not ignore_errors and not self.success:
+ return False, output
+ return self.success, output
+
def execute(self, code):
with StringIO() as out, redirect_stdout(out), redirect_stderr(out):
self.success = True
@@ -61,7 +89,14 @@ class Handler(socketserver.StreamRequestHandler):
data = json.loads(data)
interpreter = interpreters[data['session']]
- success, output = interpreter.execute(textwrap.dedent(data['code']))
+ code = textwrap.dedent(data['code'])
+ if data['repl_mode']:
+ success, output = interpreter.execute_repl(
+ code,
+ data['ignore_errors']
+ )
+ else:
+ success, output = interpreter.execute(code)
response = { 'success': success, 'output': output }
self.wfile.write((json.dumps(response) + '\n').encode('utf-8'))
diff --git a/macros/luatex/latex/pyluatex/pyluatex.lua b/macros/luatex/latex/pyluatex/pyluatex.lua
index d7e27bc6cb..6e9c6abb63 100644
--- a/macros/luatex/latex/pyluatex/pyluatex.lua
+++ b/macros/luatex/latex/pyluatex/pyluatex.lua
@@ -26,6 +26,7 @@ local json = require("pyluatex-json")
local socket = require("socket")
pyluatex = pyluatex or {
+ ignore_errors = false,
verbose = false,
session = "default"
}
@@ -38,10 +39,15 @@ local tcp = nil
local env_end = nil
local env_lines = nil
local parent_env = nil
+local env_repl_mode = false
local last_code = nil
local last_output = nil
+local function trim(s)
+ return (s:gsub("^%s*(.-)%s*$", "%1"))
+end
+
local function err_cmd(message)
return "\\PackageError{PyLuaTeX}{" .. message .. "}{}"
end
@@ -94,7 +100,7 @@ local function split_lines(str)
return t
end
-function pyluatex.execute(code, auto_print, write)
+function pyluatex.execute(code, auto_print, write, repl_mode)
local full_code
if auto_print then
full_code = "print(str(" .. code .. "), end='')"
@@ -104,12 +110,19 @@ function pyluatex.execute(code, auto_print, write)
if pyluatex.verbose then log_input(full_code) end
- local success, output = request({ session = pyluatex.session, code = full_code })
+ local success, output = request(
+ {
+ session = pyluatex.session,
+ code = full_code,
+ repl_mode = repl_mode,
+ ignore_errors = pyluatex.ignore_errors
+ }
+ )
last_code = split_lines(code)
last_output = split_lines(output)
- if success then
- if pyluatex.verbose then log_output(output) end
+ if success or pyluatex.ignore_errors then
+ if pyluatex.verbose or not success then log_output(output) end
if write then
tex.print(last_output)
@@ -135,10 +148,14 @@ local function record_line(line)
local s, e = line:find(env_end, 1, true)
if s ~= nil then
luatexbase.remove_from_callback("process_input_buffer", "pyluatex_record_line")
- table.insert(env_lines, line:sub(1, s - 1))
+ local code_in_line = line:sub(1, s - 1)
+ if trim(code_in_line):len() > 0 then
+ -- only include this line if it contains non-whitespace characters
+ table.insert(env_lines, code_in_line)
+ end
local code = table.concat(env_lines, "\n")
- local success = pyluatex.execute(code, false, false)
- if success then
+ local success = pyluatex.execute(code, false, false, env_repl_mode)
+ if success or pyluatex.ignore_errors then
return line:sub(s)
else
return env_end .. err_cmd("Python error (see above)") .. line:sub(e + 1)
@@ -149,20 +166,14 @@ local function record_line(line)
end
end
-function pyluatex.record_env(quiet)
- local name
+function pyluatex.record_env(name, repl_mode)
if parent_env ~= nil then
name = parent_env
parent_env = nil
- else
- if quiet then
- name = "pythonq"
- else
- name = "python"
- end
end
env_end = "\\end{" .. name .. "}"
env_lines = {}
+ env_repl_mode = repl_mode
luatexbase.add_to_callback("process_input_buffer", record_line, "pyluatex_record_line")
end
@@ -172,12 +183,18 @@ function pyluatex.set_parent_env(name)
end
end
-function pyluatex.run_file(path, write)
+function pyluatex.run_file(path, write, repl_mode)
local f = io.open(path, "r")
if f then
local code = f:read("*a")
f:close()
- pyluatex.execute(code, false, write)
+ -- ignore trailing new line if present
+ if code:sub(-2) == "\r\n" then
+ code = code:sub(0, -3)
+ elseif code:sub(-1) == "\n" then
+ code = code:sub(0, -2)
+ end
+ pyluatex.execute(code, false, write, repl_mode)
else
tex.sprint(err_cmd("File not found: " .. path))
end
@@ -191,4 +208,26 @@ function pyluatex.get_last_output()
return last_output
end
+local function parse_bool(name, value)
+ if value == "true" then
+ return true
+ elseif value == "false" then
+ return false
+ else
+ tex.sprint(
+ err_cmd("Invalid value '" .. value .. "' for option " .. name)
+ )
+ end
+end
+
+function pyluatex.set_option(name, value)
+ if name == "ignoreerrors" then
+ pyluatex.ignore_errors = parse_bool(name, value)
+ elseif name == "verbose" then
+ pyluatex.verbose = parse_bool(name, value)
+ else
+ tex.sprint(err_cmd("Unknown option '" .. name .. "'"))
+ end
+end
+
return pyluatex
diff --git a/macros/luatex/latex/pyluatex/pyluatex.pdf b/macros/luatex/latex/pyluatex/pyluatex.pdf
index 49fee5afac..0318eda2cb 100644
--- a/macros/luatex/latex/pyluatex/pyluatex.pdf
+++ b/macros/luatex/latex/pyluatex/pyluatex.pdf
Binary files differ
diff --git a/macros/luatex/latex/pyluatex/pyluatex.sty b/macros/luatex/latex/pyluatex/pyluatex.sty
index 81fa87ea82..ab015d9638 100644
--- a/macros/luatex/latex/pyluatex/pyluatex.sty
+++ b/macros/luatex/latex/pyluatex/pyluatex.sty
@@ -9,7 +9,7 @@
%% version 2005/12/01 or later.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{pyluatex}[2021/08/07 v0.3.0 Execute Python code on the fly]
+\ProvidesPackage{pyluatex}[2021/11/05 v0.4.0 Execute Python code on the fly]
\RequirePackage{expl3}
\ExplSyntaxOn
@@ -25,19 +25,32 @@
\RequirePackage{kvoptions}
\DeclareStringOption[python3]{executable}
-\DeclareVoidOption{verbose}{\directlua{pyluatex.verbose = true}}
+\DeclareBoolOption{ignoreerrors}
+\DeclareBoolOption{verbose}
\ProcessKeyvalOptions*
+\ifpyluatex@ignoreerrors
+ \directlua{pyluatex.ignore_errors = true}
+\else
+ \directlua{pyluatex.ignore_errors = false}
+\fi
+\ifpyluatex@verbose
+ \directlua{pyluatex.verbose = true}
+\else
+ \directlua{pyluatex.verbose = false}
+\fi
\directlua{pyluatex.start([==[\pyluatex@executable]==])}
\newcommand*{\PyLTVerbatimEnv}{\directlua{pyluatex.set_parent_env([==[\@currenvir]==])}}
-\newenvironment{python}{\directlua{pyluatex.record_env(false)}}%
+\newenvironment{python}{\directlua{pyluatex.record_env("python", false)}}%
{\directlua{pyluatex.print_env()}}
-\newenvironment{pythonq}{\directlua{pyluatex.record_env(true)}}{}
+\newenvironment{pythonq}{\directlua{pyluatex.record_env("pythonq", false)}}{}
-\newcommand*{\pyluatex@inline}[3]{\directlua{pyluatex.execute([==[#1]==], #2, #3)}}
+\newenvironment{pythonrepl}{\directlua{pyluatex.record_env("pythonrepl", true)}}{}
+
+\newcommand*{\pyluatex@inline}[3]{\directlua{pyluatex.execute([==[#1]==], #2, #3, false)}}
\newcommand*{\py}[1]{\pyluatex@inline{#1}{true}{true}}
\newcommand*{\pyq}[1]{\pyluatex@inline{#1}{true}{false}}
@@ -46,8 +59,10 @@
\newcommand*{\pycq}[1]{\pyluatex@inline{#1}{false}{false}}
\newcommand*{\pysession}[1]{\directlua{pyluatex.session = [==[#1]==]}}
+\newcommand*{\pyoption}[2]{\directlua{pyluatex.set_option([==[#1]==], [==[#2]==])}}
-\newcommand*{\pyfile}[1]{\directlua{pyluatex.run_file([==[#1]==], true)}}
-\newcommand*{\pyfileq}[1]{\directlua{pyluatex.run_file([==[#1]==], false)}}
+\newcommand*{\pyfile}[1]{\directlua{pyluatex.run_file([==[#1]==], true, false)}}
+\newcommand*{\pyfileq}[1]{\directlua{pyluatex.run_file([==[#1]==], false, false)}}
+\newcommand*{\pyfilerepl}[1]{\directlua{pyluatex.run_file([==[#1]==], false, true)}}
\endinput
diff --git a/macros/luatex/latex/pyluatex/pyluatex.tex b/macros/luatex/latex/pyluatex/pyluatex.tex
index d6c0325350..86374249af 100644
--- a/macros/luatex/latex/pyluatex/pyluatex.tex
+++ b/macros/luatex/latex/pyluatex/pyluatex.tex
@@ -15,7 +15,7 @@
\usepackage{url}
\title{The \emph{pyluatex} package}
\author{Tobias Enderle\\\url{https://github.com/tndrle/PyLuaTeX}}
-\date{v0.3.0 (2021/08/07)}
+\date{v0.4.0 (2021/11/05)}
\begin{document}
\maketitle
\raggedright
@@ -29,10 +29,6 @@ PyLuaTeX allows you to execute Python code and to include the resulting output i
\section{Example}
\begin{enumerate}
\item \LaTeX{} document \inlcode|example.tex|\\[0.5ex]
-\textbf{Note:} PyLuaTeX starts Python 3 using the command \inlcode|python3| by default.
-If \inlcode|python3| does not start Python 3 on your system, find the correct command
-and replace \inlcode|\usepackage{pyluatex}| with \inlcode|\usepackage[executable={your python command}]{pyluatex}|.
-For example, \inlcode|\usepackage[executable=python.exe]{pyluatex}|.
\begin{tcblisting}{breakable,listing only,
size=fbox,colframe=black!8,boxrule=3pt,colback=black!8}
\documentclass{article}
@@ -58,28 +54,35 @@ $\sqrt{371} = \py{math.sqrt(371)}$
\randint{2}{5}
\end{document}
\end{tcblisting}
+\textbf{Note:} PyLuaTeX starts Python 3 using the command \inlcode|python3| by default.
+If \inlcode|python3| does not start Python 3 on your system, find the correct command
+and replace \inlcode|\usepackage{pyluatex}| with \inlcode|\usepackage[executable={your python command}]{pyluatex}|.
+For example, \inlcode|\usepackage[executable=python.exe]{pyluatex}|.
\item Compile using Lua\LaTeX{} (shell escape is required)
\begin{tcblisting}{breakable,listing only,
size=fbox,colframe=black!8,boxrule=3pt,colback=black!8}
lualatex -shell-escape example.tex
\end{tcblisting}
-\end{enumerate}
\textbf{Note:} Running \LaTeX{} with the shell escape option enabled allows arbitrary code to be
executed. For this reason, it is recommended to compile trusted documents only.
-
+\end{enumerate}
\subsection{Further Examples}
The folder \inlcode|example| contains additional example documents:
\begin{itemize}
-\item \inlcode|readme-example.tex|\\[0.5ex]
- The example above
-\item \inlcode|sessions.tex|\\[0.5ex]
- Demonstrates the use of different Python sessions in a document
+\item \inlcode|beamer.tex|\\[0.5ex]
+ Demonstrates the use of PyLuaTeX environments and typesetting in \textit{BEAMER} presentations. In particular, the \inlcode|fragile| option for frames is highlighted.
\item \inlcode|data-visualization.tex|\\[0.5ex]
Demonstrates the visualization of data using \textit{pgfplots} and \textit{pandas}
\item \inlcode|matplotlib-external.tex|\\[0.5ex]
Demonstrates how \textit{matplotlib} plots can be generated and included in a document
\item \inlcode|matplotlib-pgf.tex|\\[0.5ex]
Demonstrates how \textit{matplotlib} plots can be generated and included in a document using \textit{PGF}
+\item \inlcode|readme-example.tex|\\[0.5ex]
+ The example above
+\item \inlcode|repl.tex|\\[0.5ex]
+ Demonstrates how a Python console/REPL can be run and typeset
+\item \inlcode|sessions.tex|\\[0.5ex]
+ Demonstrates the use of different Python sessions in a document
\item \inlcode|typesetting-example.tex|\\[0.5ex]
The code typesetting example below
\item \inlcode|typesetting-listings.tex|\\[0.5ex]
@@ -127,12 +130,19 @@ For an example, see the Typesetting Code section.
\subsection{Package Options}
\begin{itemize}
\item \inlcode|verbose|\\[0.5ex]
- If this option is enabled, Python input and output is written to the log file.\\[0.5ex]
+ If this option is set, Python input and output is written to the \LaTeX{} log file.\\[0.5ex]
\textit{Example:} \inlcode|\usepackage[verbose]{pyluatex}|
\item \inlcode|executable|\\[0.5ex]
Specifies the path to the Python executable. (default: \inlcode|python3|)\\[0.5ex]
\textit{Example:} \inlcode|\usepackage[executable=/usr/local/bin/python3]{pyluatex}|
+\item \inlcode|ignoreerrors|\\[0.5ex]
+ By default, PyLuaTeX aborts the compilation process when Python reports an error.
+ If the \inlcode|ignoreerrors| option is set, the compilation process is not aborted.\\[0.5ex]
+ \textit{Example:} \inlcode|\usepackage[ignoreerrors]{pyluatex}|
\end{itemize}
+Package options (except for \inlcode|executable|) can be changed in the document with the
+\inlcode|\pyoption| command, e.g. \inlcode|\pyoption{verbose}{true}| or \inlcode|\pyoption{ignoreerrors}{false}|.
+
\subsection{Macros}
\begin{itemize}
\item \inlcode|\py{code}|\\[0.5ex]
@@ -157,6 +167,10 @@ For an example, see the Typesetting Code section.
Selects \inlcode|session| as Python session for subsequent Python code.\\[0.5ex]
The session that is active at the beginning is \inlcode|default|.\\[0.5ex]
\textit{Example:} \inlcode|\pysession{main}|
+\item \inlcode|\pyoption{option}{value}|\\[0.5ex]
+ Assigns \inlcode|value| to the package option \inlcode|option| anywhere in the document. For more information consider
+ the Package Options section.\\[0.5ex]
+ \textit{Example:} \inlcode|\pyoption{verbose}{true}|
\end{itemize}
\subsection{Environments}
\begin{itemize}
@@ -175,8 +189,13 @@ For an example, see the Typesetting Code section.
\end{tcblisting}
\item \inlcode|pythonq|\\[0.5ex]
Same as the \inlcode|python| environment, but any output is suppressed.
+\item \inlcode|pythonrepl|\\[0.5ex]
+ Executes the provided block of Python code in an interactive console/REPL. Code and output are
+ stored together in the output buffer and can be typeset as explained in section
+ Typesetting Code or as shown in the example \inlcode|repl.tex| in the folder
+ \inlcode|example|.
\end{itemize}
-You can create your own environments based on the \inlcode|python| and \inlcode|pythonq| environments.
+You can create your own environments based on the \inlcode|python|, \inlcode|pythonq| and \inlcode|pythonrepl| environments.
However, since they are verbatim environments, you have to use the macro \inlcode|\PyLTVerbatimEnv|
in your environment definition, e.g.
\begin{tcblisting}{breakable,listing only,
@@ -249,6 +268,8 @@ Using a different code listings package like \textit{minted}, or typesetting inl
You can also define your own environments that combine Python code and typesetting.
See the \inlcode|typesetting-*.tex| examples in the \inlcode|example| folder.
+To emulate an interactive Python console/REPL, the \inlcode|pythonrepl| environment can be used.
+
\section{How It Works}
PyLuaTeX runs a Python \inlcode|InteractiveInterpreter|\footnote{\url{https://docs.python.org/3/library/code.html#code.InteractiveInterpreter}} (actually several if you use different sessions) in the background for on the fly code execution.
Python code from your \LaTeX{} file is sent to the background interpreter through a TCP socket.