目标:我想创建一个从宏内部执行 pythontex 代码块的命令。
应用:我有一个宏,用于排版一些方程式。我希望它们的使用副作用是在指定环境中执行静态代码。
这个问题可以归结为解决“为什么 verbatim 在... 内不起作用?”对于这个特定的 MWE。在这个问题中Geoffrey Poore 指出这些环境不能内联使用,但这并不能阻止我需要解决方案。(例如,放置filecontents*
在宏旁边,将其逐字内容传递到在会话内执行它的命令中)。
\documentclass{minimal}
\usepackage{pythontex}
\newcommand\runsSomeCode[0]{
\begin{sympycode}
j = 8
\end{sympycode}
}
\begin{document}
\runsSomeCode{}
\begin{equation}
j = \sympy{j}
\end{equation}
\end{document}
无论是否使用Verbatim
或sympycode
块,结果都是预期的:
! FancyVerb Error:
Extraneous input `j = 8 \end {sympycode} {}' between \begin{sympycode}[<key=val
ue>] and line end
.
\FV@Error ... {FancyVerb Error:
\space \space #1
}
l.11 \runsSomeCode{}
此解决方案的必要条件
运行代码:我不是在寻找文本格式化解决方案。我不是在寻找等宽字体或代码列表。我根本不需要 Python 输出。
适合会话:我有多个会话,它们可能需要或不需要在其上下文中执行相同的代码。如果没有这个要求,我只需在
sympyblock
宏外部创建一个 Python 函数并通过\sympy{}
或调用它即可\py{}
。必须通过宏调用:我没有能力在文档的顶层结构中创建自定义环境。
必须在数学模式下运行
避免使用外部输入文件:我使用构建我的文档
.latexmkrc
,因此在编译结束时删除临时文件的解决方案是可以接受的。使用filecontents
来生成输出也是公平的游戏。
目前,如果这导致我的文档无法被 depythontex 处理,我根本不担心。
答案1
也许你可以使用解析's +v
-argument-type 让 LaTeX 逐字读取内容。
这壳牌-package 提供命令\ShellEscape
作为将命令写入控制台/shell 的一种方式。
您可能可以使用它来pythontex
“从 LaTeX-run 内部”进行调用。
如果在我的系统上我将以下代码保存为test.tex
\documentclass{minimal}
\usepackage{shellesc}
\ShellEscape{pythontex \jobname.pytxcode }
\usepackage{amsmath}
\usepackage{xparse}
\usepackage[keeptemps, gobble=auto]{pythontex}
\newcommand\runSomeCodeCatcodeBegingroup{%
\begingroup
\catcode`\^^I=12 %
\catcode`\^^M=12 %
\newlinechar=\endlinechar
}%
\NewDocumentCommand{\runSomeCode}{}{%
\runSomeCodeCatcodeBegingroup
\runSomeCodeInternal
}%
\begingroup
\catcode`\^^M=12\relax%
\NewDocumentCommand{\runSomeCodeInternal}{+v+v}{%
\endgroup%
\NewDocumentCommand{\runSomeCodeInternal}{+v+v}{%
\scantokens{\endgroup#1[##1]^^M##2^^M#2}%
\ignorespaces%
}%
}%
\runSomeCodeInternal{\begin{sympycode}}{\end{sympycode}}%
\NewDocumentCommand{\newCodeSnippet}{m}{%
\runSomeCodeCatcodeBegingroup
\newCodeSnippetInternal{#1}%
}%
\NewDocumentCommand{\newCodeSnippetInternal}{m+v+v}{%
\endgroup
\newcommand*{#1}[1][#2]{\scantokens{\runSomeCode{##1}{#3}}}%
\ignorespaces
}%
\begin{document}
\newCodeSnippet{\functionExpression}{SessionA}{functionExpression = x**9 + 7*z}
\runSomeCode{SessionA}{x, z = symbols('x z')}
\functionExpression
\runSomeCode{SessionB}{
x = 2
z = 3
}
\functionExpression[SessionB]
\begin{equation*}
\sympy[SessionA]{x} = \sympy[SessionB]{x};
\sympy[SessionA]{z} = \sympy[SessionB]{z}
\to \frac{d}{dz} f\left(z\right) = \sympy[SessionA]{functionExpression} = \sympy[SessionB]{functionExpression}
\end{equation*}
\runSomeCode{SessionC}{
x = 1
z = 1
}
\functionExpression[SessionC]
\begin{equation*}
\sympy[SessionA]{x} = \sympy[SessionC]{x};
\sympy[SessionA]{z} = \sympy[SessionC]{z}
\to \frac{d}{dz} f\left(z\right) = \sympy[SessionA]{functionExpression} = \sympy[SessionC]{functionExpression}
\end{equation*}
\end{document}
并通过 shell 命令编译两次
pdflatex -shell-escape test.tex
,然后我得到这个:
答案2
我找到了一种适用于 和 的解决方案fancyvrb
。pythontex
此解决方案适用于fancyvrb
该解决方案在文档中包filecontentsdef
。希望这能帮助其他人解决看似常见问题的问题。
\usepackage[keeptemps, gobble=auto]{pythontex}
\usepackage{xparse}
\usepackage{filecontentsdef}
% [1] store tabs as active characters so they can be handled by fancyvrb
{\catcode`\^^I=\active\gdef\FCDtabtomacro{\noexpand^^I}}
% [2]
\begin{filecontentsdefmacro}{\testwe}
x, z = symbols('x z')
functionExpression = x**9 + 7*z
\end{filecontentsdefmacro}
% Define our macro that should execute the verbatim code block
\NewDocumentCommand\nestedcode{m}{{% [3]
\renewcommand*\FCDprintenvname{sympycode}% [4]
\renewcommand*\FCDprintenvoptions{% [5] environment options can be set
\unexpanded{[#1]}%
}%
\filecontentsprint\testwe % [6] we could pass a csname here
\frac{d}{dz} f\left(z\right) = \sympy[#1]{functionExpression}
}}
在您自己的文档中应用此解决方案的步骤如下:
- 根据
filecontentsdef
包,我们将标签保存为活动角色。 - 通过宏功能定义您的内容
filecontentsdef
。这些宏稍后将用作我们希望执行的代码的标识符。 - 构建任何你想要的宏。我利用了
xparse
在这里使用它是为了在“复杂”的调用层次结构中强调这段代码(如果有任何效果的话)。如果你想防止污染定义,那么将宏主体包装在一个组中按照正常的 LaTeX 惯例。 - 设置保存的宏希望扩展的环境
- 设置该环境接受的任何可选参数(例如 的会话名称
sympy
) - 调用
\filecontentsprint
以在所需的上下文中扩展已保存的内容。
为了演示所需的行为,以下文档:
\begin{document}
An expression, nested within an equasion environment, producing text while
modifying a session whose name was passed by argument:
\begin{equation}
\nestedcode{sessionname}
\end{equation}
Confirming that the session does, in fact, subsequently contain those symbols:
\begin{equation}
\sympy[sessionname]{x}
\end{equation}
\end{document}
产生所需的输出。
答案3
很明显,您必须为字符提供正确的 catcode,这可以通过以下方式完成:
正确的方法:查找包的文档,看看是否有任何命令可以在不改变 catcode 的情况下工作。
对于
verbatim
有\texttt
,对于pythontex
有\py
和\pyc
,对于listings
有\lstinline
。也可以看看:
手动方式:您必须以某种方式弄清楚环境需要哪些 catcode,然后手动将字符转换为正确的 catcode 并将其提供给环境。
自动方式等效(旧引擎):将内容写入外部文件,
\input
该文件,然后(可选)删除该文件。自动方式:使用合适的 catcodes 创建一个标记列表,然后
\scantokens
得出结果。 (用于以下答案)
没有其他办法,因为为了知道哪个 catcode 是正确的,你必须运行代码,一旦执行代码,就无法“撤消”效果。
下面的解决方案使用的\scantokens
方法与其他答案...但希望更容易理解。
\documentclass{article}
\usepackage{fancyvrb}
\begin{document}
\ExplSyntaxOn
% ======== Define \my__tl_set_verbatim : similar to \tl_set:Nn, but second argument is of xparse's +v type. ========
\NewDocumentCommand \my__tl_set_verbatim {m +v} {
\tl_set:Nn #1 {#2}
}
% ======== Define some auxiliary helper TLs. ========
\tl_set:Nx \my__newline_char_other_cat {\char_generate:nn {`\^^M} {12}} % A newline character with "other" category code.
\my__tl_set_verbatim \my__begin_verbatim {\begin{Verbatim}[frame=single, numbers=left]}
\my__tl_set_verbatim \my__end_verbatim {\end{Verbatim}}
% ======== Define the main command. ========
\cs_set:Npn \my__command:n #1 {
\exp_args:Nx \scantokens {
\my__begin_verbatim \my__newline_char_other_cat
x~ =~ #1 \my__newline_char_other_cat
\my__end_verbatim \my__newline_char_other_cat
}
}
% ======== Use that command. ========
\my__command:n {123}
\ExplSyntaxOff
\end{document}
该解决方案使用 fancyvrb 的Verbatim
环境进行演示,但它应该适用于其他环境。
结果是:
当然,要使用\ExplSyntaxOn
模式之外的命令,请定义一个适当命名的命令。
实用提示:
为了进行调试,将命令定义更改为
\cs_set:Npn \my__command:n #1 { \tl_set:Nx \my__tmp { \my__begin_verbatim \my__newline_char_other_cat x~ =~ #1 \my__newline_char_other_cat \my__end_verbatim \my__newline_char_other_cat } \tl_analysis_show:N \my__tmp \exp_args:NV \scantokens { \my__tmp } }
查找文档以
\tl_analysis_show:N
获取更多信息。不起作用的事情:
使用
\detokenize{\end{Verbatim}}
. 因为环境期望\end{Verbatim}
完全按照 而不是(和\end {Verbatim}
之间有一个空格)来结束环境。\end
{
在定义行中包含换行符
\my__begin_verbatim
:\my__tl_set_verbatim \my__begin_verbatim {\begin{Verbatim}[frame=single, numbers=left] }
根据我的其他答案xparse 的
+v
参数将在模式下将换行符转换为空格字符\ExplSyntaxOn
。(但是,切换到\ExplSyntaxOff
定义之前是有效的)