# Background

Code generates a book's title and author within start/stop environments. I'd like to redefine the content from those environments as a macro that can be reused elsewhere. For example:

\startbooktitle

Famous Novel Title

\stopbooktitle


# Code

The following code (and countless variations of it) fails to create a macro named BookTitle with a value of Famous Novel Title:

  \definestartstop[booktitle][
before={\define\BookTitle\bgroup},
after={\egroup},
]


# Problem

Regardless of whether bgroup/egroup or begingroup/endgroup are used, the error is:

 ["lasttexerror"]="! Too many }'s",


Using startbuffer and endbuffer results in what looks like infinite recursion.

Dropping the define inside the bgroups compiles, but then the \BookTitle macro is not available elsewhere, and even if it were, the contents between startbooktitle/stopbooktitle would not be captured:

before={\bgroup\define\BookTitle},


# Question

How do you define a macro using the before/after options within a definestartstop command that captures the entire contents of the start/stop command when called?

In other words, how do you transcode:

\startbooktitle

Famous Novel Title

\stopbooktitle


into:

\define\BookTitle{Famous Novel Title}


Are there other possibilities that are preferable to before/after?

• You can't do that with \definestartstop. You have to use a delimited macro for that, i.e. \unexpanded\def\startbooktitle#1\stopbooktitle{do something with #1}. – Henri Menke Apr 22 '20 at 7:54
• \unexpanded is not strictly necessary but good practice to add if your command is not expandable. – Henri Menke Apr 22 '20 at 22:44

Macro definition in TeX require explicit braces, i.e.

\def\foo{...}


Implicit braces like \bgroup and \egroup or any other token that was \let to { or } will not work. The following is invalid

\def\foo\bgroup...\egroup


There are a couple of commands in TeX that allow implicit braces as delimiters, such as \hbox but \def is not one of them. Since you cannot have unbalanced explicit braces in TeX (apart from a few \halign related hacks) you cannot grab the content of an environment from within \definestartstop. However, TeX has your back and you can use a delimited macro for this purpose

\unexpanded\def\stopbooktitle{\stopbooktitle} % Don't expand me, I only delimit things
\unexpanded\def\startbooktitle#1\stopbooktitle{\define\BookTitle{#1}}

\starttext

\startbooktitle

Famous Novel Title

\stopbooktitle

\meaning\BookTitle

\stoptext


Unfortunately, as you can see this does not trim whitespace and the empty lines are converted to \par and the end-of-line character after the word “Title” is converted into a space. You could now build some complex macro machinery with twisted expansion to remove these tokens, but since I am lazy I just do it in Lua.

\startluacode

function userdata.booktitle()
local t = token.scan_toks()

-- trim from the start
for n = 1, #t do
local cur_tok = t[n].cmdname
if cur_tok == "par_end" or cur_tok == "spacer" then
table.remove(t, n)
else
break
end
end

-- trim from the end
for n = #t, 1, -1 do
local cur_tok = t[n].cmdname
if cur_tok == "par_end" or cur_tok == "spacer" then
table.remove(t, n)
else
break
end
end

token.put_next(t)
end

\stopluacode

\unexpanded\def\stopbooktitle{\stopbooktitle} % Don't expand me, I only delimit things
\unexpanded\def\startbooktitle#1\stopbooktitle{%
\expandafter\define\expandafter\BookTitle\expandafter{\ctxlua{userdata.booktitle()}{#1}}}

\starttext

\startbooktitle

Famous Novel Title

\stopbooktitle

\meaning\BookTitle

\stoptext


• It's more common to use \let\stopbooktitle\relax and you can replace your Lua code in this case with string.strip which removes spaces at the begin and end. – Wolfgang Schuster Apr 23 '20 at 7:38
• @WolfgangSchuster But will string.strip remove \par? At the point where I pass #1 to Lua, the newlines have already been converted to \par. – Henri Menke Apr 23 '20 at 10:39
• Works here without problem for the example, to get rid of \expandafter I set the \BookTitle also with Lua: \ctxlua{context.setvalue("BookTitle",string.strip("#1"))}. – Wolfgang Schuster Apr 23 '20 at 11:41
• @WolfgangSchuster Oh, but that will fully expand #1 which might not be desired and can lead to errors if there is fragile stuff in #1. – Henri Menke Apr 23 '20 at 11:57