# Avoid passing an empty [optional argument] an environment (with verbatim content) using xparse?

this query is related to How to pass an optional argument to an environment with verbatim content? (whose solution did not work in my case) and with Emulating @ifnextchar in expl3 (which is the one I'm trying to adapt).

I have defined an environment (listcontents) using xparse which encapsulates \filecontentsdef (which is verbatim plus \macro) using the argument !O{} and [key = val] which works well if ALWAYS pass the optional argument.

My idea is to be able to write the environment without having to use an empty optional argument [], this is where I am lost, I have read the documentation of \peek_meaning:NF but I do not understand it at all (the \catcodes is confusing for me).

It works correctly if use:

\begin{listcontents}[]
\begin{listcontents}[key=val]
\begin{listcontents}[
key=val
]

and I would like to do it in the following way:

\begin{listcontents}
\begin{listcontents}
[not key = value, only a bracket whit text]

that is, if you do not find [key = val] right after }, insert a line ending \^^M and [not key = value, just a square bracket with text] would be recorded by the environment. The package xsim (which is written only usingexpl3) works similar to filecontents, has a code to solve this problem, but, it is very complicated to adapt for me.

This is my sample file:

\documentclass{article}
\usepackage{fancyvrb,filecontentsdef,xparse}
\pagestyle{empty}
\begin{document}
\makeatletter
\ExplSyntaxOn
\keys_define:nn { listcontents }
{
save-env   .tl_set:N         = \l_env_save_tl,
save-env   .initial:n        = content,
show-env   .bool_set:N       = \l_env_show_tl,
show-env   .initial:n        = true,
name-tmp   .tl_set:N         = \l_tmp_name_tl,
name-tmp   .initial:n        = \jobname.tsc,
}

\NewDocumentEnvironment{ listcontents }{ !O{} }
{
\group_begin:
\IfNoValueF { #1 } { \keys_set:nn { listcontents } { #1 } }
%\peek_meaning_ignore_spaces:NF [ { \char_set_catcode_active:N \^^M \char_set_catcode_end_line:N \^^M  \\ }
%\peek_meaning:NF \c_space_tl { \char_set_catcode_active:N \^^M \char_set_catcode_end_line:N \^^M  \^^M  }
% Acording a doc of v1.4 this its a correct line
\csname filecontentsdef*\endcsname{ \l_tmp_name_tl }{ \l_tmpa_tl }
}
{
\endfilecontentsdef
\group_end:
\group_begin:
\IfNoValueF { #1 } { \keys_set:nn { listcontents } { #1 } }
\IfBooleanT{ \l_env_show_tl } { \filecontentsexec\l_tmpa_tl }
\group_end:
}
\ExplSyntaxOff
\makeatother
\section{[not key = value, just a square bracket with text]}
\begin{listcontents}
[not key = value, just a square bracket with text]
This is correct for key=val, but space between letters in [...] disappeared
% some comented lines
\begin{verbatim*}
\begin{listcontents}
[not key = value, just a square bracket with text]
\end{verbatim*}
and \verb+[not key = value, just a square bracket with text]+ its not save
in \verb+\jobname.tsc+ and \verb+\l_tmpa_tl+.
\end{listcontents}

The \textbf{file generate} is:
\VerbatimInput[frame=single]{\jobname.tsc}

\section{Omit a [optional argument] after begin\{listcontents\} }
\begin{listcontents}
This is what I want to happen when omit []
% some comented lines
\begin{verbatim*}
\begin{listcontents}
This is what I want to happen when omit []
% some comented lines
\end{verbatim*}
if ommit \verb*+[ ]+ the first lines and and space between
letters disappeared, the contents of environments not save
in \verb+\jobname.tsc+ and \verb+\l_tmpa_tl+.
\end{listcontents}

The \textbf{file generate} is:
\VerbatimInput[frame=single]{\jobname.tsc}
\end{document}

The output looks like this:

Can this be done using xparse/expl3?

regards

• I think that you just want \NewDocumentEnvironment{ listcontents }{o} and then continue with \IfNoValueF { #1 }{...} in the environment definition. Now both \begin{listcontents}...\end{listcontents} and \begin{listcontents}[show-env=true]...\end{listcontents} are valid. – user30471 Apr 23 '19 at 13:14
• @Andrew: Thanks, it was the first thing I tried, but using {o} is not enough for what I want. – Pablo González L Apr 23 '19 at 20:27
• I still don't understand what you want to do. Does {o!} give what you want? Certainly using {O{}} followed by \IfNoValueF{#1}{...} makes no sense because O sets #1 to {} if no argument is given so \IfNoValueF{#1}{...} will always execute {...}. – user30471 Apr 26 '19 at 3:06
• \filecontentsdef juggles with the category code of the endline character; when no optional argument is used, it's too late because the endline character after \begin{listcontents} has already been scanned and converted to a space, which has the effect of not including the following line in the contents; the text is typeset that way because of how \filecontentsdef sets things up. – egreg Apr 26 '19 at 10:07

## 1 Answer

You have to delay the search for the optional argument, because otherwise the endline character after \begin{listcontent} is subject to tokenization and converted to a space before \filecontentsdef can act

\documentclass{article}
\usepackage{fancyvrb,filecontentsdef,xparse}
\pagestyle{empty}

\makeatletter
\ExplSyntaxOn
\keys_define:nn { listcontents }
{
save-env   .tl_set:N         = \l_env_save_tl,
save-env   .initial:n        = content,
show-env   .bool_set:N       = \l_env_show_bool,
show-env   .initial:n        = true,
name-tmp   .tl_set:N         = \l_tmp_name_tl,
name-tmp   .initial:n        = \c_sys_jobname_str.tsc,
save-macro .bool_set:N       = \l_save_macro_bool,
save-macro .initial:n        = true,
}

\NewDocumentEnvironment{ listcontents }{}
{
\char_set_catcode_active:N \^^M
\pedro_start_listcontents:w
}
{
\endfilecontentsdef
\group_end:
\IfBooleanT{ \l_env_show_bool } { \filecontentsexec\l_tmpa_tl }
}

\cs_new_protected:Npn \pedro_listcontents_set:w [#1]
{
\keys_set:nn { listcontents } { #1 }
}

\group_begin:
\char_set_catcode_active:N \^^M
\cs_new_protected:Npn \pedro_start_listcontents:w #1 ^^M
{
\tl_if_blank:nF { #1 } { \pedro_listcontents_set:w #1 }
\group_begin:
\use:c { filecontentsdef* } { \l_tmp_name_tl } { \l_tmpa_tl } ^^M
}
\group_end:
\ExplSyntaxOff
\makeatother

\begin{document}

\section{No optional argument}
\begin{listcontents}
[text in brackets]
Some text
% a comment
\begin{verbatim*}
\foo
\end{verbatim*}
Some text
\end{listcontents}

The \textbf{generated file} is:
\VerbatimInput[frame=single]{\jobname.tsc}

\section{Optional argument}
\begin{listcontents}[name-tmp=\jobname-2.tsc]
Some text
% a comment
\begin{verbatim*}
\foo
\end{verbatim*}
Some text
\end{listcontents}

The \textbf{generated file} is:
\VerbatimInput[frame=single]{\jobname-2.tsc}
\end{document}

• Thank you very much, it works perfectly, I almost achieved something by copying the xsim code, but yours is much more readable and has allowed me to finish with my project. You can look at this question (tex.stackexchange.com/q/487828/7832) related to this topic?...Saludos y Gracias Totales :) – Pablo González L Apr 26 '19 at 21:29
• egreg: If you have some time, you could place an alternative response that accept {listcontents}[key val whit vertical space]. Greetings. – Pablo González L Jul 2 '19 at 0:47