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 \defs 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 \defs 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.

Tags:

Macros

Scripts