olsak / OpTeX

OpTeX - LuaTeX format with extended Plain TeX macros
http://petr.olsak.net/optex/
36 stars 14 forks source link

Add equivalent to `\providecommand` from LaTeX #159

Closed robertbachmann closed 7 months ago

robertbachmann commented 7 months ago

LaTeX has \providecommand{\NAME}{...}, that is a shorthand for:

\unless\ifcsname NAME\endcsname \newcommand{\NAME}{...} \fi

(see https://latexref.xyz/_005cprovidecommand.html)

In the past I've used this pattern in OpTeX:

\isdefined{foo}\iffalse
    \def\foo{...}
\fi

However since I define multiple macros that way (use case explained below), it got way to verbose and I decided to write my own macro \softdef and \softsdef:

\def\softdef#1{\ea\ifcsname\csstring #1\endcsname%
  \afterfi{\ea\def\csname softdef:dummy\endcsname}%
  \else\afterfi{\def#1}\fi}
\def\softsdef#1{\ifcsname #1\endcsname%
  \afterfi{\ea\def\csname softdef:dummy\endcsname}%
  \else\afterfi{\ea\def\csname #1\endcsname}\fi}

It's written so that for example \long\protected\softdef\foo would also do the right thing.

Notes:

My proposal:

I think I would prefer base since I think autoloading might get confusing:

This case would work:

    \softdef\foo{...}

This case would most likely not work without explicit '\loadtrick\softdef' beforehand:

    \long\softdef\qux{...}

However if you don't want to have these macros in the base format, it's also fine. I can do '\loadtrick\softdef' beforehand.

Please let me know, so I can prepare a pull-request for option a or b.


Side note: My use case

For the pandoc optex writer I'm using some helper macros. Backgroun: When converting **xy**, pandoc does

So for HTML the user can easily customize with CSS:

strong { font-weight: normal;  color: red; }

For LaTeX he would need to overwrite \textbf which might cause some unwanted side effects.

For the OpTeX writer I decided¹ against {\bf xy} instead I produce \strong{xy} and provide a default defintion for \strong at the top of the generated document/document fragment².

\softdef\strong#1{\begingroup\bf #1\endgroup}
\regmacro{}{}{\let\strong=\useit}

If the user wants a different style he can simply:

    \def \strong#1{\begingroup\Red\it #1\endgroup}

\strong is just one example. Another example would be [Some marked text]{.mark} (<mark> html element) or * * * (<hr>)

¹ can be added as toggle-able option if someone really needs it ² fragment: Pandoc can generate standalone documents or partial documents (fragments). For TeX these fragments would then be usually included via \input.

olsak commented 7 months ago

Maybe, it should be better to create a "prefix" for more general usage, like \newpublic is. For example its name can be \newonly and we can use \newonly\def\foo, \newonly\edef\foo, \newonly\mathchardef\foo, \newonly\newcount\foo and we can use \newonly{\long\def}\foo . This prefix can be created as loadable trick.

robertbachmann commented 7 months ago

Yes, that would work as well! The only thing I would need that \newonly would work with both \def and \sdef.

I think the following should work:

<h2><a name="newonly"></a>Only define a macro if it is not already defined</h2>

<p CLASS="rmarg">
autoload:<br>\newonly
</p>

<p>
LaTeX has <code>\providecommand{\NAME}{...}</code>
that only defines <code>\NAME</code> if it was not already defined.
We can achieve this in OpTeX by wrapping <code>\def\NAME{...}</code> inside
<code>\isdefined{\NAME}\iffalse ...\fi</code>.
However, this will get verbose if we need to define many macros in this manner.
Therefore, we implement a similar functionality as a prefix macro <code>\newonly</code>.
</p>

<p>Usage examples:</p>
<pre>
\newonly\def\mymacro#1{...}
\newonly\let\myfont=\bf  \newonly\let\myfont\bf
\newonly\slet{myfont}{bf}
\newonly{\protected\long\def}\foo#1{...}
\newonly\newtoks\mytoks
</pre>

<p>The implementation:</p>
<pre>
\def\newonly#1#2{\begingroup%
   \edef\tmpA{\_noprefix{#2}}%
   \edef\tmpB{\string #2}%
   \ea\ifcsname\tmpA\endcsname%
   \ifx\tmpA\tmpB% #2 has no backslash
   \gdef\newonlyA{#1{newonlyB}}\else%
   \gdef\newonlyA{#1\newonlyB}\fi%
   \else%
   \ifx\tmpA\tmpB%
   \gdef\newonlyA{#1{#2}}\else%
   \gdef\newonlyA{#1#2}\fi%
   \fi\endgroup\newonlyA}
</pre>

<p CLASS=datum>(0129) -- Robert Bachmann 2024-02-11<p>

edit: I can prepare a PR in the evening or on monday

olsak commented 7 months ago
robertbachmann commented 7 months ago

Thanks for your feedback.

It's a bit hard to find a good name: things I like so far \onlynew and \onlyifnew:


LaTeX has \providecommand{\NAME}{...} that only defines \NAME if it was not already defined. We can achieve this in OpTeX by wrapping \def\NAME{...} inside \isdefined{\NAME}\iffalse ...\fi. However, this will get verbose if we need to define many macros in this manner. Therefore, we implement a similar functionality as a prefix macro \onlyifnew.

Usage examples:

\onlyifnew\def\mymacro#1{...}
\onlyifnew\let\myfont=\bf  \onlyifnew\let\myfont\bf
\onlyifnew\slet{myfont}{bf}
\onlyifnew\newtoks\mytoks
\onlyifnew{\protected\long\def}\foo#1{...}

In the last case, it could be useful to define a shortcut such as:

\def\mylpdef{\onlyifnew{\protected\long\def}} 
\mylpdef\foo#1{...}

The implementation:

\def\onlyifnew#1#2{\begingroup% 
    \edef\tmpA{\csstring #2}%
    \edef\tmpB{\string #2}%
    \ea\ifcsname\tmpA\endcsname% 
    \ifx\tmpA\tmpB% #2 has no backslash
    \def\onlyifnewA{#1{onlyifnewB}}\else%
    \def\onlyifnewA{#1\onlyifnewB}\fi%
    \else%
    \ifx\tmpA\tmpB%
    \def\onlyifnewA{#1{#2}}\else%
    \def\onlyifnewA{#1#2}\fi%
    \fi\ea\endgroup\onlyifnewA}
olsak commented 7 months ago

OK, would you prepare the pull request?