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