pythonimmediate.simple module
Simple interface, suitable for users who may not be aware of \(\TeX\) subtleties, such as category codes.
This is only guaranteed to work properly in normal category code situations
(in other words, \makeatletter
or \ExplSyntaxOn
are not officially supported).
In all other cases, use the advanced API.
Nevertheless, there are still some parts that you must be aware of:
Any “output”, resulted from any command related to Python, can only be used to typeset text, it must not be used to pass “values” to other \(\TeX\) commands.
This is already mentioned in the documentation of the \(\TeX\) file in
\py
command, just repeated here.For example:
\py{1+1} % legal, typeset 2 \setcounter{abc}{\py{1+1}} % illegal % the following is still illegal no many how many ``\expanded`` and such are used \edef\tmp{\py{1+1}} \setcounter{abc}{\tmp} \py{ r'\\setcounter{abc}{' + str(1+1) + '}' } % legal workaround
Similar for commands defined with
newcommand()
:@newcommand def test(): print_TeX("123", end="")
Then the \(\TeX\) code:
The result is \test. % legal, typesets "123" \setcounter{abc}{\test} % illegal
There’s the function
fully_expand()
, but some \(\TeX\) commands cannot be used inside this.Refer to the Note on unexpandable and fragile macros section for more details.
Regarding values being passed from \(\TeX\) to Python, you can do one of the following:
Either simply do
\py{value = r'''#1'''}
, or\py{value = get_arg_str()}{#1}
.
You can easily see both forms are equivalent, except that in the first form,
#1
cannot end with unbalanced\
or contain triple single quotes.
Start with reading newcommand()
and execute()
. Then read the rest of the functions in this module.
- pythonimmediate.simple.define_char(char: str) Callable[[T2], T2] [source]
Define a character to do some specific action.
Can be used as a decorator:
@define_char("×") def multiplication_sign(): print_TeX(end=r"\times")
Note
It’s not recommended to define commonly-used characters, for example if you define
n
then commands whose name containsn
cannot be used anymore.As another example, if you define
-
, then commands like\draw (1, 2) -- (3, 4);
in TikZ cannot be used.undefine_char()
can be used to undo the effect.
- pythonimmediate.simple.escape_str(s: str) str [source]
Given an arbitrary string, output some [LaTeX] code that when executed will typeset the original string.
Example:
>>> escape_str("123") '123' >>> escape_str(r"a%#_^~\?") 'a\\%\\#\\_\\^{}\\~{}\\textbackslash{}?' >>> escape_str("[") '[' >>> escape_str(" ") '\\ \\ ' >>> escape_str("\n") '\\\\'
\n
gets escaped to\\
so that multiple consecutive\n
gives multiple line breaks.<
and>
also needs to be escaped, but only in OT1 font encoding, see https://tex.stackexchange.com/q/2369/250119:>>> escape_str("<>") '\\textless{}\\textgreater{}'
Some Unicode characters may not be supported regardless.
Be careful with initial
[
in text which may be interpreted as an optional argument in specific cases. For example https://tex.stackexchange.com/questions/34580/escape-character-in-latex/686889#comment1155297_34586.
- pythonimmediate.simple.execute(block: str, expecting_exit: bool = False) None [source]
Run a block of \(\TeX\)-code (might consist of multiple lines).
A simple example is
execute('123')
which simply typesets123
(with a trailing newline, see the note innewcommand()
documentation).This is similar to
print_TeX()
but it’s executed immediately, and any error is immediately reported with Python traceback points to the caller.On the other hand, because this is executed immediately, each part must be “self-contained”. With
print_TeX()
you can do the following:print_TeX(r"\abc", end="") print_TeX(r"def", end="")
and a single command
\abcdef
will be executed, but it does not work with this method.As another consequence, all
execute()
are done before allprint_TeX()
in the same block. For example:print_TeX(r"3") execute(r"1") print_TeX(r"4") execute(r"2")
will typeset
1 2 3 4
.Note
For advanced users: it’s implemented with
\scantokens
, so catcode-changing commands are allowed inside.
- pythonimmediate.simple.f1(s: str, *, globals: Optional[dict] = None, locals: Optional[dict] = None, escape: Optional[Union[Tuple[str, ...], str]] = None) str [source]
Helper function to construct a string from Python expression parts, similar to f-strings, but allow using arbitrary delimiters.
This is useful for constructing \(\TeX\) code, because the
{
and}
characters are frequently used in \(\TeX\).Example:
>>> a = 1 >>> b = 2 >>> f1("a=`a`, b=`b`") 'a=1, b=2' >>> f1("`") Traceback (most recent call last): ... ValueError: Unbalanced delimiters!
The escape/delimiter character can be escaped by doubling it:
>>> f1("a``b") 'a`b' >>> f1("a ` '````' ` b") 'a `` b'
It’s possible to customize the delimiters:
>>> f1("a=!a!", escape="!") 'a=1'
It’s possible to use different delimiters for the opening and the closing:
>>> f1("a=⟨a⟩, b=⟨b⟩", escape="⟨⟩") 'a=1, b=2' >>> f1("<", escape="<>") Traceback (most recent call last): ... ValueError: Unbalanced delimiters!
There’s no way to escape them, they must be balanced in the string:
>>> f1("a=⟨ '⟨'+str(a)+'⟩' ⟩", escape="⟨⟩") 'a=⟨1⟩'
The default value of escape is stored in
f1.default_escape
, it can be reassigned:>>> f1.default_escape="!" >>> f1("a=!a!") 'a=1' >>> f1.default_escape="`"
It’s even possible to use multiple characters as the delimiter:
>>> f1("a=**a**, b=**b**", escape=["**"]) 'a=1, b=2' >>> f1("a=[[a]], b=[[b]]", escape=["[[", "]]"]) 'a=1, b=2'
In the first case above, remember to put the delimiter in a list because otherwise it will be interpreted as an opening and a closing delimiter.
Currently, format specifiers such as
:d
are not supported.
- pythonimmediate.simple.fully_expand(content: str) str [source]
Expand all macros in the given string.
Note on unexpandable and fragile macros
Not all \(\TeX\) commands/macros can be used inside this function. If you try to, they will either return the input unchanged, or error out.
In such cases, the problem is impossible to solve, so just look for workarounds.
An example is the
\py
command, which is already mentioned in the initial introduction to thesimple
module:>> fully_expand(r"\py{1+1}") r"\py{1+1}"
Some other examples (assuming
\usepackage{ifthen}
is used):>> execute(r'\ifthenelse{1=2}{equal}{not equal}') # legal, typeset "not equal" >> fully_expand(r'\ifthenelse{1=2}{equal}{not equal}') # error out >> execute(r'\uppercase{abc}') # legal, typeset "ABC" >> fully_expand(r'\uppercase{abc}') # just returned verbatim r'\uppercase {abc}'
There might be a few workarounds, but they’re all dependent on the exact macros involved.
For if-style commands, assign the desired result to a temporary variable:
>> execute(r'\ifthenelse{1=2}{\pyc{result = "equal"}}{\pyc{result = "not equal"}}') >> result "not equal"
For for-loop-style commands, you can do something similar (demonstrated here with
tikz
’s\foreach
command):>> result = [] >> execute(r'\foreach \x in {1, 2, ..., 10} {\pyc{result.append(var["x"])}}') >> result ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
For macros such as
\uppercase{...}
, there’s no simple workaround. Reimplement it yourself (such as with.upper()
in Python).
- pythonimmediate.simple.get_arg_estr() str [source]
Get a mandatory argument from the input stream, then process it as described in Note on argument expansion of estr-type functions.
Note on argument expansion of
estr
-type functionsThese functions return a string, however they’re meant for strings other than \(\TeX\) code.
The argument is fully expanded, and any “escaped character” are passed as the character itself to Python.
The behavior is similar to that of the
\py
command argument processing, see also the \(\TeX\) package documentation.Some examples: After the \(\TeX\) code
\def\myvalue{123abc}
is executed, then:if the argument in \(\TeX\) code is
{abc}
, then the Python function will return"abc"
(similar to as ifget_arg_str()
is used),if the argument in \(\TeX\) code is
{\%a\#b\\\~}
, then the Python function will returnr"%a#b\~"
,if the argument in \(\TeX\) code is
{\myvalue}
, then the Python function will return"123abc"
.
- pythonimmediate.simple.get_arg_str() str [source]
Get a mandatory argument from the input stream.
It’s slightly difficult to explain what this does, so here’s an example:
@newcommand def foo(): a = get_arg_str() b = get_arg_str() c = get_arg_str()
The above defines a \(\TeX\) function
\foo
, if it’s called in \(\TeX\) code as:\foo{xx}{yy}{zz}
then the variable
a
will be the string"xx"
,b
will be"yy"
, andc
will be"zz"
.Note on parameter tokenization
Note
This function, as well as all the _str functions, return the argument that might be mangled in the following ways:
A space might be added after each command that consist of multiple characters
the
^^xx
or^^xxxx
etc. notation is replaced by the character itself, or vice versa – literal tab character might get replaced with^^I
multiple space characters may be collapsed into one
newline character may become space character
literally-typed
\par
in the source code may become double-newline character. (however you should never literally type\\par
in the source code anyway he)Even something like
\\par
in the source code may become double-newline character. (this is rare)
For example,
\*\hell^^6F{ }
may become the stringr"\*\hello { }"
in Python.As such, verbatim-like commands in the argument are not supported. See
get_verb_arg()
for a workaround.Nevertheless, if the argument consist of only “normal” \(\TeX\) commands, re-executing the string should do the same thing as the original code.
In case of doubt, print out the string and check it manually.
Note
For advanced users:
This function corresponds to the
m
-type argument inxparse
package.It gets the argument, detokenize it, pass it through
_replace_double_hash()
, and return the result.This is the simple API, as such it assumes normal category code values. Refer to
get_next()
for a more advanced API.
- pythonimmediate.simple.get_env_body_verb_approximate(envname: Optional[str] = None) tuple[str, str, int] [source]
This function is to be used inside
newenvironment()
’s registered function in order to get the environment body.It can only be used once, and it only works if the argument is not already tokenized, i.e., it appears at “top-level”/does not appear inside any command or some environments such as
align
. (basically where a\verb|...|
command can appear.)- Returns
a tuple
(body, filename, line_number)
wherebody
is the body of the environment,filename
is the filename of the TeX file, andline_number
is the line number of the last line of the environment.
Note
The returned code may have some tab characters converted to
^^I
, trailing spaces stripped etc.For advanced users: Basically it takes the following content until
\end{⟨value of \@currenvir⟩}
, as by thesaveenv
package.
- pythonimmediate.simple.get_multiline_verb_arg() str [source]
Get a multi-line verbatim argument. Usage is identical to
get_verb_arg()
, except newline characters in the argument is supported.Note
in unusual category regime (such as that in
\ExplSyntaxOn
), it may return wrong result.
- pythonimmediate.simple.get_next_char() str [source]
Return the character of the following token as with
peek_next_char()
, but also removes it from the input stream.
- pythonimmediate.simple.get_optional_arg_estr() Optional[str] [source]
Get an optional argument. See also Note on argument expansion of estr-type functions.
- pythonimmediate.simple.get_optional_arg_str() Optional[str] [source]
Get an optional argument. See also Note on parameter tokenization.
Note
For advanced users: This function corresponds to the
o
-type argument inxparse
package.
- pythonimmediate.simple.get_verb_arg() str [source]
Get a verbatim argument.
Similar to
\verb
, defined \(\TeX\) commands that use this function can only be used at top level i.e. not inside any arguments.This function behavior is identical to
v
-type argument inxparse
package, you can use it like this:\foo{xx%^\?} % delimited with braces \foo|xx%^\?| % delimited with another symbol
See also
get_multiline_verb_arg()
to support newline character in the argument.Note
Hard TAB character in the argument gives an error until the corresponding LaTeX3 bug is fixed, see https://tex.stackexchange.com/q/508001/250119.
- pythonimmediate.simple.is_balanced(content: str) bool [source]
Check if the given string is balanced, i.e. if all opening and closing braces match.
This is a bit tricky to implement, it’s recommended to use the library function.
For example:
>>> is_balanced("a{b}c") True >>> is_balanced("a{b}c}") False >>> is_balanced(r"\{") True
- pythonimmediate.simple.newcommand(name: str, f: Optional[Callable]) Callable [source]
Define a new \(\TeX\)-command.
The corresponding advanced API is
Token.set_func()
.Example:
@newcommand def myfunction(): print_TeX("Hello world!", end="")
The above is mostly equivalent to
\newcommand{\myfunction}{Hello world!}
, it defines a \(\TeX\) command\myfunction
that printsHello world!
.See also documentation of
print_TeX()
to understand the example above.It can be used as either a decorator or a function:
def myfunction(): print_TeX("Hello world!", end="") newcommand("myfunction", myfunction)
An explicit command name can also be provided:
@newcommand("hello") def myfunction(): print_TeX("Hello world!", end="")
The above defines a \(\TeX\) command
\hello
that printsHello world!
.If name is not provided, it’s automatically deduced from the Python function name.
The above is not by itself very useful. Read the documentation of the following functions sequentially for a guide on how to define commands that take arguments:
Then see also (as natural extensions of the above):
Note on trailing newline
Note
Regarding the
end=""
part in the example above, it’s used to prevent a spurious space, otherwise if you run the \(\TeX\) code:123\myfunction 456
it will be “equivalent” to:
123Hello world! 456
and the newline inserts a space.
Internally, the feature is implemented by appending
%
to the last line, as most of the time the code:123Hello world!% 456
will be equivalent to:
123Hello world!456
but in unusual category code cases or cases of malformed \(\TeX\) code (such as a trailing backslash at the end of the line), it may not be equivalent. Use the advanced API, or always include a final newline and manually add the comment character, if these cases happen.
- pythonimmediate.simple.newenvironment(name: str, f: Optional[Callable]) Callable [source]
Define a new \(\TeX\) normal environment.
Note that the environment will normally not have access to the body of the environment, see
newenvironment_verb()
for some alternatives.- Parameters
name – the name of the environment, e.g.
"myenv"
or"myenv*"
.f – a function that should execute the code for the begin part of the environment,
yield
, then execute the code for the end part of the environment.engine – the engine to use.
Usage example:
@newenvironment("myenv") def myenv(): x=random.randint(1, 100) print_TeX(f"begin {x}") yield print_TeX(f"end {x}")
then the following \(\TeX\) code:
\begin{myenv} \begin{myenv} Hello world! \end{myenv} \end{myenv}
might typeset the following content:
begin 42 begin 24 Hello world! end 24 end 42
Functions such as
get_arg_str()
etc. can also be used in the first part of the function to take arguments.If the name is omitted, the function name is used as the environment name.
It can be used either as a decorator or a function, see
newcommand()
for details.
- pythonimmediate.simple.newenvironment_verb(name: str, f: Optional[Callable]) Callable [source]
Define a new \(\TeX\) environment that reads its body verbatim.
Note that space characters at the end of each line are removed.
The environment must not take any argument. For example the following built-in
tabular
environment takes some argument, so cannot be implemented with this function:\begin{tabular}[t] ... \end{tabular}
It can be used either as a decorator or a function, see
newenvironment()
for details.Some usage example:
@newenvironment_verb("myenv") def myenv(body: str): execute(body.replace("a", "b"))
If later the following \(\TeX\) code is executed:
\begin{myenv} aaa \end{myenv}
then the value of the variable
body
will be"aaa\n"
, and the following content will be typeset:bbb
Note
For advanced users: unlike a typical environment, the code will not be executed in a new \(\TeX\) group.
- pythonimmediate.simple.parse_keyval(content: str, allow_duplicate: bool = False) dict[str, Optional[str]] [source]
Parse a key-value string into a dictionary.
>>> parse_keyval("a=b,c=d") {'a': 'b', 'c': 'd'} >>> parse_keyval("a,c=d") {'a': None, 'c': 'd'} >>> parse_keyval("a = b , c = d") {'a': 'b', 'c': 'd'} >>> parse_keyval("a ={ b }, c ={ d}") {'a': ' b ', 'c': ' d'} >>> parse_keyval("a={b,c}, c=d") {'a': 'b,c', 'c': 'd'} >>> parse_keyval("a=b,a=c") Traceback (most recent call last): ... ValueError: Duplicate key: 'a' >>> parse_keyval("a=b,a=c", allow_duplicate=True) {'a': 'c'}
- pythonimmediate.simple.parse_keyval_items(content: str) list[tuple[str, Optional[str]]] [source]
>>> parse_keyval_items("a=b,c=d") [('a', 'b'), ('c', 'd')] >>> parse_keyval_items("a,c=d") [('a', None), ('c', 'd')] >>> parse_keyval_items("a = b , c = d") [('a', 'b'), ('c', 'd')] >>> parse_keyval_items("a ={ b }, c ={ d}") [('a', ' b '), ('c', ' d')] >>> parse_keyval_items("a={b,c}, c=d") [('a', 'b,c'), ('c', 'd')] >>> parse_keyval_items("{a=b},c=d") [('{a=b}', None), ('c', 'd')]
- pythonimmediate.simple.peek_next_char() str [source]
Get the character of the following token, or empty string if it’s not a character.
This function can be used as follows to simulate
e
-type argument specifiers ofxparse
:if peek_next_char()=="^": get_next_char() result = get_arg_str() # the following content in the input stream is ``^{...}`` and we stored the content inside to ``result`` else: pass # there's no ``^...`` following in the input stream
It can also simulate
s
-type argument specifier (optional star):if peek_next_char()=="*": get_next_char() # there's a star else: pass # there's no star
It can even simulate
o
-type argument specifier (optional argument delimited by[...]
):if peek_next_char()=="[": get_next_char() # skip the `[` result="" while True: if peek_next_char(): c=get_next_char() if c=="]": break result+=c else: # following in the input stream must be a control sequence, such as `\relax` result+=get_arg_str() # now result contains the content inside the `[...]` else: pass # there's no optional argument
Note that the above does not take into account the balance of braces or brackets, so:
If the following content in the input is
[ab{cd]ef}gh]
then the result will beab{cd
.If the following content in the input is
[ab[cd]ef]
then the result will beab[cd
.
Note
For advanced users:
This function uses
peek_next_meaning()
under the hood to get the meaning of the following token. See that function documentation for a warning on undefined behavior.Will also return nonempty if the next token is an implicit character token. This case is not supported and might give random error.
- pythonimmediate.simple.print_TeX(*args: Any, **kwargs: Any) None [source]
Unlike other packages, the normal
print()
function prints to the console.This function can be used to print \(\TeX\) code to be executed. For example:
\begin{pycode} print_TeX("Hello world!") \end{pycode}
It can also be used inside a custom-defined command, see the documentation of
newcommand()
for an example.The signature is identical to that of
print()
. Passing explicitfile=
argument is not allowed.Remark: normally the
print()
function prints out a newline with each invocation. A newline will appear as a spurious space in the output Useprint_TeX(end="")
to prevent this, see Note on trailing newline.
- pythonimmediate.simple.put_next(arg: str) None [source]
Put some content forward in the input stream.
- Parameters
arg –
The content, must be a single line.
Note that there must not be any verbatim-like commands in the argument, so for example
put_next(r"\verb|a|")
is not allowed.If there might be verbatim-like arguments, the problem is (almost) unsolvable. Refer to
print_TeX()
orexecute()
for workarounds, or use the advanced interface such aspythonimmediate.BalancedTokenList.put_next()
.
For example:
>>> put_next("{abc}") >>> put_next("{def}") >>> get_arg_str() 'def' >>> get_arg_str() 'abc'
Note
For advanced users: the argument is tokenized in the current category regime.
- pythonimmediate.simple.renewcommand(name: str, f: Optional[Callable]) Callable [source]
Redefine a \(\TeX\)-command. Usage is similar to
newcommand()
.
- pythonimmediate.simple.split_balanced(content: str, /, sep: str, maxsplit: int = -1, do_strip_braces_in_result: bool = True) List[str] [source]
Split the given string at the given substring, but only if the parts are balanced.
This is a bit tricky to implement, it’s recommended to use the library function.
If either content or sep is unbalanced, the function will raise ValueError.
It is recommended to set do_strip_braces_in_result to
True
(the default), otherwise the user will not have any way to “quote” the separator in each entry.For example:
>>> split_balanced("a{b,c},c{d}", ",") ['a{b,c}', 'c{d}'] >>> split_balanced("a{b,c},{d,d},e", ",", do_strip_braces_in_result=False) ['a{b,c}', '{d,d}', 'e'] >>> split_balanced("a{b,c},{d,d},e", ",") ['a{b,c}', 'd,d', 'e'] >>> split_balanced(" a = b = c ", "=", maxsplit=1) [' a ', ' b = c '] >>> split_balanced(r"\{,\}", ",") ['\\{', '\\}'] >>> split_balanced("{", "{") Traceback (most recent call last): ... ValueError: Content is not balanced!
- pythonimmediate.simple.strip_optional_braces(content: str) str [source]
Strip the optional braces from the given string, if the whole string is wrapped in braces.
For example:
>>> strip_optional_braces("{a}") 'a' >>> strip_optional_braces("a") 'a' >>> strip_optional_braces("{a},{b}") '{a},{b}'
- pythonimmediate.simple.undefine_char(char: str) None [source]
The opposite of
define_char()
.
- pythonimmediate.simple.var = VarManager()
Get and set value of “variables” in \(\TeX\).
Can be used like this:
var["myvar"]="myvalue" # "equivalent" to \def\myvar{myvalue} var["myvar"]=r"\textbf{123}" # "equivalent" to \def\myvar{\textbf{123}} var["myvar"]=r"\def\test#1{#1}" # "equivalent" to \tl_set:Nn \myvar {\def\test#1{#1}} (note that `#` doesn't need to be doubled here unlike in `\def`) var()["myvar"]="myvalue" # pass explicit engine # after ``\def\myvar{myvalue}`` (or ``\edef``, etc.) on TeX you can do: print(var["myvar"]) # get the value of the variable, return a string
It’s currently undefined behavior if the passed-in value is not a “variable”. (in some future version additional checking might be performed in debug run)
Notes in Note on parameter tokenization apply – in other words, after you set a value it may become slightly different when read back.