1

In trying to get a better understanding of kvoptions I have the following minimal example package and document. In the package, I construct a new macro command based on the logical states of three Boolean options. The presence of the Boolean false settings seems not to be a problem, but my question is whether or not this is an acceptable way to manipulate the options given that I get no errors or warnings.

The three options in this case are mutually exclusive and this seems to be the only way to ensure that. This is but a minimal example taken from a larger context in which there is already an existing command for changing the setup randomly within a document. No one could know that from this MWE (lack of context is a danger of using MWEs IHMO as exemplified here). I had already planned to document the requirement to specify only one option (again, not knowable from this MWE). I used a setup command because I have repeatedly been told that that is "the best way." So in the presence of correct functioning and absence of errors or warnings, how am I to know that something shouldn't be done? If there is a better way to handle the Booleans (and I agree it looks somewhat awkward here), I'm open to learning it. That's why I asked the question.

MWE package:

\ProvidesPackage{tiny}[2021-01-05 v1.0 A tiny demo package]
\RequirePackage{kvoptions}
\SetupKeyvalOptions{%
  family=tiny,%
  prefix=tiny@,%
  setkeys=\kvsetkeys%
}%

\DeclareBoolOption[false]{optionone}
\DeclareBoolOption[false]{optiontwo}
\DeclareBoolOption[true]{optionthree}
\ProcessKeyvalOptions{tiny}

\newcommand*{\tinysetup}{%
  \kvsetkeys{tiny}%
}%

% This block doesn't quite work as expected with \tinysetup{}
%\AtBeginDocument{%
%  \iftiny@optionone
%    \newcommand*{\momentum}[1]{#1\,\mathrm{kg}\cdot\mathrm{m}\cdot\mathrm{s}^{-1}}
%    optionone is active\par
%    \tiny@optiontwofalse
%    optiontwo is not active\par
%    \tiny@optionthreefalse
%    optionthree is not active\par
%  \fi
%  \iftiny@optiontwo
%    \newcommand*{\momentum}[1]{#1\,\mathrm{kg}\cdot\mathrm{m}/\mathrm{s}}
%    \tiny@optiononefalse
%    optionone is not active\par
%    optiontwo is active\par
%    \tiny@optionthreefalse
%    optionthree is not active\par
%  \fi  
%  \iftiny@optionthree
%    \newcommand*{\momentum}[1]{#1\,\mathrm{N}\cdot\mathrm{s}}
%    \tiny@optiononefalse
%    optionone is not active\par
%    \tiny@optiontwofalse
%    optiontwo is not active\par
%    optionthree is active\par
%  \fi
%}%

% This block seems to work as expected with \tinysetup{}
\AtBeginDocument{%
  \newcommand*{\momentum}[1]{#1\,%
    \iftiny@optionone
      \tiny@optiontwofalse
      \tiny@optionthreefalse
      \mathrm{kg}\cdot\mathrm{m}\cdot\mathrm{s}^{-1}%
    \fi
    \iftiny@optiontwo
      \tiny@optiononefalse
      \tiny@optionthreefalse
      \mathrm{kg}\cdot\mathrm{m}/\mathrm{s}%
    \fi
    \iftiny@optionthree
      \tiny@optiononefalse
      \tiny@optiontwofalse
      \mathrm{N}\cdot\mathrm{s}%
    \fi
  }% % end of \newcommand
}%

And here's the MWE document that uses the package:

% !TEX TS-program = lualatexmk
% !TEX encoding = UTF-8 Unicode

\documentclass{article}
\usepackage{tiny}

\begin{document}
Hello.

% should invoke optionthree
\( \momentum{3} \)

% should invoke optionone
\tinysetup{optionthree=false,optionone}
\( \momentum{3} \)

% should invoke optiontwo
\tinysetup{optionone=false,optiontwo}
\( \momentum{3} \)

% should again invoke optionthree
\tinysetup{optiontwo=false,optionthree}
\( \momentum{3} \)

\end{document}
8
  • 2
    If you want to allow setup commands in the document body it makes no sense at all to do something in AtBeginDocument. It also looks wrong to use a boolean here. What should happen if someone sets two options to true? Jan 6, 2021 at 8:17
  • 1
    I'd use some choice based key (but I think those aren't available in kvoptions). The problem why your first block doesn't work is because the way you coded it the Booleans are evaluated once in \AtBeginDocument and the state they had at this point is determining the macro behaviour.
    – Skillmon
    Jan 6, 2021 at 11:25
  • 1
    @LaTeXereXeTaL I've updated my answer to show an alternative implementation to three bools, and to show how one could use kvoptions for this.
    – Skillmon
    Jan 6, 2021 at 13:18
  • 1
    if they are mutually exclusive having a single option with option=one or option=two or option=three seems more natural than having multiple boolean options, but it's a matter of choice really. Jan 6, 2021 at 19:56
  • 1
    @DavidCarlisle please, please, say that the pun was intended :)
    – Skillmon
    Jan 6, 2021 at 22:17

2 Answers 2

3

Using another key=value package

Edit: I've changed this code example to also use the \chardef variant (see below for a short explanation).

I'd drop kvoptions and use another key=value implementation which offers choice like keys for this. Viable options would be l3keys (with l3keys2e for package options support), pgfkeys (with pgfopts for package options support), expkv (with expkv-opt for package options support, and expkv-def for predefined key types), or options.

Since I'm the author of expkv, what follows is an example implementation using it.

Package:

\ProvidesPackage{tiny}[2021-01-05 v1.0 A tiny demo package]

\RequirePackage{expkv-opt,expkv-def}

\ekvdefinekeys{tiny}
  {
    protected choice option =
      {
         one   = {\chardef\tiny@option0 }
        ,two   = {\chardef\tiny@option1 }
        ,three = {\chardef\tiny@option2 }
      }
    ,initial option = three
  }

\ekvoProcessGlobalOptions{tiny}
\ekvoProcessLocalOptions{tiny}

\ekvsetdef\tinysetup{tiny}

% This block seems to work as expected with \tinysetup{}
\AtBeginDocument{%
  \newcommand*{\momentum}[1]{#1\,%
    \ifcase\tiny@option
      \mathrm{kg}\cdot\mathrm{m}\cdot\mathrm{s}^{-1}%
    \or
      \mathrm{kg}\cdot\mathrm{m}/\mathrm{s}%
    \or
      \mathrm{N}\cdot\mathrm{s}%
    \fi
  }% % end of \newcommand
}

Document:

\documentclass{article}
\usepackage{tiny}

\begin{document}
Hello.

% should invoke optionthree
\( \momentum{3} \)

% should invoke optionone
\tinysetup{option=one}
\( \momentum{3} \)

% should invoke optiontwo
\tinysetup{option=two}
\( \momentum{3} \)

% should again invoke optionthree
\tinysetup{option=three}
\( \momentum{3} \)

\end{document}

Sticking to kvoptions

If you want to stick to kvoptions I'd drop the Bool options and use three Void options which directly set the Booleans (similar to the three choices in the code above). This way those keys don't take any values, but you get the correct behaviour of mutually exclusive options.

Package:

\ProvidesPackage{tiny}[2021-01-05 v1.0 A tiny demo package]

\RequirePackage{kvoptions}
\SetupKeyvalOptions{%
  family=tiny,%
  prefix=tiny@,%
  setkeys=\kvsetkeys
}

\newif\iftiny@one
\newif\iftiny@two
\newif\iftiny@three
\tiny@threetrue

\DeclareVoidOption{optionone}  {\tiny@onetrue\tiny@twofalse\tiny@threefalse}
\DeclareVoidOption{optiontwo}  {\tiny@onefalse\tiny@twotrue\tiny@threefalse}
\DeclareVoidOption{optionthree}{\tiny@onefalse\tiny@twofalse\tiny@threetrue}

\ProcessKeyvalOptions{tiny}

\newcommand*\tinysetup{\kvsetkeys{tiny}}

% This block seems to work as expected with \tinysetup{}
\AtBeginDocument{%
  \newcommand*{\momentum}[1]{#1\,%
    \iftiny@one
      \mathrm{kg}\cdot\mathrm{m}\cdot\mathrm{s}^{-1}%
    \fi
    \iftiny@two
      \mathrm{kg}\cdot\mathrm{m}/\mathrm{s}%
    \fi
    \iftiny@three
      \mathrm{N}\cdot\mathrm{s}%
    \fi
  }% % end of \newcommand
}%

Document:

\documentclass{article}
\usepackage{tiny}

\begin{document}
Hello.

% should invoke optionthree
\( \momentum{3} \)

% should invoke optionone
\tinysetup{optionone}
\( \momentum{3} \)

% should invoke optiontwo
\tinysetup{optiontwo}
\( \momentum{3} \)

% should again invoke optionthree
\tinysetup{optionthree}
\( \momentum{3} \)

\end{document}

Alternative implementation

Instead of using three \newifs, a perhaps more elegant implementation could be to use one macro that stores the respective choice as a number (the code uses kvoptions, the principle is applicable to the expkv example or any other key=value package as well):

Package:

\ProvidesPackage{tiny}[2021-01-05 v1.0 A tiny demo package]

\RequirePackage{kvoptions}
\SetupKeyvalOptions{%
  family=tiny,%
  prefix=tiny@,%
  setkeys=\kvsetkeys
}

% initial value
\chardef\tiny@option2

\DeclareVoidOption{optionone}  {\chardef\tiny@option0 }
\DeclareVoidOption{optiontwo}  {\chardef\tiny@option1 }
\DeclareVoidOption{optionthree}{\chardef\tiny@option2 }

\ProcessKeyvalOptions{tiny}

\newcommand*\tinysetup{\kvsetkeys{tiny}}

% This block seems to work as expected with \tinysetup{}
\AtBeginDocument{%
  \newcommand*{\momentum}[1]{#1\,%
    \ifcase\tiny@option
      \mathrm{kg}\cdot\mathrm{m}\cdot\mathrm{s}^{-1}%
    \or
      \mathrm{kg}\cdot\mathrm{m}/\mathrm{s}%
    \or
      \mathrm{N}\cdot\mathrm{s}%
    \fi
  }% % end of \newcommand
}

Document:

\documentclass{article}
\usepackage{tiny}

\begin{document}
Hello.

% should invoke optionthree
\( \momentum{3} \)

% should invoke optionone
\tinysetup{optionone}
\( \momentum{3} \)

% should invoke optiontwo
\tinysetup{optiontwo}
\( \momentum{3} \)

% should again invoke optionthree
\tinysetup{optionthree}
\( \momentum{3} \)

\end{document}
2
  • Excellent suggestions. I only recently learned about \ifcase, and I like the use of void options in `kvoptions'. Jan 7, 2021 at 2:22
  • @Skillmon And the version using expl3? ...I miss it :( Jan 8, 2021 at 5:33
2

This answer is just for fun (and because Skillmon didn't put something using expl3 :D). As you put in the description of your question, sometimes, it's hard to understand the context without having a long MWE, anyway, since expl3 came to stay, it's better to try to get used to it :), here is my best attempt.

\documentclass{article}
\begin{filecontents*}[overwrite]{tiny.sty}
\RequirePackage{l3keys2e}
\ProvidesExplPackage
    {tiny}
    {2021-01-05}
    {1.0}
    {A tiny demo package}

% Define a bool vars
\bool_new:N \l_tiny_option_one_bool
\bool_new:N \l_tiny_option_two_bool
\bool_new:N \l_tiny_option_three_bool

% Define a keys
\keys_define:nn { tiny }
  {
    option .choice:,
    option / one   .code:n    = \bool_set_true:N \l_tiny_option_one_bool
                                \bool_set_false:N \l_tiny_option_two_bool
                                \bool_set_false:N \l_tiny_option_three_bool,
    option / two   .code:n    = \bool_set_true:N \l_tiny_option_two_bool
                                \bool_set_false:N \l_tiny_option_one_bool
                                \bool_set_false:N \l_tiny_option_three_bool,
    option / three .code:n    = \bool_set_true:N \l_tiny_option_three_bool
                                \bool_set_false:N \l_tiny_option_one_bool
                                \bool_set_false:N \l_tiny_option_two_bool,
    option         .initial:n = three,
    option .value_required:n  = true,
  }

% Process
\ProcessKeysOptions { tiny }

% Setup
\NewDocumentCommand\tinysetup{ m }
  {
    \keys_set:nn { tiny } { #1 }
  }

% Command
\AtBeginDocument{
  \NewDocumentCommand\momentum{ m }
    {
      \group_begin:
        \bool_if:NT \l_tiny_option_one_bool
          {
            #1\,\mathrm{kg}\cdot\mathrm{m}\cdot\mathrm{s}^{-1}
          }
        \bool_if:NT \l_tiny_option_two_bool
          {
            #1\,\mathrm{kg}\cdot\mathrm{m}/\mathrm{s}
          }
        \bool_if:NT \l_tiny_option_three_bool
          {
            #1\,\mathrm{N}\cdot\mathrm{s}
          }
      \group_end:
    }
}
\end{filecontents*}
\usepackage{tiny} % default option=three
%\usepackage[option=one]{tiny} % load option=one
\begin{document}

Hello.

% should invoke option=three
\( \momentum{3} \)

% should invoke option=one
\tinysetup{option=one}
\( \momentum{3} \)

% should invoke option=two
\tinysetup{option=two}
\( \momentum{3} \)

% should again invoke option=three
\tinysetup{option=three}
\( \momentum{3} \)

\end{document}
3
  • Thank you very much. Jan 8, 2021 at 5:59
  • I'd use a single (non-Boolean) variable and use something like \int_cases:nn or \str_cases:nn in expl3 instead.
    – Skillmon
    Jan 8, 2021 at 12:24
  • @Skillmon Yes, that would be the best way, I just tried to meet the specification of the question. Jan 8, 2021 at 16:39

You must log in to answer this question.

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