-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Description
Hi! Thank you for developing PyLuaTeX. It is an incredibly fast and useful package.
I would like to propose an add-on package, pyluatex-pysub.sty, which introduces a pythontex-like syntax (!{...}) for inline variable substitution.
Some users migrating from pythontex to pyluatex miss the \begin{pysub} environment, which allows for clean and readable string interpolation without explicitly writing \py{...} every time.
I believe this would make PyLuaTeX even more accessible and user-friendly, especially for those creating dynamic educational materials or exams.
Please let me know if you have any suggestions for improvement. I'd be happy to make any changes or integrate it directly into the core code if you prefer!
Key Features
- Familiar Syntax: Introduces the
pysubenvironment and\pys{}command. Within these, users can simply write!{var}instead of\py{var}. - Built-in Auto-Escaping: Added a new
!e{...}syntax that automatically escapes LaTeX special characters (like_,&,%) via a Python helper function. This prevents compilation errors when dealing with arbitrary strings. - Nested Braces Support: The Lua backend uses
%b{}for pattern matching, fully supporting nested braces inside the tag. This allows for complex Python expressions like!{ data["scores"]["math"] }without breaking the parser.
Minimal Working Example
\documentclass{article}
\usepackage{pyluatex-pysub} % please refer to `pyluatex-pysub.sty` below
\begin{pythonq}
import math
username = "user_name & co"
# A dictionary with nested structures
data = {
"scores": {
"math": 95,
"physics": 88
}
}
\end{pythonq}
\begin{document}
\begin{pysub}
% 1. Raw evaluation for numbers/math
Pi is approx !{round(math.pi, 2)}.
% 2. Nested braces support (Dictionary/List access)
My math score is !{ data["scores"]["math"] }.
% 3. Auto-escaped strings
Username is !e{username}.
\end{pysub}
\end{document}pyluatex-pysub.sty
\ProvidesPackage{pyluatex-pysub}[2026/02/13 PyLuaTeX add-on for PythonTeX-like variable substitution]
\RequirePackage{pyluatex}
\RequirePackage{luacode}
\RequirePackage{environ}
% ============================================================
% 1. Python Side: Helper function for LaTeX escaping
% ============================================================
\begin{pythonq}
def latex_escape(s):
"""
Escapes LaTeX special characters in a string to prevent compilation errors.
Useful for safely printing arbitrary Python strings in LaTeX.
"""
if s is None:
return ""
s = str(s)
# The order of replacement matters
s = s.replace("\\", r"\textbackslash{}")
s = s.replace("_", r"\_")
s = s.replace("&", r"\&")
s = s.replace("%", r"\%")
s = s.replace("$", r"\$")
s = s.replace("#", r"\#")
s = s.replace("{", r"\{")
s = s.replace("}", r"\}")
s = s.replace("^", r"\textasciicircum{}")
s = s.replace("~", r"\textasciitilde{}")
return s
\end{pythonq}
% ============================================================
% 2. Lua Side: Substitution engine supporting nested braces
% ============================================================
\begin{luacode*}
function process_pysub(content)
-- First, process !e{...} : Escaped output (safe for strings with special characters)
-- Using %b{} ensures that nested braces (e.g., !e{ data["key"] }) are handled correctly.
local result = string.gsub(content, "!e(%b{})", function(s)
local inner = string.sub(s, 2, -2) -- Remove the outermost braces
return "\\py{latex_escape(" .. inner .. ")}"
end)
-- Next, process !{...} : Raw output (for numbers, SymPy LaTeX output, etc.)
result = string.gsub(result, "!(%b{})", function(s)
local inner = string.sub(s, 2, -2)
return "\\py{" .. inner .. "}"
end)
tex.print(result)
end
\end{luacode*}
% ============================================================
% 3. LaTeX Side: Environments and Commands
% ============================================================
% The pysub environment allows multi-line substitution
\NewEnviron{pysub}{%
\directlua{process_pysub(\luastringO{\BODY})}%
}
% The \pys command allows inline substitution
\newcommand{\pys}[1]{%
\directlua{process_pysub(\luastringO{#1})}%
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels