diff --git a/list-of-macros.md b/list-of-macros.md index 58898a2..73d8bc7 100644 --- a/list-of-macros.md +++ b/list-of-macros.md @@ -102,6 +102,7 @@ tests: [tests/test\_packages/test\_latex\_builtins.py](tests/test_packages/test_ \\paragraph, \\part, \\phantom, +\\providecommand (defines macro, but does not overwrite existing ones), \\qquad, \\quad, \\ref, diff --git a/tests/test_newcommand.py b/tests/test_newcommand.py index 42f2741..d245b7f 100644 --- a/tests/test_newcommand.py +++ b/tests/test_newcommand.py @@ -1,7 +1,5 @@ - # -# - test of \newcommand with default value for optional argument -# - test of \def +# Special tests for \newcommand, \renewcommand, \providecommand and \def # @@ -90,6 +88,56 @@ def get_plain(latex): Xa:aY """ ), + ( + # \newcommand currenly overwrites existing commands + # LaTeX: + r""" + \newcommand{\test}[1]{A #1} + \newcommand{\test}[1]{B #1} + \test{C} + """, + # Plain: + r""" + B C + """ + ), + ( + # \renewcommand should overwrite existing commands + # LaTeX: + r""" + \newcommand{\test}[1]{A #1} + \renewcommand{\test}[1]{B #1} + \test{C} + """, + # Plain: + r""" + B C + """ + ), + ( + # \providecommand should NOT overwrite existing commands + # LaTeX: + r""" + \newcommand{\test}[1]{A #1} + \providecommand{\test}[1]{B #1} + \test{C} + """, + # Plain: + r""" + A C + """ + ), + ( + # LaTeX: + r""" + \providecommand{\test}[1]{B #1} + \test{C} + """, + # Plain: + r""" + B C + """ + ), ] diff --git a/tests/test_packages/test_latex_builtins.py b/tests/test_packages/test_latex_builtins.py index 0169fa2..b9a8345 100644 --- a/tests/test_packages/test_latex_builtins.py +++ b/tests/test_packages/test_latex_builtins.py @@ -122,6 +122,8 @@ def test_macros_latex(latex, plain_expected): (r'\part{ho?}', 'ho?'), (r'A\phantom{X}B', 'A B'), (r'A\phantom{\label{l}}B', 'AB'), + (r'A\providecommand{\xxx}{X}B', 'AB'), + (r'A\providecommand*{\xxx}[1][x]{X}B', 'AB'), (r'A\renewcommand{\xxx}{X}B', 'AB'), (r'A\renewcommand*{\xxx}[1][x]{X}B', 'AB'), (r'\section[hi]{ho}', 'ho.'), diff --git a/yalafi/handlers.py b/yalafi/handlers.py index 95b4481..00858d1 100644 --- a/yalafi/handlers.py +++ b/yalafi/handlers.py @@ -26,32 +26,52 @@ from yalafi import scanner -def h_newcommand(parser, buf, mac, args, delim, pos): +def g_newcommand(overwrite=True): r""" - Macro handler for `\newcommand` and `\renewcommand`. + Generator for ``\newcommand``, ``\renewcommand`` and ``\providecommand``. + + Args: + overwrite: Boolean deciding, whether the returned handler + overwrites existing commands. Defaults to True. + + Returns: + Macro Handler for ``\newcommand``, ``\renewcommand`` and + ``\providecommand``. Set ``overwrite=False`` for + ``\providecommand``. """ - name = parser.get_text_direct(args[1]) - if name in parser.parms.newcommand_ignore: + # pylint: disable-next=redefined-outer-name + def h_newcommand(parser, buf, mac, args, delim, pos): + r""" + Macro handler for `\newcommand` and `\renewcommand`. + """ + name = parser.get_text_direct(args[1]) + if name in parser.parms.newcommand_ignore: + return [] + if not overwrite and name in parser.the_macros: + return [] + nargs = parser.get_text_expanded(args[2]) + nargs = int(nargs) if nargs.isdecimal() else 0 + for a in [b for b in args[4] if type(b) is defs.ArgumentToken]: + if a.arg < 1 or a.arg > nargs: + return utils.latex_error(parser, 'illegal argument #' + str(a.arg) + + ' in definition of macro ' + name, a.pos) + if args[3]: + if nargs < 1: + return utils.latex_error(parser, + 'illegal default value in definition of macro ' + name, + args[1][0].pos) + parser.the_macros[name] = defs.Macro(parser.parms, + name, args='O' + 'A' * (nargs - 1), + repl=args[4], defaults=[args[3]], scanned=True) + else: + parser.the_macros[name] = defs.Macro(parser.parms, + name, args='A' * nargs, + repl=args[4], scanned=True) return [] - nargs = parser.get_text_expanded(args[2]) - nargs = int(nargs) if nargs.isdecimal() else 0 - for a in [b for b in args[4] if type(b) is defs.ArgumentToken]: - if a.arg < 1 or a.arg > nargs: - return utils.latex_error(parser, 'illegal argument #' + str(a.arg) - + ' in definition of macro ' + name, a.pos) - if args[3]: - if nargs < 1: - return utils.latex_error(parser, - 'illegal default value in definition of macro ' + name, - args[1][0].pos) - parser.the_macros[name] = defs.Macro(parser.parms, - name, args='O' + 'A' * (nargs - 1), - repl=args[4], defaults=[args[3]], scanned=True) - else: - parser.the_macros[name] = defs.Macro(parser.parms, - name, args='A' * nargs, - repl=args[4], scanned=True) - return [] + return h_newcommand + + +h_newcommand = g_newcommand(overwrite=True) def h_theorem(name): diff --git a/yalafi/parameters.py b/yalafi/parameters.py index 186aaa8..7c5937b 100644 --- a/yalafi/parameters.py +++ b/yalafi/parameters.py @@ -155,6 +155,7 @@ def init_macros(self): Macro(self, '\\paragraph', args='*OA', repl=hs.h_heading), Macro(self, '\\part', args='*OA', repl=hs.h_heading), Macro(self, '\\phantom', args='A', repl=hs.h_phantom), + Macro(self, '\\providecommand', args='*AOOA', repl=hs.g_newcommand(overwrite=False)), Macro(self, '\\renewcommand', args='*AOOA', repl=hs.h_newcommand), Macro(self, '\\section', args='*OA', repl=hs.h_heading), Macro(self, '\\subsection', args='*OA', repl=hs.h_heading),