从 pgfkeys 转移到 latex3 key-val 系统来处理包选项

从 pgfkeys 转移到 latex3 key-val 系统来处理包选项

我已被告知这个问题从 移动pgfoptslatex3处理包选项。我想这样做,但我必须承认我有点迷茫。我最常用的模式示例将对我有很大帮助。

问题:如何用键值系统替换下面pgfopts的包选项管理系统?如果您发现此包中可以改进的地方,那么分两步回答会很好:mypackagelatex3

  1. 最直译的翻译可能pgfoptslatex3
  2. 更新后的软件包可能带来的改进

我的包.sty

% preamble
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{mypackage}[2024/01/01 MyPackage]
\RequirePackage{xparse}
\RequirePackage{pgfopts}
\makeatletter

% package options
\pgfkeys{
    /mypackage/.is family,
    /mypackage/.cd,
    % options
    option/.store in = \mypackage@option,
    option = a,
    eoption/.estore in = \mypackage@eoption,
    eoption = b,
    % color
    color/.estore in = \mypackage@color,
    color = ,
    blue/.code = \pgfkeys{/mypackage/color = blue},
    blue/.value forbidden,
    red/.code = \pgfkeys{/mypackage/color = red},
    red/.value forbidden,
    green/.code = \pgfkeys{/mypackage/color = green},
    green/.value forbidden,
    % choice
    choice/.is choice,
    choice/.estore in = \mypackage@choice,
    choice/true/.code = \pgfkeys{/mypackage/choice = true},
    choice/false/.code = \pgfkeys{/mypackage/choice = false},
    choice/.default = true,
    choice = false,
}
\ProcessPgfPackageOptions{/mypackage}

% \print{identifier}: print current mypackage options
\ExplSyntaxOn
\NewDocumentCommand{\print}{m}{
    \begin{tabular}{|l|l|}
    \hline
    \multicolumn{2}{|c|}{\texttt{mypackage}\ options:\ \texttt{#1}}\\
    \hline
    option & \mypackage@option \\
    eoption & \mypackage@eoption \\
    color & \mypackage@color \\
    choice & \mypackage@choice \\
    \hline
    \end{tabular}\par\bigskip
}
\ExplSyntaxOff

% \setup{package}{option = xxxx}: set options for the specified package
\ExplSyntaxOn
\NewDocumentCommand{\setup}{mm}{\mypackage_setup:en{#1}{#2}}
\cs_new:Nn \mypackage_setup:nn{\pgfkeys{/#1,#2}}
\cs_generate_variant:Nn \mypackage_setup:nn{en}
\ExplSyntaxOff

% \reset{package}: reset options to default for the specified package
\ExplSyntaxOn
\NewDocumentCommand{\reset}{m}{\mypackage_reset:e{#1}}
\cs_new:Nn \mypackage_reset:n{\setup{#1}{#1@initial}}
\cs_generate_variant:Nn \mypackage_reset:n{e}
\ExplSyntaxOff

% \newstyle(*expanded){package}{style}{options}: create a style for the package
\ExplSyntaxOn
\NewDocumentCommand{\newstyle}{smmm}{\mypackage_newstyle:neen{#1}{#2}{#3}{#4}}
\cs_new:Nn \mypackage_newstyle:nnnn{
    \IfBooleanTF{#1}{
        \setup{#2}{#2@#3/.style/.expanded={#4}}
    }{
        \setup{#2}{#2@#3/.style={#4}}
    }
}
\cs_generate_variant:Nn \mypackage_newstyle:nnnn{neen}
\ExplSyntaxOff

% \setstyle{package}{style}: set the provided style for the given package
\ExplSyntaxOn
\NewDocumentCommand{\setstyle}{mm}{\mypackage_setstyle:ee{#1}{#2}}
\cs_new:Nn \mypackage_setstyle:nn{\setup{#1}{#1@#2}}
\cs_generate_variant:Nn \mypackage_setstyle:nn{ee}
\ExplSyntaxOff

% initial style
\newstyle*{mypackage}{initial}{
    option = \mypackage@option,
    eoption = \mypackage@eoption,
    color = \mypackage@color,
    choice = \mypackage@choice,
}

% closing
\makeatother

我的文档.tex

\documentclass[varwidth = true]{standalone}
\usepackage[blue]{mypackage}

\newstyle{mypackage}{mystyle}{option = A, eoption = B, green, choice = false}

\begin{document}
\print{1}
\setup{mypackage}{red}
\print{2}
\setup{mypackage}{choice}
\print{3}
\reset{mypackage}
\print{4}
\begingroup
\setup{mypackage}{option = X, eoption = Y}
\print{5}
\endgroup
\print{6}
\setstyle{mypackage}{mystyle}
\print{7}
\end{document}

输出

输出

答案1

好吧,在l3keys/ltkeys设置键和定义键是两个独立的事情,所以定义新样式的方式需要一点改变。事实上,你\setup以一种可能用于设置任意包的键的方式定义你,这很奇怪,或者至少是不寻常的,而且没有必要,ltkeys因为\SetKeys已经通过其可选参数允许这样做。你的样式也是如此,你在这里允许任意注入很奇怪,在我看来,你应该将其限制在你自己包的命名空间内。

尽管如此,这或多或少只是直接复制了您的代码,只是为了使其与ltkeys而不是一起工作pgfkeys,没有任何进一步的改进(这些改进在本答案的后面)。请注意,并非所有您需要的处理程序都可以从层获得ltkeys,您必须使用l3keys它们的名称。我建议您将整个包更改为 ,而expl3不是使用这种奇怪的 L3 和 L2e 代码混合。

\begin{filecontents}[overwrite]{\jobname.sty}
% preamble
\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{\@currname}[2024/01/01 MyPackage]
\RequirePackage{xparse}

\ExplSyntaxOn
\DeclareKeys
  {
    % options
     option  .store      = \mypackage@option
    ,option  .initial:n  = a
    ,eoption .tl_set_e:c = mypackage@eoption
    ,eoption .initial:n  = b
    % colors
    ,color   .tl_set_e:c = mypackage@color
    ,blue    .meta:n     = { color = blue }
    ,blue    .value_forbidden:n = true
    ,red     .meta:n     = { color = red }
    ,red     .value_forbidden:n = true
    ,green   .meta:n     = { color = green }
    ,green   .value_forbidden:n = true
    % choice
    ,choice  .choices:nn = { true, false }
      { \cs_set_eq:NN \mypackage@choice \l_keys_choice_tl }
    ,choice  .default:n  = true
    ,choice  .initial:n  = false
  }
\ExplSyntaxOff
\ProcessKeyOptions

% \print{identifier}: print current mypackage options
\ExplSyntaxOn
\NewDocumentCommand{\print}{m}{
    \begin{tabular}{|l|l|}
    \hline
    \multicolumn{2}{|c|}{\texttt{mypackage}\ options:\ \texttt{#1}}\\
    \hline
    option & \mypackage@option \\
    eoption & \mypackage@eoption \\
    color & \mypackage@color \\
    choice & \mypackage@choice \\
    \hline
    \end{tabular}\par\bigskip
}
\ExplSyntaxOff

% \setup{package}{option = xxxx}: set options for the specified package
\ExplSyntaxOn
\NewDocumentCommand{\setup}{mm}{\mypackage_setup:en{#1}{#2}}
\cs_new:Nn \mypackage_setup:nn{\SetKeys[#1]{#2}}
\cs_generate_variant:Nn \mypackage_setup:nn{en}
\ExplSyntaxOff

% \reset{package}: reset options to default for the specified package
\ExplSyntaxOn
\NewDocumentCommand{\reset}{m}{\mypackage_reset:e{#1}}
\cs_new:Nn \mypackage_reset:n{\setup{#1}{#1@initial}}
\cs_generate_variant:Nn \mypackage_reset:n{e}
\ExplSyntaxOff

% \newstyle(*expanded){package}{style}{options}: create a style for the package
\ExplSyntaxOn
\NewDocumentCommand{\newstyle}{smmm}{\mypackage_newstyle:neen{#1}{#2}{#3}{#4}}
\cs_new:Nn \mypackage_newstyle:nnnn{
    \IfBooleanTF{#1}{
      \keys_define:ne {#2} { \exp_not:n { #2@#3 } .meta:n = {#4} }
    }{
      \keys_define:nn {#2} { #2@#3 .meta:n = {#4} }
    }
}
\cs_generate_variant:Nn \mypackage_newstyle:nnnn{neen}
\ExplSyntaxOff

% \setstyle{package}{style}: set the provided style for the given package
\ExplSyntaxOn
\NewDocumentCommand{\setstyle}{mm}{\mypackage_setstyle:ee{#1}{#2}}
\cs_new:Nn \mypackage_setstyle:nn{\setup{#1}{#1@#2}}
\cs_generate_variant:Nn \mypackage_setstyle:nn{ee}
\ExplSyntaxOff

% initial style
\newstyle*{\@currname}{initial}{
    option = \mypackage@option,
    eoption = \mypackage@eoption,
    color = \mypackage@color,
    choice = \mypackage@choice,
}
% closing
\end{filecontents}

\documentclass[varwidth = true]{standalone}
\usepackage[blue]{\jobname}

\newstyle{\jobname}{mystyle}{option = A, eoption = B, green, choice = false}

\begin{document}
\print{1}
\setup{\jobname}{red}
\print{2}
\setup{\jobname}{choice}
\print{3}
\reset{\jobname}
\print{4}
\begingroup
\setup{\jobname}{option = X, eoption = Y}
\print{5}
\endgroup
\print{6}
\setstyle{\jobname}{mystyle}
\print{7}
\end{document}

这是您的代码版本,其中我做了一些更改(上面已经暗示过):

  • 仅为您自己的包提供直接接口
  • 尽可能转移到 L3 代码
  • 确保\newstyle它是一种新风格
  • 不要过度扩展 -list reset(使用\exp_not:V
  • 为使用的 LaTeX 内核设置更接近实际的最低日期
  • 将应受保护的宏定义为受保护
\begin{filecontents}[overwrite]{\jobname.sty}
% not entirely sure this is absolutely correct, but it's a much better guess
% than 1994 (2022/06/01 it is at least due to `\keys_precompile:nnN`)
\NeedsTeXFormat{LaTeX2e}[2022/06/01]
\ProvidesExplPackage{\@currname}{2024-01-01}{0.0}{MyPackage}

\keys_define:nn { mypackage }
  {
    % options
     option  .tl_set:N   = \l_mypackage_option_tl
    ,option  .initial:n  = a
    ,eoption .tl_set_e:N = \l_mypackage_eoption_tl
    ,eoption .initial:n  = b
    % colors
    ,color   .tl_set_e:N = \l_mypackage_color_tl
    % empty initial value is implicitly set if the tl doesn't exist yet
    ,blue    .meta:n     = { color = blue }
    ,blue    .value_forbidden:n = true
    ,red     .meta:n     = { color = red }
    ,red     .value_forbidden:n = true
    ,green   .meta:n     = { color = green }
    ,green   .value_forbidden:n = true
    % choice
    ,choice  .choices:nn = { true, false }
      { \tl_set_eq:NN \l_mypackage_choice_tl \l_keys_choice_tl }
    ,choice  .default:n  = true
    ,choice  .initial:n  = false
  }
\ProcessKeyOptions[mypackage]

% \print{identifier}: print current mypackage options
\NewDocumentCommand{\print}{m}{
    \begin{tabular}{|l|l|}
    \hline
    \multicolumn{2}{|c|}{\texttt{mypackage}~ options:~ \texttt{#1}} \\
    \hline
    option  & \l_mypackage_option_tl  \\
    eoption & \l_mypackage_eoption_tl \\
    color   & \l_mypackage_color_tl   \\
    choice  & \l_mypackage_choice_tl  \\
    \hline
    \end{tabular}\par\bigskip
}

% \mypackagesetup{option = xxxx}: set options for mypackage
\NewDocumentCommand \mypackagesetup { m }
  { \keys_set:nn { mypackage } {#1} }

% \newstyle(*expanded){style}{options}: create a style for the package
\NewDocumentCommand \newstyle { smm }
  {
    \IfBooleanTF {#1}
      { \mypackage_newstyle:ee {#2} {#3} }
      { \mypackage_newstyle:en {#2} {#3} }
  }
\cs_new_protected:Npn \mypackage_newstyle:nn #1#2
  {
    \keys_if_exist:nnTF { mypackage } { mypackage@#1 }
      { \msg_error:nnn  { mypackage } { style-exists } {#1} }
      { \keys_define:nn { mypackage } { mypackage@#1 .meta:n = {#2} } }
  }
\msg_new:nnn { mypackage } { style-exists }
  { The~ style~ `#1'~ already~ exists.~ Ignoring~ new~ definition. }
\cs_generate_variant:Nn \mypackage_newstyle:nn { en, ee }

% \setstyle{style}: set the provided style for the given package
\NewDocumentCommand \setstyle { m } { \mypackage_setstyle:e {#1} }
\cs_new_protected:Npn \mypackage_setstyle:n #1
  { \keys_set:nn { mypackage } { mypackage@#1 } }
\cs_generate_variant:Nn \mypackage_setstyle:n { e }

% \mypackagereset: reset options to default
% initial style (speed optimised, this isn't done via key=value at use time)
\cs_generate_variant:Nn \keys_precompile:nnN { ne }
\keys_precompile:neN { mypackage }
  {
     option  = \exp_not:V \l_mypackage_option_tl
    ,eoption = \exp_not:V \l_mypackage_eoption_tl
    ,color   = \exp_not:V \l_mypackage_color_tl
    ,choice  = \exp_not:V \l_mypackage_choice_tl
  }
  \l_tmpa_tl
\exp_args:NNnV \NewDocumentCommand \mypackagereset {} \l_tmpa_tl
% closing
\end{filecontents}

\documentclass[varwidth = true]{standalone}
\usepackage[blue]{\jobname}

\newstyle{mystyle}{option = A, eoption = B, green, choice = false}

\begin{document}
\print{1}
\mypackagesetup{red}
\print{2}
\mypackagesetup{choice}
\print{3}
\mypackagereset
\print{4}
\begingroup
\mypackagesetup{option = X, eoption = Y}
\print{5}
\endgroup
\print{6}
\setstyle{mystyle}
\print{7}
\end{document}

以下是 中的所有密钥处理程序pgfkeys(如 3.1.10 版第 87.4 节所述pgfmanual)及其在l3keysltkeys层中的对应项(如您所见,它要小得多,但在里面\DeclareKeys您也可以使用列中的所有处理程序l3keys)。此外,我还包含了一个包含expkv-def类型的列(因为我是 的作者expkv,这是一个无耻的广告)。

请记住,它的l3keys灵感来自pgfkeys,但有一些非常不同的设计选择(例如,定义和设置的分离),因此列表看起来相当稀疏。但是,l3keys这个表中缺少 中定义的相当多的处理程序,因为它们在 中没有对应项pgfkeys

pgfkeys l3keys ltkeys expkv-def
.cd 不适用 不适用 不适用
.is family 不适用l1 不适用 不适用e1
.default .default:n 不适用 default
.value required .value_required:n = true 不适用 内置e2
.value forbidden .value_forbidden:n = true 不适用 内置e2
.code .code:n .code code/noval
.ecode 不适用 不适用 ecode/enoval
.code 2 args 不适用 不适用 不适用
.ecode 2 args 不适用 不适用 不适用
.code n args 不适用 不适用 不适用
.ecode n args 不适用 不适用 不适用
.code args 不适用 不适用 不适用
.ecode args 不适用 不适用 不适用
.add code 不适用 不适用 不适用
.prefix code 不适用 不适用 不适用
.append code 不适用 不适用 also code/also noval
.style .meta:n 不适用 meta/nmeta
.estyle 不适用 不适用 exp-符号 +meta
.style 2 args 不适用 不适用 不适用
.estyle 2 args 不适用 不适用 不适用
.style n args 不适用 不适用 不适用
.add style 不适用 不适用 不适用
.style args 不适用 不适用 不适用
.estyle args 不适用 不适用 不适用
.prefix style 不适用 不适用 不适用
.append style 不适用 不适用 also meta
.initial 不适用l2 不适用 不适用e3
.get 不适用 不适用 不适用
.add 不适用 不适用 不适用
.prefix 不适用 不适用 不适用
.append 不适用 不适用 不适用
.link 不适用 不适用 不适用
.store in .tl_set:N .store store
.estore in .tl_set_e:N 不适用 estore
.is if .legacy_if_set:n .if bool
.is choice .choice:/.choices:nn 不适用 choice
.expand once 不适用 不适用 exp-符号
.expand twice 不适用 不适用 exp-符号
.expanded 不适用 不适用 exp-符号
.evaluated 不适用 不适用 不适用
.list 不适用l3 不适用 不适用
.forward to 不适用 不适用 also meta
.search also 不适用 不适用 unknown redirect
.try 不适用l4 不适用 不适用e4
.retry 不适用 不适用 不适用
.lastretry 不适用 不适用 不适用
.show value 不适用 不适用 不适用
.show code 不适用 不适用 不适用

可以使用l1.groups:n键进行过滤,但.cd不支持该功能的 -part。

l2 提供.initial:nl3keys是不可比的,那个是用来设置值的(因为定义和设置键是分离的)。

l3 为选择类似键,有 .multichoice:/ .multichoices:nn

l4 您可以使用 检查密钥是否存在,\keys_if_exists:nnTF但没有.try类似的处理程序。此外,您还可以使用 \keys_set_known:nnN仅设置已知密钥(例如.try 一次设置所有密钥)。

e1.cd部分可以使用set,没有内置按键过滤expkv,只有设置。

e2 内置行为,因为expkv区分带有值的键和不带有值的键(并且两者可能共享相同的用户级名称)。

e3 就像l3keys,定义和设置键是解耦的, initial类型设置一个值,并且不能与 进行 .initial比较pgfkeys

e4\ekvifdefined您可以使用和 检查密钥是否存在,但在使用时 \ekvifdefinedNoVal没有类似的处理程序。.try

相关内容