'How to get current line of source file when processing a macro?

I want to pre-process C source code with jinja2 and I would like for some macros to be able to output #line lines:

#!/usr/bin/env python3
from jinja2 import *
@pass_context
def mymacro(ctx):
    return '#line ?? "??"'
env = Environment()
env.globals["mymacro"] = mymacro
rr = env.from_string(
    """
// file.h
{{ mymacro() }}
"""
).render()
print(rr)

How do I get current line within mymacro global? I tried inspecting jinja2.runtime.Context, but I can't find anything helpful. Is this possible? Note that the line of macro invocation is visible when an exception is thrown - so it is stored somewhere.



Solution 1:[1]

This is the line that brought a solution:

template = tb.tb_frame.f_globals.get("__jinja_template__")

Source: debug.py#L55
The variable tb being a exception traceback, in this context.

And then, looking further, I realised that Jinja is using this line __jinja_template__ to frame where the template lines are in the stack of Python.

With that, and the function get_corresponding_lineno that they are using a few lines later in the debug.py file:

template = tb.tb_frame.f_globals.get("__jinja_template__")

if template is not None:
    lineno = template.get_corresponding_lineno(tb.tb_lineno)

Source: debug.py#L58

It was now quite clear how to achieve it:

  • get the whole Python stack
  • loop over it until you find the template boundary
  • translate the current line of Python code in a line of the template with the help of get_corresponding_lineno

This gives:

#!/usr/bin/env python3
from jinja2 import *
from inspect import stack, currentframe

def mymacro():
    for frameInfo in stack():
        if frameInfo.frame.f_globals.get("__jinja_template__") is not None:
            template = frameInfo.frame.f_globals.get("__jinja_template__")
            break
    return (
        '#line '
        f'{template.get_corresponding_lineno(currentframe().f_back.f_lineno)}'
    )
    
env = Environment()
env.globals["mymacro"] = mymacro

rr = env.from_string(
"""
// file.h

{{ mymacro() }}
"""
).render()

print(rr)

Which prints us:


// file.h

#line 4

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1