10

I have been advised in this question to move from pgfopts to latex3 to handle package options. I would like to do that but I must admit that I am a little lost. Examples with the patterns I'm using the most would tremendously help me.

QUESTION: How to replace the pgfopts package option management system of mypackage below by latex3 key-value system? If you see things in this package which could be improved, then an answer in two steps would be great:

  1. the most literal translation possible from pgfopts to latex3
  2. possible improvements of the resulting updated package

mypackage.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

mydocument.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}

output

output

1
  • 3
    Inside a package loaded with either \RequirePackage or \usepackage you don't have to use \makeatletter and \makeatother, that's better handled by the LaTeX kernel instead, you should remove them.
    – Skillmon
    Commented Feb 18 at 19:24

1 Answer 1

12

Well, in l3keys/ltkeys setting keys and defining keys are two separated things, so the way you define new styles needs a bit of change. The fact that you're defining your \setup in a way that it might be used to set arbitrary package's keys is weird or at least unusual, and not necessary with ltkeys because \SetKeys already allows that via its optional argument. Same is true for your styles, it's weird that you allow arbitrary injections here, imho, you should limit that to your own package's namespace.

Nevertheless, this is more or less a direct copy of your code just to make it work with ltkeys instead of pgfkeys, no further improvements whatsoever (those are further down this answer). Note that not all the handlers you need are available from the ltkeys layer, and you have to use the l3keys names for them. I'd suggest you change your entire package to expl3 instead of using this strange mix of L3 and L2e code.

\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}

Here is a version of your code that has a few changes that I'd make (and hinted at above already):

  • only provide direct interfaces for your own package
  • move to L3 code where possible
  • in \newstyle actually make sure it is a new style
  • don't over-expand for the reset-list (use \exp_not:V)
  • set a much closer to reality minimum date for the used LaTeX kernel
  • define macros that should be protected as protected
\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}

The following is a big list of all the key-handlers in pgfkeys (as described in pgfmanual section 87.4 for version 3.1.10) and their counterparts in l3keys and the ltkeys-layer (which is much smaller as you can see, but inside \DeclareKeys you can use all the handlers from the l3keys column as well). Additionally I included a column with expkv-def types (because I'm the author of expkv and this is a shameless advertisement).

Keep in mind that l3keys is inspired by pgfkeys, but has a few very different design choices (separation of defining and setting for instance), hence the list looks rather sparse. But there are quite some handlers defined in l3keys missing in this table as they don't have a counterpart in pgfkeys.

pgfkeys l3keys ltkeys expkv-def
.cd N/A N/A N/A
.is family N/Al1 N/A N/Ae1
.default .default:n N/A default
.value required .value_required:n = true N/A built ine2
.value forbidden .value_forbidden:n = true N/A built ine2
.code .code:n .code code/noval
.ecode N/A N/A ecode/enoval
.code 2 args N/A N/A N/A
.ecode 2 args N/A N/A N/A
.code n args N/A N/A N/A
.ecode n args N/A N/A N/A
.code args N/A N/A N/A
.ecode args N/A N/A N/A
.add code N/A N/A N/A
.prefix code N/A N/A N/A
.append code N/A N/A also code/also noval
.style .meta:n N/A meta/nmeta
.estyle N/A N/A exp-notation + meta
.style 2 args N/A N/A N/A
.estyle 2 args N/A N/A N/A
.style n args N/A N/A N/A
.add style N/A N/A N/A
.style args N/A N/A N/A
.estyle args N/A N/A N/A
.prefix style N/A N/A N/A
.append style N/A N/A also meta
.initial N/Al2 N/A N/Ae3
.get N/A N/A N/A
.add N/A N/A N/A
.prefix N/A N/A N/A
.append N/A N/A N/A
.link N/A N/A N/A
.store in .tl_set:N .store store
.estore in .tl_set_e:N N/A estore
.is if .legacy_if_set:n .if bool
.is choice .choice:/.choices:nn N/A choice
.expand once N/A N/A exp-notation
.expand twice N/A N/A exp-notation
.expanded N/A N/A exp-notation
.evaluated N/A N/A N/A
.list N/Al3 N/A N/A
.forward to N/A N/A also meta
.search also N/A N/A unknown redirect
.try N/Al4 N/A N/Ae4
.retry N/A N/A N/A
.lastretry N/A N/A N/A
.show value N/A N/A N/A
.show code N/A N/A N/A

l1 key filtering is possible with .groups:n, the .cd-part of the functionality isn't supported.

l2 the .initial:n provided by l3keys isn't comparible, that one is used to set a value (since defining and setting keys is decoupled).

l3 for choice like keys there is .multichoice:/.multichoices:nn.

l4 You can check for key existance with \keys_if_exists:nnTF but there is no .try like handler to that. Additionally you can use \keys_set_known:nnN to only set known keys (like .try on all keys at once).

e1 the .cd part is possible with set, there is no key filtering built into expkv, only sets.

e2 built in behaviour because expkv differentiates between keys taking a value and those without (and both might share the same user-level name).

e3 just like l3keys, defining and setting keys is decoupled, the initial type sets a value and isn't comparable to .initial of pgfkeys.

e4 You can check for key existance with \ekvifdefined and \ekvifdefinedNoVal, but there is no .try like handler at use time.

8
  • So essentially the \setstyle is congruent with PGF's .style sort of?
    – yannisl
    Commented Feb 19 at 4:14
  • 2
    @yannisl inside \DeclareKeys/\keys_define:nn a .meta:n is congruent to a PGF .style, the \newstyle is a wrapper around that, \setstyle is just the interface to that mechanism as given in the OP. I'd honestly not set a mypackage@-prefix for those styles such that they can be used directly inside a key=value list, but that's the given interface.
    – Skillmon
    Commented Feb 19 at 7:17
  • A huge difference btween PGF and l3keys is that with PGF one can define new handlers, so for example I have a .bool defined that uses l3 bool. Or a .fontsize that can accept values and store them in a tl. Such code can be defined once and extends the range of available handlers. Also the fact that with PGF spaces are significant or not significant as you wish. But perhaps I will post a series of questions over the next few days so one can compare with concrete examples.
    – yannisl
    Commented Feb 19 at 21:04
  • Your package looks promising, needs a bit of improvement of the manual with some examples comparing l3keys to it, maybe they are there but I missed them.
    – yannisl
    Commented Feb 19 at 21:06
  • 2
    @yannisl if you find shortcomings in the documentation I'd be more than happy to get a bug ticket opened so we can discuss them and I can work on them. Or even PRs. If you don't have the necessary account you can also send me these per mail. Though the manual does no direct comparison with other packages to document features in general currently, only the very brief comparisons in the last section.
    – Skillmon
    Commented Feb 19 at 21:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .