5

I am in search for a package that would allow me to do write a command of the following sort:

\tofile{filename}{One}{Two} FROM HERE
  here is some nonsensical list of commands,
  \unexpand\expandafter\gdef\indlude{#1}\def\expand\myOtherCrazyMacro
  but who cares, I can even place my C code here
  #include <stdio.h>
  main() {
    printf("#2",\n);
  }
  this }}{{is some other illegal LaTeX code, which uses % percents, 
  & other _^^^_ ASCII stuff. 
TO THERE

such that everything between "FROM HERE" to "TO THERE" would appear in the filename, except that "#1" and "#2" which gets replaced by the macro arguments.

My own bashful package does something very similar, so I suppose I could hack it somehow, but I was hoping to find a ready made solution, or something more decent than the miserable hacking I did in my package.

After running this macro, the contents of the file named filename should be:

  here is some nonsensical list of commands,
  \unexpand\expandafter\gdef\indlude{One}\def\expand\myOtherCrazyMacro
  but who cares, I can even place my C code here
  #include <stdio.h>
  main() {
    printf("Two",\n);
  }
  this }}{{is some other illegal LaTeX code, which uses % percents, 
  & other _^^^_ ASCII stuff. 
Yossi Gil
  • 15,951
  • Not if you want to be able to use # also as a printable character. It's either the parameter indicator or a printable character, it can't be both. – egreg Jan 22 '14 at 15:28
  • This is not a real issue I think. One can live without it, or use a different place holder for the parameter. I suppose one can do a fine grained parsing to distinguish between the two contradicting meanings. – Yossi Gil Jan 22 '14 at 17:17

1 Answers1

6

Using # with a double meaning is impossible with standard methods. Here's something that seems to work:

\documentclass{article}
\makeatletter
\newwrite\tofile@write
\def\tofile#1#2#3FROMHERE
 {%
  \begingroup
  \immediate\openout\tofile@write=#1\relax
  \let\do\@makeother\dospecials
  \endlinechar=`\^^J
  \@tofile{#2}{#3}%
 }
\def\TOTHERE{TOTHERE}
\def\@tofile#1#2#3^^J{%
  \def\@test{#3}%
  \ifx\@test\TOTHERE
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\endgroup}%
  {\toks@{#3}%
   \begingroup\catcode`\#=6 \endlinechar=\m@ne
   \everyeof{\noexpand}%
   \xdef\@temp##1##2{\scantokens\expandafter{\the\toks@}}%
   \endgroup
   \immediate\write\tofile@write{\@temp{#1}{#2}}%
   \@tofile{#1}{#2}}%
}
\makeatother

\begin{document}

\tofile{filename}{One}{Two}FROMHERE
Something \absurd{||-\for\yossi
Else #1 or #2
TOTHERE

\end{document}

The produced file is

Something \absurd{||-\for\yossi
Else One or Two

Another solution using LaTeX3 features. The opening string should be equal to the ending one and not separated from the second argument; use only printable and non (TeX) special ASCII characters. The first argument used in the example is \jobname.txt just not to clobber other files in my file system, you can use whatever name you want.

More substitutions could be accommodated, let me know.

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\tofile}{m}
 {
  \yossi_tofile:n { #1 }
 }

\iow_new:N \g_yossi_tofile_write_stream

\cs_new_protected:Npn \yossi_tofile:n #1
 {
  \group_begin:
  \tex_endlinechar:D `\^^J
  \iow_open:Nn \g_yossi_tofile_write_stream { #1 }
  \yossi_tofile_aux:nnw
 }

\cs_new_protected:Npn \yossi_tofile_aux:nnw #1 #2 #3 ~%
 {
  \tl_set:Nn \l_yossi_eof_tl { #3 }
  \tl_trim_spaces:N \l_yossi_eof_tl
  \cs_set_eq:NN \do \char_set_catcode_other:N
  \dospecials
  \yossi_readline:nnw { #1 } { #2 }
 }

\cs_new_protected:Npn \yossi_readline:nnw #1 #2 #3 ^^J
 {
  \str_if_eq:VnTF \l_yossi_eof_tl { #3 }
   {
    \iow_close:N \g_yossi_tofile_write_stream
    \group_end:
   }
   {
    \yossi_replace_write:nnn { #1 } { #2 } { #3 }
    \yossi_readline:nnw { #1 } { #2 }
   }
 }

\cs_new_protected:Npn \yossi_replace_write:nnn #1 #2 #3
 {
  \tl_set:Nn \l_tmpa_tl { #3 }
  \regex_replace_all:nnN { \#1 } { #1 } \l_tmpa_tl
  \regex_replace_all:nnN { \#2 } { #2 } \l_tmpa_tl
  \iow_now:NV \g_yossi_tofile_write_stream \l_tmpa_tl
 }

\cs_generate_variant:Nn \iow_now:Nn { NV }
\cs_generate_variant:Nn \str_if_eq:nnTF { V }

\ExplSyntaxOff

\begin{document}

\tofile{\jobname.txt}{One}{Two}<<EOF
here is some nonsensical list of commands,
\unexpand\expandafter\gdef\indlude{#1}\def\expand\myOtherCrazyMacro
but who cares, I can even place my C code here
#include <stdio.h>
main() {
  printf("#2",\n);
}
this }}{{is some other illegal LaTeX code, which uses % percents, 
& other _^^^_ ASCII stuff. 
<<EOF

\end{document}

This is the file that's written out:

here is some nonsensical list of commands,
\unexpand\expandafter\gdef\indlude{One}\def\expand\myOtherCrazyMacro
but who cares, I can even place my C code here
#include <stdio.h>
main() {
  printf("Two",\n);
}
this }}{{is some other illegal LaTeX code, which uses % percents,
& other _^^^_ ASCII stuff.

Thanks to Joseph Wright for suggesting replacements via regular expressions.


A new version, allowing any number of arguments; however they have to be specified in a different way, see the example code.

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\tofile}{m}
 {
  \manual_tofile:n { #1 }
 }

\iow_new:N \g_manual_tofile_write_stream
\tl_const:Nn \c_manual_specials_tl { \  \\ \{ \} \$ \& \# \^ \_ \% \~ }
\tl_new:N \l_manual_argument_tl
\tl_new:N \l_manual_line_tl
\seq_new:N \l_manual_arguments_seq
\int_new:N \l_manual_arguments_int

\cs_new_protected:Npn \manual_dospecials:
 {
  \tl_map_inline:Nn \c_manual_specials_tl
   {
    \char_set_catcode_other:N ##1
   }
 }

\cs_new_protected:Npn \manual_tofile:n #1
 {
  \group_begin:
  \tex_endlinechar:D `\^^J
  \iow_open:Nn \g_manual_tofile_write_stream { #1 }
  \manual_tofile_aux:nw
 }

\cs_new_protected:Npn \manual_tofile_aux:nw #1 #2 ~%
 {% #1 is the list of arguments, #2 is the terminator
  \tl_set:Nn \l_manual_eof_tl { #2 }
  \tl_trim_spaces:N \l_manual_eof_tl
  \seq_set_split:Nnn \l_manual_arguments_seq { } { #1 }
  \int_set:Nn \l_manual_arguments_int { \seq_count:N \l_manual_arguments_seq }
  \manual_dospecials:
  \manual_readline:w
 }

\cs_new_protected:Npn \manual_readline:w #1 ^^J
 {
  \str_if_eq:VnTF \l_manual_eof_tl { #1 }
   {
    \iow_close:N \g_manual_tofile_write_stream
    \group_end:
   }
   {
    \manual_replace_write:n { #1 }
    \manual_readline:w
   }
 }

\cs_new_protected:Npn \manual_replace_write:n #1
 {
  \tl_set:Nn \l_manual_line_tl { #1 }
  \int_step_inline:nnnn { 1 } { 1 } { \l_manual_arguments_int }
   {
    \tl_set:Nx \l_manual_argument_tl { \seq_item:Nn \l_manual_arguments_seq { ##1 } }
    \regex_replace_all:nnN { \# ##1 } { \u{l_manual_argument_tl} } \l_manual_line_tl
   }
  \iow_now:NV \g_manual_tofile_write_stream \l_manual_line_tl
 }

\cs_generate_variant:Nn \iow_now:Nn { NV }
\cs_generate_variant:Nn \str_if_eq:nnTF { V }

\ExplSyntaxOff

\begin{document}
\tofile{test.c}{{One}{Two}{Three}}<<EOF
/* #1, #2 */
#include <stdio.h>
main() {
  printf("#3",\n);
}
<<EOF

\end{document}

\tofile{\jobname.txt}{{One}{Two}{ASCII}}<<EOF
here is some nonsensical list of commands,
\unexpand\expandafter\gdef\indlude{#1}\def\expand\myOtherCrazyMacro
but who cares, I can even place my C code here
#include <stdio.h>
main() {
  printf("#2",\n);
}
this }}{{is some other illegal LaTeX code, which uses % percents, 
& other _^^^_ #3 stuff. 
<<EOF

\end{document}

The arguments here are three, specified as braced items in the second argument to \tofile:

\tofile{\jobname.txt}{ {One} {Two} {ASCII} }<<EOF

(spaces added for clarity, they are ignored).

egreg
  • 1,121,712
  • 2
    Have you considered doing a serach-and-replace on the collected tokens rather than a retokenization? You could then allow # to be used in two ways: (i) if immediately followed by 1 or 2 to be replaced by the arguments, (ii) otherwise to be left as is. – Joseph Wright Jan 22 '14 at 17:30
  • 1
    @JosephWright That's for the evening. ;-) With l3regex, of course. – egreg Jan 22 '14 at 17:31