diff options
author | Karl Berry <karl@freefriends.org> | 2021-11-06 20:40:00 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2021-11-06 20:40:00 +0000 |
commit | 5c835cb7fc402dcaf0f66aad522767cb5d078237 (patch) | |
tree | 3ddf594876f9fb802903d48191ffebcdc3d3171a /Master/texmf-dist | |
parent | 557478dc45d15c122ff22b79ec1e321c2062b909 (diff) |
pyluatex (6nov21)
git-svn-id: svn://tug.org/texlive/trunk@60977 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist')
-rw-r--r-- | Master/texmf-dist/doc/lualatex/pyluatex/README.md | 10 | ||||
-rw-r--r-- | Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf | bin | 79524 -> 82190 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.tex | 47 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/pyluatex/pyluatex-interpreter.py | 37 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.lua | 73 | ||||
-rw-r--r-- | Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.sty | 29 |
6 files changed, 153 insertions, 43 deletions
diff --git a/Master/texmf-dist/doc/lualatex/pyluatex/README.md b/Master/texmf-dist/doc/lualatex/pyluatex/README.md index 3165301bd89..feb77a04bcf 100644 --- a/Master/texmf-dist/doc/lualatex/pyluatex/README.md +++ b/Master/texmf-dist/doc/lualatex/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/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf b/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf Binary files differindex 49fee5afac4..0318eda2cbd 100644 --- a/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf +++ b/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf diff --git a/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.tex b/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.tex index d6c03253508..86374249af9 100644 --- a/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.tex +++ b/Master/texmf-dist/doc/lualatex/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. diff --git a/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex-interpreter.py b/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex-interpreter.py index 4a17b5f5b0f..0e8b32035fd 100644 --- a/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex-interpreter.py +++ b/Master/texmf-dist/tex/lualatex/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/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.lua b/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.lua index d7e27bc6cb9..6e9c6abb631 100644 --- a/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.lua +++ b/Master/texmf-dist/tex/lualatex/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/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.sty b/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.sty index 81fa87ea827..ab015d9638d 100644 --- a/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.sty +++ b/Master/texmf-dist/tex/lualatex/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 |