From c5b5e3fd195d647f3107171ca73a29b83bf4d7b3 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Tue, 27 Jul 2021 20:36:31 +0000 Subject: pyluatex (27jul21) git-svn-id: svn://tug.org/texlive/trunk@60095 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/doc/lualatex/pyluatex/README.md | 6 ++ .../texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf | Bin 65417 -> 78301 bytes .../texmf-dist/doc/lualatex/pyluatex/pyluatex.tex | 100 +++++++++++++++++++-- .../texmf-dist/tex/lualatex/pyluatex/pyluatex.lua | 85 ++++++++++++------ .../texmf-dist/tex/lualatex/pyluatex/pyluatex.sty | 19 ++-- 5 files changed, 166 insertions(+), 44 deletions(-) diff --git a/Master/texmf-dist/doc/lualatex/pyluatex/README.md b/Master/texmf-dist/doc/lualatex/pyluatex/README.md index a555ec20086..6c8015dce92 100644 --- a/Master/texmf-dist/doc/lualatex/pyluatex/README.md +++ b/Master/texmf-dist/doc/lualatex/pyluatex/README.md @@ -7,6 +7,11 @@ 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} @@ -31,6 +36,7 @@ $\sqrt{371} = \py{math.sqrt(371)}$ \randint{2}{5} \end{document} ``` + 2\. Compile using LuaLaTeX (shell escape is required) ``` lualatex -shell-escape example.tex diff --git a/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf b/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf index 4e93fb29355..58d2de88e15 100644 Binary files a/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf and b/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.pdf differ diff --git a/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.tex b/Master/texmf-dist/doc/lualatex/pyluatex/pyluatex.tex index c64d7579459..ffaf503914c 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.1.3 (2021/07/15)} +\date{v0.2.0 (2021/07/26)} \begin{document} \maketitle \raggedright @@ -28,7 +28,11 @@ PyLuaTeX allows you to execute Python code and to include the resulting output i \section{Example} \begin{enumerate} -\item \LaTeX{} document \inlcode|example.tex| +\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} @@ -54,6 +58,8 @@ $\sqrt{371} = \py{math.sqrt(371)}$ \randint{2}{5} \end{document} \end{tcblisting} +\end{enumerate} +\begin{enumerate} \item Compile using Lua\LaTeX{} (shell escape is required) \begin{tcblisting}{breakable,listing only, size=fbox,colframe=black!8,boxrule=3pt,colback=black!8} @@ -76,12 +82,20 @@ The folder \inlcode|example| contains additional example documents: 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|typesetting-example.tex|\\[0.5ex] + The code typesetting example below +\item \inlcode|typesetting-listings.tex|\\[0.5ex] + A detailed example for typesetting code and output with the \textit{listings} package +\item \inlcode|typesetting-minted.tex|\\[0.5ex] + A detailed example for typesetting code and output with the \textit{minted} package \end{itemize} For more intricate use cases have a look at our tests in the folder \inlcode|test|. \section{Installation} -PyLuaTeX is available on CTAN\footnote{\url{https://ctan.org/pkg/pyluatex}} and in MiKTeX. -It will be available in TeX Live soon (when you read this it probably already is). +PyLuaTeX is available in TeX Live, MiKTeX, and on CTAN\footnote{\url{https://ctan.org/pkg/pyluatex}} as \inlcode|pyluatex|. + +To install PyLuaTeX in \textbf{TeX Live} run \inlcode|tlmgr install pyluatex|.\\[0.5ex] +In \textbf{MiKTeX}, PyLuaTeX can be installed in the \textit{MiKTeX Console}. To install PyLuaTeX \textbf{manually}, do the following steps: \begin{enumerate} @@ -106,6 +120,12 @@ TEXMF/tex/latex/pyluatex/ \section{Reference} PyLuaTeX offers a simple set of options, macros and environments. +Most macros and environments are available as \textit{quiet} versions as well. +They have the suffix \inlcode|q| in their name, e.g. \inlcode|\pycq| or \inlcode|\pyfileq|. +The quiet versions suppress any output, even if the Python code explicitly calls \inlcode|print()|. +This is helpful if you want to process code or output further and do your own typesetting. +For an example, see the Typesetting Code section. + \subsection{Package Options} \begin{itemize} \item \inlcode|verbose|\\[0.5ex] @@ -118,14 +138,23 @@ PyLuaTeX offers a simple set of options, macros and environments. \subsection{Macros} \begin{itemize} \item \inlcode|\py{code}|\\[0.5ex] - Executes \inlcode|code| and writes the output to the document.\\[0.5ex] + Executes (object-like) \inlcode|code| and writes its string representation to the document.\\[0.5ex] \textit{Example:} \inlcode|\py{3 + 7}| +\item \inlcode|\pyq{code}|\\[0.5ex] + Executes (object-like) \inlcode|code|. Any output is suppressed.\\[0.5ex] + \textit{Example:} \inlcode|\pyq{3 + 7}| \item \inlcode|\pyc{code}|\\[0.5ex] - Executes \inlcode|code|\\[0.5ex] - \textit{Example:} \inlcode|\pyc{x = 5}| + Executes \inlcode|code|. Output (e.g. from a call to \inlcode|print()|) is written to the document.\\[0.5ex] + \textit{Examples:} \inlcode|\pyc{x = 5}|, \inlcode|\pyc{print('hello')}| +\item \inlcode|\pycq{code}|\\[0.5ex] + Executes \inlcode|code|. Any output is suppressed.\\[0.5ex] + \textit{Example:} \inlcode|\pycq{x = 5}| \item \inlcode|\pyfile{path}|\\[0.5ex] - Executes the Python file specified by \inlcode|path|.\\[0.5ex] + Executes the Python file specified by \inlcode|path|. Output (e.g. from a call to \inlcode|print()|) is written to the document.\\[0.5ex] \textit{Example:} \inlcode|\pyfile{main.py}| +\item \inlcode|\pyfileq{path}|\\[0.5ex] + Executes the Python file specified by \inlcode|path|. Any output is suppressed.\\[0.5ex] + \textit{Example:} \inlcode|\pyfileq{main.py}| \item \inlcode|\pysession{session}|\\[0.5ex] 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] @@ -146,6 +175,8 @@ PyLuaTeX offers a simple set of options, macros and environments. print(x) \end{python} \end{tcblisting} +\item \inlcode|pythonq|\\[0.5ex] + Same as the \inlcode|python| environment, but any output is suppressed. \end{itemize} \section{Requirements} \begin{itemize} @@ -156,6 +187,59 @@ PyLuaTeX offers a simple set of options, macros and environments. Our automated tests currently use TeX Live 2021 and Python 3.7+ on Ubuntu 20.04, macOS Catalina 10.15 and Windows Server 2019. +\section{Typesetting Code} +Sometimes, in addition to having Python code executed and the output written to your document, you also want to show the code itself in your document. +PyLuaTeX does not offer any macros or environments that directly typeset code. +However, PyLuaTeX has a \textbf{code and output buffer} which you can use to create your own typesetting functionality. +This provides a lot of flexibility for your typesetting. + +After a PyLuaTeX macro or environment has been executed, the corresponding Python code and output can be accessed via the Lua functions \inlcode|pyluatex.get_last_code()| and \inlcode|pyluatex.get_last_output()|, respectively. +Both functions return a Lua table\footnote{\url{https://www.lua.org/pil/2.5.html}} where each table item corresponds to a line of code or output. + +A simple example for typesetting code and output using the \textit{listings} package would be: +\begin{tcblisting}{breakable,listing only, + size=fbox,colframe=black!8,boxrule=3pt,colback=black!8} +\documentclass{article} + +\usepackage{pyluatex} +\usepackage{listings} +\usepackage{luacode} + +\begin{luacode} +function pytypeset() + tex.print("\\begin{lstlisting}[language=Python]") + tex.print(pyluatex.get_last_code()) + tex.print("\\end{lstlisting}") + tex.print("") -- ensure newline +end +\end{luacode} + +\newcommand*{\pytypeset}{% + \noindent\textbf{Input:} + \directlua{pytypeset()} + \textbf{Output:} + \begin{center} + \directlua{tex.print(pyluatex.get_last_output())} + \end{center} +} + +\begin{document} + +\begin{pythonq} +greeting = 'Hello PyLuaTeX!' +print(greeting) +\end{pythonq} +\pytypeset + +\end{document} +\end{tcblisting} + +Notice that we use the \inlcode|pythonq| environment, which suppresses any output. +After that, the custom macro \inlcode|\pytypeset| is responsible for typesetting the code and its output. + +Using a different code listings package like \textit{minted}, or typesetting inline code is very easy. +See the \inlcode|typesetting-*.tex| examples in the \inlcode|example| folder. + \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.lua b/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.lua index 4f904afe157..506b8bf9cf8 100644 --- a/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.lua +++ b/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.lua @@ -35,10 +35,11 @@ local folder = file.pathpart(file.collapsepath(status.filename, true)) local script = file.join(folder, "pyluatex-interpreter.py") local tcp = nil -local python_lines = {} -local python_output = nil +local env_end = nil +local env_lines = nil -local env_end = "\\end{python}" +local last_code = nil +local last_output = nil local function err_cmd(message) return "\\PackageError{PyLuaTeX}{" .. message .. "}{}" @@ -80,74 +81,100 @@ local function log_output(code) texio.write_nl("PyLuaTeX output: " .. code) end -local function print_lines(str) - for s in str:gmatch("[^\r\n]+") do - tex.sprint(s) +local function split_lines(str) + if str:sub(-1) ~= "\n" then + str = str .. "\n" end + + local t = {} + for s in str:gmatch("(.-)\r?\n") do + table.insert(t, s) + end + return t end -function pyluatex.execute(code, write) - if pyluatex.verbose then log_input(code) end +function pyluatex.execute(code, auto_print, write) + local full_code + if auto_print then + full_code = "print(str(" .. code .. "), end='')" + else + full_code = code + end + + if pyluatex.verbose then log_input(full_code) end + + local success, output = request({ session = pyluatex.session, code = full_code }) + last_code = split_lines(code) + last_output = split_lines(output) - local success, output = request({ session = pyluatex.session, code = code }) if success then if pyluatex.verbose then log_output(output) end + if write then - print_lines(output) - else - return output + tex.print(last_output) end else - if not pyluatex.verbose then log_input(code) end + if not pyluatex.verbose then log_input(full_code) end log_output(output) if write then tex.sprint(err_cmd("Python error (see above)")) end end - return nil + + return success end function pyluatex.print_env() - if python_output ~= nil then - print_lines(python_output) - python_output = nil + if last_output ~= nil then + tex.print(last_output) end end local function record_line(line) - local s, e = line:find(env_end) + 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(python_lines, line:sub(1, s - 1)) - local code = table.concat(python_lines, "\n") - local output = pyluatex.execute(code, false) - if output ~= nil then - python_output = output + table.insert(env_lines, line:sub(1, s - 1)) + local code = table.concat(env_lines, "\n") + local success = pyluatex.execute(code, false, false) + if success then return line:sub(s) else return env_end .. err_cmd("Python error (see above)") .. line:sub(e + 1) end else - table.insert(python_lines, line) + table.insert(env_lines, line) return "" end end -function pyluatex.record_env() - python_lines = {} - python_output = nil +function pyluatex.record_env(quiet) + if quiet then + env_end = "\\end{pythonq}" + else + env_end = "\\end{python}" + end + env_lines = {} luatexbase.add_to_callback("process_input_buffer", record_line, "pyluatex_record_line") end -function pyluatex.run_file(path) +function pyluatex.run_file(path, write) local f = io.open(path, "r") if f then local code = f:read("*a") f:close() - pyluatex.execute(code, true) + pyluatex.execute(code, false, write) else tex.sprint(err_cmd("File not found: " .. path)) end end +function pyluatex.get_last_code() + return last_code +end + +function pyluatex.get_last_output() + return last_output +end + return pyluatex diff --git a/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.sty b/Master/texmf-dist/tex/lualatex/pyluatex/pyluatex.sty index b9ce3f97179..0709ba76000 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/07/15 v0.1.3 Execute Python code on the fly] +\ProvidesPackage{pyluatex}[2021/07/26 v0.2.0 Execute Python code on the fly] \RequirePackage{expl3} \ExplSyntaxOn @@ -30,17 +30,22 @@ \directlua{pyluatex.start([==[\pyluatex@executable]==])} -\newenvironment{python}{\directlua{pyluatex.record_env()}}% +\newenvironment{python}{\directlua{pyluatex.record_env(false)}}% {\directlua{pyluatex.print_env()}} -\newcommand*{\py}[1]{% -\directlua{pyluatex.execute([==[print(str(#1), end='')]==], true)}% -} +\newenvironment{pythonq}{\directlua{pyluatex.record_env(true)}}{} + +\newcommand*{\python@inline}[3]{\directlua{pyluatex.execute([==[#1]==], #2, #3)}} + +\newcommand*{\py}[1]{\python@inline{#1}{true}{true}} +\newcommand*{\pyq}[1]{\python@inline{#1}{true}{false}} -\newcommand*{\pyc}[1]{\directlua{pyluatex.execute([==[#1]==], true)}} +\newcommand*{\pyc}[1]{\python@inline{#1}{false}{true}} +\newcommand*{\pycq}[1]{\python@inline{#1}{false}{false}} \newcommand*{\pysession}[1]{\directlua{pyluatex.session = [==[#1]==]}} -\newcommand*{\pyfile}[1]{\directlua{pyluatex.run_file([==[#1]==])}} +\newcommand*{\pyfile}[1]{\directlua{pyluatex.run_file([==[#1]==], true)}} +\newcommand*{\pyfileq}[1]{\directlua{pyluatex.run_file([==[#1]==], false)}} \endinput -- cgit v1.2.3