3

I often find myself wanton of a LaTeX utility with the following semantics:

In a file path/foo.tex:

\OneTimeOnly{
   % Code that is inserted only once, the first time the file is included.
   ...
}

% Code that is inserted with each inclusion.
...

\OnlyFor{instance-A}{
   % Code that is inserted only when file is included with 'instance-A' argument.
   ...
}

\OnlyFor{instance-B,instance-C}{
   % Code that is inserted only when file is included with either 'instance-B'
   % or 'instance-C' arguments.
   ...
}

% More code inserted with each inclusion.
...

...

In the main .tex file:

\begin{document}
...

% Inserts foo code in 'instance-A' mode.
\InsertModule{path/foo}{instance-A}

...

% Inserts foo code in 'instance-B' mode.
\InsertModule{path/foo}{instance-B}

...

% Inserts foo code in 'instance-C' mode.
\InsertModule{path/foo}{instance-C}

...
\end{document}

That is, foo.tex behaves like a command with a static one-time-only component (for initialization, etc.) and a single argument controlling which part(s) of the code are inserted. However, the code is directly injected and has no special scope (i.e. commands in foo.tex would use and modify the same counters as the base file).

In addition to facilitating code reuse, the main feature I'm looking for here is simple, clean code in both the base file and foo.tex. Something comparable to the example above, where all the logic, states, definitions, conditionals, key lookup, etc. have been spirited away to a class file, etc.

The ideal utility would also have the properties that:

  • setup in the base file is limited to an \input or \usepackage command and (optionally) a single initialization command, e.g. \InitModule{path/foo}

  • multiple modules can be used in the same base file, e.g.

    \InsertModule{path/foo}{instance-A}
    \InsertModule{another/longer/path/bar}{another-instance}
    Each module retains its own persistent state for the \OneTimeOnly command (presumably keyed to its unique file path). I realize this might not be possible in LaTeX, and an acceptable alternative is having to write either
    \DefineModule{path/foo}{
       \OneTimeOnly{ ... }
       \OnlyFor{instance-A}{ ... }
    }
    or
    \OneTimeOnly{path/foo}{ ... }
    \OnlyFor{instance-A}{ ... }
    in foo.tex.

  • the parameter passed to the module (e.g. instance-A) can contain numbers and at least one of: hyphens, spaces, or underscores

  • the parameter passed to the module is accessible within the module using a command or token, e.g. \ForArg or #1. Even better: if the \InsertModule command can append additional arguments accessible by the injected code, e.g. \InsertModule{path/foo}{instance-A}{\lambda}, with the second argument accessible as \ForArg{2} or #2, etc.

  • code injection can occur in math mode (this is entirely optional, but would be generally useful)

I realize this is a tall order, and I'd intended on coding such a utility myself, but the macro writing advice consistently given to programmers not intimately familiar with coding TeX macros, packages, etc. is: don't.

Is there an existing package that serves this function?

If not, can I appeal to any TeX guru out there to provide me with a class, etc. implementing this behaviour? I shall forever be indebted to you. :)

5

This is actually simpler to do than you might expect.

  • If you put everything between \makeatletter and \makeatother in a separate file you can use this as a single include.

  • Multiple modules have persistent state for the \OnlyOnce command

  • The "instance" parameter can contain numbers, hyphens, spaces, or underscores.

  • The instance parameter can be accessed using the macro \Instance. You can pass extra key-value style arguments which can be accessed in the module using the command \GetKeyValue{some name}. You could add support for defaults, keys that do other configuration tasks, etc.

  • I assume code injection works fine in math mode, but haven't tested it.

.

\documentclass{article}

\makeatletter
    \usepackage{pgfkeys}
\pgfkeys{/generic-reusable/.unknown/.code={%
    \expandafter\edef\csname gr-arg-\pgfkeyscurrentkey\endcsname{\unexpanded\expandafter{\pgfkeyscurrentvalue}}%
}}

\def\gr@markincluded#1{\global\expandafter\let\csname gr-file-#1-alreadyincluded\endcsname\empty}
\def\gr@ifincluded#1{\@ifundefined{gr-file-#1-alreadyincluded}}

\def\OneTimeOnly{%
    \gr@ifincluded{\gr@filename}%
        {\@firstofone}%
        {\@gobble}%
}


\def\OnlyFor#1{%
    \gr@OnlyFor@#1,\gr@nil
}

\def\gr@OnlyFor@#1,{%
    \def\gr@temp{#1}%
    \def\next{\@ifnextchar\gr@nil{\@gobbletwo}{\gr@OnlyFor@}}%
    \ifx\gr@temp\Instance
        \let\next\gr@OnlyFor@dobody 
    \fi
    \next
}

\def\GetKeyValue#1{\csname gr-arg-/generic-reusable/#1\endcsname}

\def\gr@OnlyFor@dobody#1\gr@nil{\@firstofone}

\def\InsertModule#1#2{% 
    \@ifnextchar[{\gr@InsertModule@{#1}{#2}}{\gr@InsertModule@{#1}{#2}[]}%
}
\def\gr@InsertModule@#1#2[#3]{%
    \pgfqkeys{/generic-reusable}{#3}
    \def\Instance{#2}%
    \def\gr@filename{#1}%
    \input #1 \relax
    \gr@markincluded{#1}
}
\makeatother

\begin{document}
\InsertModule{generic-reusable-file}{instance-A}[key1=somevalue1,key2=somevalue2]

\vskip10pt

\InsertModule{generic-reusable-file}{instance-B}

\vskip10pt

\InsertModule{generic-reusable-file}{instance-C}[some other key=some value]
\end{document}

And the output is:

instance-A
only once code somevalue1 somevalue2
every time code
’instance-A’ code.

instance-B
every time code
’instance-B’ or ’instance-C’ code

instance-C
every time code
some value ’instance-B’ or ’instance-C’ code
  • "Simpler" is definitely a matter of perspective. Many thanks! – COTO Feb 2 '18 at 17:12
  • In implementing this today, I noticed that the "only once" code is actually being inserted in every instance. This same thing appears to be happening with your test example. Any thoughts? – COTO Feb 5 '18 at 15:46
  • Oops, let me fix that. – Hood Chatham Feb 5 '18 at 16:36
  • The problem was that the line \expandafter\let\csname gr-file-#1-alreadyincluded\endcsname\empty was intended to mark the file as having been used, but it was a local assignment which immediately went out of scope. The fix is to add a \global in front. – Hood Chatham Feb 5 '18 at 16:44
  • That did the trick. However, I'll need to appeal to your expertise once more. A bare-bones test of my particular implementation is: pastebin.com/UBDTxxNE pastebin.com/KXZfWBNt pastebin.com/kN5fSMLc I've confirmed that the code works without the template, and that the reusable code template you've provided works for inserting text-only content, but I get some cryptic errors when using the two together ("Paragraph ended before \next was complete.", "Extra }", "Undefined control sequence.", etc.) Again, any assistance would be much appreciated. – COTO Feb 5 '18 at 21:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.