2

I am trying to create a command that can write command definitions into a file, while also defining them. So I want a command \teecommand that takes one argument. This argument should be written to a file as is, and also expanded.

My current attempt is:

\documentclass{memoir}

\newwrite\commandsfile \immediate\openout\commandsfile=commands.tex

\DeclareDocumentCommand\teecommand{s m}{% \write\commandsfile{\unexpanded{#2}}% \IfBooleanTF{#1}{}{#2} % Only expand the command if no * is given }

\teecommand{\newcommand{\foo}{this is foo}} \teecommand{\newcommand{\foobar}[1]{this is bar: #1}}

\begin{document} \foo\ \foobar{123} \end{document}

The output looks like this, as expected: enter image description here

The file commands.tex is also created, with the following content:

\newcommand {\foo }{this is foo}
\newcommand {\foobar }[1]{this is bar: ##1}

The first line is as expected, but the second line has a doubled up #. This unfortunately breaks the application trying to parse the .tex file (the MathJax based equation previewer from LaTeX Workshop).

Question: How can I fix the command \teecommand to give me the correct output with #1 instead of ##1 for argument placeholders?

Note: I am using memoir in the example above as this is what I need to use in my actual document, and I want to avoid having a solution that is incompatible with it.

  • you only need xparse with older latex formats, the commands are pre-defined in latex in current releases. – David Carlisle Apr 20 '22 at 15:57
  • @DavidCarlisle Thanks for the info, updated the question – Lukas Lang Apr 20 '22 at 16:06
  • 1
    (even though the questions are slightly different, I decide to flag anyway because all of the answers there should be applicable here with minor modification + I think it's less desirable to duplicate the answer content here, would be harder to update) – user202729 Apr 21 '22 at 06:02

2 Answers2

2

I finally found a solution, albeit an ugly one (please let me know if you can think of a better solution that doesn't need a temporary file):

\documentclass{memoir}

\newwrite\commandsfile \immediate\openout\commandsfile=commands.tex

\makeatletter \newwrite\commandtempfile \newcommand{\teecommand}{\begingroup\catcode`#=12\relax@teecommand} \DeclareDocumentCommand@teecommand{s m}{% \endgroup% \write\commandsfile{\unexpanded{#2}}% \IfBooleanTF{#1}{}{% \immediate\openout\commandtempfile=currentcommand.tmp% \immediate\write\commandtempfile{\unexpanded{#2}}% \immediate\closeout\commandtempfile% \input{currentcommand.tmp} } } \makeatother

\teecommand{\newcommand{\foo}{this is foo}} \teecommand{\newcommand{\foobar}[1]{this is bar: #1}}

\begin{document} \foo\ \foobar{123} \end{document}

The idea is to use the trick from this answer to prevent # from being expanded. The issue is now that I want to selectively expand it to actually define the command. To do that, I write the command to a temporary file that I immediately input again. A nicer solution would really be appreciated

Heiko Oberdiek
  • 271,626
  • Instead of temporary file you can use \scantokens. \scantokens is like unexpanded-writing the tokens of its "argument" to text-file and reading back that text-file, hereby tokenizing the things that are read outgoing from current category code régime. – Ulrich Diez Apr 21 '22 at 22:13
  • E.g., something like \NewDocumentCommand{\teecommand}{s +v}{\begingroup\newlinechar=\endlinechar\relax\immediate\write\commandsfile{\unexpanded{#2}\csname @percentchar\endcsname}\endgroup\IfBooleanTF{#1}{}{\begingroup\newlinechar=\endlinechar\relax\scantokens{\endgroup#2\relax}}} – Ulrich Diez Apr 21 '22 at 22:47
2

You can do a regex replacement of # with the same character, but with category code “other”.

\documentclass{memoir}

\ExplSyntaxOn

\iow_new:N \g_lukas_teecommand_iow \iow_open:Nn \g_lukas_teecommand_iow { \c_sys_jobname_str.cmd }

\NewDocumentCommand\teecommand{s m} { \tl_set:Nn \l_tmpa_tl { #2 } \regex_replace_all:nnN { \cP# } { \cO# } \l_tmpa_tl \iow_now:NV \g_lukas_teecommand_iow \l_tmpa_tl \IfBooleanT{#1}{#2} % Only expand the command if no * is given }

\cs_generate_variant:Nn \iow_now:Nn { NV }

\ExplSyntaxOff

\teecommand{\newcommand{\foo}{this is foo}} \teecommand{\newcommand{\foobar}[1]{this is bar: #1}} \teecommand{\newcommand{\fooagain}[2]{this is again: #1 and #2}}

\stop

The contents of the written file (I changed the name in order not to risk clobbering my files):

\newcommand {\foo }{this is foo}
\newcommand {\foobar }[1]{this is bar: #1}
\newcommand {\fooagain }[2]{this is again: #1 and #2}
egreg
  • 1,121,712