JelteF / PyLaTeX

A Python library for creating LaTeX files
https://jeltef.github.io/PyLaTeX/
MIT License
2.24k stars 287 forks source link

Prevent Environment subclass X appending to another X #341

Open aapomalk opened 2 years ago

aapomalk commented 2 years ago

Context

I'm using Beamer with PyLaTeX. I have created my own class Frame (from PyLaTeX Environment) and there I'm overriding append so that Frame cannot be appended to Frame (otherwise LaTeX gives an error).

    def append(self, content):
        super().append(content)
        self.check_content_type(content)

    def check_content_type(self, element):
        if (element is not None) and (type(element) is Frame):
            string = 'Frame cannot contain another Frame.'
            raise TypeError(string)

Problem

The problem here is that if one uses:

with doc.create(Frame()) as frame1:
    frame1.append('some text')
    with doc.create(Frame()) as frame2:
        frame2.append('some text')

Then the error comes when compiling LaTeX and it's difficult for user to see where the error has happened.

But if one uses:

with doc.create(Frame()) as frame1:
    frame1.append('some text')
    with frame1.create(Frame()) as frame2:
        frame2.append('some text')

Then the error comes when running Python and points to the user where the error has happened. But problem here is that this only comes if user doesn't know that frame cannot contain another frame.

When modifying script the indentation is easy to mess up but hard to debug without proper error messages from Python.

Correct code

The correct code would then be:

with doc.create(Frame()) as frame1:
    frame1.append('some text')
with doc.create(Frame()) as frame2:
    frame2.append('some text')

Because frames cannot be within each other.

Workaround

Currently I'm using following workaround in my document class:

    @contextmanager
    def create(self, child):

        if type(child) is Frame and self.within_frame:
            raise TypeError('Frame cannot contain another Frame.')

        if type(child) is Frame:
            self.within_frame = True

        # copy-paste from PyLaTeX
        prev_data = self.data
        self.data = child.data  # This way append works appends to the child

        yield child  # allows with ... as to be used as well

        self.data = prev_data
        self.append(child)
        # end of copy-paste

        self.within_frame = False

The problem is just that now I have to add the append rules to document and Frame and in long run this gets tedious as number of classes grow.

Expected behavior

I'd wish that overriding append in any class would be enough and that there would be no need to add any code for document class.

Or if I'm missing something and there would be already a solution where I only need to edit Frame without the document workaround then I'd be pleased.