[#2] Add support for Python as a script language
This MR implements all the current interface that the JS kernel provides for Python kernel. Decisions that were made to make it work (does probably need more attention when reviewing):
- The Python's wrappers in general are not subclass from QObject because QObjects are neither copyable nor movable so I was having trouble pre-instantiating the DocumentWrapperPy class and pass its object to Python script.
- Kernel::MessageType was extracted to a Messenger class that is a subclass of QObject, with this it's possible to reuse the same MessageType in multiple kernel implementations without needing to include the JS kernel. The Python wrapper classes can communicate with the ScriptOutputWidget through a reference to a Messenger object, this way the wrapper classes remain copyable and movable and can send SIGNALs.
- There is a superclass KernelInterface that both Python and JS kernels are subclasses and the MainWindow has a QMap<QString, KernelInterface*> that maps an file extension with a kernel implementation. If the file extension that is being used doesn't have a corresponding kernel implementation in this QMap the JS kernel will be used to avoid handling nullptr.
- Currently all messages that are sent to ScriptOutputWidget::processMessage are shown in a new line in the UI, so to connect the Python stdout/stderr with this widget I created a class called PyOutputBuffer, this class acts like a buffer to store all strings written to stdout/stderr through Python method
write
and flushes each complete line (string followed by a '\n') to ScriptOutputWidget::processMessage. Bellow has an example of writes to stdout that produces the same result as if it was printing on terminal.
print(*range(10))
print('Hello', end=' ')
print('Rocs')
print(*'abcde', sep=' -> ', end='!')
- The JS kernel has support to create properties dynamically (properties that were not defined in the Wrapper classes). By default pybind11 classes don't have support to dynamic attributes but can be added annotating the class with pybind11::dynamic_attr(), so to add a similar functionality to Python kernel I had some different approaches (Spoiler alert!! Only the last one of this approaches worked):
- Update the __setitem__ method of __dict__ so we know exactly when a new attribute was set and update c++ objects accordingly. Didn't work because apparently the __setitem__ method of a dict object is readonly se we cannot do this.
- Create a subclass of dict that defines __setitem__ method and set the __dict__ attribute to this new class. Didn't work because pybind11 don't support creating a class in c++ that is a subclass of a python class.
- Create a class that "acts" like python's dict and hope that Duck Typing take cares of the rest. Didn't work because we cannot set __dict__ attribute to an object that is neither a dict object nor a subclass of dict.
- Set __setattr__ method of the class and calling the proper methods based on which attribute is being set. This worked but I had to enumerate all properties in an if-else statement which is not very good for code maintainability.