Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I have an environment with portions of the content that being conditionally typeset based on options passed to the environment. The content of this environment is predefined elsewhere so depending on the options different output is produced. This portion of the code works fine.

Now I am trying to report an error at the end of the environment if an unrecognized option was specified to detect any typos that might go unnoticed, and have run into what seems like an expansion issue. I tried all the possible uses of \edef and \expandafters that remotely made sense to me but have not found the right combination.

I do this be storing a list of all the options that were specified, and at the end I check that control sequence \<optionName>Encountered was defined for each of the specified options. In the body of the environment, I define:

\global\expandafter\def\csname <optionName> Encountered\endcsname{}%

for every possible <optionName> that could have been specified, and check against the list of options that were specified.

Note:

  • There are other options in the list of options to which this sort of checking is not to be applied, so I do need to individually add them to the list as opposed to just storing #1 and doing the \foreach loop at the end over that value.
  • I would prefer that the options themselves be stored in the list (not their corresponding csname), as then I can better report the error message at the end of the environment.
  • Also, the options may also be defined in a macro.

References:

Expected Output:

When the code in \ReportAnyUnusedOptions is uncommented and things are working, this should produce:

enter image description here

Code:

The problem code in \ReportAnyUnusedOptions has been commented to allow this to compile.

\documentclass{article}
\usepackage{pgffor}

\def\ListOfOptions{}

\makeatletter
\newcommand{\AddToListOfOptions}[1]{%
    \g@addto@macro\ListOfOptions{#1}%
    \typeout{***** Added option to check: #1}%
}
\makeatother


\newcommand{\ReportAnyUnusedOptions}{%
    Checking options: \par%
        \foreach \y in \ListOfOptions {%
%           \y%
%           \ifcsname\y Encountered\endcsname%
%               ~Encountered!\par%
%           \else%
%               ~Not Encountered!\par%
%           \fi%
        }%
}%

\newenvironment{ApplyOptions}[1]{%
   % Add to list of options to later check that they were used
    \foreach \x in {#1} {%
        % There are cases here where an option would not be
        % check later, so some will be skipped
        \edef\Option{\x}%
        \AddToListOfOptions{\Option}%
    }%
}{%
    \ReportAnyUnusedOptions%
}


\begin{document}
\begin{ApplyOptions}{Option A, Option C}%
    % Option A:
    % if "Option A" was specified, it would be typset here
    \global\expandafter\def\csname Option A Encountered\endcsname{}%
    %
    % Option B:
    % if "Option B" was specified, it would be typset here
    \global\expandafter\def\csname Option B Encountered\endcsname{}%
\end{ApplyOptions}

% Also possible to specify the options via a macro
\newcommand*{\MyOptions}{Option AA, Option CC}%
\begin{ApplyOptions}{\MyOptions}%
    % Option A:
    % if "Option AA" was specified, it would be typset here
    \global\expandafter\def\csname Option AA Encountered\endcsname{}%
    %
    % Option B:
    % if "Option BB" was specified, it would be typset here
    \global\expandafter\def\csname Option BB Encountered\endcsname{}%
\end{ApplyOptions}
\end{document}
share|improve this question
    
Maybe I misunderstand you, but isn't this what some *keyval* packages solve? –  tohecz May 24 '12 at 8:20
    
@tohecz: When I saw you comment this morning, I thought "Wow, that sure makes sense". But not that I think about it more it is not a direct solution, but one could probably adapt a keyval package to do this. In my case, the option names are not limited to a certain list, so the key values would need to be built on the fly. –  Peter Grill May 24 '12 at 18:56
    
Well, I thought that this is the isuue, but maybe dynamical (regex-defined?) keys are the next thing needed in keyval packages... –  tohecz May 24 '12 at 18:58
add comment

3 Answers

up vote 4 down vote accepted

Here's an expl3 version:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\seq_new:N \g_optchk_options_seq

\cs_new_protected:Npn \optchk_report: 
 {
  Checking~options: \par
  \seq_map_inline:Nn \g_optchk_options_seq
   {
    ##1 ~ \cs_if_exist:cF { ##1 ~ Encountered } {~ Not} ~ Encountered \par
   }
 }
\cs_new_protected:Npn \optchk_add:n #1
 {
  \seq_gclear:N \g_optchk_options_seq
  % Add to list of options to later check that they were used
  \seq_gset_split:Nno \g_optchk_options_seq { , } { #1 }
  \seq_map_inline:Nn \g_optchk_options_seq
   { \typeout{*****~Added~option~to~check:~##1} }
 }
 \cs_generate_variant:Nn \seq_gset_split:Nnn {Nno}

\NewDocumentEnvironment{ApplyOptions}{ m }
 { \optchk_add:n { #1 } }
 { \optchk_report: }

\ExplSyntaxOff

\begin{document}
\begin{ApplyOptions}{Option A, Option C}%
    % Option A:
    % if "Option A" was specified, it would be typset here
    \expandafter\gdef\csname Option A Encountered\endcsname{}%
    %
    % Option B:
    % if "Option B" was specified, it would be typset here
    \expandafter\gdef\csname Option B Encountered\endcsname{}%
\end{ApplyOptions}
\end{document}

A limit of the \foreach approach is, as usual, the fact that all cycles are executed in a different group. The approach with sequences (or clists) allows for choosing global assignments when needed and makes management easier.

Here we define the environment to add the specified options to the sequence (and we have no delimiting problem). The "end part" of the environment does your \ReportAnyUnusedOptions under a different name.

Note: I've changed \seq_gset_split:Nnn into \seq_gset_split:Nno so that the argument passed to the environment is expanded once. Depending on the nature of the options one might prefer to use \seq_gset_split:Nnx (generating the appropriate variant) but this might be dangerous if the options contain themselves macros.

share|improve this answer
    
I am not able to get this to work in the case where the options are defined via a macro. –  Peter Grill May 23 '12 at 21:43
    
@PeterGrill Edited to cope with this case. –  egreg May 23 '12 at 21:51
add comment

enter image description here

Your list of options was not holding what you thought. After two options it was defined as

\ListOfOptions=macro:
->\Option \Option .

ie it only told you the number of options not which ones.

so change so it holds

> \ListOfOptions=macro:
->\Option A Encountered \Option C Encountered .

Then:

\documentclass{article}
\usepackage{pgffor}

\def\ListOfOptions{}

\makeatletter
\newcommand{\AddToListOfOptions}[1]{%
    \expandafter\g@addto@macro\expandafter\ListOfOptions \csname #1 Encountered\endcsname
    \typeout{***** Added option to check: #1}%
}


\def\foo#1>{}

\newcommand{\ReportAnyUnusedOptions}{%
    Checking options: \par%
    \def\x{\@tfor\y:=}%
       \expandafter\x\ListOfOptions\do{%
           \expandafter\show\y\relax
           \expandafter\ifx\y\@undefined
            \expandafter\foo\meaning\y!\par%
           \else%
            NOT \expandafter\foo\meaning\y!\par%
           \fi}%
}%

\newenvironment{ApplyOptions}[1]{%
   % Add to list of options to later check that they were used
    \foreach \x in {#1} {%
        % There are cases here where an option would not be
        % check later, so some will be skipped
        \edef\Option{\x}%
        \AddToListOfOptions{\Option}%
    }%
}{%
    \ReportAnyUnusedOptions%
}

\makeatother
\begin{document}
\begin{ApplyOptions}{Option A, Option C}%
    % Option A:
    % if "Option A" was specified, it would be typset here
    \global\expandafter\def\csname Option A Encountered\endcsname{}%
    %
    % Option B:
    % if "Option B" was specified, it would be typset here
    \global\expandafter\def\csname Option B Encountered\endcsname{}%
\end{ApplyOptions}
\end{document}
share|improve this answer
    
I just used \@tfor rather than the pgf foreach thing as I know what it does:-) –  David Carlisle May 23 '12 at 8:59
    
The OP should be using LaTeX’s \@for instead of \foreach. When the list has only one holder macro (\x above) and no ellipsis (...), then \foreach is a waste of resources: its use then doesn't justify the cost. It was designed for the giant PGF/TikZ, were speed isn't a curable goal. Also, it gives scoped results. It doesn't normalize lists. Hence leading and trailing spaces around 'Options' can give the OP surprising results. –  Ahmed Musa May 23 '12 at 17:27
    
@DavidCarlisle: Ummm... I think you have the logic backwards. The NOT should be when the expected csname is @undefined.. –  Peter Grill May 23 '12 at 19:30
2  
Oh perhaps:-) fortunately having logic backwards gives an isomorphic structure, so it's just as good as the original one. It may model the real world less well, but that's a minor issue for a pure mathematician. –  David Carlisle May 23 '12 at 19:37
    
@DavidCarlisle: I am not able to get this to work for the case where the options are specified via a macro (see updated MWE). Also, would really prefer to store the option name to make the error message at the end more meaningful.. –  Peter Grill May 23 '12 at 21:46
show 4 more comments

I want to save Peter one full loop per environment call. That is why I posted this solution. Also, \cptdocommalist normalizes the list.

\documentclass{article}
\usepackage{catoptions}
\makeatletter
\newcommand{\optionsnotused}{%
  Checking options:
  \def\do##1##2{\endgraf{\tt % You can remove \tt if you like.
    \ifdefTF{##2}{%
      Option `##1' used: \expandafter\strip@prefix\meaning##2%
      \undefcs{##2}% remove any \gdef'ed temporary commands.
    }{%
      Option `##1' not used%
    }%
  }}%
  \listofoptions
}
\newenvironment{applyoptions}[1]{%
  % \listofoptions is re-initialized by the environment. I am not sure 
  % if this is what Peter intended. If not, take '\def\listofoptions{}'
  % outside the environment definition:
  \def\listofoptions{}%
  \ifmacroTF{#1}{\let\cpt@tempa#1}{\def\cpt@tempa{#1}}%
  \cptdocommalist*\cpt@tempa{%
    \edef\option{##1}% Does Peter really need to define \option here?
    \xdef\listofoptions{\expandcsonce\listofoptions
      \unexpanded{\do{##1}}{\noexpandcsn{##1-used}}}%
    \typeout{Option '\detokenize{##1}' added, to check at end of environment.}%
  }%
}{%
  \optionsnotused
  \ignorespacesafterend
}
\makeatother
\begin{document}
\begin{applyoptions}{Option A, Option B}
  % If 'Option A' was specified, it would be typeset here.
  % Let us simulate its use here:
  \csngdef*{Option A-used}{Used here by Peter Gill}%
  % Let's assume that 'Option B' wasn't used here.
\end{applyoptions}
\end{document}
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.