from fastcore.test import *
helper.latex.macros_and_commands
Latex functions for identifying macros and commands (to replace)
Identify macros and commands (to replace)
The following functions were originally written for latex.formatting
, but were moved here.
custom_commands
custom_commands (preamble:str)
*Return a dict mapping commands (and math operators) defined in preamble
to the number of arguments display text of the commands.
Assumes that the newcommands only have at most one default parameter (newcommands with multiple default parameters are not valid in LaTeX).
Ignores all comented newcommands.*
Type | Details | |
---|---|---|
preamble | str | The preamble of a LaTeX document. |
Returns | list | Each tuple consists of 1. the name of the custom command 2. the number of parameters 3. The default argument if specified or None otherwise, and 4. the display text of the command. |
# Basic
= r'\newcommand{\con}{\mathcal{C}}'
text_1 'con', 0, None, r'\mathcal{C}')])
test_eq(custom_commands(text_1), [(
# With a parameter
= r'\newcommand{\field}[1]{\mathbb{#1}}'
text_2 'field', 1, None, r'\mathbb{#1}')])
test_eq(custom_commands(text_2), [(
# With multiple parameters, the first of which has a default value of `2`
= r'\newcommand{\plusbinomial}[3][2]{(#2 + #3)^#1}'
text_3 'plusbinomial', 3, '2', r'(#2 + #3)^#1')])
test_eq(custom_commands(text_3), [(
# The display text has backslashes `\` and curly brances `{}``
= r'\newcommand{\beq}{\begin{displaymath}}'
text_4 'beq', 0, None, '\\begin{displaymath}')])
test_eq(custom_commands(text_4), [(
# Basic with spaces in the newcommand declaration
= r'\newcommand {\con} {\mathcal{C}}'
text_6 'con', 0, None, r'\mathcal{C}')])
test_eq(custom_commands(text_6), [(
# With a parameter and spaces in the newcommand declaration
= r'\newcommand {\field} [1] {\mathbb{#1}}'
text_7 'field', 1, None, r'\mathbb{#1}')])
test_eq(custom_commands(text_7), [(
# With multiple parameters, a default value, and spaces in the newcommand declaration
= r'\newcommand {\plusbinomial} [3] [2] {(#2 + #3)^#1}'
text_8 'plusbinomial', 3, '2', r'(#2 + #3)^#1')])
test_eq(custom_commands(text_8), [(
# With a comment `%'; commented out command declarations should not be detected.
= r'% \newcommand{\con}{\mathcal{C}}'
text_9
test_eq(custom_commands(text_9), [])
# Spanning multiple lines
= r'''\newcommand{\mat}[4]{\left[\begin{array}{cc}#1 & #2 \\
text_10 #3 & #4\end{array}\right]}'''
test_eq(
custom_commands(text_10),'mat', 4, None,
[('\\left[\\begin{array}{cc}#1 & #2 \\\\\n #3 & #4\\end{array}\\right]')])
# Math operator
= r'\DeclareMathOperator{\Hom}{Hom}'
text_11 'Hom', 0, None, 'Hom')])
test_eq(custom_commands(text_11), [(
= r'\DeclareMathOperator{\tConf}{\widetilde{Conf}}'
text_12 'tConf', 0, None, r'\widetilde{Conf}')])
test_eq(custom_commands(text_12), [(
# `\def` commands
# \def is a bit complicated because arguments can either be provided with []
# or can be provided with {}.
= r'\def\A{{\cO_{K}}}'
text_13 'A', 0, None, r'{\cO_{K}}')])
test_eq(custom_commands(text_13), [(
# newcommand and renewcommand don't require {} for the
# command name, cf. https://arxiv.org/abs/1703.05365
= r'\newcommand\A{{\mathbb A}}'
text_14 'A', 0, None, r'{\mathbb A}')])
test_eq(custom_commands(text_14), [(
# A test for https://arxiv.org/abs/0902.4637
= r'\newcommand{\til}[1]{{\widetilde{#1}}}'
text_15 'til', 1, None, '{\\widetilde{#1}}')]) test_eq(custom_commands(text_15), [(
regex_pattern_detecting_command
regex_pattern_detecting_command (command_tuple:tuple[str,int,typing.Opti onal[str],str])
*Return a regex.pattern
object (not a re.pattern
object) detecting the command with the specified number of parameters, optional argument, and display text.
Assumes that the curly braces used to write the invocations of the commands are balanced and properly nested. Assumes that there are no two commands of the same name.*
Type | Details | |
---|---|---|
command_tuple | tuple | Consists of 1. the name of the custom command 2. the number of parameters 3. The default argument if specified or None otherwise, and 4. the display text of the command. |
Returns | Pattern |
# Basic
= regex_pattern_detecting_command(('Sur', 0, None, r'\mathrm{Sur}'))
pattern = r'The number of element of $\Sur(\operatorname{Cl} \mathcal{O}_L, A)$ is ...'
text = pattern.search(text)
match = match.span()
start, end r'\Sur')
test_eq(text[start:end],
= regex_pattern_detecting_command(('frac', 2, None, r'\mathrm{Sur}'))
pattern = r'\frac{\frac{2}{5}}{7}'
text = pattern.search(text)
match = match.span()
start, end
test_eq(text[start:end], text)
= regex_pattern_detecting_command(('frac', 2, None, r'\mathrm{Sur}'))
pattern = r'\frac{error}{7'
text = pattern.search(text)
match None)
test_is(match, # start, end = match.span()
# test_eq(text[start:end], text)
= regex_pattern_detecting_command(('frac', 2, None, r'\mathrm{Sur}'))
pattern = r'\frac{\frac{2}{5}}{7'
text = pattern.search(text)
match = match.span()
start, end r'\frac{2}{5}')
test_eq(text[start:end],
# One parameter
= regex_pattern_detecting_command(('field', 1, None, r'\mathbb{#1}'))
pattern = r'\field{Q}'
text # print(pattern.pattern)
= pattern.search(text)
match = match.span()
start, end
test_eq(text[start:end], text)
# Multiple parameters
= regex_pattern_detecting_command(('mat', 4, None, r'\left[\begin{array}{cc}#1 & #2 \\ #3 & #4\end{array}\right]'))
pattern = r'\mat{{123}}{asdfasdf{}{}}{{{}}}{{asdf}{asdf}{}}' # This is a balanced str.
text = pattern.search(text)
match = match.span()
start, end
test_eq(text[start:end], text)1), r'{123}')
test_eq(match.group(
# Multiple parameters, one of which is optional parameter
= regex_pattern_detecting_command(('plusbinomial', 3, '2', r'(#2 + #3)^#1'))
pattern # When the optional parameter is used
= r'\plusbinomial{x}{y}'
text = pattern.search(text)
match = match.span()
start, end
test_eq(text[start:end], text)
# When the optional parameter is not used
= r'\plusbinomial[4]{x}{y}'
text = pattern.search(text)
match = match.span()
start, end
test_eq(text[start:end], text)
# One parameter that is optional.
= regex_pattern_detecting_command(('greet', 1, 'world', r'Hello #1!'))
pattern # When the optional parameter is used
= r'\greet'
text = pattern.search(text)
match = match.span()
start, end
test_eq(text[start:end], text)
# When the optional parameter is not used
= r'\greet[govna]'
text = pattern.search(text)
match = match.span()
start, end
test_eq(text[start:end], text)
# In the following example, `\del` is a command defined as `\delta`.
# Any invocation `\delta` should detected as invocations of `\del``
= (r'del', 0, None, r'\delta')
command_tuple = regex_pattern_detecting_command(command_tuple)
pattern = r'\del should be detected.'
text = pattern.search(text)
match = match.span()
start, end r'\del')
test_eq(text[start:end], = r'\delta should not be detected.'
text = pattern.search(text)
match assert match is None
# test_eq(replace_command_in_text(text, command_tuple), r'\delta should be replaced. \delta should not.')
# In the following example, the command takes one argument, but sometimes the command
# is `\del`
= ('til', 1, None, '{\\widetilde{#1}}')
command_tuple = regex_pattern_detecting_command(command_tuple)
pattern = r'\til \calh_g'
text = pattern.search(text)
match # start, end = match.span()