Is there a way to convert \def macro in \newcommand macro automatically?
\def
and \newcommand
are not fully equivalent, since \newcommand
will throw an error if it is asked to redefine an existing command. \newcommand
is therefore preferred if you want to define new commands, but it can not be used to redefine existing commands. This is not something a script can easily detect, so I will ignore it for now, even if it is relevant to your intended use case, where you have, for example
\newenvironment{Cases}{%
\left\lbrace
\def\arraystretch{1.2}%
\array{@{}l@{\quad}l@{}}
}{%
\endarray\right.%
}
Furthermore, \def
and \newcommand
don't support exactly the same syntax definitions. \newcommand
can only define macros with up to nine (mandatory) arguments, where the first argument may be optional (and wrapped in square brackets instead). \def
can also support other delimited macros. I will assume that your \def
s are only of the form \def\<cmd>{...}
or \def\<cmd>#1...#n
. This was already mentioned by JouleV in the comments. This is an issue for
\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}} %CLENET
\newcommand
also does not allow for prefixes like \long
, \outer
, \protected
. So if you use those you can't easily switch to \newcommand
, either. (Except maybe for \long
/not \long
which is mapped to \newcommand
/\newcommand*
.) I will assume you use none of those prefixes and I will use \newcommand
, which makes its arguments \long
, but you could of course use \newcommand*
to get "un\long
" arguments.
If we allow for these simplifications
s/\\def(\\[^{#]*)(?:#+([0-9]))*{/\\newcommand{$1}[0$2]{/g
yields acceptable results.
The following (crude approximation of a) Lua script does something quite similar. I didn't manage to get the matching for the argument structure #1
, #1#2
, ... done in a single group, so I opted for a loop doing that instead.
local io = require('io')
local string = string
local filename = arg[1] or nil
function newcommandify(content)
content = string.gsub(content, "\\def(\\[^{#]*){",
"\\newcommand{%1}{")
local iargs = ''
for i=1,9 do
iargs = iargs .. "#+" .. i
content = string.gsub(content, "\\def(\\[^{#]*)" .. iargs .. "{",
"\\newcommand{%1}[".. i .. "]{")
end
return content
end
if filename then
local file_handle = assert(io.open(filename, 'r'))
local content = file_handle:read('*all')
io.close(file_handle)
content = newcommandify(content)
file_handle = assert(io.open(filename .. "-newcommand", 'w'))
file_handle:write(content)
io.close(file_handle)
end
Save it as def-to-newcommand.lua
and run
texlua def-to-newcommand.lua <yourfilename>
to obtain <yourfilename>-newcommand
with \def
s converted to \newcommand
.
Why do you wish to transform assignments in terms of \def
into assignments in terms of \newcommand
?
If it is only about ensuring that macros that are already defined won't get overridden, you can do another approach:
Basically \newcommand
behaves as follows:
It checks for a star and optional argument and applies the kernel-macro \@ifdefinable
for finding out whether the control sequence token in question is already defined within the current scope.
If so, \@ifdefinable
will throw an error-message and not (re)define the control sequence token in question.
If not so, the control-sequence-token in question will be defined in terms of \def
(, probably preceded by \long
). In case of processing optional arguments, routines for detecting the presence of an optional argument will be included into the definitions performed by \newcommand
, also.
Caveats with \newcommand
:
\newcommand
itself is a macro/is a "mechanism" which is based on macros. If the arguments of macros contain tokens that are defined in terms of\outer
, (La)TeX will deliver an error-message. Thus: If you try to define a command via\newcommand
which is already defined in terms of\outer
, you will not get the error-message about the control-sequence already being defined but you will get an error-message! Forbidden control sequence found while scanning use of \new@command.
\newcommand
will check for the command being already defined only within the current scope. If not, it will define that command only within the current scope. Therefore the check of\newcommand
is not completely safe when intending to define a macro globally, e.g., via\newcommand\foo{bar}\global\let\foo=\foo
.Actually the
\newcommand
-"mechanism" does not check for the command being already defined, but it checks whether the command is already defined to something other than the meaning of the\relax
primitive. It does so by applying\csname..\endcsname
to the name of the command (which is formed by applying\string
and removing the leading backslash) and then via\ifx
checking whether the meaning of the command equals the meaning of the\relax
-primitive. It does so because\csname..\endcsname
itself does (regardless the value of the\globaldefs
-parameter) (only) within the current scope assign the meaning of the\relax
-primitive to the tokens it forms in case they are undefined at the time of forming them. Therefore\newcommand
silently redefines control-sequence-tokens that are already defined and whose meaning equals the\relax
-primitive. E.g., you can do\let\MyCommand=\relax % now \MyCommand is defined and its meaning equals the meaning of the \relax-primitive \newcommand\MyCommand{This is the redefinition of MyCommand which gets carried out without warnings/error-messages.}
You can also do this within a local scope/within a group for causing
\newcommand
to take\MyCommand
for undefined within that scope.A caveat is: If you do something like
\begingroup \let\MyCommand=\relax % now \MyCommand is defined and its meaning equals the meaning of the \relax-primitive \newcommand\MyCommand{This is the redefinition of MyCommand which gets carried out without warnings/error-messages.} \global\let\MyCommand=\MyCommand \endgroup
,
\MyCommand
might already have been defined outside the group and it will be overridden outside the group/within all scopes without whatsoever notification.
My suggested approach is:
Assignments in terms of \def
are always of pattern
⟨prefix⟩\def⟨control sequence token⟩⟨parameter text⟩{⟨replacement text⟩}
, with ⟨prefix⟩ =emptiness or a combination of \global
and/or \long
and/or \outer
.
(With \gdef
or \edef
or \xdef
the pattern looks accordingly.)
You can implement a macro \definedchecker
which via catching a left-brace-delimited argument (→see the TeXbook for #1#{
-notation) gathers the ⟨prefix⟩, the \def
, the ⟨control sequence token⟩ and the ⟨parameter text⟩ and then checks whether this contains \def
or \gdef
or \edef
or \xdef
and if so uses that information for splitting the ⟨prefix⟩, the \def
, the ⟨control sequence token⟩ and the ⟨parameter text⟩ apart before applying \@ifdefinable
—but be aware that the \@ifdefinable
-check is not completely save when it is about defining a control sequence token not only within the current scope but globally.
In other words: You can implement a macro \definedchecker
which is to be prepended to your \def
/\gdef
/\edef
/\xdef
-sequences and which checks whether the control sequence token in question is already defined within the current scope and if so delivers an error-message and if not so performs the assignment.
!!!Be aware that checking for a control sequence token being defined within the current scope is not completely safe when it comes to defining it globally!!!
% Compile this example by calling LaTeX from a shell/command-prompt/console
% where you can also see the messages of the program!
%
\documentclass{article}
\makeatletter
%%<-------------------------------------------------------------------->
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral0\expandafter\@secondoftwo\string{\expandafter
\@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
\@secondoftwo\string}\expandafter\expandafter\@firstoftwo{ }{}%
\@secondoftwo}{\expandafter\expandafter\@firstoftwo{ }{}\@firstoftwo}%
}%
%%<-------------------------------------------------------------------->
%% Code for definedchecker
%%......................................................................
\@ifdefinable\UD@gobbleto@def{\long\def\UD@gobbleto@def#1\def{}}%
\@ifdefinable\UD@gobbleto@gdef{\long\def\UD@gobbleto@gdef#1\gdef{}}%
\@ifdefinable\UD@gobbleto@edef{\long\def\UD@gobbleto@edef#1\edef{}}%
\@ifdefinable\UD@gobbleto@xdef{\long\def\UD@gobbleto@xdef#1\xdef{}}%
\newcommand\UD@CheckWhetherNoDef[1]{\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbleto@def#1\def}}%
\newcommand\UD@CheckWhetherNoGdef[1]{\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbleto@gdef#1\gdef}}%
\newcommand\UD@CheckWhetherNoEdef[1]{\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbleto@edef#1\edef}}%
\newcommand\UD@CheckWhetherNoXdef[1]{\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbleto@xdef#1\xdef}}%
\@ifdefinable\UD@catchto@gef{\long\def\UD@catch@def#1\def#2#3#{\innerdefinedchecker{#1}{\def}{#2}{#3}}}%
\@ifdefinable\UD@catchto@gdef{\long\def\UD@catch@gdef#1\gdef#2#3#{\innerdefinedchecker{#1}{\gdef}{#2}{#3}}}%
\@ifdefinable\UD@catchto@edef{\long\def\UD@catch@edef#1\edef#2#3#{\innerdefinedchecker{#1}{\edef}{#2}{#3}}}%
\@ifdefinable\UD@catchto@xdef{\long\def\UD@catch@xdef#1\xdef#2#3#{\innerdefinedchecker{#1}{\xdef}{#2}{#3}}}%
\newcommand\innerdefinedchecker[5]{\@ifdefinable{#3}{#1#2#3#4{#5}}}%
\@ifdefinable\definedchecker{%
\long\def\definedchecker#1#{%
\UD@CheckWhetherNoDef{#1}{%
\UD@CheckWhetherNoGdef{#1}{%
\UD@CheckWhetherNoEdef{#1}{%
\UD@CheckWhetherNoXdef{#1}{%
#1%
}{\UD@catch@xdef#1}%
}{\UD@catch@edef#1}%
}{\UD@catch@gdef#1}%
}{\UD@catch@def#1}%
}%
}%
\makeatother
\definedchecker\xdef\eclaire{\noexpand\mathbb}
\show\eclaire
\definedchecker\def\R{\ensuremath{\eclaire R}}
\show\R
\definedchecker\outer\global\long\def\myWeirdCommand#1Delimier#2delimiter#{This is my weird command}
\show\myWeirdCommand
% These throw "command already defined"-errors and leave the previous meanings untouched:
\definedchecker\edef\eclaire{Weird Redefinition of eclaire}
\show\eclaire
\definedchecker\gdef\R{Weird Redefinition of R}
\show\R
% Don't do this as \myWeirdCommand is outer.
% \newcommand\myWeirdCommand{Blah}
\begin{document}
\end{document}
Here are the messages that I get into test.log when saving this as test.tex and compiling with pdflatex:
> \eclaire=macro:
->\mathbb .
l.111 \show\eclaire
?
> \R=macro:
->\ensuremath {\eclaire R}.
l.114 \show\R
?
> \myWeirdCommand=\long\outer macro:
#1Delimier#2delimiter{->This is my weird command{.
l.117 \show\myWeirdCommand
?
! LaTeX Error: Command \eclaire already defined.
Or name \end... illegal, see p.192 of the manual.
See the LaTeX manual or LaTeX Companion for explanation.
Type H <return> for immediate help.
...
l.121 ...ef\eclaire{Weird Redefinition of eclaire}
?
> \eclaire=macro:
->\mathbb .
l.122 \show\eclaire
?
! LaTeX Error: Command \R already defined.
Or name \end... illegal, see p.192 of the manual.
See the LaTeX manual or LaTeX Companion for explanation.
Type H <return> for immediate help.
...
l.124 ...edchecker\gdef\R{Weird Redefinition of R}
?
> \R=macro:
->\ensuremath {\eclaire R}.
l.125 \show\R
From these messages you can see:
In case the command in question within the current scope is already defined to have a meaning other the meaning of the \relax
-primitive, \definedchecker
delivers an error and does not carry out the assignment.
Otherwise the assignment is carried out.
Be aware that, regarding avoiding overriding definitions of macros that are already defined, this strategy is not completely safe when it comes to defining macros globally.
A Python program def_to_newcommand.py
:
#!/usr/bin/env python3
import argparse
import re
def main():
args = parse_command_line()
data = read(args.input)
data = convert(data)
write(args.output, data)
def parse_command_line():
parser = argparse.ArgumentParser(
description='Replace \\def with \\newcommand where possible.',
)
parser.add_argument(
'input',
help='TeX input file with \\def',
)
parser.add_argument(
'--output',
'-o',
required=True,
help='TeX output file with \\newcommand',
)
return parser.parse_args()
def read(path):
with open(path, mode='rb') as handle:
return handle.read()
def convert(data):
return re.sub(
rb'((?:\\(?:expandafter|global|long|outer|protected)'
rb'(?: +|\r?\n *)?)*)?'
rb'\\def *(\\[a-zA-Z]+) *(?:#+([0-9]))*\{',
replace,
data,
)
def replace(match):
prefix = match.group(1)
if (
prefix is not None and
(
b'expandafter' in prefix or
b'global' in prefix or
b'outer' in prefix or
b'protected' in prefix
)
):
return match.group(0)
result = rb'\newcommand'
if prefix is None or b'long' not in prefix:
result += b'*'
result += b'{' + match.group(2) + b'}'
if match.lastindex == 3:
result += b'[' + match.group(3) + b']'
result += b'{'
return result
def write(path, data):
with open(path, mode='wb') as handle:
handle.write(data)
print('=> File written: {0}'.format(path))
if __name__ == '__main__':
main()
Usage example:
python3 def_to_newcommand.py foo.tex --output foo_changed.tex
The following definitions
\def\eclaire{\mathbb}
\def\R{\ensuremath{\eclaire R}}
\def\rond#1{\build#1_{}^{\>\circ}}
\def\build#1#2#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\long\def\eclaire{\mathbb}
\long\def\R{\ensuremath{\eclaire R}}
\long\def\rond#1{\build#1_{}^{\>\circ}}
\long\def\build#1#2#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\long\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\global\long\def\eclaire{\mathbb}
\global\def\eclaire{\mathbb}
\protected\def\eclaire{\mathbb}
\long
\def \eclaire {\mathbb}
are converted to:
\newcommand*{\eclaire}{\mathbb}
\newcommand*{\R}{\ensuremath{\eclaire R}}
\newcommand*{\rond}[1]{\build#1_{}^{\>\circ}}
\newcommand*{\build}[3]{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\newcommand{\eclaire}{\mathbb}
\newcommand{\R}{\ensuremath{\eclaire R}}
\newcommand{\rond}[1]{\build#1_{}^{\>\circ}}
\newcommand{\build}[3]{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\long\def\build#1_#2^#3{\mathrel{\mathop{\kern 0pt#1}\limits_{#2}^{#3}}}
\global\long\def\eclaire{\mathbb}
\global\def\eclaire{\mathbb}
\protected\def\eclaire{\mathbb}
\newcommand{\eclaire}{\mathbb}
Remarks:
\def
is mapped to\newcommand*
and\long\def
is mapped to\newcommand
.If
\global
,\protected
, or\outer
can be detected, the definition remains unchanged.Only an empty parameter text (
\def\foo{...}
) or undelimited arguments (\def\foo#1#2#3#4#5#6#7#8#9{...}
) are supported.\newcommand
does not support arbitrary parameter texts (\def\foo bar{...}
) and delimited arguments (\def\foo[#1]{...}
).Some support for white space after command names (
\def \foo {...}
).If
\expandafter
is detected, the definition remains unchanged. (Example:\expandafter\expandafter\expandafter\def\generatecmd{foo}{...}
)Of course,
\newcommand
throws an error, if the command is already defined. This requires manual intervention, because different resolutions exist:- The command name can be changed to avoid the name clash.
- If the overwrite is on purpose,
\renewcommand
resolves the issue.