使用 lthooks 在本地挂载命令

使用 lthooks 在本地挂载命令

跟随上一个问题作为我的练习,我尝试使用 l3keys 和 lthooks 模仿 thmtools 与 amsthm 的行为。现在我被困在钩子部分。thmtools 为每个定理环境和通用(应用于所有定理)定义了 prehead、posthead、prefoot 和 postfoot 钩子。prehead 和 postfoot 钩子很简单。对于 posthead 钩子,我发现应该在 amsthm 命令之后添加代码\deferred@thm@head。(或者也许是\@begintheorem?区别在于\ignorespaces。)

对于通用钩子,只需执行

\NewHook{ amsthm-keys/allthms/posthead }
\AddToHook { cmd/deferred@thm@head/after } { \UseHook { amsthm-keys/allthms/posthead } }

但对于本地钩子,我只想在\deferred@thm@head特定环境之后添加代码。据我所知,添加钩子始终是全局的,因此我不知道如何在不更改定义的情况下做到这一点\deferred@thm@head

以下是 MWE:

\documentclass{article}
\usepackage{amsthm,kantlipsum,tcolorbox}

\ExplSyntaxOn

\keys_define:nn { mbert/thm }
  {
    name         .tl_set:N = \l_mbert_thm_name_tl,
    preheadhook  .tl_set:N = \l_mbert_thm_preheadhook_tl,
    postheadhook .tl_set:N = \l_mbert_thm_postheadhook_tl,
    prefoothook  .tl_set:N = \l_mbert_thm_prefoothook_tl,
    postfoothook .tl_set:N = \l_mbert_thm_postfoothook_tl,
  }

\tl_new:N \l_mbert_thm_defaultkeys_tl
\keys_precompile:nnN { mbert/thm }
  {
    name         = \text_titlecase:n { \l_mbert_thm_envname_tl },
    preheadhook  = {},
    postheadhook = {},
    prefoothook  = {},
    postfoothook = {},
  }
  \l_mbert_thm_defaultkeys_tl

\NewHook{ amsthm-keys/allthms/prehead }
\NewHook{ amsthm-keys/allthms/posthead }
\NewHook{ amsthm-keys/allthms/prefoot }
\NewHook{ amsthm-keys/allthms/postfoot }

\AddToHook { cmd/deferred@thm@head/after } { \UseHook { amsthm-keys/allthms/posthead } }
% How to hook into \deferred@thm@head locally?

\NewDocumentCommand { \NewThm } { m O{} }
  { 
    \tl_set:Nn \l_mbert_thm_envname_tl { #1 }
    \tl_use:N \l_mbert_thm_defaultkeys_tl
    \keys_set:nn { mbert/thm } { #2 }
    \exp_args:NnV \AddToHook { env/#1/begin } \l_mbert_thm_preheadhook_tl % local prehead hook
    \AddToHook { env/#1/begin } { \UseHook { amsthm-keys/allthms/prehead } } % generic prehead hook
    \AddToHook { env/#1/end } { \UseHook { amsthm-keys/allthms/postfoot } } % generic postfoot hook
    \exp_args:NnV \AddToHook { env/#1/end } \l_mbert_thm_postfoothook_tl % local postfoot hook
    \mbert_thm_new:ne { #1 } { \l_mbert_thm_name_tl }
  }

\cs_new_eq:NN \mbert_thm_new:nn \newtheorem
\cs_generate_variant:Nn \mbert_thm_new:nn { ne }

\ExplSyntaxOff

\NewThm{theorem}[
    preheadhook=\begin{tcolorbox},
    postfoothook=\end{tcolorbox}
    ]
\NewThm{cor}[
    name=Corollary,
    postheadhook=ABC
    ]
\NewThm{lemma}

\AddToHook{amsthm-keys/allthms/posthead}{***}
\AddToHook{amsthm-keys/allthms/postfoot}{END THEOREM}

\begin{document}

\begin{theorem}
\kant[2][1]
\end{theorem}

\begin{cor}
\kant[2][1]
\end{cor}

\begin{lemma}
\kant[2][1]
\end{lemma}

\end{document}

在此处输入图片描述

我显然不能添加类似

\exp_args:NnV \AddToHook { cmd/deferred@thm@head/after } \l_mbert_thm_postheadhook_tl

到 的定义\NewThm,则 的每个声明postheadhook=都会将代码添加到\deferred@thm@head

本质上我想要的效果

\AddToHook{env/theorem/begin}{\apptocmd{\deferred@thm@head}{CODE}{}{}}

但使用内核钩子。从这个答案,我知道没有直接的方法可以获取的行为\apptocmd。是否有关于本地挂接其他软件包的命令的一般建议?


额外尝试

\AddToHookNext{cmd/deferred@thm@head/after}在评论中@cfr的帮助下,嵌套的想法\AddToHook{env/<envname>/begin}看起来很有希望。事实上,添加例如

\AddToHook { env/cor/begin } { \AddToHookNext { cmd/deferred@thm@head/after } {HHH} }

达到了预期的效果。但是,当我尝试在 的定义中自动执行此操作时,在为 赋值\NewThm时什么也没有发生:postheadhook

\NewDocumentCommand { \NewThm } { m O{} }
  { 
    \tl_set:Nn \l_mbert_thm_envname_tl { #1 }
    \tl_use:N \l_mbert_thm_defaultkeys_tl
    \keys_set:nn { mbert/thm } { #2 }
    \exp_args:NnV \AddToHook { env/#1/begin } \l_mbert_thm_preheadhook_tl % local prehead hook
    \AddToHook { env/#1/begin } { \UseHook { amsthm-keys/allthms/prehead } } % generic prehead hook
%%% This next line is new.
    \AddToHook { env/#1/begin } { \exp_args:NnV \AddToHookNext { cmd/deferred@thm@head/after } \l_mbert_thm_postheadhook_tl }
    \AddToHook { env/#1/end } { \UseHook { amsthm-keys/allthms/postfoot } } % generic postfoot hook
    \exp_args:NnV \AddToHook { env/#1/end } \l_mbert_thm_postfoothook_tl % local postfoot hook
    \mbert_thm_new:ne { #1 } { \l_mbert_thm_name_tl }
  }

这可能是一个扩展问题吗?

又一次尝试

我不明白为什么,但如果我在添加的行前面加上前缀,上述尝试似乎有效\exp_args:Nnf,如下所示

\exp_args:Nnf \AddToHook { env/#1/begin } { \exp_args:NnV \AddToHookNext { cmd/deferred@thm@head/after } \l_mbert_thm_postheadhook_tl }

postheadhooko 型扩展不执行任何操作,如果包含纯文本以外的内容,e 型扩展会产生错误。只是幸运,还是这就是使用 f 型扩展的方法?

答案1

但对于本地钩子,我只想在 \deferred@thm@head特定环境之后添加代码。据我所知,添加钩子始终是全局的,因此我不知道如何在不改变定义的情况下做到这一点\deferred@thm@head

[…]

本质上我想要的效果

\AddToHook{env/theorem/begin}{\apptocmd{\deferred@thm@head}{CODE}{}{}}

但使用内核钩子。

这里有 5 种不同的选择:

\documentclass{article}
\pagestyle{empty}

\makeatletter \ExplSyntaxOn
    \def\pretext#1{before <#1>}
    \def\posttext#1{after <#1>}

    \NewDocumentEnvironment
        { one }
        { }
        { \pretext{one} }
        { \posttext{one} }

    \NewDocumentEnvironment
        { two }
        { }
        { \pretext{two} }
        { \posttext{two} }


    %%%%%%%%%%%%%%%%
    %%% Option 1 %%%
    %%%%%%%%%%%%%%%%
    %% Use \globaldefs to trick \hook_gput_code into actually behaving like a
    %% hypothetical \hook_put_code. This is a complete hack and wildly
    %% unsupported, so PLEASE DON'T ACTUALLY DO THIS.
    %
    % \hook_gput_code:nnn { env / two / begin } { . } {
    %     \PackageWarning{BAD~ IDEA!}{PLEASE~ DON'T~ ACTUALLY~ DO~ THIS!}
    %     \int_set:Nn \globaldefs { -1 }
    %     \hook_gput_code:nnn { cmd / pretext / after } { . } { \textbf { NEW } }
    %     \int_set:Nn \globaldefs { 0 }
    % }
    %%% End Option 1


    %%%%%%%%%%%%%%%%
    %%% Option 2 %%%
    %%%%%%%%%%%%%%%%
    %% Add the patch using a "cmd/..." hook at the beginning of the
    %% environment, and remove it at the end.
    %
    % \hook_gput_code:nnn { env / two / begin } { . } {
    %     \hook_gput_code:nnn { cmd / pretext / after } { . } { \textbf { NEW } }
    % }
    %
    % \hook_gput_code:nnn { env / two / end } { . } {
    %     \hook_gremove_code:nn { cmd / pretext / after } { . }
    % }
    %%% End Option 2


    %%%%%%%%%%%%%%%%
    %%% Option 3 %%%
    %%%%%%%%%%%%%%%%
    %% Add a one-time "cmd/..." hook at the beginning of the environment.
    %
    % \hook_gput_code:nnn { env / two / begin } { . } {
    %     \hook_gput_next_code:nn { cmd / pretext / after } { \textbf { NEW } }
    % }
    %%% End Option 3


    %%%%%%%%%%%%%%%%
    %%% Option 4 %%%
    %%%%%%%%%%%%%%%%
    %% Locally patch the command by directly using expl3 commands.
    %
    % \cs_generate_variant:Nn \cs_set:Npn { NpV }
    %
    % \cctab_const:Nn \c_package_cctab {
    %     \cctab_select:N \c_document_cctab
    %     \char_set_catcode_letter:N @
    % }
    %
    % \hook_gput_code:nnn { env / two / begin } { . } {
    %     \tl_set_rescan:Nnx
    %         \l_tmpa_tl
    %         { \cctab_select:N \c_package_cctab }
    %         { \cs_replacement_spec:N \pretext }
    %
    %     \tl_put_right:Nn \l_tmpa_tl { \textbf { NEW } }
    %     \cs_set:NpV \pretext #1 { \l_tmpa_tl }
    % }
    %%% End Option 4


    %%%%%%%%%%%%%%%%
    %%% Option 5 %%%
    %%%%%%%%%%%%%%%%
    %% Globally patch the command, but make the patch value depend on the current
    %% environment.
    %
    % \prop_new:N \g__example_aftertext_prop
    %
    % \hook_gput_code:nnn { cmd / pretext / after } { . } {
    %     \prop_item:NV \g__example_aftertext_prop \@currenvir
    % }
    %
    % \prop_gput:Nnn \g__example_aftertext_prop { two } { \textbf { NEW } }
    %%% End Option 5
\ExplSyntaxOff \makeatother

\begin{document}
    \begin{one}
        \emph{body}
    \end{one}

    \begin{two}
        \emph{body}
    \end{two}

    \begin{one}
        \emph{body}
    \end{one}

    \begin{two}
        \emph{body}
    \end{two}
\end{document}

示例输出

我不明白为什么,但如果我在添加的行前面加上前缀,上述尝试似乎有效\exp_args:Nnf,如下所示

\exp_args:Nnf \AddToHook { env/#1/begin } { \exp_args:NnV \AddToHookNext { cmd/deferred@thm@head/after } \l_mbert_thm_postheadhook_tl }

postheadhooko 型扩展不执行任何操作,如果包含纯文本以外的内容,e 型扩展会产生错误。只是幸运,还是这就是使用 f 型扩展的方法?

e如果postheadhook包含任何易碎内容,-type 扩展将会失败。o-type 扩展不起作用,因为它只扩展一次,并且\exp_args:NnV需要更多的扩展才能起作用。

f类型扩展在 99% 的情况下都不合适,但在这里似乎没问题。我会轻微地更喜欢

\exp_args:Nnf \AddToHook { env/#1/begin } {
    %          v   vvvvvvvvvvvv
    \exp_args:NNnV \exp_stop_f: \AddToHookNext { cmd/deferred@thm@head/after }
    \l_mbert_thm_postheadhook_tl
}

以防万一\AddToHookNext在扩展时爆炸,但它(和大多数其他 LaTeX 命令)受到保护,所以在这种情况下没有什么区别。

(最好的情况是类似这样的情况\hook_gput_code:nne … \hook_gput_next_code:ne … \exp_not:V \l_mbert_thm_postheadhook_tl,但由于某些未知原因,这会引发错误。)

相关内容