9

I've created an environment for which various parameters are set through key values. Instead of creating a hornet's nest of various booleans, I would just like to have a slight variant form for some of the keys to handle some of the formatting.

In particular, while none of the keys are mandatory, I need a way of handling things if I omit some of the keys. So, I figured I'd create starred variants.

To help you understand what I'm trying to do in the following MWE, here's the basic idea: I have three optional tags to add to a quote (title, description, and author). Each is meant to show up on its own line, when used. If the key is not used, then nothing should be done. But if the author key is not called, then I need a means of handling the line breaks to avoid unwanted extra whitespace. The starred variants are to be used in such a situation.

Here's a MWE:

\documentclass{article}
\newcommand{\attrib}[1]{\nopagebreak{\par\raggedleft\footnotesize #1\par}}
%%----------
\makeatletter
%%----------
\let\ae@source@title\relax
\let\ae@source@description\relax
\let\ae@source@author\relax
%%----------
\newlength{\widthofwidestline}
\newlength{\afterEpigraphSkip}
\setlength{\widthofwidestline}{3in}
\setlength{\afterEpigraphSkip}{1\baselineskip}
%%----------
\RequirePackage{pgfkeys}
\pgfkeys
  {
    /ae/my/epigraph/environment/set/options/.cd,
    title/.code         = {\def\ae@source@title{#1\\}},
    description/.code   = {\def\ae@source@description{#1\\}},
    author/.code        = {\def\ae@source@author{#1}},
%% variants for handling `title` and `description` when `author`
%% (and possibly other keys) are missing.
    title*/.code         = {\def\ae@source@title{#1}},
    description*/.code   = {\def\ae@source@description{#1}},
%% length parameters
    longest line/.code  = {\settowidth{\widthofwidestline}{#1}},
    afterskip/.code     = {\setlength{\afterEpigraphSkip}{#1}},
  }
\newenvironment{epigraph}[1]
  {\pgfkeys{ /ae/my/epigraph/environment/set/options/.cd, #1 }%
   \par\hspace*{\fill}%
   \begin{minipage}[t]{\dimexpr\widthofwidestline+1em}%
  }
  {\attrib{%
     \ae@source@title
     \ae@source@description
     \ae@source@author
     \makebox[0pt][r]{\rule[-0.8pt]{1in}{0.4pt}}%
    }%
   \end{minipage}%
   \hspace*{\fill}%
   \vspace{\afterEpigraphSkip}%
  }
\makeatother

\pagestyle{empty}
\begin{document}

\begin{epigraph}{title={title},
                 description={description},
                 author={me},
                 longest line=abcdefghijklm}
  Scenario 1
\end{epigraph}

\begin{epigraph}{author={me},
                 longest line=abcdefghijklm}
  Scenario 2
\end{epigraph}

\begin{epigraph}{title={title},
                 description={description},
                 longest line=abcdefghijklm}
  Scenario 3
\end{epigraph}

\begin{epigraph}{title={title},
                 description*={description},
                 longest line=abcdefghijklm}
  Scenario 4
\end{epigraph}

\begin{epigraph}{title*={title},
                 longest line=abcdefghijklm}
  Scenario 5
\end{epigraph}

\begin{epigraph}{description*={description},
                 longest line=abcdefghijklm}
  Scenario 6
\end{epigraph}

\end{document}

Scenario 3 illustrates what I want to avoid. This MWE produces:

enter image description here

I'm not so much interested in creating a new epigraph command: I know there are such packages. I also know that I could set and process booleans to handle this issue automatically. But as I mentioned, I really would like to avoid creating boolean switches.

I'm more interested in a larger issue where key values are being used, but where occasionally you might want to handle some of the keys in a slightly different manner than usual: hence the starred-variant.

I haven't found anything in the documentation that would allow for this. I also didn't find anything on this site related to starred variants of keys.

I'm primarily interested in feedback about best practices in such a situation.

7
  • You could define title/.style={title*={#1\\}}. By the way, is there a reason you use .code handlers that simply define a macro? You could just use an actual value-key (handler: .initial) and reduce the overhead. Jul 22, 2013 at 19:32
  • @Qrrbrbirlbel I don't think I understand how your .style method would work. Could you elaborate?
    – A.Ellett
    Jul 22, 2013 at 19:36
  • @Qrrbrbirlbel I'm very new to using key-values. It seemed to me that I was doing more than just defining a value, but also setting up additional formatting. Hence the use of .code. If you could illustrate how to avoid making so many calls via .code, I'd be very interested. After all, I'm more interested in best practices here than just some hack that gets the job done.
    – A.Ellett
    Jul 22, 2013 at 19:41
  • The only difference between title and title* that I see is that the un-starred version includes a final \\. If you define title as a key that simply forwards its argument appended by \\ to title*. On the other hand, I would actually post-process the values so that the user doesn’t need to work so much; of course, this would involve some checking for empty values and is probably depending on how complex the input scenarios can get. Jul 22, 2013 at 19:43
  • @Qrrbrbirlbel With regard to the title key, are you suggesting something more along the lines of title*/.initial=#1, followed by title/.style={title*={#1\\}}?
    – A.Ellett
    Jul 22, 2013 at 19:43

2 Answers 2

4

I'm using actual value-keys. These are setup with the .initial handler (every value has an initial value). This reduces overhead, in my opinion. As it just a wrapper for a \csname solution. A key of the path /tree/sub/name is stored in the macro \pgfk@/tree/sub/name. I don't see much of the reason why I'd define a key that simply sets another family of macros.

The macros starting with \@epi@… in the environment are merely temporarily macros since they are only defined in the environment (which groups its content).

Code

\documentclass{article}
\newcommand{\attrib}[1]{\nopagebreak{\par\raggedleft\footnotesize #1\par}}
%%----------
\makeatletter
%%----------
\newlength{\widthofwidestline}
\newlength{\afterEpigraphSkip}
\setlength{\widthofwidestline}{3in}
%%----------
\RequirePackage{pgfkeys}
\def\epigraphset{\pgfqkeys{/ae/my/epigraph/environment/set/options}}
\def\epigraphgetvalue#1{\pgfkeysgetvalue{/ae/my/epigraph/environment/set/options/#1}}
\epigraphset{
    title/.initial      =,
    description/.initial=,
    author/.initial     =,
    longest line/.code  = {\settowidth{\widthofwidestline}{#1}},
    afterskip/.code     = {\setlength{\afterEpigraphSkip}{#1}},
    afterskip=1\baselineskip
  }
\newenvironment{epigraph}[1]{
  \epigraphset{#1}%
  \par\hspace*{\fill}%
  \begin{minipage}[t]{\dimexpr\widthofwidestline+1em}%
}{
  \attrib{%
     \epigraphgetvalue{title}\@epi@title
     \epigraphgetvalue{description}\@epi@desc
     \epigraphgetvalue{author}\@epi@author
%     \noindent % needed (?) if you use \\
     \@epi@title
     \ifx\@epi@desc\pgfkeys@empty\else
       \par\@epi@desc
     \fi
     \ifx\@epi@author\pgfkeys@empty\else
       \par\@epi@author
     \fi
     \makebox[0pt][r]{\rule[-0.8pt]{1in}{0.4pt}}%
  }%
  \end{minipage}%
  \hspace*{\fill}%
  \vspace{\afterEpigraphSkip}%
}
\makeatother

\pagestyle{empty}
\begin{document}
\epigraphset{longest line=abcdefghijklm}
\begin{epigraph}{title=title,description=description,author=me}
  TDA
\end{epigraph}

\begin{epigraph}{author=me}
  \_\_A
\end{epigraph}
\begin{epigraph}{description=description}
  \_D\_
\end{epigraph}
\begin{epigraph}{title=title}
  T\_\_
\end{epigraph}

\begin{epigraph}{title=title,description=description}
  TD\_
\end{epigraph}
\begin{epigraph}{title=title,author=author}
  T\_A
\end{epigraph}
\begin{epigraph}{description=description,author=author}
  \_DA
\end{epigraph}

\end{document}

Output

enter image description here

6
  • Thank you. This helps me understand quite a few details about pgfkeys that were murky to me at best. But I do still have another question. According to my understanding of pgfkeysgetvaule{<key>}{<macro>}---as explained in the manual---I would have attempted to run a test as \ifx\@epi@title\relax\else....\fi. But this is clearly wrong (it doesn't work when I try it!). How do I reconcile what the manual says about the macro being set to \relax when the \ifx<macro>\relax will not test correctly? Why would I know that I should resort to testing against \@empty?
    – A.Ellett
    Jul 23, 2013 at 0:21
  • Oh. Maybe I know the answer to my latest question: it's because pgfkeys is doing something like \def<macro>{\relax} and \@empty is defined as \def\@empty{\relax}. So the meaning of the two macros is the same. Where as the meaning of \relax is not the meaning of \<macro> when defined to be \relax. Right?
    – A.Ellett
    Jul 23, 2013 at 0:26
  • @A.Ellett \@empty means literally empty: \def\@empty{}. And this also true for the value-key (which is \defed into the special pgfk@/… macro). The \pgfkeysgetvalue macro \lets the second argument to the \pgfk@/… macro, basically \let\@epi@title\pgfk@/ae/…/title. Thus \@epi@title is as it would have been defined as \def\@epi@title{} and can now compared to \@empty. That has nothing to do with \relax. Jul 23, 2013 at 0:34
  • So, what is the pgf manual talking about when it says that the macro will be equal to \relax? [see page 786 of the manual---I have the 2.10 CVS version.]
    – A.Ellett
    Jul 23, 2013 at 0:43
  • @A.Ellett If the key has not been set excludes setting the key with .initial. There is no value-key that is not set. (This is just TeX: \csnameing an undefined macro name expands to \relax.) If the key has not been set by the user it simply has the value of its .initialization. If the user sets a key that has not been .initialized they will get an error message. Jul 23, 2013 at 0:48
5

I don't know about PGF keys, but my idea would be to test whether the values are empty or not:

\documentclass{article}
\usepackage{xparse}

\NewDocumentCommand{\attrib}{m}{%
  \nopagebreak{\par\raggedleft\footnotesize #1\par}}

\ExplSyntaxOn
\keys_define:nn { ae/epigraph }
 {
  title .tl_set:N = \l_ae_epigraph_title_tl,
  title .initial:n = {},
  description .tl_set:N = \l_ae_epigraph_description_tl,
  description .initial:n = {},
  author .tl_set:N = \l_ae_epigraph_author_tl,
  author .initial:n = {},
  longestline .code:n = \ae_settowidth:n { #1 },
  afterskip .skip_set:N = \l_ae_epigraph_afterskip_skip,
  afterskip .initial:n = 1\baselineskip,
 }
\dim_new:N \l_ae_epigraph_longestline_dim
\dim_set:Nn \l_ae_epigraph_longestline_dim { 3in }

\cs_new_protected:Npn \ae_settowidth:n #1
 {
  \hbox_set:Nn \l_tmpa_box { #1 }
  \dim_set:Nn \l_ae_epigraph_longestline_dim { \box_wd:N \l_tmpa_box }
 }


\NewDocumentEnvironment{epigraph}{O{}}
  {
   \keys_set:nn { ae/epigraph } { #1 }
   \par\hspace*{\fill}
   \begin{minipage}[t]{\dim_eval:n { \l_ae_epigraph_longestline_dim+1em } }
  }
  {
   \par
   \group_begin:
   \raggedleft\footnotesize
   \tl_if_empty:NF \l_ae_epigraph_title_tl
    { \l_ae_epigraph_title_tl \\ }
   \tl_if_empty:NF \l_ae_epigraph_description_tl
    { \l_ae_epigraph_description_tl \\ }
   \tl_if_empty:NF \l_ae_epigraph_author_tl
    { \l_ae_epigraph_author_tl \\ }
   \vspace{-\prevdepth}
   \group_end:
   \vspace{1ex}
   \hrule
   \end{minipage}%
   \hspace*{\fill}%
   \vspace{\l_ae_epigraph_afterskip_skip}
  }
\ExplSyntaxOff

\pagestyle{empty}
\begin{document}

\begin{epigraph}[
  title={title},
  description={description},
  author={me},
  longestline=abcdefghijklm
]
  Scenario 1
\end{epigraph}

\begin{epigraph}[
  author={me},
  longestline=abcdefghijklm
]
  Scenario 2
\end{epigraph}

\begin{epigraph}[
  title={title},
  description={description},
  longestline=abcdefghijklm
]
  Scenario 3
\end{epigraph}

\begin{epigraph}[
  title={title},
  description={description},
  longestline=abcdefghijklm
]
  Scenario 4
\end{epigraph}

\begin{epigraph}[
  title={title},
  longestline=abcdefghijklm
]
  Scenario 5
\end{epigraph}

\begin{epigraph}[
  description={description},
  longestline=abcdefghijklm
]
  Scenario 6
\end{epigraph}

\end{document}

enter image description here

2
  • I like your solution (as usual). But, I don't understand how you're averting the application of \` when there's nothing else in the *list*. I'm guessing that it has something to do with \vspace{-\prevdepth}`, but I don't understand the mechanics of why this would work. Could you elaborate?
    – A.Ellett
    Jul 22, 2013 at 20:08
  • The \\ in \raggedleft issues \par; nothing special. The \vspace{-\prevdepth} is to ensure constant distance from the last baseline to the rule. Yes, the second On should have been Off
    – egreg
    Jul 22, 2013 at 20:11

You must log in to answer this question.

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