This is because the key uses \let <internal-macro> #1 (not \def), where #1 is the value passed in, after some sanitization. Then if an empty value is given, #1 is empty and \let eats a token which belongs to the pgfkeys internals, here happens to be an \fi.
Example below shows one possible way to guard against invalid formatters. \let is now only executed when the #1 contains exactly a single control sequence, otherwise an error is raised.
The helper conditional function \__tcobox_if_single_cs:n(TF) is the same as \__tcobox_if_valid_box_name:n(TF) proposed in #264, but with a more general name.
\documentclass{article}
\usepackage{tcolorbox}
\tcbuselibrary{documentation, theorems}
\makeatletter
\ExplSyntaxOn
\tcbset{
% defined in "documentation" library
index~key~formatter/.code=
{ \__tcobox_store_formatter_in:nN {#1} \kvtcb@doc@format@key },
index~keys~formatter/.code=
{ \__tcobox_store_formatter_in:nN {#1} \kvtcb@doc@format@keys },
% defined in "theorems" library
description~formatter/.code=
{ \__tcobox_store_formatter_in:nN {#1} \__tcobox_theo_format_description:n }
}
% assume it will be added to both "documentation" and "theorems" libraries
\cs_if_exist:NF \__tcobox_store_formatter_in:nN
{
\cs_new_protected:Npn \__tcobox_store_formatter_in:nN #1#2
{
% There's no reliable way to check whether a command actually takes
% a single mandatory argument, so we only test for a single control
% sequence here.
\__tcobox_if_single_cs:nTF {#1}
{ \cs_set_eq:NN #2 #1 }
{
\tcb@error
{
Invalid~formatter~ "\tl_to_str:n {#1}" ~passed~to \MessageBreak
"\pgfkeyscurrentkey". \MessageBreak
A~formatter~should~be~a~single~command~taking~one \MessageBreak
mandatory~argument
}
}
}
}
% also useful in \newtcobox etc. (see #264), so can be put in tcolorbox.sty
\prg_new_conditional:Npnn \__tcobox_if_single_cs:n #1 { p, TF }
{
\bool_lazy_and:nnTF
{ \tl_if_single_p:n {#1} } % false if #1 is empty
{ \token_if_cs_p:N #1 }
{ \prg_return_true: }
{ \prg_return_false: }
}
\ExplSyntaxOff
\makeatother
\newcommand{\myformatter}[1]{<#1>: }
% valid formatters
\tcbset{index key formatter=\textbf}
\tcbset{index key formatter=\myformatter}
\tcbset{description formatter=\textit}
\tcbset{description formatter=\myformatter}
% invalid formatters
\tcbset{index key formatter=}
\tcbset{index key formatter=a}
\tcbset{index key formatter=abc}
\tcbset{description formatter=}
\tcbset{description formatter=a}
\tcbset{description formatter=abc}
\begin{document}
content
\end{document}
In v6.2.0, passing an empty value to a formatter key like
\tcbset{index key formatter=}
would end with a partial\if...
, silently.This is because the key uses
\let <internal-macro> #1
(not\def
), where#1
is the value passed in, after some sanitization. Then if an empty value is given,#1
is empty and\let
eats a token which belongs to thepgfkeys
internals, here happens to be an\fi
.In v6.2.0 there are three such keys:
documentation
library:/tcb/index key formatter
and/tcb/index keys formatter
(see #259) https://github.com/T-F-S/tcolorbox/blob/067f384e2ba85ba3fd91958eb6adb8a1bcea6c29/tex/latex/tcolorbox/tcbdocumentation.code.tex#L202-L203theorems
library:/tcb/description formatter
https://github.com/T-F-S/tcolorbox/blob/067f384e2ba85ba3fd91958eb6adb8a1bcea6c29/tex/latex/tcolorbox/tcbtheorems.code.tex#L261-L264Example below shows one possible way to guard against invalid formatters.
\let
is now only executed when the#1
contains exactly a single control sequence, otherwise an error is raised.The helper conditional function
\__tcobox_if_single_cs:n(TF)
is the same as\__tcobox_if_valid_box_name:n(TF)
proposed in #264, but with a more general name.